diff --git a/index.html b/index.html index bb65a66..5eab5a3 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,10 @@ - + - Vite + React + + NEAR Multi-Chain
diff --git a/package.json b/package.json index e127dd3..52995e4 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "@near-wallet-selector/my-near-wallet": "^8.9.5", "axios": "^1.6.8", "bitcoinjs-lib": "^6.1.5", + "bn.js": "^5.2.1", "bs58check": "^3.0.1", + "elliptic": "^6.5.5", "ethers": "^6.11.1", "hash.js": "^1.1.7", "keccak": "^3.0.4", @@ -27,9 +29,13 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "rxjs": "^7.8.1", "vite-plugin-node-polyfills": "^0.21.0", "web3": "^4.6.0" }, + "overrides": { + "near-api-js": "^3.0.4" + }, "devDependencies": { "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", @@ -40,4 +46,4 @@ "eslint-plugin-react-refresh": "^0.4.5", "vite": "^5.1.6" } -} +} \ No newline at end of file diff --git a/src/assets/logo-black.svg b/public/logo-black.svg similarity index 100% rename from src/assets/logo-black.svg rename to public/logo-black.svg diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index ee87bc9..e765b13 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,3 +1,5 @@ +import { NearContext } from './context'; + import { useEffect, useState } from "react"; import Navbar from "./components/Navbar" import { Wallet } from "./services/near-wallet"; @@ -11,29 +13,22 @@ const MPC_CONTRACT = 'multichain-testnet-2.testnet'; const wallet = new Wallet({ network: 'testnet', createAccessKeyFor: MPC_CONTRACT }); function App() { - const [isSignedIn, setIsSignedIn] = useState(false); + const [signedAccountId, setSignedAccountId] = useState(''); const [status, setStatus] = useState("Please login to request a signature"); const [chain, setChain] = useState('eth'); - useEffect(() => { - const initFunction = async () => { - const isSignedIn = await wallet.startUp(); - setIsSignedIn(isSignedIn); - } - - initFunction(); - }, []); + useEffect(() => { wallet.startUp(setSignedAccountId) }, []); return ( - <> - + +

🔗 NEAR Multi Chain

Safely control accounts on other chains through the NEAR MPC service. Learn more in the documentation.

- {isSignedIn && + {signedAccountId &&
@@ -48,16 +43,16 @@ function App() {
- {chain === 'eth' && } - {chain === 'btc' && } + {chain === 'eth' && } + {chain === 'btc' && }
}
- {status} + {status}
- +
) } diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico deleted file mode 100644 index 405779a..0000000 Binary files a/src/assets/favicon.ico and /dev/null differ diff --git a/src/assets/logo-white.svg b/src/assets/logo-white.svg deleted file mode 100644 index 47cb783..0000000 --- a/src/assets/logo-white.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/Bitcoin.jsx b/src/components/Bitcoin.jsx index d8fa44d..c5dea7c 100644 --- a/src/components/Bitcoin.jsx +++ b/src/components/Bitcoin.jsx @@ -1,12 +1,16 @@ +import { useState, useEffect, useContext } from "react"; +import { NearContext } from "../context"; + import { Bitcoin as Bitcoin } from "../services/bitcoin"; -import { useState, useEffect } from "react"; import { useDebounce } from "../hooks/debounce"; import PropTypes from 'prop-types'; const BTC_NETWORK = 'testnet'; const BTC = new Bitcoin('https://blockstream.info/testnet/api', BTC_NETWORK); -export function BitcoinView({ props: { setStatus, wallet, MPC_CONTRACT } }) { +export function BitcoinView({ props: { setStatus, MPC_CONTRACT } }) { + const { wallet, signedAccountId } = useContext(NearContext); + const [receiver, setReceiver] = useState("tb1q86ec0aszet5r3qt02j77f3dvxruk7tuqdlj0d5"); const [amount, setAmount] = useState(1000); const [loading, setLoading] = useState(false); @@ -15,44 +19,56 @@ export function BitcoinView({ props: { setStatus, wallet, MPC_CONTRACT } }) { const [senderAddress, setSenderAddress] = useState("") const [senderPK, setSenderPK] = useState("") - const [derivation, setDerivation] = useState("test"); + const [derivation, setDerivation] = useState("bitcoin-1"); const derivationPath = useDebounce(derivation, 500); - useEffect(() => { setBtcAddress(derivationPath) }, [derivationPath]); + useEffect(() => { + setSenderAddress('Waiting for you to stop typing...') + }, [derivation]); - async function setBtcAddress() { - setStatus('Querying your address and balance'); - setSenderAddress('Deriving address...'); + useEffect(() => { + setBtcAddress() - const { address, publicKey } = await BTC.deriveAddress(wallet.accountId, derivationPath); - const balance = await BTC.getBalance(address); + async function setBtcAddress() { + setStatus('Querying your address and balance'); + setSenderAddress(`Deriving address from path ${derivationPath}...`); - setSenderAddress(address); - setSenderPK(publicKey); - setStatus(`Your Bitcoin address is: ${address}, balance: ${balance} satoshi`); - } + const { address, publicKey } = await BTC.deriveAddress(signedAccountId, derivationPath); + setSenderAddress(address); + setSenderPK(publicKey); + + const balance = await BTC.getBalance(address); + setStatus(`Your Bitcoin address is: ${address}, balance: ${balance} satoshi`); + } + }, [signedAccountId, derivationPath]); async function chainSignature() { setStatus('🏗️ Creating transaction'); const payload = await BTC.createPayload(senderAddress, receiver, amount); - - setStatus('🕒 Asking MPC to sign the transaction, this might take a while...'); - const signedTransaction = await BTC.requestSignatureToMPC(wallet, MPC_CONTRACT, derivationPath, payload, senderPK); - console.log(signedTransaction) - - setStatus('✅ Signed payload ready to be relayed to the Bitcoin network'); - setSignedTransaction(signedTransaction); - setStep('relay'); + setStatus('🕒 Asking MPC to sign the transaction, this might take a while...'); + try{ + const signedTransaction = await BTC.requestSignatureToMPC(wallet, MPC_CONTRACT, derivationPath, payload, senderPK); + setStatus('✅ Signed payload ready to be relayed to the Bitcoin network'); + setSignedTransaction(signedTransaction); + setStep('relay'); + } catch (e) { + setStatus(`❌ Error: ${e.message}`); + setLoading(false); + } } async function relayTransaction() { setLoading(true); setStatus('🔗 Relaying transaction to the Bitcoin network... this might take a while'); - + try { const txHash = await BTC.relayTransaction(signedTransaction); - setStatus(`✅ Successful: https://blockstream.info/testnet/tx/${txHash}`); + setStatus( + <> + ✅ Successful + + ); } catch (e) { setStatus(`❌ Error: ${e.message}`); } @@ -61,12 +77,6 @@ export function BitcoinView({ props: { setStatus, wallet, MPC_CONTRACT } }) { setLoading(false); } - const handleDerivationChange = (event) => { - setStatus('Derivation path changed'); - setSenderAddress('Waiting for you to stop typing...'); - setDerivation(event.target.value); - } - const UIChainSignature = async () => { setLoading(true); await chainSignature(); @@ -76,22 +86,22 @@ export function BitcoinView({ props: { setStatus, wallet, MPC_CONTRACT } }) { return ( <>
- +
- + setDerivation(e.target.value)} disabled={loading} />
{senderAddress}
- setReceiver(e.target.value)} disabled={loading}/> + setReceiver(e.target.value)} disabled={loading} />
- setAmount(e.target.value)} step="1" disabled={loading}/> + setAmount(e.target.value)} step="1" disabled={loading} />
satoshi units
@@ -107,7 +117,6 @@ export function BitcoinView({ props: { setStatus, wallet, MPC_CONTRACT } }) { BitcoinView.propTypes = { props: PropTypes.shape({ setStatus: PropTypes.func.isRequired, - wallet: PropTypes.object.isRequired, MPC_CONTRACT: PropTypes.string.isRequired, }).isRequired }; \ No newline at end of file diff --git a/src/components/Ethereum.jsx b/src/components/Ethereum.jsx index 30b7148..b9fd01b 100644 --- a/src/components/Ethereum.jsx +++ b/src/components/Ethereum.jsx @@ -1,12 +1,15 @@ +import { useState, useEffect, useContext } from "react"; +import { NearContext } from "../context"; + import { Ethereum } from "../services/ethereum"; -import { useEffect, useState } from "react"; import { useDebounce } from "../hooks/debounce"; import PropTypes from 'prop-types'; const Sepolia = 11155111; const Eth = new Ethereum('https://rpc2.sepolia.org', Sepolia); -export function EthereumView({ props: { setStatus, wallet, MPC_CONTRACT } }) { +export function EthereumView({ props: { setStatus, MPC_CONTRACT } }) { + const { wallet, signedAccountId } = useContext(NearContext); const [receiver, setReceiver] = useState("0xe0f3B7e68151E9306727104973752A415c2bcbEb"); const [amount, setAmount] = useState(0.01); @@ -15,21 +18,27 @@ export function EthereumView({ props: { setStatus, wallet, MPC_CONTRACT } }) { const [signedTransaction, setSignedTransaction] = useState(null); const [senderAddress, setSenderAddress] = useState("") - const [derivation, setDerivation] = useState("test"); + const [derivation, setDerivation] = useState("ethereum-1"); const derivationPath = useDebounce(derivation, 1000); - useEffect(() => { setEthAddress(derivationPath) }, [derivationPath]); + useEffect(() => { + setSenderAddress('Waiting for you to stop typing...') + }, [derivation]); - async function setEthAddress() { - setStatus('Querying your address and balance'); - setSenderAddress('Deriving address...'); + useEffect(() => { + setEthAddress() - const { address } = await Eth.deriveAddress(wallet.accountId, derivationPath); - const balance = await Eth.getBalance(address); - - setSenderAddress(address); - setStatus(`Your Ethereum address is: ${address}, balance: ${balance} ETH`); - } + async function setEthAddress() { + setStatus('Querying your address and balance'); + setSenderAddress(`Deriving address from path ${derivationPath}...`); + + const { address } = await Eth.deriveAddress(signedAccountId, derivationPath); + setSenderAddress(address); + + const balance = await Eth.getBalance(address); + setStatus(`Your Ethereum address is: ${address}, balance: ${balance} ETH`); + } + }, [signedAccountId, derivationPath]); async function chainSignature() { setStatus('🏗️ Creating transaction'); @@ -41,7 +50,7 @@ export function EthereumView({ props: { setStatus, wallet, MPC_CONTRACT } }) { setSignedTransaction(signedTransaction); setStatus(`✅ Signed payload ready to be relayed to the Ethereum network`); setStep('relay'); - } catch(e) { + } catch (e) { setStatus(`❌ Error: ${e.message}`); setLoading(false); } @@ -51,9 +60,12 @@ export function EthereumView({ props: { setStatus, wallet, MPC_CONTRACT } }) { setLoading(true); setStatus('🔗 Relaying transaction to the Ethereum network... this might take a while'); - try{ + try { const txHash = await Eth.relayTransaction(signedTransaction); - setStatus(`✅ Successful: https://sepolia.etherscan.io/tx/${txHash}`); + setStatus(<> + ✅ Successful + + ); } catch (e) { setStatus(`❌ Error: ${e.message}`); } @@ -62,12 +74,6 @@ export function EthereumView({ props: { setStatus, wallet, MPC_CONTRACT } }) { setLoading(false); } - const handleDerivationChange = (event) => { - setStatus('Derivation path changed'); - setSenderAddress('Waiting for you to stop typing...'); - setDerivation(event.target.value); - } - const UIChainSignature = async () => { setLoading(true); await chainSignature(); @@ -77,22 +83,22 @@ export function EthereumView({ props: { setStatus, wallet, MPC_CONTRACT } }) { return ( <>
- +
- + setDerivation(e.target.value)} disabled={loading} />
{senderAddress}
- setReceiver(e.target.value)} disabled={loading}/> + setReceiver(e.target.value)} disabled={loading} />
- setAmount(e.target.value)} step="0.01" disabled={loading}/> + setAmount(e.target.value)} step="0.01" disabled={loading} />
Ethereum units
@@ -108,7 +114,6 @@ export function EthereumView({ props: { setStatus, wallet, MPC_CONTRACT } }) { EthereumView.propTypes = { props: PropTypes.shape({ setStatus: PropTypes.func.isRequired, - wallet: PropTypes.object.isRequired, MPC_CONTRACT: PropTypes.string.isRequired, }).isRequired }; \ No newline at end of file diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index a3a2c55..513feba 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -1,27 +1,26 @@ -import logo from "../assets/logo-black.svg"; -import PropTypes from 'prop-types'; -import { Wallet } from "../services/near-wallet"; +import { useContext } from "react"; +import { NearContext } from "../context"; -const Navbar = ({wallet, isSignedIn}) =>{ - const signIn = () => { wallet.signIn() } - const signOut = () => { wallet.signOut() } - return(