From 8b3e0f00e00f80acb04762f0861711b9b3b5a7a6 Mon Sep 17 00:00:00 2001 From: Hendrik Hofstadt Date: Wed, 11 Aug 2021 13:48:15 +0200 Subject: [PATCH] Minor fixes and bidirectional transfers Change-Id: I7cbb02fe79b799c1ce350cee9c5b73ea17483385 --- bridge_ui/package-lock.json | 25 +- bridge_ui/package.json | 3 +- bridge_ui/src/components/Attest/Create.tsx | 153 +++--- bridge_ui/src/components/Attest/Send.tsx | 254 +++++----- bridge_ui/src/components/Attest/Source.tsx | 170 +++---- bridge_ui/src/components/Attest/Target.tsx | 130 ++--- bridge_ui/src/components/Attest/index.tsx | 140 +++--- .../src/components/EthereumSignerKey.tsx | 50 +- bridge_ui/src/components/KeyAndBalance.tsx | 64 +-- bridge_ui/src/components/SolanaWalletKey.tsx | 34 +- .../src/components/ToggleConnectedButton.tsx | 102 ++-- bridge_ui/src/components/Transfer/Redeem.tsx | 145 +++--- bridge_ui/src/components/Transfer/Send.tsx | 328 ++++++------- bridge_ui/src/components/Transfer/Source.tsx | 210 ++++---- bridge_ui/src/components/Transfer/Target.tsx | 130 ++--- bridge_ui/src/components/Transfer/index.tsx | 146 +++--- bridge_ui/src/hooks/useAttestSignedVAA.ts | 26 +- bridge_ui/src/hooks/useGetBalanceEffect.ts | 230 ++++----- bridge_ui/src/hooks/useTransferSignedVAA.ts | 26 +- bridge_ui/src/hooks/useWrappedAsset.ts | 148 +++--- bridge_ui/src/muiTheme.js | 62 +-- bridge_ui/src/react-app-env.d.ts | 2 +- bridge_ui/src/sdk/index.ts | 116 ++--- bridge_ui/src/store/attestSlice.ts | 210 ++++---- bridge_ui/src/store/index.ts | 30 +- bridge_ui/src/store/selectors.ts | 172 +++---- bridge_ui/src/store/transferSlice.ts | 256 +++++----- bridge_ui/src/utils/array.ts | 8 +- bridge_ui/src/utils/attestFrom.ts | 314 ++++++------ bridge_ui/src/utils/consts.ts | 98 ++-- bridge_ui/src/utils/createWrappedOn.ts | 132 +++-- bridge_ui/src/utils/getAttestedAsset.ts | 120 ++--- bridge_ui/src/utils/postVaa.ts | 111 +++++ bridge_ui/src/utils/redeemOn.ts | 104 +++- bridge_ui/src/utils/transferFrom.ts | 455 +++++++++--------- 35 files changed, 2459 insertions(+), 2245 deletions(-) create mode 100644 bridge_ui/src/utils/postVaa.ts diff --git a/bridge_ui/package-lock.json b/bridge_ui/package-lock.json index 8ecc63f8..207078b3 100644 --- a/bridge_ui/package-lock.json +++ b/bridge_ui/package-lock.json @@ -20,6 +20,7 @@ "@typechain/ethers-v5": "^7.0.1", "bridge": "file:rust_modules\\core", "ethers": "^5.4.1", + "prettier": "^2.3.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-redux": "^7.2.4", @@ -29467,7 +29468,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", - "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -35735,7 +35735,6 @@ "resolved": "https://registry.npmjs.org/wasm-dce/-/wasm-dce-1.0.2.tgz", "integrity": "sha512-Fq1+nu43ybsjSnBquLrW/cULmKs61qbv9k8ep13QUe0nABBezMoNAA+j6QY66MW0/eoDVDp1rjXDqQ2VKyS/Xg==", "dev": true, - "peer": true, "dependencies": { "@babel/core": "^7.0.0-beta.39", "@babel/traverse": "^7.0.0-beta.39", @@ -35749,7 +35748,6 @@ "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", "dev": true, - "peer": true, "bin": { "babylon": "bin/babylon.js" }, @@ -36585,7 +36583,6 @@ "resolved": "https://registry.npmjs.org/webassembly-floating-point-hex-parser/-/webassembly-floating-point-hex-parser-0.1.2.tgz", "integrity": "sha512-TUf1H++8U10+stJbFydnvrpG5Sznz5Rilez/oZlV5zI0C/e4cSxd8rALAJ8VpTvjVWxLmL3SVSJUK6Ap9AoiNg==", "dev": true, - "peer": true, "engines": { "node": "*" } @@ -36595,7 +36592,6 @@ "resolved": "https://registry.npmjs.org/webassembly-interpreter/-/webassembly-interpreter-0.0.30.tgz", "integrity": "sha512-+Jdy2piEvz9T5j751mOE8+rBO12p+nNW6Fg4kJZ+zP1oUfsm+151sbAbM8AFxWTURmWCGP+r8Lxwfv3pzN1bCQ==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0-beta.36", "long": "^3.2.0", @@ -36615,7 +36611,6 @@ "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", "dev": true, - "peer": true, "engines": { "node": ">=0.6" } @@ -46630,6 +46625,7 @@ "dev": true, "optional": true, "requires": { + "bitcore-lib": "^8.25.10", "unorm": "^1.4.1" } }, @@ -62340,8 +62336,7 @@ "prettier": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", - "peer": true + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==" }, "pretty-bytes": { "version": "5.6.0", @@ -67424,7 +67419,6 @@ "resolved": "https://registry.npmjs.org/wasm-dce/-/wasm-dce-1.0.2.tgz", "integrity": "sha512-Fq1+nu43ybsjSnBquLrW/cULmKs61qbv9k8ep13QUe0nABBezMoNAA+j6QY66MW0/eoDVDp1rjXDqQ2VKyS/Xg==", "dev": true, - "peer": true, "requires": { "@babel/core": "^7.0.0-beta.39", "@babel/traverse": "^7.0.0-beta.39", @@ -67437,8 +67431,7 @@ "version": "7.0.0-beta.47", "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", - "dev": true, - "peer": true + "dev": true } } }, @@ -67448,7 +67441,8 @@ "integrity": "sha512-R4s75XH+o8qM+WaRrAU9S2rbAMDzob18/S3V8R9ZoFpZkPWLAohWWlzWAp1ybeTkOuuku/X1zJtxiV0pBYxZww==", "dev": true, "requires": { - "loader-utils": "^1.1.0" + "loader-utils": "^1.1.0", + "wasm-dce": "^1.0.0" }, "dependencies": { "json5": { @@ -68170,15 +68164,13 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/webassembly-floating-point-hex-parser/-/webassembly-floating-point-hex-parser-0.1.2.tgz", "integrity": "sha512-TUf1H++8U10+stJbFydnvrpG5Sznz5Rilez/oZlV5zI0C/e4cSxd8rALAJ8VpTvjVWxLmL3SVSJUK6Ap9AoiNg==", - "dev": true, - "peer": true + "dev": true }, "webassembly-interpreter": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/webassembly-interpreter/-/webassembly-interpreter-0.0.30.tgz", "integrity": "sha512-+Jdy2piEvz9T5j751mOE8+rBO12p+nNW6Fg4kJZ+zP1oUfsm+151sbAbM8AFxWTURmWCGP+r8Lxwfv3pzN1bCQ==", "dev": true, - "peer": true, "requires": { "@babel/code-frame": "^7.0.0-beta.36", "long": "^3.2.0", @@ -68189,8 +68181,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", - "dev": true, - "peer": true + "dev": true } } }, diff --git a/bridge_ui/package.json b/bridge_ui/package.json index f7757224..030a3689 100644 --- a/bridge_ui/package.json +++ b/bridge_ui/package.json @@ -53,6 +53,7 @@ "@truffle/hdwallet-provider": "^1.4.1", "copy-dir": "^1.3.0", "truffle": "^5.4.1", - "wasm-loader": "^1.3.0" + "wasm-loader": "^1.3.0", + "prettier": "^2.3.2" } } diff --git a/bridge_ui/src/components/Attest/Create.tsx b/bridge_ui/src/components/Attest/Create.tsx index c595577a..466084eb 100644 --- a/bridge_ui/src/components/Attest/Create.tsx +++ b/bridge_ui/src/components/Attest/Create.tsx @@ -1,70 +1,83 @@ -import { Button, CircularProgress, makeStyles } from "@material-ui/core"; -import { useCallback } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { useSolanaWallet } from "../../contexts/SolanaWalletContext"; -import useAttestSignedVAA from "../../hooks/useAttestSignedVAA"; -import { setIsCreating } from "../../store/attestSlice"; -import { - selectAttestIsCreating, - selectAttestTargetChain, -} from "../../store/selectors"; -import { CHAIN_ID_SOLANA } from "../../utils/consts"; -import createWrappedOn, { - createWrappedOnSolana, -} from "../../utils/createWrappedOn"; - -const useStyles = makeStyles((theme) => ({ - transferButton: { - marginTop: theme.spacing(2), - textTransform: "none", - width: "100%", - }, -})); - -function Create() { - const dispatch = useDispatch(); - const classes = useStyles(); - const targetChain = useSelector(selectAttestTargetChain); - const { wallet } = useSolanaWallet(); - const solPK = wallet?.publicKey; - const signedVAA = useAttestSignedVAA(); - const isCreating = useSelector(selectAttestIsCreating); - const handleCreateClick = useCallback(() => { - if ( - targetChain === CHAIN_ID_SOLANA && - createWrappedOn[targetChain] === createWrappedOnSolana && - signedVAA - ) { - dispatch(setIsCreating(true)); - createWrappedOnSolana(wallet, solPK?.toString(), signedVAA); - } - }, [dispatch, targetChain, wallet, solPK, signedVAA]); - return ( -
- - {isCreating ? ( - - ) : null} -
- ); -} - -export default Create; +import { Button, CircularProgress, makeStyles } from "@material-ui/core"; +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useSolanaWallet } from "../../contexts/SolanaWalletContext"; +import useAttestSignedVAA from "../../hooks/useAttestSignedVAA"; +import { setIsCreating } from "../../store/attestSlice"; +import { + selectAttestIsCreating, + selectAttestTargetChain, +} from "../../store/selectors"; +import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../../utils/consts"; +import createWrappedOn, { + createWrappedOnEth, + createWrappedOnSolana, +} from "../../utils/createWrappedOn"; +import { useEthereumProvider } from "../../contexts/EthereumProviderContext"; + +const useStyles = makeStyles((theme) => ({ + transferButton: { + marginTop: theme.spacing(2), + textTransform: "none", + width: "100%", + }, +})); + +function Create() { + const dispatch = useDispatch(); + const classes = useStyles(); + const targetChain = useSelector(selectAttestTargetChain); + const { wallet } = useSolanaWallet(); + const solPK = wallet?.publicKey; + const signedVAA = useAttestSignedVAA(); + const isCreating = useSelector(selectAttestIsCreating); + const { provider, signer } = useEthereumProvider(); + const handleCreateClick = useCallback(() => { + if ( + targetChain === CHAIN_ID_SOLANA && + createWrappedOn[targetChain] === createWrappedOnSolana && + signedVAA + ) { + dispatch(setIsCreating(true)); + createWrappedOnSolana(wallet, solPK?.toString(), signedVAA); + } + if ( + targetChain === CHAIN_ID_ETH && + createWrappedOn[targetChain] === createWrappedOnEth && + signedVAA + ) { + (async () => { + dispatch(setIsCreating(true)); + createWrappedOnEth(provider, signer, signedVAA); + })(); + } + }, [dispatch, targetChain, wallet, solPK, signedVAA]); + return ( +
+ + {isCreating ? ( + + ) : null} +
+ ); +} + +export default Create; diff --git a/bridge_ui/src/components/Attest/Send.tsx b/bridge_ui/src/components/Attest/Send.tsx index 4b9186db..6a475562 100644 --- a/bridge_ui/src/components/Attest/Send.tsx +++ b/bridge_ui/src/components/Attest/Send.tsx @@ -1,127 +1,127 @@ -import { Button, CircularProgress, makeStyles } from "@material-ui/core"; -import { useCallback } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { useEthereumProvider } from "../../contexts/EthereumProviderContext"; -import { useSolanaWallet } from "../../contexts/SolanaWalletContext"; -import useWrappedAsset from "../../hooks/useWrappedAsset"; -import { setIsSending, setSignedVAAHex } from "../../store/attestSlice"; -import { - selectAttestIsSendComplete, - selectAttestIsSending, - selectAttestIsTargetComplete, - selectAttestSourceAsset, - selectAttestSourceChain, - selectAttestTargetChain, -} from "../../store/selectors"; -import { uint8ArrayToHex } from "../../utils/array"; -import attestFrom, { - attestFromEth, - attestFromSolana, -} from "../../utils/attestFrom"; -import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../../utils/consts"; - -const useStyles = makeStyles((theme) => ({ - transferButton: { - marginTop: theme.spacing(2), - textTransform: "none", - width: "100%", - }, -})); - -// TODO: move attest to its own workflow - -function Send() { - const classes = useStyles(); - const dispatch = useDispatch(); - const sourceChain = useSelector(selectAttestSourceChain); - const sourceAsset = useSelector(selectAttestSourceAsset); - const targetChain = useSelector(selectAttestTargetChain); - const isTargetComplete = useSelector(selectAttestIsTargetComplete); - const isSending = useSelector(selectAttestIsSending); - const isSendComplete = useSelector(selectAttestIsSendComplete); - const { provider, signer } = useEthereumProvider(); - const { wallet } = useSolanaWallet(); - const solPK = wallet?.publicKey; - const { - isLoading: isCheckingWrapped, - // isWrapped, - wrappedAsset, - } = useWrappedAsset(targetChain, sourceChain, sourceAsset, provider); - // TODO: check this and send to separate flow - const isWrapped = true; - console.log(isCheckingWrapped, isWrapped, wrappedAsset); - // TODO: dynamically get "to" wallet - const handleAttestClick = useCallback(() => { - // TODO: more generic way of calling these - if (attestFrom[sourceChain]) { - if ( - sourceChain === CHAIN_ID_ETH && - attestFrom[sourceChain] === attestFromEth - ) { - //TODO: just for testing, this should eventually use the store to communicate between steps - (async () => { - dispatch(setIsSending(true)); - try { - const vaaBytes = await attestFromEth(provider, signer, sourceAsset); - console.log("bytes in attest", vaaBytes); - vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes))); - } catch (e) { - console.error(e); - dispatch(setIsSending(false)); - } - })(); - } - if ( - sourceChain === CHAIN_ID_SOLANA && - attestFrom[sourceChain] === attestFromSolana - ) { - //TODO: just for testing, this should eventually use the store to communicate between steps - (async () => { - dispatch(setIsSending(true)); - try { - const vaaBytes = await attestFromSolana( - wallet, - solPK?.toString(), - sourceAsset - ); - console.log("bytes in attest", vaaBytes); - vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes))); - } catch (e) { - console.error(e); - dispatch(setIsSending(false)); - } - })(); - } - } - }, [dispatch, sourceChain, provider, signer, wallet, solPK, sourceAsset]); - return ( - <> -
- - {isSending ? ( - - ) : null} -
- - ); -} - -export default Send; +import { Button, CircularProgress, makeStyles } from "@material-ui/core"; +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useEthereumProvider } from "../../contexts/EthereumProviderContext"; +import { useSolanaWallet } from "../../contexts/SolanaWalletContext"; +import useWrappedAsset from "../../hooks/useWrappedAsset"; +import { setIsSending, setSignedVAAHex } from "../../store/attestSlice"; +import { + selectAttestIsSendComplete, + selectAttestIsSending, + selectAttestIsTargetComplete, + selectAttestSourceAsset, + selectAttestSourceChain, + selectAttestTargetChain, +} from "../../store/selectors"; +import { uint8ArrayToHex } from "../../utils/array"; +import attestFrom, { + attestFromEth, + attestFromSolana, +} from "../../utils/attestFrom"; +import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../../utils/consts"; + +const useStyles = makeStyles((theme) => ({ + transferButton: { + marginTop: theme.spacing(2), + textTransform: "none", + width: "100%", + }, +})); + +// TODO: move attest to its own workflow + +function Send() { + const classes = useStyles(); + const dispatch = useDispatch(); + const sourceChain = useSelector(selectAttestSourceChain); + const sourceAsset = useSelector(selectAttestSourceAsset); + const targetChain = useSelector(selectAttestTargetChain); + const isTargetComplete = useSelector(selectAttestIsTargetComplete); + const isSending = useSelector(selectAttestIsSending); + const isSendComplete = useSelector(selectAttestIsSendComplete); + const { provider, signer } = useEthereumProvider(); + const { wallet } = useSolanaWallet(); + const solPK = wallet?.publicKey; + const { + isLoading: isCheckingWrapped, + // isWrapped, + wrappedAsset, + } = useWrappedAsset(targetChain, sourceChain, sourceAsset, provider); + // TODO: check this and send to separate flow + const isWrapped = true; + console.log(isCheckingWrapped, isWrapped, wrappedAsset); + // TODO: dynamically get "to" wallet + const handleAttestClick = useCallback(() => { + // TODO: more generic way of calling these + if (attestFrom[sourceChain]) { + if ( + sourceChain === CHAIN_ID_ETH && + attestFrom[sourceChain] === attestFromEth + ) { + //TODO: just for testing, this should eventually use the store to communicate between steps + (async () => { + dispatch(setIsSending(true)); + try { + const vaaBytes = await attestFromEth(provider, signer, sourceAsset); + console.log("bytes in attest", vaaBytes); + vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes))); + } catch (e) { + console.error(e); + dispatch(setIsSending(false)); + } + })(); + } + if ( + sourceChain === CHAIN_ID_SOLANA && + attestFrom[sourceChain] === attestFromSolana + ) { + //TODO: just for testing, this should eventually use the store to communicate between steps + (async () => { + dispatch(setIsSending(true)); + try { + const vaaBytes = await attestFromSolana( + wallet, + solPK?.toString(), + sourceAsset + ); + console.log("bytes in attest", vaaBytes); + vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes))); + } catch (e) { + console.error(e); + dispatch(setIsSending(false)); + } + })(); + } + } + }, [dispatch, sourceChain, provider, signer, wallet, solPK, sourceAsset]); + return ( + <> +
+ + {isSending ? ( + + ) : null} +
+ + ); +} + +export default Send; diff --git a/bridge_ui/src/components/Attest/Source.tsx b/bridge_ui/src/components/Attest/Source.tsx index a3a22452..4630ac83 100644 --- a/bridge_ui/src/components/Attest/Source.tsx +++ b/bridge_ui/src/components/Attest/Source.tsx @@ -1,85 +1,85 @@ -import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core"; -import { useCallback } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { - selectAttestIsSourceComplete, - selectAttestShouldLockFields, - selectAttestSourceAsset, - selectAttestSourceChain, -} from "../../store/selectors"; -import { - incrementStep, - setSourceAsset, - setSourceChain, -} from "../../store/attestSlice"; -import { CHAINS } from "../../utils/consts"; -import KeyAndBalance from "../KeyAndBalance"; - -const useStyles = makeStyles((theme) => ({ - transferField: { - marginTop: theme.spacing(5), - }, -})); - -function Source() { - const classes = useStyles(); - const dispatch = useDispatch(); - const sourceChain = useSelector(selectAttestSourceChain); - const sourceAsset = useSelector(selectAttestSourceAsset); - const isSourceComplete = useSelector(selectAttestIsSourceComplete); - const shouldLockFields = useSelector(selectAttestShouldLockFields); - const handleSourceChange = useCallback( - (event) => { - dispatch(setSourceChain(event.target.value)); - }, - [dispatch] - ); - const handleAssetChange = useCallback( - (event) => { - dispatch(setSourceAsset(event.target.value)); - }, - [dispatch] - ); - const handleNextClick = useCallback( - (event) => { - dispatch(incrementStep()); - }, - [dispatch] - ); - return ( - <> - - {CHAINS.map(({ id, name }) => ( - - {name} - - ))} - - - - - - ); -} - -export default Source; +import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core"; +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { + selectAttestIsSourceComplete, + selectAttestShouldLockFields, + selectAttestSourceAsset, + selectAttestSourceChain, +} from "../../store/selectors"; +import { + incrementStep, + setSourceAsset, + setSourceChain, +} from "../../store/attestSlice"; +import { CHAINS } from "../../utils/consts"; +import KeyAndBalance from "../KeyAndBalance"; + +const useStyles = makeStyles((theme) => ({ + transferField: { + marginTop: theme.spacing(5), + }, +})); + +function Source() { + const classes = useStyles(); + const dispatch = useDispatch(); + const sourceChain = useSelector(selectAttestSourceChain); + const sourceAsset = useSelector(selectAttestSourceAsset); + const isSourceComplete = useSelector(selectAttestIsSourceComplete); + const shouldLockFields = useSelector(selectAttestShouldLockFields); + const handleSourceChange = useCallback( + (event) => { + dispatch(setSourceChain(event.target.value)); + }, + [dispatch] + ); + const handleAssetChange = useCallback( + (event) => { + dispatch(setSourceAsset(event.target.value)); + }, + [dispatch] + ); + const handleNextClick = useCallback( + (event) => { + dispatch(incrementStep()); + }, + [dispatch] + ); + return ( + <> + + {CHAINS.map(({ id, name }) => ( + + {name} + + ))} + + + + + + ); +} + +export default Source; diff --git a/bridge_ui/src/components/Attest/Target.tsx b/bridge_ui/src/components/Attest/Target.tsx index 9bda1e38..5eeb7ed6 100644 --- a/bridge_ui/src/components/Attest/Target.tsx +++ b/bridge_ui/src/components/Attest/Target.tsx @@ -1,65 +1,65 @@ -import { Button, MenuItem, TextField } from "@material-ui/core"; -import { useCallback, useMemo } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { - selectAttestIsTargetComplete, - selectAttestShouldLockFields, - selectAttestSourceChain, - selectAttestTargetChain, -} from "../../store/selectors"; -import { incrementStep, setTargetChain } from "../../store/attestSlice"; -import { CHAINS } from "../../utils/consts"; -import KeyAndBalance from "../KeyAndBalance"; - -function Target() { - const dispatch = useDispatch(); - const sourceChain = useSelector(selectAttestSourceChain); - const chains = useMemo( - () => CHAINS.filter((c) => c.id !== sourceChain), - [sourceChain] - ); - const targetChain = useSelector(selectAttestTargetChain); - const isTargetComplete = useSelector(selectAttestIsTargetComplete); - const shouldLockFields = useSelector(selectAttestShouldLockFields); - const handleTargetChange = useCallback( - (event) => { - dispatch(setTargetChain(event.target.value)); - }, - [dispatch] - ); - const handleNextClick = useCallback( - (event) => { - dispatch(incrementStep()); - }, - [dispatch] - ); - return ( - <> - - {chains.map(({ id, name }) => ( - - {name} - - ))} - - {/* TODO: determine "to" token address */} - - - - ); -} - -export default Target; +import { Button, MenuItem, TextField } from "@material-ui/core"; +import { useCallback, useMemo } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { + selectAttestIsTargetComplete, + selectAttestShouldLockFields, + selectAttestSourceChain, + selectAttestTargetChain, +} from "../../store/selectors"; +import { incrementStep, setTargetChain } from "../../store/attestSlice"; +import { CHAINS } from "../../utils/consts"; +import KeyAndBalance from "../KeyAndBalance"; + +function Target() { + const dispatch = useDispatch(); + const sourceChain = useSelector(selectAttestSourceChain); + const chains = useMemo( + () => CHAINS.filter((c) => c.id !== sourceChain), + [sourceChain] + ); + const targetChain = useSelector(selectAttestTargetChain); + const isTargetComplete = useSelector(selectAttestIsTargetComplete); + const shouldLockFields = useSelector(selectAttestShouldLockFields); + const handleTargetChange = useCallback( + (event) => { + dispatch(setTargetChain(event.target.value)); + }, + [dispatch] + ); + const handleNextClick = useCallback( + (event) => { + dispatch(incrementStep()); + }, + [dispatch] + ); + return ( + <> + + {chains.map(({ id, name }) => ( + + {name} + + ))} + + {/* TODO: determine "to" token address */} + + + + ); +} + +export default Target; diff --git a/bridge_ui/src/components/Attest/index.tsx b/bridge_ui/src/components/Attest/index.tsx index 34444623..34e0d63b 100644 --- a/bridge_ui/src/components/Attest/index.tsx +++ b/bridge_ui/src/components/Attest/index.tsx @@ -1,70 +1,70 @@ -import { - Container, - Step, - StepButton, - StepContent, - Stepper, -} from "@material-ui/core"; -import { useDispatch, useSelector } from "react-redux"; -import useGetBalanceEffect from "../../hooks/useGetBalanceEffect"; -import { - selectAttestActiveStep, - selectAttestSignedVAAHex, -} from "../../store/selectors"; -import { setStep } from "../../store/attestSlice"; -import Create from "./Create"; -import Send from "./Send"; -import Source from "./Source"; -import Target from "./Target"; - -// TODO: ensure that both wallets are connected to the same known network - -function Attest() { - useGetBalanceEffect(); - const dispatch = useDispatch(); - const activeStep = useSelector(selectAttestActiveStep); - const signedVAAHex = useSelector(selectAttestSignedVAAHex); - return ( - - - - dispatch(setStep(0))}> - Select a source - - - - - - - dispatch(setStep(1))}> - Select a target - - - - - - - dispatch(setStep(2))}> - Send attestation - - - - - - - dispatch(setStep(3))} - disabled={!signedVAAHex} - > - Create wrapper - - - - - - - - ); -} - -export default Attest; +import { + Container, + Step, + StepButton, + StepContent, + Stepper, +} from "@material-ui/core"; +import { useDispatch, useSelector } from "react-redux"; +import useGetBalanceEffect from "../../hooks/useGetBalanceEffect"; +import { + selectAttestActiveStep, + selectAttestSignedVAAHex, +} from "../../store/selectors"; +import { setStep } from "../../store/attestSlice"; +import Create from "./Create"; +import Send from "./Send"; +import Source from "./Source"; +import Target from "./Target"; + +// TODO: ensure that both wallets are connected to the same known network + +function Attest() { + useGetBalanceEffect(); + const dispatch = useDispatch(); + const activeStep = useSelector(selectAttestActiveStep); + const signedVAAHex = useSelector(selectAttestSignedVAAHex); + return ( + + + + dispatch(setStep(0))}> + Select a source + + + + + + + dispatch(setStep(1))}> + Select a target + + + + + + + dispatch(setStep(2))}> + Send attestation + + + + + + + dispatch(setStep(3))} + disabled={!signedVAAHex} + > + Create wrapper + + + + + + + + ); +} + +export default Attest; diff --git a/bridge_ui/src/components/EthereumSignerKey.tsx b/bridge_ui/src/components/EthereumSignerKey.tsx index 09d8a462..f77c8006 100644 --- a/bridge_ui/src/components/EthereumSignerKey.tsx +++ b/bridge_ui/src/components/EthereumSignerKey.tsx @@ -1,25 +1,25 @@ -import { Typography } from "@material-ui/core"; -import { useEthereumProvider } from "../contexts/EthereumProviderContext"; -import ToggleConnectedButton from "./ToggleConnectedButton"; - -const EthereumSignerKey = () => { - const { connect, disconnect, signerAddress, providerError } = - useEthereumProvider(); - return ( - <> - - {providerError ? ( - - {providerError} - - ) : null} - - ); -}; - -export default EthereumSignerKey; +import { Typography } from "@material-ui/core"; +import { useEthereumProvider } from "../contexts/EthereumProviderContext"; +import ToggleConnectedButton from "./ToggleConnectedButton"; + +const EthereumSignerKey = () => { + const { connect, disconnect, signerAddress, providerError } = + useEthereumProvider(); + return ( + <> + + {providerError ? ( + + {providerError} + + ) : null} + + ); +}; + +export default EthereumSignerKey; diff --git a/bridge_ui/src/components/KeyAndBalance.tsx b/bridge_ui/src/components/KeyAndBalance.tsx index 7835e5f0..8d07beb0 100644 --- a/bridge_ui/src/components/KeyAndBalance.tsx +++ b/bridge_ui/src/components/KeyAndBalance.tsx @@ -1,32 +1,32 @@ -import { Typography } from "@material-ui/core"; -import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts"; -import EthereumSignerKey from "./EthereumSignerKey"; -import SolanaWalletKey from "./SolanaWalletKey"; - -function KeyAndBalance({ - chainId, - balance, -}: { - chainId: ChainId; - balance?: string; -}) { - if (chainId === CHAIN_ID_ETH) { - return ( - <> - - {balance} - - ); - } - if (chainId === CHAIN_ID_SOLANA) { - return ( - <> - - {balance} - - ); - } - return null; -} - -export default KeyAndBalance; +import { Typography } from "@material-ui/core"; +import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts"; +import EthereumSignerKey from "./EthereumSignerKey"; +import SolanaWalletKey from "./SolanaWalletKey"; + +function KeyAndBalance({ + chainId, + balance, +}: { + chainId: ChainId; + balance?: string; +}) { + if (chainId === CHAIN_ID_ETH) { + return ( + <> + + {balance} + + ); + } + if (chainId === CHAIN_ID_SOLANA) { + return ( + <> + + {balance} + + ); + } + return null; +} + +export default KeyAndBalance; diff --git a/bridge_ui/src/components/SolanaWalletKey.tsx b/bridge_ui/src/components/SolanaWalletKey.tsx index 10ec46fa..6056720f 100644 --- a/bridge_ui/src/components/SolanaWalletKey.tsx +++ b/bridge_ui/src/components/SolanaWalletKey.tsx @@ -1,17 +1,17 @@ -import { useSolanaWallet } from "../contexts/SolanaWalletContext"; -import ToggleConnectedButton from "./ToggleConnectedButton"; - -const SolanaWalletKey = () => { - const { connect, disconnect, connected, wallet } = useSolanaWallet(); - const pk = wallet?.publicKey?.toString() || ""; - return ( - - ); -}; - -export default SolanaWalletKey; +import { useSolanaWallet } from "../contexts/SolanaWalletContext"; +import ToggleConnectedButton from "./ToggleConnectedButton"; + +const SolanaWalletKey = () => { + const { connect, disconnect, connected, wallet } = useSolanaWallet(); + const pk = wallet?.publicKey?.toString() || ""; + return ( + + ); +}; + +export default SolanaWalletKey; diff --git a/bridge_ui/src/components/ToggleConnectedButton.tsx b/bridge_ui/src/components/ToggleConnectedButton.tsx index 3cfae25b..529ab278 100644 --- a/bridge_ui/src/components/ToggleConnectedButton.tsx +++ b/bridge_ui/src/components/ToggleConnectedButton.tsx @@ -1,51 +1,51 @@ -import { Button, makeStyles, Tooltip } from "@material-ui/core"; - -const useStyles = makeStyles((theme) => ({ - button: { - display: "block", - margin: `${theme.spacing(1)}px auto`, - width: "100%", - maxWidth: 400, - }, -})); - -const ToggleConnectedButton = ({ - connect, - disconnect, - connected, - pk, -}: { - connect(): any; - disconnect(): any; - connected: boolean; - pk: string; -}) => { - const classes = useStyles(); - const is0x = pk.startsWith("0x"); - return connected ? ( - - - - ) : ( - - ); -}; - -export default ToggleConnectedButton; +import { Button, makeStyles, Tooltip } from "@material-ui/core"; + +const useStyles = makeStyles((theme) => ({ + button: { + display: "block", + margin: `${theme.spacing(1)}px auto`, + width: "100%", + maxWidth: 400, + }, +})); + +const ToggleConnectedButton = ({ + connect, + disconnect, + connected, + pk, +}: { + connect(): any; + disconnect(): any; + connected: boolean; + pk: string; +}) => { + const classes = useStyles(); + const is0x = pk.startsWith("0x"); + return connected ? ( + + + + ) : ( + + ); +}; + +export default ToggleConnectedButton; diff --git a/bridge_ui/src/components/Transfer/Redeem.tsx b/bridge_ui/src/components/Transfer/Redeem.tsx index bac87ec6..be408504 100644 --- a/bridge_ui/src/components/Transfer/Redeem.tsx +++ b/bridge_ui/src/components/Transfer/Redeem.tsx @@ -1,67 +1,78 @@ -import { Button, CircularProgress, makeStyles } from "@material-ui/core"; -import { useCallback } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { useEthereumProvider } from "../../contexts/EthereumProviderContext"; -import useTransferSignedVAA from "../../hooks/useTransferSignedVAA"; -import { - selectTransferIsRedeeming, - selectTransferTargetChain, -} from "../../store/selectors"; -import { setIsRedeeming } from "../../store/transferSlice"; -import { CHAIN_ID_ETH } from "../../utils/consts"; -import redeemOn, { redeemOnEth } from "../../utils/redeemOn"; - -const useStyles = makeStyles((theme) => ({ - transferButton: { - marginTop: theme.spacing(2), - textTransform: "none", - width: "100%", - }, -})); - -function Redeem() { - const dispatch = useDispatch(); - const classes = useStyles(); - const targetChain = useSelector(selectTransferTargetChain); - const { provider, signer } = useEthereumProvider(); - const signedVAA = useTransferSignedVAA(); - const isRedeeming = useSelector(selectTransferIsRedeeming); - const handleRedeemClick = useCallback(() => { - if ( - targetChain === CHAIN_ID_ETH && - redeemOn[targetChain] === redeemOnEth && - signedVAA - ) { - dispatch(setIsRedeeming(true)); - redeemOnEth(provider, signer, signedVAA); - } - }, [dispatch, targetChain, provider, signer, signedVAA]); - return ( -
- - {isRedeeming ? ( - - ) : null} -
- ); -} - -export default Redeem; +import { Button, CircularProgress, makeStyles } from "@material-ui/core"; +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useEthereumProvider } from "../../contexts/EthereumProviderContext"; +import useTransferSignedVAA from "../../hooks/useTransferSignedVAA"; +import { + selectTransferIsRedeeming, + selectTransferTargetChain, +} from "../../store/selectors"; +import { setIsRedeeming } from "../../store/transferSlice"; +import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../../utils/consts"; +import redeemOn, { redeemOnEth, redeemOnSolana } from "../../utils/redeemOn"; +import { useSolanaWallet } from "../../contexts/SolanaWalletContext"; + +const useStyles = makeStyles((theme) => ({ + transferButton: { + marginTop: theme.spacing(2), + textTransform: "none", + width: "100%", + }, +})); + +function Redeem() { + const dispatch = useDispatch(); + const classes = useStyles(); + const targetChain = useSelector(selectTransferTargetChain); + const { wallet } = useSolanaWallet(); + const solPK = wallet?.publicKey; + const { provider, signer } = useEthereumProvider(); + const signedVAA = useTransferSignedVAA(); + const isRedeeming = useSelector(selectTransferIsRedeeming); + const handleRedeemClick = useCallback(() => { + if ( + targetChain === CHAIN_ID_ETH && + redeemOn[targetChain] === redeemOnEth && + signedVAA + ) { + dispatch(setIsRedeeming(true)); + redeemOnEth(provider, signer, signedVAA); + } + if ( + targetChain === CHAIN_ID_SOLANA && + redeemOn[targetChain] === redeemOnSolana && + signedVAA + ) { + dispatch(setIsRedeeming(true)); + redeemOnSolana(wallet, solPK?.toString(), signedVAA); + } + }, [dispatch, targetChain, provider, signer, signedVAA]); + return ( +
+ + {isRedeeming ? ( + + ) : null} +
+ ); +} + +export default Redeem; diff --git a/bridge_ui/src/components/Transfer/Send.tsx b/bridge_ui/src/components/Transfer/Send.tsx index bc1fb1b9..a88e4221 100644 --- a/bridge_ui/src/components/Transfer/Send.tsx +++ b/bridge_ui/src/components/Transfer/Send.tsx @@ -1,164 +1,164 @@ -import { Button, CircularProgress, makeStyles } from "@material-ui/core"; -import { useCallback } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { useEthereumProvider } from "../../contexts/EthereumProviderContext"; -import { useSolanaWallet } from "../../contexts/SolanaWalletContext"; -import useWrappedAsset from "../../hooks/useWrappedAsset"; -import { - selectTransferAmount, - selectTransferIsSendComplete, - selectTransferIsSending, - selectTransferIsTargetComplete, - selectTransferSourceAsset, - selectTransferSourceChain, - selectTransferSourceParsedTokenAccount, - selectTransferTargetChain, -} from "../../store/selectors"; -import { setIsSending, setSignedVAAHex } from "../../store/transferSlice"; -import { uint8ArrayToHex } from "../../utils/array"; -import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../../utils/consts"; -import transferFrom, { - transferFromEth, - transferFromSolana, -} from "../../utils/transferFrom"; - -const useStyles = makeStyles((theme) => ({ - transferButton: { - marginTop: theme.spacing(2), - textTransform: "none", - width: "100%", - }, -})); - -// TODO: move attest to its own workflow - -function Send() { - const classes = useStyles(); - const dispatch = useDispatch(); - const sourceChain = useSelector(selectTransferSourceChain); - const sourceAsset = useSelector(selectTransferSourceAsset); - const amount = useSelector(selectTransferAmount); - const targetChain = useSelector(selectTransferTargetChain); - const isTargetComplete = useSelector(selectTransferIsTargetComplete); - const isSending = useSelector(selectTransferIsSending); - const isSendComplete = useSelector(selectTransferIsSendComplete); - const { provider, signer, signerAddress } = useEthereumProvider(); - const { wallet } = useSolanaWallet(); - const solPK = wallet?.publicKey; - const sourceParsedTokenAccount = useSelector( - selectTransferSourceParsedTokenAccount - ); - const tokenPK = sourceParsedTokenAccount?.publicKey; - const decimals = sourceParsedTokenAccount?.decimals; - const { - isLoading: isCheckingWrapped, - // isWrapped, - wrappedAsset, - } = useWrappedAsset(targetChain, sourceChain, sourceAsset, provider); - // TODO: check this and send to separate flow - const isWrapped = true; - console.log(isCheckingWrapped, isWrapped, wrappedAsset); - // TODO: dynamically get "to" wallet - const handleTransferClick = useCallback(() => { - // TODO: we should separate state for transaction vs fetching vaa - // TODO: more generic way of calling these - if (transferFrom[sourceChain]) { - if ( - sourceChain === CHAIN_ID_ETH && - transferFrom[sourceChain] === transferFromEth && - decimals - ) { - //TODO: just for testing, this should eventually use the store to communicate between steps - (async () => { - dispatch(setIsSending(true)); - try { - const vaaBytes = await transferFromEth( - provider, - signer, - sourceAsset, - decimals, - amount, - targetChain, - solPK?.toBytes() - ); - console.log("bytes in transfer", vaaBytes); - vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes))); - } catch (e) { - console.error(e); - dispatch(setIsSending(false)); - } - })(); - } - if ( - sourceChain === CHAIN_ID_SOLANA && - transferFrom[sourceChain] === transferFromSolana && - decimals - ) { - //TODO: just for testing, this should eventually use the store to communicate between steps - (async () => { - dispatch(setIsSending(true)); - try { - const vaaBytes = await transferFromSolana( - wallet, - solPK?.toString(), - tokenPK, - sourceAsset, - amount, - decimals, - signerAddress, - targetChain - ); - console.log("bytes in transfer", vaaBytes); - vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes))); - } catch (e) { - console.error(e); - dispatch(setIsSending(false)); - } - })(); - } - } - }, [ - dispatch, - sourceChain, - provider, - signer, - signerAddress, - wallet, - solPK, - tokenPK, - sourceAsset, - amount, - decimals, - targetChain, - ]); - return ( - <> -
- - {isSending ? ( - - ) : null} -
- - ); -} - -export default Send; +import { Button, CircularProgress, makeStyles } from "@material-ui/core"; +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useEthereumProvider } from "../../contexts/EthereumProviderContext"; +import { useSolanaWallet } from "../../contexts/SolanaWalletContext"; +import useWrappedAsset from "../../hooks/useWrappedAsset"; +import { + selectTransferAmount, + selectTransferIsSendComplete, + selectTransferIsSending, + selectTransferIsTargetComplete, + selectTransferSourceAsset, + selectTransferSourceChain, + selectTransferSourceParsedTokenAccount, + selectTransferTargetChain, +} from "../../store/selectors"; +import { setIsSending, setSignedVAAHex } from "../../store/transferSlice"; +import { uint8ArrayToHex } from "../../utils/array"; +import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../../utils/consts"; +import transferFrom, { + transferFromEth, + transferFromSolana, +} from "../../utils/transferFrom"; + +const useStyles = makeStyles((theme) => ({ + transferButton: { + marginTop: theme.spacing(2), + textTransform: "none", + width: "100%", + }, +})); + +// TODO: move attest to its own workflow + +function Send() { + const classes = useStyles(); + const dispatch = useDispatch(); + const sourceChain = useSelector(selectTransferSourceChain); + const sourceAsset = useSelector(selectTransferSourceAsset); + const amount = useSelector(selectTransferAmount); + const targetChain = useSelector(selectTransferTargetChain); + const isTargetComplete = useSelector(selectTransferIsTargetComplete); + const isSending = useSelector(selectTransferIsSending); + const isSendComplete = useSelector(selectTransferIsSendComplete); + const { provider, signer, signerAddress } = useEthereumProvider(); + const { wallet } = useSolanaWallet(); + const solPK = wallet?.publicKey; + const sourceParsedTokenAccount = useSelector( + selectTransferSourceParsedTokenAccount + ); + const tokenPK = sourceParsedTokenAccount?.publicKey; + const decimals = sourceParsedTokenAccount?.decimals; + const { + isLoading: isCheckingWrapped, + // isWrapped, + wrappedAsset, + } = useWrappedAsset(targetChain, sourceChain, sourceAsset, provider); + // TODO: check this and send to separate flow + const isWrapped = true; + console.log(isCheckingWrapped, isWrapped, wrappedAsset); + // TODO: dynamically get "to" wallet + const handleTransferClick = useCallback(() => { + // TODO: we should separate state for transaction vs fetching vaa + // TODO: more generic way of calling these + if (transferFrom[sourceChain]) { + if ( + sourceChain === CHAIN_ID_ETH && + transferFrom[sourceChain] === transferFromEth && + decimals + ) { + //TODO: just for testing, this should eventually use the store to communicate between steps + (async () => { + dispatch(setIsSending(true)); + try { + const vaaBytes = await transferFromEth( + provider, + signer, + sourceAsset, + decimals, + amount, + targetChain, + solPK?.toBytes() + ); + console.log("bytes in transfer", vaaBytes); + vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes))); + } catch (e) { + console.error(e); + dispatch(setIsSending(false)); + } + })(); + } + if ( + sourceChain === CHAIN_ID_SOLANA && + transferFrom[sourceChain] === transferFromSolana && + decimals + ) { + //TODO: just for testing, this should eventually use the store to communicate between steps + (async () => { + dispatch(setIsSending(true)); + try { + const vaaBytes = await transferFromSolana( + wallet, + solPK?.toString(), + tokenPK, + sourceAsset, + amount, + decimals, + signerAddress, + targetChain + ); + console.log("bytes in transfer", vaaBytes); + vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes))); + } catch (e) { + console.error(e); + dispatch(setIsSending(false)); + } + })(); + } + } + }, [ + dispatch, + sourceChain, + provider, + signer, + signerAddress, + wallet, + solPK, + tokenPK, + sourceAsset, + amount, + decimals, + targetChain, + ]); + return ( + <> +
+ + {isSending ? ( + + ) : null} +
+ + ); +} + +export default Send; diff --git a/bridge_ui/src/components/Transfer/Source.tsx b/bridge_ui/src/components/Transfer/Source.tsx index f9fa8f2c..09544706 100644 --- a/bridge_ui/src/components/Transfer/Source.tsx +++ b/bridge_ui/src/components/Transfer/Source.tsx @@ -1,105 +1,105 @@ -import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core"; -import { useCallback } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { - selectTransferAmount, - selectTransferIsSourceComplete, - selectTransferShouldLockFields, - selectTransferSourceAsset, - selectTransferSourceBalanceString, - selectTransferSourceChain, -} from "../../store/selectors"; -import { - incrementStep, - setAmount, - setSourceAsset, - setSourceChain, -} from "../../store/transferSlice"; -import { CHAINS } from "../../utils/consts"; -import KeyAndBalance from "../KeyAndBalance"; - -const useStyles = makeStyles((theme) => ({ - transferField: { - marginTop: theme.spacing(5), - }, -})); - -function Source() { - const classes = useStyles(); - const dispatch = useDispatch(); - const sourceChain = useSelector(selectTransferSourceChain); - const sourceAsset = useSelector(selectTransferSourceAsset); - const uiAmountString = useSelector(selectTransferSourceBalanceString); - const amount = useSelector(selectTransferAmount); - const isSourceComplete = useSelector(selectTransferIsSourceComplete); - const shouldLockFields = useSelector(selectTransferShouldLockFields); - const handleSourceChange = useCallback( - (event) => { - dispatch(setSourceChain(event.target.value)); - }, - [dispatch] - ); - const handleAssetChange = useCallback( - (event) => { - dispatch(setSourceAsset(event.target.value)); - }, - [dispatch] - ); - const handleAmountChange = useCallback( - (event) => { - dispatch(setAmount(event.target.value)); - }, - [dispatch] - ); - const handleNextClick = useCallback( - (event) => { - dispatch(incrementStep()); - }, - [dispatch] - ); - return ( - <> - - {CHAINS.map(({ id, name }) => ( - - {name} - - ))} - - - - - - - ); -} - -export default Source; +import { Button, makeStyles, MenuItem, TextField } from "@material-ui/core"; +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { + selectTransferAmount, + selectTransferIsSourceComplete, + selectTransferShouldLockFields, + selectTransferSourceAsset, + selectTransferSourceBalanceString, + selectTransferSourceChain, +} from "../../store/selectors"; +import { + incrementStep, + setAmount, + setSourceAsset, + setSourceChain, +} from "../../store/transferSlice"; +import { CHAINS } from "../../utils/consts"; +import KeyAndBalance from "../KeyAndBalance"; + +const useStyles = makeStyles((theme) => ({ + transferField: { + marginTop: theme.spacing(5), + }, +})); + +function Source() { + const classes = useStyles(); + const dispatch = useDispatch(); + const sourceChain = useSelector(selectTransferSourceChain); + const sourceAsset = useSelector(selectTransferSourceAsset); + const uiAmountString = useSelector(selectTransferSourceBalanceString); + const amount = useSelector(selectTransferAmount); + const isSourceComplete = useSelector(selectTransferIsSourceComplete); + const shouldLockFields = useSelector(selectTransferShouldLockFields); + const handleSourceChange = useCallback( + (event) => { + dispatch(setSourceChain(event.target.value)); + }, + [dispatch] + ); + const handleAssetChange = useCallback( + (event) => { + dispatch(setSourceAsset(event.target.value)); + }, + [dispatch] + ); + const handleAmountChange = useCallback( + (event) => { + dispatch(setAmount(event.target.value)); + }, + [dispatch] + ); + const handleNextClick = useCallback( + (event) => { + dispatch(incrementStep()); + }, + [dispatch] + ); + return ( + <> + + {CHAINS.map(({ id, name }) => ( + + {name} + + ))} + + + + + + + ); +} + +export default Source; diff --git a/bridge_ui/src/components/Transfer/Target.tsx b/bridge_ui/src/components/Transfer/Target.tsx index 6ee21aff..e01642b8 100644 --- a/bridge_ui/src/components/Transfer/Target.tsx +++ b/bridge_ui/src/components/Transfer/Target.tsx @@ -1,65 +1,65 @@ -import { Button, MenuItem, TextField } from "@material-ui/core"; -import { useCallback, useMemo } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { - selectTransferIsTargetComplete, - selectTransferShouldLockFields, - selectTransferSourceChain, - selectTransferTargetChain, -} from "../../store/selectors"; -import { incrementStep, setTargetChain } from "../../store/transferSlice"; -import { CHAINS } from "../../utils/consts"; -import KeyAndBalance from "../KeyAndBalance"; - -function Target() { - const dispatch = useDispatch(); - const sourceChain = useSelector(selectTransferSourceChain); - const chains = useMemo( - () => CHAINS.filter((c) => c.id !== sourceChain), - [sourceChain] - ); - const targetChain = useSelector(selectTransferTargetChain); - const isTargetComplete = useSelector(selectTransferIsTargetComplete); - const shouldLockFields = useSelector(selectTransferShouldLockFields); - const handleTargetChange = useCallback( - (event) => { - dispatch(setTargetChain(event.target.value)); - }, - [dispatch] - ); - const handleNextClick = useCallback( - (event) => { - dispatch(incrementStep()); - }, - [dispatch] - ); - return ( - <> - - {chains.map(({ id, name }) => ( - - {name} - - ))} - - {/* TODO: determine "to" token address */} - - - - ); -} - -export default Target; +import { Button, MenuItem, TextField } from "@material-ui/core"; +import { useCallback, useMemo } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { + selectTransferIsTargetComplete, + selectTransferShouldLockFields, + selectTransferSourceChain, + selectTransferTargetChain, +} from "../../store/selectors"; +import { incrementStep, setTargetChain } from "../../store/transferSlice"; +import { CHAINS } from "../../utils/consts"; +import KeyAndBalance from "../KeyAndBalance"; + +function Target() { + const dispatch = useDispatch(); + const sourceChain = useSelector(selectTransferSourceChain); + const chains = useMemo( + () => CHAINS.filter((c) => c.id !== sourceChain), + [sourceChain] + ); + const targetChain = useSelector(selectTransferTargetChain); + const isTargetComplete = useSelector(selectTransferIsTargetComplete); + const shouldLockFields = useSelector(selectTransferShouldLockFields); + const handleTargetChange = useCallback( + (event) => { + dispatch(setTargetChain(event.target.value)); + }, + [dispatch] + ); + const handleNextClick = useCallback( + (event) => { + dispatch(incrementStep()); + }, + [dispatch] + ); + return ( + <> + + {chains.map(({ id, name }) => ( + + {name} + + ))} + + {/* TODO: determine "to" token address */} + + + + ); +} + +export default Target; diff --git a/bridge_ui/src/components/Transfer/index.tsx b/bridge_ui/src/components/Transfer/index.tsx index 3a9a04d8..b64401e5 100644 --- a/bridge_ui/src/components/Transfer/index.tsx +++ b/bridge_ui/src/components/Transfer/index.tsx @@ -1,73 +1,73 @@ -import { - Container, - Step, - StepButton, - StepContent, - Stepper, -} from "@material-ui/core"; -import { useDispatch, useSelector } from "react-redux"; -import useGetBalanceEffect from "../../hooks/useGetBalanceEffect"; -import { - selectTransferActiveStep, - selectTransferSignedVAAHex, -} from "../../store/selectors"; -import { setStep } from "../../store/transferSlice"; -import Redeem from "./Redeem"; -import Send from "./Send"; -import Source from "./Source"; -import Target from "./Target"; - -// TODO: ensure that both wallets are connected to the same known network -// TODO: loaders and such, navigation block? -// TODO: refresh displayed token amount after transfer somehow, could be resolved by having different components appear -// TODO: warn if amount exceeds balance - -function Transfer() { - useGetBalanceEffect(); - const dispatch = useDispatch(); - const activeStep = useSelector(selectTransferActiveStep); - const signedVAAHex = useSelector(selectTransferSignedVAAHex); - return ( - - - - dispatch(setStep(0))}> - Select a source - - - - - - - dispatch(setStep(1))}> - Select a target - - - - - - - dispatch(setStep(2))}> - Send tokens - - - - - - - dispatch(setStep(3))} - disabled={!signedVAAHex} - > - Redeem tokens - - - - - - - - ); -} - -export default Transfer; +import { + Container, + Step, + StepButton, + StepContent, + Stepper, +} from "@material-ui/core"; +import { useDispatch, useSelector } from "react-redux"; +import useGetBalanceEffect from "../../hooks/useGetBalanceEffect"; +import { + selectTransferActiveStep, + selectTransferSignedVAAHex, +} from "../../store/selectors"; +import { setStep } from "../../store/transferSlice"; +import Redeem from "./Redeem"; +import Send from "./Send"; +import Source from "./Source"; +import Target from "./Target"; + +// TODO: ensure that both wallets are connected to the same known network +// TODO: loaders and such, navigation block? +// TODO: refresh displayed token amount after transfer somehow, could be resolved by having different components appear +// TODO: warn if amount exceeds balance + +function Transfer() { + useGetBalanceEffect(); + const dispatch = useDispatch(); + const activeStep = useSelector(selectTransferActiveStep); + const signedVAAHex = useSelector(selectTransferSignedVAAHex); + return ( + + + + dispatch(setStep(0))}> + Select a source + + + + + + + dispatch(setStep(1))}> + Select a target + + + + + + + dispatch(setStep(2))}> + Send tokens + + + + + + + dispatch(setStep(3))} + disabled={!signedVAAHex} + > + Redeem tokens + + + + + + + + ); +} + +export default Transfer; diff --git a/bridge_ui/src/hooks/useAttestSignedVAA.ts b/bridge_ui/src/hooks/useAttestSignedVAA.ts index c563c26f..d4c5bd66 100644 --- a/bridge_ui/src/hooks/useAttestSignedVAA.ts +++ b/bridge_ui/src/hooks/useAttestSignedVAA.ts @@ -1,13 +1,13 @@ -import { useMemo } from "react"; -import { useSelector } from "react-redux"; -import { selectAttestSignedVAAHex } from "../store/selectors"; -import { hexToUint8Array } from "../utils/array"; - -export default function useAttestSignedVAA() { - const signedVAAHex = useSelector(selectAttestSignedVAAHex); - const signedVAA = useMemo( - () => (signedVAAHex ? hexToUint8Array(signedVAAHex) : undefined), - [signedVAAHex] - ); - return signedVAA; -} +import { useMemo } from "react"; +import { useSelector } from "react-redux"; +import { selectAttestSignedVAAHex } from "../store/selectors"; +import { hexToUint8Array } from "../utils/array"; + +export default function useAttestSignedVAA() { + const signedVAAHex = useSelector(selectAttestSignedVAAHex); + const signedVAA = useMemo( + () => (signedVAAHex ? hexToUint8Array(signedVAAHex) : undefined), + [signedVAAHex] + ); + return signedVAA; +} diff --git a/bridge_ui/src/hooks/useGetBalanceEffect.ts b/bridge_ui/src/hooks/useGetBalanceEffect.ts index 68998cd2..9e07f478 100644 --- a/bridge_ui/src/hooks/useGetBalanceEffect.ts +++ b/bridge_ui/src/hooks/useGetBalanceEffect.ts @@ -1,115 +1,115 @@ -import { Connection, PublicKey } from "@solana/web3.js"; -import { formatUnits } from "ethers/lib/utils"; -import { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { useEthereumProvider } from "../contexts/EthereumProviderContext"; -import { useSolanaWallet } from "../contexts/SolanaWalletContext"; -import { TokenImplementation__factory } from "../ethers-contracts"; -import { - selectTransferSourceAsset, - selectTransferSourceChain, -} from "../store/selectors"; -import { setSourceParsedTokenAccount } from "../store/transferSlice"; -import { CHAIN_ID_ETH, CHAIN_ID_SOLANA, SOLANA_HOST } from "../utils/consts"; - -function createParsedTokenAccount( - publicKey: PublicKey | undefined, - amount: string, - decimals: number, - uiAmount: number, - uiAmountString: string -) { - return { - publicKey: publicKey?.toString(), - amount, - decimals, - uiAmount, - uiAmountString, - }; -} - -function useGetBalanceEffect() { - const dispatch = useDispatch(); - const sourceChain = useSelector(selectTransferSourceChain); - const sourceAsset = useSelector(selectTransferSourceAsset); - const { wallet } = useSolanaWallet(); - const solPK = wallet?.publicKey; - const { provider, signerAddress } = useEthereumProvider(); - useEffect(() => { - // TODO: loading state - dispatch(setSourceParsedTokenAccount(undefined)); - if (!sourceAsset) { - return; - } - let cancelled = false; - if (sourceChain === CHAIN_ID_SOLANA && solPK) { - let mint; - try { - mint = new PublicKey(sourceAsset); - } catch (e) { - return; - } - const connection = new Connection(SOLANA_HOST, "finalized"); - connection - .getParsedTokenAccountsByOwner(solPK, { mint }) - .then(({ value }) => { - if (!cancelled) { - if (value.length) { - dispatch( - setSourceParsedTokenAccount( - createParsedTokenAccount( - value[0].pubkey, - value[0].account.data.parsed?.info?.tokenAmount?.amount, - value[0].account.data.parsed?.info?.tokenAmount?.decimals, - value[0].account.data.parsed?.info?.tokenAmount?.uiAmount, - value[0].account.data.parsed?.info?.tokenAmount - ?.uiAmountString - ) - ) - ); - } else { - // TODO: error state - } - } - }) - .catch(() => { - if (!cancelled) { - // TODO: error state - } - }); - } - if (sourceChain === CHAIN_ID_ETH && provider && signerAddress) { - const token = TokenImplementation__factory.connect(sourceAsset, provider); - token - .decimals() - .then((decimals) => { - token.balanceOf(signerAddress).then((n) => { - if (!cancelled) { - dispatch( - setSourceParsedTokenAccount( - // TODO: verify accuracy - createParsedTokenAccount( - undefined, - n.toString(), - decimals, - Number(formatUnits(n, decimals)), - formatUnits(n, decimals) - ) - ) - ); - } - }); - }) - .catch(() => { - if (!cancelled) { - // TODO: error state - } - }); - } - return () => { - cancelled = true; - }; - }, [dispatch, sourceChain, sourceAsset, solPK, provider, signerAddress]); -} - -export default useGetBalanceEffect; +import { Connection, PublicKey } from "@solana/web3.js"; +import { formatUnits } from "ethers/lib/utils"; +import { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useEthereumProvider } from "../contexts/EthereumProviderContext"; +import { useSolanaWallet } from "../contexts/SolanaWalletContext"; +import { TokenImplementation__factory } from "../ethers-contracts"; +import { + selectTransferSourceAsset, + selectTransferSourceChain, +} from "../store/selectors"; +import { setSourceParsedTokenAccount } from "../store/transferSlice"; +import { CHAIN_ID_ETH, CHAIN_ID_SOLANA, SOLANA_HOST } from "../utils/consts"; + +function createParsedTokenAccount( + publicKey: PublicKey | undefined, + amount: string, + decimals: number, + uiAmount: number, + uiAmountString: string +) { + return { + publicKey: publicKey?.toString(), + amount, + decimals, + uiAmount, + uiAmountString, + }; +} + +function useGetBalanceEffect() { + const dispatch = useDispatch(); + const sourceChain = useSelector(selectTransferSourceChain); + const sourceAsset = useSelector(selectTransferSourceAsset); + const { wallet } = useSolanaWallet(); + const solPK = wallet?.publicKey; + const { provider, signerAddress } = useEthereumProvider(); + useEffect(() => { + // TODO: loading state + dispatch(setSourceParsedTokenAccount(undefined)); + if (!sourceAsset) { + return; + } + let cancelled = false; + if (sourceChain === CHAIN_ID_SOLANA && solPK) { + let mint; + try { + mint = new PublicKey(sourceAsset); + } catch (e) { + return; + } + const connection = new Connection(SOLANA_HOST, "finalized"); + connection + .getParsedTokenAccountsByOwner(solPK, { mint }) + .then(({ value }) => { + if (!cancelled) { + if (value.length) { + dispatch( + setSourceParsedTokenAccount( + createParsedTokenAccount( + value[0].pubkey, + value[0].account.data.parsed?.info?.tokenAmount?.amount, + value[0].account.data.parsed?.info?.tokenAmount?.decimals, + value[0].account.data.parsed?.info?.tokenAmount?.uiAmount, + value[0].account.data.parsed?.info?.tokenAmount + ?.uiAmountString + ) + ) + ); + } else { + // TODO: error state + } + } + }) + .catch(() => { + if (!cancelled) { + // TODO: error state + } + }); + } + if (sourceChain === CHAIN_ID_ETH && provider && signerAddress) { + const token = TokenImplementation__factory.connect(sourceAsset, provider); + token + .decimals() + .then((decimals) => { + token.balanceOf(signerAddress).then((n) => { + if (!cancelled) { + dispatch( + setSourceParsedTokenAccount( + // TODO: verify accuracy + createParsedTokenAccount( + undefined, + n.toString(), + decimals, + Number(formatUnits(n, decimals)), + formatUnits(n, decimals) + ) + ) + ); + } + }); + }) + .catch(() => { + if (!cancelled) { + // TODO: error state + } + }); + } + return () => { + cancelled = true; + }; + }, [dispatch, sourceChain, sourceAsset, solPK, provider, signerAddress]); +} + +export default useGetBalanceEffect; diff --git a/bridge_ui/src/hooks/useTransferSignedVAA.ts b/bridge_ui/src/hooks/useTransferSignedVAA.ts index 316ea859..d13b1f76 100644 --- a/bridge_ui/src/hooks/useTransferSignedVAA.ts +++ b/bridge_ui/src/hooks/useTransferSignedVAA.ts @@ -1,13 +1,13 @@ -import { useMemo } from "react"; -import { useSelector } from "react-redux"; -import { selectTransferSignedVAAHex } from "../store/selectors"; -import { hexToUint8Array } from "../utils/array"; - -export default function useTransferSignedVAA() { - const signedVAAHex = useSelector(selectTransferSignedVAAHex); - const signedVAA = useMemo( - () => (signedVAAHex ? hexToUint8Array(signedVAAHex) : undefined), - [signedVAAHex] - ); - return signedVAA; -} +import { useMemo } from "react"; +import { useSelector } from "react-redux"; +import { selectTransferSignedVAAHex } from "../store/selectors"; +import { hexToUint8Array } from "../utils/array"; + +export default function useTransferSignedVAA() { + const signedVAAHex = useSelector(selectTransferSignedVAAHex); + const signedVAA = useMemo( + () => (signedVAAHex ? hexToUint8Array(signedVAAHex) : undefined), + [signedVAAHex] + ); + return signedVAA; +} diff --git a/bridge_ui/src/hooks/useWrappedAsset.ts b/bridge_ui/src/hooks/useWrappedAsset.ts index 8538a825..b5a1475a 100644 --- a/bridge_ui/src/hooks/useWrappedAsset.ts +++ b/bridge_ui/src/hooks/useWrappedAsset.ts @@ -1,74 +1,74 @@ -import { ethers } from "ethers"; -import { useEffect, useState } from "react"; -import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts"; -import { - getAttestedAssetEth, - getAttestedAssetSol, -} from "../utils/getAttestedAsset"; -export interface WrappedAssetState { - isLoading: boolean; - isWrapped: boolean; - wrappedAsset: string | null; -} - -function useWrappedAsset( - checkChain: ChainId, - originChain: ChainId, - originAsset: string, - provider: ethers.providers.Web3Provider | undefined -) { - const [state, setState] = useState({ - isLoading: false, - isWrapped: false, - wrappedAsset: null, - }); - useEffect(() => { - let cancelled = false; - (async () => { - if (checkChain === CHAIN_ID_ETH && provider) { - setState({ isLoading: true, isWrapped: false, wrappedAsset: null }); - const asset = await getAttestedAssetEth( - provider, - originChain, - originAsset - ); - if (!cancelled) { - setState({ - isLoading: false, - isWrapped: !!asset && asset !== ethers.constants.AddressZero, - wrappedAsset: asset, - }); - } - } else if (checkChain === CHAIN_ID_SOLANA) { - setState({ isLoading: true, isWrapped: false, wrappedAsset: null }); - try { - const asset = await getAttestedAssetSol(originChain, originAsset); - if (!cancelled) { - setState({ - isLoading: false, - isWrapped: !!asset, - wrappedAsset: asset, - }); - } - } catch (e) { - if (!cancelled) { - // TODO: warning for this - setState({ - isLoading: false, - isWrapped: false, - wrappedAsset: null, - }); - } - } - } else { - setState({ isLoading: false, isWrapped: false, wrappedAsset: null }); - } - })(); - return () => { - cancelled = true; - }; - }, [checkChain, originChain, originAsset, provider]); - return state; -} - -export default useWrappedAsset; +import { ethers } from "ethers"; +import { useEffect, useState } from "react"; +import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts"; +import { + getAttestedAssetEth, + getAttestedAssetSol, +} from "../utils/getAttestedAsset"; +export interface WrappedAssetState { + isLoading: boolean; + isWrapped: boolean; + wrappedAsset: string | null; +} + +function useWrappedAsset( + checkChain: ChainId, + originChain: ChainId, + originAsset: string, + provider: ethers.providers.Web3Provider | undefined +) { + const [state, setState] = useState({ + isLoading: false, + isWrapped: false, + wrappedAsset: null, + }); + useEffect(() => { + let cancelled = false; + (async () => { + if (checkChain === CHAIN_ID_ETH && provider) { + setState({ isLoading: true, isWrapped: false, wrappedAsset: null }); + const asset = await getAttestedAssetEth( + provider, + originChain, + originAsset + ); + if (!cancelled) { + setState({ + isLoading: false, + isWrapped: !!asset && asset !== ethers.constants.AddressZero, + wrappedAsset: asset, + }); + } + } else if (checkChain === CHAIN_ID_SOLANA) { + setState({ isLoading: true, isWrapped: false, wrappedAsset: null }); + try { + const asset = await getAttestedAssetSol(originChain, originAsset); + if (!cancelled) { + setState({ + isLoading: false, + isWrapped: !!asset, + wrappedAsset: asset, + }); + } + } catch (e) { + if (!cancelled) { + // TODO: warning for this + setState({ + isLoading: false, + isWrapped: false, + wrappedAsset: null, + }); + } + } + } else { + setState({ isLoading: false, isWrapped: false, wrappedAsset: null }); + } + })(); + return () => { + cancelled = true; + }; + }, [checkChain, originChain, originAsset, provider]); + return state; +} + +export default useWrappedAsset; diff --git a/bridge_ui/src/muiTheme.js b/bridge_ui/src/muiTheme.js index ad39cdaf..4733bc1a 100644 --- a/bridge_ui/src/muiTheme.js +++ b/bridge_ui/src/muiTheme.js @@ -1,31 +1,31 @@ -import { createTheme, responsiveFontSizes } from "@material-ui/core"; - -export const theme = responsiveFontSizes( - createTheme({ - palette: { - type: "dark", - background: { - default: "#010114", - paper: "#010114", - }, - divider: "#4e4e54", - primary: { - main: "rgba(0, 116, 255, 0.8)", // #0074FF - }, - secondary: { - main: "rgb(0,239,216,0.8)", // #00EFD8 - }, - error: { - main: "#FD3503", - }, - }, - overrides: { - MuiButton: { - root: { - borderRadius: 0, - textTransform: "none", - }, - }, - }, - }) -); +import { createTheme, responsiveFontSizes } from "@material-ui/core"; + +export const theme = responsiveFontSizes( + createTheme({ + palette: { + type: "dark", + background: { + default: "#010114", + paper: "#010114", + }, + divider: "#4e4e54", + primary: { + main: "rgba(0, 116, 255, 0.8)", // #0074FF + }, + secondary: { + main: "rgb(0,239,216,0.8)", // #00EFD8 + }, + error: { + main: "#FD3503", + }, + }, + overrides: { + MuiButton: { + root: { + borderRadius: 0, + textTransform: "none", + }, + }, + }, + }) +); diff --git a/bridge_ui/src/react-app-env.d.ts b/bridge_ui/src/react-app-env.d.ts index ece12df6..6431bc5f 100644 --- a/bridge_ui/src/react-app-env.d.ts +++ b/bridge_ui/src/react-app-env.d.ts @@ -1 +1 @@ -/// +/// diff --git a/bridge_ui/src/sdk/index.ts b/bridge_ui/src/sdk/index.ts index 5bf3a802..3403c77b 100644 --- a/bridge_ui/src/sdk/index.ts +++ b/bridge_ui/src/sdk/index.ts @@ -1,58 +1,58 @@ -import { - AccountMeta, - PublicKey, - TransactionInstruction, -} from "@solana/web3.js"; -import { - GrpcWebImpl, - PublicrpcClientImpl, -} from "../proto/publicrpc/v1/publicrpc"; -import { ChainId } from "../utils/consts"; - -// begin from clients\solana\main.ts -export function ixFromRust(data: any): TransactionInstruction { - let keys: Array = data.accounts.map(accountMetaFromRust); - return new TransactionInstruction({ - programId: new PublicKey(data.program_id), - data: Buffer.from(data.data), - keys: keys, - }); -} - -function accountMetaFromRust(meta: any): AccountMeta { - return { - pubkey: new PublicKey(meta.pubkey), - isSigner: meta.is_signer, - isWritable: meta.is_writable, - }; -} -// end from clients\solana\main.ts - -export async function getSignedVAA( - emitterChain: ChainId, - emitterAddress: string, - sequence: string -) { - const rpc = new GrpcWebImpl("http://localhost:8080", {}); - const api = new PublicrpcClientImpl(rpc); - // TODO: potential infinite loop, support cancellation? - let result; - while (!result) { - console.log("wait 1 second"); - await new Promise((resolve) => setTimeout(resolve, 1000)); - console.log("check for signed vaa", emitterChain, emitterAddress, sequence); - try { - result = await api.GetSignedVAA({ - messageId: { - emitterChain, - emitterAddress, - sequence, - }, - }); - console.log(result); - } catch (e) { - console.log(e); - } - } - return result; -} +import { + AccountMeta, + PublicKey, + TransactionInstruction, +} from "@solana/web3.js"; +import { + GrpcWebImpl, + PublicrpcClientImpl, +} from "../proto/publicrpc/v1/publicrpc"; +import { ChainId } from "../utils/consts"; + +// begin from clients\solana\main.ts +export function ixFromRust(data: any): TransactionInstruction { + let keys: Array = data.accounts.map(accountMetaFromRust); + return new TransactionInstruction({ + programId: new PublicKey(data.program_id), + data: Buffer.from(data.data), + keys: keys, + }); +} + +function accountMetaFromRust(meta: any): AccountMeta { + return { + pubkey: new PublicKey(meta.pubkey), + isSigner: meta.is_signer, + isWritable: meta.is_writable, + }; +} +// end from clients\solana\main.ts + +export async function getSignedVAA( + emitterChain: ChainId, + emitterAddress: string, + sequence: string +) { + const rpc = new GrpcWebImpl("http://localhost:8080", {}); + const api = new PublicrpcClientImpl(rpc); + // TODO: potential infinite loop, support cancellation? + let result; + while (!result) { + console.log("wait 1 second"); + await new Promise((resolve) => setTimeout(resolve, 1000)); + console.log("check for signed vaa", emitterChain, emitterAddress, sequence); + try { + result = await api.GetSignedVAA({ + messageId: { + emitterChain, + emitterAddress, + sequence, + }, + }); + console.log(result); + } catch (e) { + console.log(e); + } + } + return result; +} diff --git a/bridge_ui/src/store/attestSlice.ts b/bridge_ui/src/store/attestSlice.ts index c7642d4a..6d7588b4 100644 --- a/bridge_ui/src/store/attestSlice.ts +++ b/bridge_ui/src/store/attestSlice.ts @@ -1,105 +1,105 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { - ChainId, - CHAIN_ID_ETH, - CHAIN_ID_SOLANA, - ETH_TEST_TOKEN_ADDRESS, - SOL_TEST_TOKEN_ADDRESS, -} from "../utils/consts"; - -const LAST_STEP = 3; - -type Steps = 0 | 1 | 2 | 3; - -export interface AttestState { - activeStep: Steps; - sourceChain: ChainId; - sourceAsset: string; - targetChain: ChainId; - signedVAAHex: string | undefined; - isSending: boolean; - isCreating: boolean; -} - -const initialState: AttestState = { - activeStep: 0, - sourceChain: CHAIN_ID_SOLANA, - sourceAsset: SOL_TEST_TOKEN_ADDRESS, - targetChain: CHAIN_ID_ETH, - signedVAAHex: undefined, - isSending: false, - isCreating: false, -}; - -export const attestSlice = createSlice({ - name: "attest", - initialState, - reducers: { - incrementStep: (state) => { - if (state.activeStep < LAST_STEP) state.activeStep++; - }, - decrementStep: (state) => { - if (state.activeStep > 0) state.activeStep--; - }, - setStep: (state, action: PayloadAction) => { - state.activeStep = action.payload; - }, - setSourceChain: (state, action: PayloadAction) => { - const prevSourceChain = state.sourceChain; - state.sourceChain = action.payload; - // TODO: remove or check env - for testing purposes - if (action.payload === CHAIN_ID_ETH) { - state.sourceAsset = ETH_TEST_TOKEN_ADDRESS; - } - if (action.payload === CHAIN_ID_SOLANA) { - state.sourceAsset = SOL_TEST_TOKEN_ADDRESS; - } - if (state.targetChain === action.payload) { - state.targetChain = prevSourceChain; - } - }, - setSourceAsset: (state, action: PayloadAction) => { - state.sourceAsset = action.payload; - }, - setTargetChain: (state, action: PayloadAction) => { - const prevTargetChain = state.targetChain; - state.targetChain = action.payload; - if (state.sourceChain === action.payload) { - state.sourceChain = prevTargetChain; - state.activeStep = 0; - // TODO: remove or check env - for testing purposes - if (state.targetChain === CHAIN_ID_ETH) { - state.sourceAsset = ETH_TEST_TOKEN_ADDRESS; - } - if (state.targetChain === CHAIN_ID_SOLANA) { - state.sourceAsset = SOL_TEST_TOKEN_ADDRESS; - } - } - }, - setSignedVAAHex: (state, action: PayloadAction) => { - state.signedVAAHex = action.payload; - state.isSending = false; - state.activeStep = 3; - }, - setIsSending: (state, action: PayloadAction) => { - state.isSending = action.payload; - }, - setIsCreating: (state, action: PayloadAction) => { - state.isCreating = action.payload; - }, - }, -}); - -export const { - incrementStep, - decrementStep, - setStep, - setSourceChain, - setSourceAsset, - setTargetChain, - setSignedVAAHex, - setIsSending, - setIsCreating, -} = attestSlice.actions; - -export default attestSlice.reducer; +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { + ChainId, + CHAIN_ID_ETH, + CHAIN_ID_SOLANA, + ETH_TEST_TOKEN_ADDRESS, + SOL_TEST_TOKEN_ADDRESS, +} from "../utils/consts"; + +const LAST_STEP = 3; + +type Steps = 0 | 1 | 2 | 3; + +export interface AttestState { + activeStep: Steps; + sourceChain: ChainId; + sourceAsset: string; + targetChain: ChainId; + signedVAAHex: string | undefined; + isSending: boolean; + isCreating: boolean; +} + +const initialState: AttestState = { + activeStep: 0, + sourceChain: CHAIN_ID_SOLANA, + sourceAsset: SOL_TEST_TOKEN_ADDRESS, + targetChain: CHAIN_ID_ETH, + signedVAAHex: undefined, + isSending: false, + isCreating: false, +}; + +export const attestSlice = createSlice({ + name: "attest", + initialState, + reducers: { + incrementStep: (state) => { + if (state.activeStep < LAST_STEP) state.activeStep++; + }, + decrementStep: (state) => { + if (state.activeStep > 0) state.activeStep--; + }, + setStep: (state, action: PayloadAction) => { + state.activeStep = action.payload; + }, + setSourceChain: (state, action: PayloadAction) => { + const prevSourceChain = state.sourceChain; + state.sourceChain = action.payload; + // TODO: remove or check env - for testing purposes + if (action.payload === CHAIN_ID_ETH) { + state.sourceAsset = ETH_TEST_TOKEN_ADDRESS; + } + if (action.payload === CHAIN_ID_SOLANA) { + state.sourceAsset = SOL_TEST_TOKEN_ADDRESS; + } + if (state.targetChain === action.payload) { + state.targetChain = prevSourceChain; + } + }, + setSourceAsset: (state, action: PayloadAction) => { + state.sourceAsset = action.payload; + }, + setTargetChain: (state, action: PayloadAction) => { + const prevTargetChain = state.targetChain; + state.targetChain = action.payload; + if (state.sourceChain === action.payload) { + state.sourceChain = prevTargetChain; + state.activeStep = 0; + // TODO: remove or check env - for testing purposes + if (state.targetChain === CHAIN_ID_ETH) { + state.sourceAsset = ETH_TEST_TOKEN_ADDRESS; + } + if (state.targetChain === CHAIN_ID_SOLANA) { + state.sourceAsset = SOL_TEST_TOKEN_ADDRESS; + } + } + }, + setSignedVAAHex: (state, action: PayloadAction) => { + state.signedVAAHex = action.payload; + state.isSending = false; + state.activeStep = 3; + }, + setIsSending: (state, action: PayloadAction) => { + state.isSending = action.payload; + }, + setIsCreating: (state, action: PayloadAction) => { + state.isCreating = action.payload; + }, + }, +}); + +export const { + incrementStep, + decrementStep, + setStep, + setSourceChain, + setSourceAsset, + setTargetChain, + setSignedVAAHex, + setIsSending, + setIsCreating, +} = attestSlice.actions; + +export default attestSlice.reducer; diff --git a/bridge_ui/src/store/index.ts b/bridge_ui/src/store/index.ts index ebd851f5..d5ff4ac0 100644 --- a/bridge_ui/src/store/index.ts +++ b/bridge_ui/src/store/index.ts @@ -1,15 +1,15 @@ -import { configureStore } from "@reduxjs/toolkit"; -import attestReducer from "./attestSlice"; -import transferReducer from "./transferSlice"; - -export const store = configureStore({ - reducer: { - attest: attestReducer, - transfer: transferReducer, - }, -}); - -// Infer the `RootState` and `AppDispatch` types from the store itself -export type RootState = ReturnType; -// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} -export type AppDispatch = typeof store.dispatch; +import { configureStore } from "@reduxjs/toolkit"; +import attestReducer from "./attestSlice"; +import transferReducer from "./transferSlice"; + +export const store = configureStore({ + reducer: { + attest: attestReducer, + transfer: transferReducer, + }, +}); + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType; +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = typeof store.dispatch; diff --git a/bridge_ui/src/store/selectors.ts b/bridge_ui/src/store/selectors.ts index fa4813a1..8bc9b3ec 100644 --- a/bridge_ui/src/store/selectors.ts +++ b/bridge_ui/src/store/selectors.ts @@ -1,86 +1,86 @@ -import { parseUnits } from "ethers/lib/utils"; -import { RootState } from "."; -import { CHAIN_ID_SOLANA } from "../utils/consts"; - -/* - * Attest - */ - -export const selectAttestActiveStep = (state: RootState) => - state.attest.activeStep; -export const selectAttestSourceChain = (state: RootState) => - state.attest.sourceChain; -export const selectAttestSourceAsset = (state: RootState) => - state.attest.sourceAsset; -export const selectAttestTargetChain = (state: RootState) => - state.attest.targetChain; -export const selectAttestSignedVAAHex = (state: RootState) => - state.attest.signedVAAHex; -export const selectAttestIsSending = (state: RootState) => - state.attest.isSending; -export const selectAttestIsCreating = (state: RootState) => - state.attest.isCreating; - -// safety checks -// TODO: could make this return a string with a user informative message -export const selectAttestIsSourceComplete = (state: RootState) => - !!state.attest.sourceChain && !!state.attest.sourceAsset; -// TODO: check wrapped asset exists or is native attest -export const selectAttestIsTargetComplete = (state: RootState) => - selectAttestIsSourceComplete(state) && !!state.attest.targetChain; -export const selectAttestIsSendComplete = (state: RootState) => - !!selectAttestSignedVAAHex(state); -export const selectAttestShouldLockFields = (state: RootState) => - selectAttestIsSending(state) || selectAttestIsSendComplete(state); - -/* - * Transfer - */ - -export const selectTransferActiveStep = (state: RootState) => - state.transfer.activeStep; -export const selectTransferSourceChain = (state: RootState) => - state.transfer.sourceChain; -export const selectTransferSourceAsset = (state: RootState) => - state.transfer.sourceAsset; -export const selectTransferSourceParsedTokenAccount = (state: RootState) => - state.transfer.sourceParsedTokenAccount; -export const selectTransferSourceBalanceString = (state: RootState) => - state.transfer.sourceParsedTokenAccount?.uiAmountString || ""; -export const selectTransferAmount = (state: RootState) => state.transfer.amount; -export const selectTransferTargetChain = (state: RootState) => - state.transfer.targetChain; -export const selectTransferSignedVAAHex = (state: RootState) => - state.transfer.signedVAAHex; -export const selectTransferIsSending = (state: RootState) => - state.transfer.isSending; -export const selectTransferIsRedeeming = (state: RootState) => - state.transfer.isRedeeming; - -// safety checks -// TODO: could make this return a string with a user informative message -export const selectTransferIsSourceComplete = (state: RootState) => - !!state.transfer.sourceChain && - !!state.transfer.sourceAsset && - !!state.transfer.sourceParsedTokenAccount && - !!state.transfer.amount && - (state.transfer.sourceChain !== CHAIN_ID_SOLANA || - !!state.transfer.sourceParsedTokenAccount.publicKey) && - !!state.transfer.sourceParsedTokenAccount.uiAmountString && - // TODO: make safe with too many decimals - parseUnits( - state.transfer.amount, - state.transfer.sourceParsedTokenAccount.decimals - ).lte( - parseUnits( - state.transfer.sourceParsedTokenAccount.uiAmountString, - state.transfer.sourceParsedTokenAccount.decimals - ) - ); -// TODO: check wrapped asset exists or is native transfer -export const selectTransferIsTargetComplete = (state: RootState) => - selectTransferIsSourceComplete(state) && !!state.transfer.targetChain; -export const selectTransferIsSendComplete = (state: RootState) => - !!selectTransferSignedVAAHex(state); -export const selectTransferShouldLockFields = (state: RootState) => - selectTransferIsSending(state) || selectTransferIsSendComplete(state); +import { parseUnits } from "ethers/lib/utils"; +import { RootState } from "."; +import { CHAIN_ID_SOLANA } from "../utils/consts"; + +/* + * Attest + */ + +export const selectAttestActiveStep = (state: RootState) => + state.attest.activeStep; +export const selectAttestSourceChain = (state: RootState) => + state.attest.sourceChain; +export const selectAttestSourceAsset = (state: RootState) => + state.attest.sourceAsset; +export const selectAttestTargetChain = (state: RootState) => + state.attest.targetChain; +export const selectAttestSignedVAAHex = (state: RootState) => + state.attest.signedVAAHex; +export const selectAttestIsSending = (state: RootState) => + state.attest.isSending; +export const selectAttestIsCreating = (state: RootState) => + state.attest.isCreating; + +// safety checks +// TODO: could make this return a string with a user informative message +export const selectAttestIsSourceComplete = (state: RootState) => + !!state.attest.sourceChain && !!state.attest.sourceAsset; +// TODO: check wrapped asset exists or is native attest +export const selectAttestIsTargetComplete = (state: RootState) => + selectAttestIsSourceComplete(state) && !!state.attest.targetChain; +export const selectAttestIsSendComplete = (state: RootState) => + !!selectAttestSignedVAAHex(state); +export const selectAttestShouldLockFields = (state: RootState) => + selectAttestIsSending(state) || selectAttestIsSendComplete(state); + +/* + * Transfer + */ + +export const selectTransferActiveStep = (state: RootState) => + state.transfer.activeStep; +export const selectTransferSourceChain = (state: RootState) => + state.transfer.sourceChain; +export const selectTransferSourceAsset = (state: RootState) => + state.transfer.sourceAsset; +export const selectTransferSourceParsedTokenAccount = (state: RootState) => + state.transfer.sourceParsedTokenAccount; +export const selectTransferSourceBalanceString = (state: RootState) => + state.transfer.sourceParsedTokenAccount?.uiAmountString || ""; +export const selectTransferAmount = (state: RootState) => state.transfer.amount; +export const selectTransferTargetChain = (state: RootState) => + state.transfer.targetChain; +export const selectTransferSignedVAAHex = (state: RootState) => + state.transfer.signedVAAHex; +export const selectTransferIsSending = (state: RootState) => + state.transfer.isSending; +export const selectTransferIsRedeeming = (state: RootState) => + state.transfer.isRedeeming; + +// safety checks +// TODO: could make this return a string with a user informative message +export const selectTransferIsSourceComplete = (state: RootState) => + !!state.transfer.sourceChain && + !!state.transfer.sourceAsset && + !!state.transfer.sourceParsedTokenAccount && + !!state.transfer.amount && + (state.transfer.sourceChain !== CHAIN_ID_SOLANA || + !!state.transfer.sourceParsedTokenAccount.publicKey) && + !!state.transfer.sourceParsedTokenAccount.uiAmountString && + // TODO: make safe with too many decimals + parseUnits( + state.transfer.amount, + state.transfer.sourceParsedTokenAccount.decimals + ).lte( + parseUnits( + state.transfer.sourceParsedTokenAccount.uiAmountString, + state.transfer.sourceParsedTokenAccount.decimals + ) + ); +// TODO: check wrapped asset exists or is native transfer +export const selectTransferIsTargetComplete = (state: RootState) => + selectTransferIsSourceComplete(state) && !!state.transfer.targetChain; +export const selectTransferIsSendComplete = (state: RootState) => + !!selectTransferSignedVAAHex(state); +export const selectTransferShouldLockFields = (state: RootState) => + selectTransferIsSending(state) || selectTransferIsSendComplete(state); diff --git a/bridge_ui/src/store/transferSlice.ts b/bridge_ui/src/store/transferSlice.ts index 8462466f..5e3b0126 100644 --- a/bridge_ui/src/store/transferSlice.ts +++ b/bridge_ui/src/store/transferSlice.ts @@ -1,128 +1,128 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { - ChainId, - CHAIN_ID_ETH, - CHAIN_ID_SOLANA, - ETH_TEST_TOKEN_ADDRESS, - SOL_TEST_TOKEN_ADDRESS, -} from "../utils/consts"; - -const LAST_STEP = 3; - -type Steps = 0 | 1 | 2 | 3; - -export interface ParsedTokenAccount { - publicKey: string | undefined; - amount: string; - decimals: number; - uiAmount: number; - uiAmountString: string; -} - -export interface TransferState { - activeStep: Steps; - sourceChain: ChainId; - sourceAsset: string; - sourceParsedTokenAccount: ParsedTokenAccount | undefined; - amount: string; - targetChain: ChainId; - signedVAAHex: string | undefined; - isSending: boolean; - isRedeeming: boolean; -} - -const initialState: TransferState = { - activeStep: 0, - sourceChain: CHAIN_ID_SOLANA, - sourceAsset: SOL_TEST_TOKEN_ADDRESS, - sourceParsedTokenAccount: undefined, - amount: "", - targetChain: CHAIN_ID_ETH, - signedVAAHex: undefined, - isSending: false, - isRedeeming: false, -}; - -export const transferSlice = createSlice({ - name: "transfer", - initialState, - reducers: { - incrementStep: (state) => { - if (state.activeStep < LAST_STEP) state.activeStep++; - }, - decrementStep: (state) => { - if (state.activeStep > 0) state.activeStep--; - }, - setStep: (state, action: PayloadAction) => { - state.activeStep = action.payload; - }, - setSourceChain: (state, action: PayloadAction) => { - const prevSourceChain = state.sourceChain; - state.sourceChain = action.payload; - // TODO: remove or check env - for testing purposes - if (action.payload === CHAIN_ID_ETH) { - state.sourceAsset = ETH_TEST_TOKEN_ADDRESS; - } - if (action.payload === CHAIN_ID_SOLANA) { - state.sourceAsset = SOL_TEST_TOKEN_ADDRESS; - } - if (state.targetChain === action.payload) { - state.targetChain = prevSourceChain; - } - }, - setSourceAsset: (state, action: PayloadAction) => { - state.sourceAsset = action.payload; - }, - setSourceParsedTokenAccount: ( - state, - action: PayloadAction - ) => { - state.sourceParsedTokenAccount = action.payload; - }, - setAmount: (state, action: PayloadAction) => { - state.amount = action.payload; - }, - setTargetChain: (state, action: PayloadAction) => { - const prevTargetChain = state.targetChain; - state.targetChain = action.payload; - if (state.sourceChain === action.payload) { - state.sourceChain = prevTargetChain; - state.activeStep = 0; - // TODO: remove or check env - for testing purposes - if (state.targetChain === CHAIN_ID_ETH) { - state.sourceAsset = ETH_TEST_TOKEN_ADDRESS; - } - if (state.targetChain === CHAIN_ID_SOLANA) { - state.sourceAsset = SOL_TEST_TOKEN_ADDRESS; - } - } - }, - setSignedVAAHex: (state, action: PayloadAction) => { - state.signedVAAHex = action.payload; - state.isSending = false; - state.activeStep = 3; - }, - setIsSending: (state, action: PayloadAction) => { - state.isSending = action.payload; - }, - setIsRedeeming: (state, action: PayloadAction) => { - state.isRedeeming = action.payload; - }, - }, -}); - -export const { - incrementStep, - decrementStep, - setStep, - setSourceChain, - setSourceAsset, - setSourceParsedTokenAccount, - setAmount, - setTargetChain, - setSignedVAAHex, - setIsSending, - setIsRedeeming, -} = transferSlice.actions; - -export default transferSlice.reducer; +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { + ChainId, + CHAIN_ID_ETH, + CHAIN_ID_SOLANA, + ETH_TEST_TOKEN_ADDRESS, + SOL_TEST_TOKEN_ADDRESS, +} from "../utils/consts"; + +const LAST_STEP = 3; + +type Steps = 0 | 1 | 2 | 3; + +export interface ParsedTokenAccount { + publicKey: string | undefined; + amount: string; + decimals: number; + uiAmount: number; + uiAmountString: string; +} + +export interface TransferState { + activeStep: Steps; + sourceChain: ChainId; + sourceAsset: string; + sourceParsedTokenAccount: ParsedTokenAccount | undefined; + amount: string; + targetChain: ChainId; + signedVAAHex: string | undefined; + isSending: boolean; + isRedeeming: boolean; +} + +const initialState: TransferState = { + activeStep: 0, + sourceChain: CHAIN_ID_SOLANA, + sourceAsset: SOL_TEST_TOKEN_ADDRESS, + sourceParsedTokenAccount: undefined, + amount: "", + targetChain: CHAIN_ID_ETH, + signedVAAHex: undefined, + isSending: false, + isRedeeming: false, +}; + +export const transferSlice = createSlice({ + name: "transfer", + initialState, + reducers: { + incrementStep: (state) => { + if (state.activeStep < LAST_STEP) state.activeStep++; + }, + decrementStep: (state) => { + if (state.activeStep > 0) state.activeStep--; + }, + setStep: (state, action: PayloadAction) => { + state.activeStep = action.payload; + }, + setSourceChain: (state, action: PayloadAction) => { + const prevSourceChain = state.sourceChain; + state.sourceChain = action.payload; + // TODO: remove or check env - for testing purposes + if (action.payload === CHAIN_ID_ETH) { + state.sourceAsset = ETH_TEST_TOKEN_ADDRESS; + } + if (action.payload === CHAIN_ID_SOLANA) { + state.sourceAsset = SOL_TEST_TOKEN_ADDRESS; + } + if (state.targetChain === action.payload) { + state.targetChain = prevSourceChain; + } + }, + setSourceAsset: (state, action: PayloadAction) => { + state.sourceAsset = action.payload; + }, + setSourceParsedTokenAccount: ( + state, + action: PayloadAction + ) => { + state.sourceParsedTokenAccount = action.payload; + }, + setAmount: (state, action: PayloadAction) => { + state.amount = action.payload; + }, + setTargetChain: (state, action: PayloadAction) => { + const prevTargetChain = state.targetChain; + state.targetChain = action.payload; + if (state.sourceChain === action.payload) { + state.sourceChain = prevTargetChain; + state.activeStep = 0; + // TODO: remove or check env - for testing purposes + if (state.targetChain === CHAIN_ID_ETH) { + state.sourceAsset = ETH_TEST_TOKEN_ADDRESS; + } + if (state.targetChain === CHAIN_ID_SOLANA) { + state.sourceAsset = SOL_TEST_TOKEN_ADDRESS; + } + } + }, + setSignedVAAHex: (state, action: PayloadAction) => { + state.signedVAAHex = action.payload; + state.isSending = false; + state.activeStep = 3; + }, + setIsSending: (state, action: PayloadAction) => { + state.isSending = action.payload; + }, + setIsRedeeming: (state, action: PayloadAction) => { + state.isRedeeming = action.payload; + }, + }, +}); + +export const { + incrementStep, + decrementStep, + setStep, + setSourceChain, + setSourceAsset, + setSourceParsedTokenAccount, + setAmount, + setTargetChain, + setSignedVAAHex, + setIsSending, + setIsRedeeming, +} = transferSlice.actions; + +export default transferSlice.reducer; diff --git a/bridge_ui/src/utils/array.ts b/bridge_ui/src/utils/array.ts index 8acaf69b..b311a396 100644 --- a/bridge_ui/src/utils/array.ts +++ b/bridge_ui/src/utils/array.ts @@ -1,4 +1,4 @@ -export const uint8ArrayToHex = (a: Uint8Array) => - Buffer.from(a).toString("hex"); -export const hexToUint8Array = (h: string) => - new Uint8Array(Buffer.from(h, "hex")); +export const uint8ArrayToHex = (a: Uint8Array) => + Buffer.from(a).toString("hex"); +export const hexToUint8Array = (h: string) => + new Uint8Array(Buffer.from(h, "hex")); diff --git a/bridge_ui/src/utils/attestFrom.ts b/bridge_ui/src/utils/attestFrom.ts index 2e5bd38d..261832a0 100644 --- a/bridge_ui/src/utils/attestFrom.ts +++ b/bridge_ui/src/utils/attestFrom.ts @@ -1,157 +1,157 @@ -import Wallet from "@project-serum/sol-wallet-adapter"; -import { - Connection, - Keypair, - PublicKey, - SystemProgram, - Transaction, -} from "@solana/web3.js"; -import { ethers } from "ethers"; -import { arrayify, zeroPad } from "ethers/lib/utils"; -import { Bridge__factory, Implementation__factory } from "../ethers-contracts"; -import { getSignedVAA, ixFromRust } from "../sdk"; -import { - CHAIN_ID_ETH, - CHAIN_ID_SOLANA, - ETH_BRIDGE_ADDRESS, - ETH_TOKEN_BRIDGE_ADDRESS, - SOLANA_HOST, - SOL_BRIDGE_ADDRESS, - SOL_TOKEN_BRIDGE_ADDRESS, -} from "./consts"; - -// TODO: allow for / handle cancellation? -// TODO: overall better input checking and error handling -export async function attestFromEth( - provider: ethers.providers.Web3Provider | undefined, - signer: ethers.Signer | undefined, - tokenAddress: string -) { - if (!provider || !signer) return; - //TODO: more catches - const signerAddress = await signer.getAddress(); - console.log("Signer:", signerAddress); - console.log("Token:", tokenAddress); - const nonceConst = Math.random() * 100000; - const nonceBuffer = Buffer.alloc(4); - nonceBuffer.writeUInt32LE(nonceConst, 0); - console.log("Initiating attestation"); - console.log("Nonce:", nonceBuffer); - const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer); - const v = await bridge.attestToken(tokenAddress, nonceBuffer); - const receipt = await v.wait(); - // TODO: log parsing should be part of a utility - // TODO: dangerous!(?) - const bridgeLog = receipt.logs.filter((l) => { - console.log(l.address, ETH_BRIDGE_ADDRESS); - return l.address === ETH_BRIDGE_ADDRESS; - })[0]; - const { - args: { sequence }, - } = Implementation__factory.createInterface().parseLog(bridgeLog); - console.log("SEQ:", sequence); - const emitterAddress = Buffer.from( - zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32) - ).toString("hex"); - const { vaaBytes } = await getSignedVAA( - CHAIN_ID_ETH, - emitterAddress, - sequence - ); - console.log("SIGNED VAA:", vaaBytes); - return vaaBytes; -} - -// TODO: need to check transfer native vs transfer wrapped -// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts) -export async function attestFromSolana( - wallet: Wallet | undefined, - payerAddress: string | undefined, //TODO: we may not need this since we have wallet - mintAddress: string -) { - if (!wallet || !wallet.publicKey || !payerAddress) return; - const nonceConst = Math.random() * 100000; - const nonceBuffer = Buffer.alloc(4); - nonceBuffer.writeUInt32LE(nonceConst, 0); - const nonce = nonceBuffer.readUInt32LE(0); - console.log("program:", SOL_TOKEN_BRIDGE_ADDRESS); - console.log("bridge:", SOL_BRIDGE_ADDRESS); - console.log("payer:", payerAddress); - console.log("token:", mintAddress); - console.log("nonce:", nonce); - const bridge = await import("bridge"); - const feeAccount = await bridge.fee_collector_address(SOL_BRIDGE_ADDRESS); - const bridgeStatePK = new PublicKey(bridge.state_address(SOL_BRIDGE_ADDRESS)); - // TODO: share connection in context? - const connection = new Connection(SOLANA_HOST, "confirmed"); - const bridgeStateAccountInfo = await connection.getAccountInfo(bridgeStatePK); - if (bridgeStateAccountInfo?.data === undefined) { - throw new Error("bridge state not found"); - } - const bridgeState = bridge.parse_state( - new Uint8Array(bridgeStateAccountInfo?.data) - ); - const transferIx = SystemProgram.transfer({ - fromPubkey: new PublicKey(payerAddress), - toPubkey: new PublicKey(feeAccount), - lamports: bridgeState.config.fee, - }); - // TODO: pass in connection - // Add transfer instruction to transaction - const { attest_ix, emitter_address } = await import("token-bridge"); - const messageKey = Keypair.generate(); - const ix = ixFromRust( - attest_ix( - SOL_TOKEN_BRIDGE_ADDRESS, - SOL_BRIDGE_ADDRESS, - payerAddress, - messageKey.publicKey.toString(), - mintAddress, - nonce - ) - ); - const transaction = new Transaction().add(transferIx, ix); - const { blockhash } = await connection.getRecentBlockhash(); - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(payerAddress); - transaction.partialSign(messageKey); - // Sign transaction, broadcast, and confirm - const signed = await wallet.signTransaction(transaction); - console.log("SIGNED", signed); - const txid = await connection.sendRawTransaction(signed.serialize()); - console.log("SENT", txid); - const conf = await connection.confirmTransaction(txid); - console.log("CONFIRMED", conf); - const info = await connection.getTransaction(txid); - console.log("INFO", info); - // TODO: log parsing should be part of a utility - // TODO: better parsing, safer - const SEQ_LOG = "Program log: Sequence: "; - const sequence = info?.meta?.logMessages - ?.filter((msg) => msg.startsWith(SEQ_LOG))[0] - .replace(SEQ_LOG, ""); - if (!sequence) { - throw new Error("sequence not found"); - } - console.log("SEQ", sequence); - const emitterAddress = Buffer.from( - zeroPad( - new PublicKey(emitter_address(SOL_TOKEN_BRIDGE_ADDRESS)).toBytes(), - 32 - ) - ).toString("hex"); - const { vaaBytes } = await getSignedVAA( - CHAIN_ID_SOLANA, - emitterAddress, - sequence - ); - console.log("SIGNED VAA:", vaaBytes); - return vaaBytes; -} - -const attestFrom = { - [CHAIN_ID_ETH]: attestFromEth, - [CHAIN_ID_SOLANA]: attestFromSolana, -}; - -export default attestFrom; +import Wallet from "@project-serum/sol-wallet-adapter"; +import { + Connection, + Keypair, + PublicKey, + SystemProgram, + Transaction, +} from "@solana/web3.js"; +import { ethers } from "ethers"; +import { arrayify, zeroPad } from "ethers/lib/utils"; +import { Bridge__factory, Implementation__factory } from "../ethers-contracts"; +import { getSignedVAA, ixFromRust } from "../sdk"; +import { + CHAIN_ID_ETH, + CHAIN_ID_SOLANA, + ETH_BRIDGE_ADDRESS, + ETH_TOKEN_BRIDGE_ADDRESS, + SOLANA_HOST, + SOL_BRIDGE_ADDRESS, + SOL_TOKEN_BRIDGE_ADDRESS, +} from "./consts"; + +// TODO: allow for / handle cancellation? +// TODO: overall better input checking and error handling +export async function attestFromEth( + provider: ethers.providers.Web3Provider | undefined, + signer: ethers.Signer | undefined, + tokenAddress: string +) { + if (!provider || !signer) return; + //TODO: more catches + const signerAddress = await signer.getAddress(); + console.log("Signer:", signerAddress); + console.log("Token:", tokenAddress); + const nonceConst = Math.random() * 100000; + const nonceBuffer = Buffer.alloc(4); + nonceBuffer.writeUInt32LE(nonceConst, 0); + console.log("Initiating attestation"); + console.log("Nonce:", nonceBuffer); + const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer); + const v = await bridge.attestToken(tokenAddress, nonceBuffer); + const receipt = await v.wait(); + // TODO: log parsing should be part of a utility + // TODO: dangerous!(?) + const bridgeLog = receipt.logs.filter((l) => { + console.log(l.address, ETH_BRIDGE_ADDRESS); + return l.address === ETH_BRIDGE_ADDRESS; + })[0]; + const { + args: { sequence }, + } = Implementation__factory.createInterface().parseLog(bridgeLog); + console.log("SEQ:", sequence); + const emitterAddress = Buffer.from( + zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32) + ).toString("hex"); + const { vaaBytes } = await getSignedVAA( + CHAIN_ID_ETH, + emitterAddress, + sequence + ); + console.log("SIGNED VAA:", vaaBytes); + return vaaBytes; +} + +// TODO: need to check transfer native vs transfer wrapped +// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts) +export async function attestFromSolana( + wallet: Wallet | undefined, + payerAddress: string | undefined, //TODO: we may not need this since we have wallet + mintAddress: string +) { + if (!wallet || !wallet.publicKey || !payerAddress) return; + const nonceConst = Math.random() * 100000; + const nonceBuffer = Buffer.alloc(4); + nonceBuffer.writeUInt32LE(nonceConst, 0); + const nonce = nonceBuffer.readUInt32LE(0); + console.log("program:", SOL_TOKEN_BRIDGE_ADDRESS); + console.log("bridge:", SOL_BRIDGE_ADDRESS); + console.log("payer:", payerAddress); + console.log("token:", mintAddress); + console.log("nonce:", nonce); + const bridge = await import("bridge"); + const feeAccount = await bridge.fee_collector_address(SOL_BRIDGE_ADDRESS); + const bridgeStatePK = new PublicKey(bridge.state_address(SOL_BRIDGE_ADDRESS)); + // TODO: share connection in context? + const connection = new Connection(SOLANA_HOST, "confirmed"); + const bridgeStateAccountInfo = await connection.getAccountInfo(bridgeStatePK); + if (bridgeStateAccountInfo?.data === undefined) { + throw new Error("bridge state not found"); + } + const bridgeState = bridge.parse_state( + new Uint8Array(bridgeStateAccountInfo?.data) + ); + const transferIx = SystemProgram.transfer({ + fromPubkey: new PublicKey(payerAddress), + toPubkey: new PublicKey(feeAccount), + lamports: bridgeState.config.fee, + }); + // TODO: pass in connection + // Add transfer instruction to transaction + const { attest_ix, emitter_address } = await import("token-bridge"); + const messageKey = Keypair.generate(); + const ix = ixFromRust( + attest_ix( + SOL_TOKEN_BRIDGE_ADDRESS, + SOL_BRIDGE_ADDRESS, + payerAddress, + messageKey.publicKey.toString(), + mintAddress, + nonce + ) + ); + const transaction = new Transaction().add(transferIx, ix); + const { blockhash } = await connection.getRecentBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.feePayer = new PublicKey(payerAddress); + transaction.partialSign(messageKey); + // Sign transaction, broadcast, and confirm + const signed = await wallet.signTransaction(transaction); + console.log("SIGNED", signed); + const txid = await connection.sendRawTransaction(signed.serialize()); + console.log("SENT", txid); + const conf = await connection.confirmTransaction(txid); + console.log("CONFIRMED", conf); + const info = await connection.getTransaction(txid); + console.log("INFO", info); + // TODO: log parsing should be part of a utility + // TODO: better parsing, safer + const SEQ_LOG = "Program log: Sequence: "; + const sequence = info?.meta?.logMessages + ?.filter((msg) => msg.startsWith(SEQ_LOG))[0] + .replace(SEQ_LOG, ""); + if (!sequence) { + throw new Error("sequence not found"); + } + console.log("SEQ", sequence); + const emitterAddress = Buffer.from( + zeroPad( + new PublicKey(emitter_address(SOL_TOKEN_BRIDGE_ADDRESS)).toBytes(), + 32 + ) + ).toString("hex"); + const { vaaBytes } = await getSignedVAA( + CHAIN_ID_SOLANA, + emitterAddress, + sequence.toString() + ); + console.log("SIGNED VAA:", vaaBytes); + return vaaBytes; +} + +const attestFrom = { + [CHAIN_ID_ETH]: attestFromEth, + [CHAIN_ID_SOLANA]: attestFromSolana, +}; + +export default attestFrom; diff --git a/bridge_ui/src/utils/consts.ts b/bridge_ui/src/utils/consts.ts index 8050b0f5..0158343c 100644 --- a/bridge_ui/src/utils/consts.ts +++ b/bridge_ui/src/utils/consts.ts @@ -1,49 +1,49 @@ -import { getAddress } from "ethers/lib/utils"; - -export type ChainId = 1 | 2 | 3 | 4; -export const CHAIN_ID_SOLANA: ChainId = 1; -export const CHAIN_ID_ETH: ChainId = 2; -export const CHAIN_ID_TERRA: ChainId = 3; -export const CHAIN_ID_BSC: ChainId = 4; -export interface ChainInfo { - id: ChainId; - name: string; -} -export const CHAINS = [ - { - id: CHAIN_ID_BSC, - name: "Binance Smart Chain", - }, - { - id: CHAIN_ID_ETH, - name: "Ethereum", - }, - { - id: CHAIN_ID_SOLANA, - name: "Solana", - }, - { - id: CHAIN_ID_TERRA, - name: "Terra", - }, -]; -export type ChainsById = { [key in ChainId]: ChainInfo }; -export const CHAINS_BY_ID: ChainsById = CHAINS.reduce((obj, chain) => { - obj[chain.id] = chain; - return obj; -}, {} as ChainsById); -export const SOLANA_HOST = "http://localhost:8899"; -export const ETH_TEST_TOKEN_ADDRESS = getAddress( - "0x0290FB167208Af455bB137780163b7B7a9a10C16" -); -export const ETH_BRIDGE_ADDRESS = getAddress( - "0x254dffcd3277c0b1660f6d42efbb754edababc2b" -); -export const ETH_TOKEN_BRIDGE_ADDRESS = getAddress( - "0xe982e462b094850f12af94d21d470e21be9d0e9c" -); -export const SOL_TEST_TOKEN_ADDRESS = - "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ"; -export const SOL_BRIDGE_ADDRESS = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"; -export const SOL_TOKEN_BRIDGE_ADDRESS = - "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE"; +import { getAddress } from "ethers/lib/utils"; + +export type ChainId = 1 | 2 | 3 | 4; +export const CHAIN_ID_SOLANA: ChainId = 1; +export const CHAIN_ID_ETH: ChainId = 2; +export const CHAIN_ID_TERRA: ChainId = 3; +export const CHAIN_ID_BSC: ChainId = 4; +export interface ChainInfo { + id: ChainId; + name: string; +} +export const CHAINS = [ + { + id: CHAIN_ID_BSC, + name: "Binance Smart Chain", + }, + { + id: CHAIN_ID_ETH, + name: "Ethereum", + }, + { + id: CHAIN_ID_SOLANA, + name: "Solana", + }, + { + id: CHAIN_ID_TERRA, + name: "Terra", + }, +]; +export type ChainsById = { [key in ChainId]: ChainInfo }; +export const CHAINS_BY_ID: ChainsById = CHAINS.reduce((obj, chain) => { + obj[chain.id] = chain; + return obj; +}, {} as ChainsById); +export const SOLANA_HOST = "http://localhost:8899"; +export const ETH_TEST_TOKEN_ADDRESS = getAddress( + "0x0290FB167208Af455bB137780163b7B7a9a10C16" +); +export const ETH_BRIDGE_ADDRESS = getAddress( + "0x254dffcd3277c0b1660f6d42efbb754edababc2b" +); +export const ETH_TOKEN_BRIDGE_ADDRESS = getAddress( + "0xe982e462b094850f12af94d21d470e21be9d0e9c" +); +export const SOL_TEST_TOKEN_ADDRESS = + "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ"; +export const SOL_BRIDGE_ADDRESS = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"; +export const SOL_TOKEN_BRIDGE_ADDRESS = + "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE"; diff --git a/bridge_ui/src/utils/createWrappedOn.ts b/bridge_ui/src/utils/createWrappedOn.ts index 695b0c03..1faadf8a 100644 --- a/bridge_ui/src/utils/createWrappedOn.ts +++ b/bridge_ui/src/utils/createWrappedOn.ts @@ -1,52 +1,80 @@ -import Wallet from "@project-serum/sol-wallet-adapter"; -import { Connection, PublicKey, Transaction } from "@solana/web3.js"; -import { ixFromRust } from "../sdk"; -import { - CHAIN_ID_SOLANA, - SOLANA_HOST, - SOL_BRIDGE_ADDRESS, - SOL_TOKEN_BRIDGE_ADDRESS, -} from "./consts"; - -export async function createWrappedOnSolana( - wallet: Wallet | undefined, - payerAddress: string | undefined, //TODO: we may not need this since we have wallet - signedVAA: Uint8Array -) { - if (!wallet || !wallet.publicKey || !payerAddress) return; - console.log("creating wrapped"); - console.log("PROGRAM:", SOL_TOKEN_BRIDGE_ADDRESS); - console.log("BRIDGE:", SOL_BRIDGE_ADDRESS); - console.log("PAYER:", payerAddress); - console.log("VAA:", signedVAA); - // TODO: share connection in context? - const connection = new Connection(SOLANA_HOST, "confirmed"); - const { create_wrapped_ix } = await import("token-bridge"); - const ix = ixFromRust( - create_wrapped_ix( - SOL_TOKEN_BRIDGE_ADDRESS, - SOL_BRIDGE_ADDRESS, - payerAddress, - signedVAA - ) - ); - const transaction = new Transaction().add(ix); - const { blockhash } = await connection.getRecentBlockhash(); - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(payerAddress); - // Sign transaction, broadcast, and confirm - const signed = await wallet.signTransaction(transaction); - console.log("SIGNED", signed); - const txid = await connection.sendRawTransaction(signed.serialize()); - console.log("SENT", txid); - const conf = await connection.confirmTransaction(txid); - console.log("CONFIRMED", conf); - const info = await connection.getTransaction(txid); - console.log("INFO", info); -} - -const createWrappedOn = { - [CHAIN_ID_SOLANA]: createWrappedOnSolana, -}; - -export default createWrappedOn; +import Wallet from "@project-serum/sol-wallet-adapter"; +import { Connection, PublicKey, Transaction } from "@solana/web3.js"; +import { ixFromRust } from "../sdk"; +import { + CHAIN_ID_SOLANA, + SOLANA_HOST, + SOL_BRIDGE_ADDRESS, + SOL_TOKEN_BRIDGE_ADDRESS, + ETH_TOKEN_BRIDGE_ADDRESS, + CHAIN_ID_ETH, +} from "./consts"; +import { ethers } from "ethers"; +import { Bridge__factory } from "../ethers-contracts"; +import { postVaa } from "./postVaa"; + +export async function createWrappedOnEth( + provider: ethers.providers.Web3Provider | undefined, + signer: ethers.Signer | undefined, + signedVAA: Uint8Array +) { + console.log(provider, signer, signedVAA); + if (!provider || !signer) return; + console.log("creating wrapped"); + const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer); + const v = await bridge.createWrapped(signedVAA); + const receipt = await v.wait(); + console.log(receipt); +} + +export async function createWrappedOnSolana( + wallet: Wallet | undefined, + payerAddress: string | undefined, //TODO: we may not need this since we have wallet + signedVAA: Uint8Array +) { + if (!wallet || !wallet.publicKey || !payerAddress) return; + console.log("creating wrapped"); + console.log("PROGRAM:", SOL_TOKEN_BRIDGE_ADDRESS); + console.log("BRIDGE:", SOL_BRIDGE_ADDRESS); + console.log("PAYER:", payerAddress); + console.log("VAA:", signedVAA); + // TODO: share connection in context? + const connection = new Connection(SOLANA_HOST, "confirmed"); + const { create_wrapped_ix } = await import("token-bridge"); + + await postVaa( + connection, + wallet, + SOL_BRIDGE_ADDRESS, + payerAddress, + Buffer.from(signedVAA) + ); + const ix = ixFromRust( + create_wrapped_ix( + SOL_TOKEN_BRIDGE_ADDRESS, + SOL_BRIDGE_ADDRESS, + payerAddress, + signedVAA + ) + ); + const transaction = new Transaction().add(ix); + const { blockhash } = await connection.getRecentBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.feePayer = new PublicKey(payerAddress); + // Sign transaction, broadcast, and confirm + const signed = await wallet.signTransaction(transaction); + console.log("SIGNED", signed); + const txid = await connection.sendRawTransaction(signed.serialize()); + console.log("SENT", txid); + const conf = await connection.confirmTransaction(txid); + console.log("CONFIRMED", conf); + const info = await connection.getTransaction(txid); + console.log("INFO", info); +} + +const createWrappedOn = { + [CHAIN_ID_SOLANA]: createWrappedOnSolana, + [CHAIN_ID_ETH]: createWrappedOnEth, +}; + +export default createWrappedOn; diff --git a/bridge_ui/src/utils/getAttestedAsset.ts b/bridge_ui/src/utils/getAttestedAsset.ts index 4cb96d52..e8196dad 100644 --- a/bridge_ui/src/utils/getAttestedAsset.ts +++ b/bridge_ui/src/utils/getAttestedAsset.ts @@ -1,60 +1,60 @@ -import { Connection, PublicKey } from "@solana/web3.js"; -import { ethers } from "ethers"; -import { arrayify, isHexString, zeroPad } from "ethers/lib/utils"; -import { Bridge__factory } from "../ethers-contracts"; -import { - ChainId, - CHAIN_ID_SOLANA, - ETH_TOKEN_BRIDGE_ADDRESS, - SOLANA_HOST, - SOL_TOKEN_BRIDGE_ADDRESS, -} from "./consts"; - -export async function getAttestedAssetEth( - provider: ethers.providers.Web3Provider, - originChain: ChainId, - originAsset: string -) { - const tokenBridge = Bridge__factory.connect( - ETH_TOKEN_BRIDGE_ADDRESS, - provider - ); - try { - // TODO: address conversion may be more complex than this - const originAssetBytes = zeroPad( - originChain === CHAIN_ID_SOLANA - ? new PublicKey(originAsset).toBytes() - : arrayify(originAsset), - 32 - ); - return await tokenBridge.wrappedAsset(originChain, originAssetBytes); - } catch (e) { - return ethers.constants.AddressZero; - } -} - -export async function getAttestedAssetSol( - originChain: ChainId, - originAsset: string -) { - if (!isHexString(originAsset)) return null; - const { wrapped_address } = await import("token-bridge"); - // TODO: address conversion may be more complex than this - const originAssetBytes = zeroPad( - arrayify(originAsset, { hexPad: "left" }), - 32 - ); - const wrappedAddress = wrapped_address( - SOL_TOKEN_BRIDGE_ADDRESS, - originAssetBytes, - originChain - ); - const wrappedAddressPK = new PublicKey(wrappedAddress); - // TODO: share connection in context? - const connection = new Connection(SOLANA_HOST, "confirmed"); - const wrappedAssetAccountInfo = await connection.getAccountInfo( - wrappedAddressPK - ); - console.log("WAAI", wrappedAssetAccountInfo); - return wrappedAssetAccountInfo ? wrappedAddressPK.toString() : null; -} +import { Connection, PublicKey } from "@solana/web3.js"; +import { ethers } from "ethers"; +import { arrayify, isHexString, zeroPad } from "ethers/lib/utils"; +import { Bridge__factory } from "../ethers-contracts"; +import { + ChainId, + CHAIN_ID_SOLANA, + ETH_TOKEN_BRIDGE_ADDRESS, + SOLANA_HOST, + SOL_TOKEN_BRIDGE_ADDRESS, +} from "./consts"; + +export async function getAttestedAssetEth( + provider: ethers.providers.Web3Provider, + originChain: ChainId, + originAsset: string +) { + const tokenBridge = Bridge__factory.connect( + ETH_TOKEN_BRIDGE_ADDRESS, + provider + ); + try { + // TODO: address conversion may be more complex than this + const originAssetBytes = zeroPad( + originChain === CHAIN_ID_SOLANA + ? new PublicKey(originAsset).toBytes() + : arrayify(originAsset), + 32 + ); + return await tokenBridge.wrappedAsset(originChain, originAssetBytes); + } catch (e) { + return ethers.constants.AddressZero; + } +} + +export async function getAttestedAssetSol( + originChain: ChainId, + originAsset: string +) { + if (!isHexString(originAsset)) return null; + const { wrapped_address } = await import("token-bridge"); + // TODO: address conversion may be more complex than this + const originAssetBytes = zeroPad( + arrayify(originAsset, { hexPad: "left" }), + 32 + ); + const wrappedAddress = wrapped_address( + SOL_TOKEN_BRIDGE_ADDRESS, + originAssetBytes, + originChain + ); + const wrappedAddressPK = new PublicKey(wrappedAddress); + // TODO: share connection in context? + const connection = new Connection(SOLANA_HOST, "confirmed"); + const wrappedAssetAccountInfo = await connection.getAccountInfo( + wrappedAddressPK + ); + console.log("WAAI", wrappedAssetAccountInfo); + return wrappedAssetAccountInfo ? wrappedAddressPK.toString() : null; +} diff --git a/bridge_ui/src/utils/postVaa.ts b/bridge_ui/src/utils/postVaa.ts new file mode 100644 index 00000000..aec17d49 --- /dev/null +++ b/bridge_ui/src/utils/postVaa.ts @@ -0,0 +1,111 @@ +import { + Connection, + Keypair, + PublicKey, + Transaction, + TransactionInstruction, +} from "@solana/web3.js"; +import Wallet from "@project-serum/sol-wallet-adapter"; +import { ixFromRust } from "../sdk"; + +export async function postVaa( + connection: Connection, + wallet: Wallet, + bridge_id: string, + payer: string, + vaa: Buffer +) { + const { + guardian_set_address, + parse_guardian_set, + verify_signatures_ix, + post_vaa_ix, + } = await import("bridge"); + let bridge_state = await getBridgeState(connection, bridge_id); + let guardian_addr = new PublicKey( + guardian_set_address(bridge_id, bridge_state.guardianSetIndex) + ); + let acc = await connection.getAccountInfo(guardian_addr); + if (acc?.data === undefined) { + return; + } + let guardian_data = parse_guardian_set(new Uint8Array(acc?.data)); + + let signature_set = Keypair.generate(); + let txs = verify_signatures_ix( + bridge_id, + payer, + bridge_state.guardianSetIndex, + guardian_data, + signature_set.publicKey.toString(), + vaa + ); + // Add transfer instruction to transaction + for (let tx of txs) { + let ixs: Array = tx.map((v: any) => { + return ixFromRust(v); + }); + let transaction = new Transaction().add(ixs[0], ixs[1]); + const { blockhash } = await connection.getRecentBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.feePayer = new PublicKey(payer); + transaction.partialSign(signature_set); + + // Sign transaction, broadcast, and confirm + const signed = await wallet.signTransaction(transaction); + console.log("SIGNED", signed); + const txid = await connection.sendRawTransaction(signed.serialize()); + console.log("SENT", txid); + const conf = await connection.confirmTransaction(txid); + console.log("CONFIRMED", conf); + } + + let ix = ixFromRust( + post_vaa_ix(bridge_id, payer, signature_set.publicKey.toString(), vaa) + ); + let transaction = new Transaction().add(ix); + const { blockhash } = await connection.getRecentBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.feePayer = new PublicKey(payer); + + const signed = await wallet.signTransaction(transaction); + console.log("SIGNED", signed); + const txid = await connection.sendRawTransaction(signed.serialize()); + console.log("SENT", txid); + const conf = await connection.confirmTransaction(txid); + console.log("CONFIRMED", conf); +} + +async function getBridgeState( + connection: Connection, + bridge_id: string +): Promise { + const { parse_state, state_address } = await import("bridge"); + let bridge_state = new PublicKey(state_address(bridge_id)); + let acc = await connection.getAccountInfo(bridge_state); + if (acc?.data === undefined) { + throw new Error("bridge state not found"); + } + return parse_state(new Uint8Array(acc?.data)); +} + +interface BridgeState { + // The current guardian set index, used to decide which signature sets to accept. + guardianSetIndex: number; + + // Lamports in the collection account + lastLamports: number; + + // Bridge configuration, which is set once upon initialization. + config: BridgeConfig; +} + +interface BridgeConfig { + // Period for how long a guardian set is valid after it has been replaced by a new one. This + // guarantees that VAAs issued by that set can still be submitted for a certain period. In + // this period we still trust the old guardian set. + guardianSetExpirationTime: number; + + // Amount of lamports that needs to be paid to the protocol to post a message + fee: number; +} diff --git a/bridge_ui/src/utils/redeemOn.ts b/bridge_ui/src/utils/redeemOn.ts index 91e4fd62..8a10f988 100644 --- a/bridge_ui/src/utils/redeemOn.ts +++ b/bridge_ui/src/utils/redeemOn.ts @@ -1,23 +1,81 @@ -import { ethers } from "ethers"; -import { Bridge__factory } from "../ethers-contracts"; -import { CHAIN_ID_ETH, ETH_TOKEN_BRIDGE_ADDRESS } from "./consts"; - -export async function redeemOnEth( - provider: ethers.providers.Web3Provider | undefined, - signer: ethers.Signer | undefined, - signedVAA: Uint8Array -) { - console.log(provider, signer, signedVAA); - if (!provider || !signer) return; - console.log("completing transfer"); - const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer); - const v = await bridge.completeTransfer(signedVAA); - const receipt = await v.wait(); - console.log(receipt); -} - -const redeemOn = { - [CHAIN_ID_ETH]: redeemOnEth, -}; - -export default redeemOn; +import { ethers } from "ethers"; +import { Bridge__factory } from "../ethers-contracts"; +import { + CHAIN_ID_ETH, + CHAIN_ID_SOLANA, + ETH_TOKEN_BRIDGE_ADDRESS, + SOL_BRIDGE_ADDRESS, + SOL_TOKEN_BRIDGE_ADDRESS, + SOLANA_HOST, +} from "./consts"; +import Wallet from "@project-serum/sol-wallet-adapter"; +import { Connection, PublicKey, Transaction } from "@solana/web3.js"; +import { postVaa } from "./postVaa"; +import { ixFromRust } from "../sdk"; + +export async function redeemOnEth( + provider: ethers.providers.Web3Provider | undefined, + signer: ethers.Signer | undefined, + signedVAA: Uint8Array +) { + console.log(provider, signer, signedVAA); + if (!provider || !signer) return; + console.log("completing transfer"); + const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer); + const v = await bridge.completeTransfer(signedVAA); + const receipt = await v.wait(); + console.log(receipt); +} + +export async function redeemOnSolana( + wallet: Wallet | undefined, + payerAddress: string | undefined, //TODO: we may not need this since we have wallet + signedVAA: Uint8Array +) { + if (!wallet || !wallet.publicKey || !payerAddress) return; + console.log("completing transfer"); + console.log("PROGRAM:", SOL_TOKEN_BRIDGE_ADDRESS); + console.log("BRIDGE:", SOL_BRIDGE_ADDRESS); + console.log("PAYER:", payerAddress); + console.log("VAA:", signedVAA); + // TODO: share connection in context? + const connection = new Connection(SOLANA_HOST, "confirmed"); + const { complete_transfer_wrapped_ix } = await import("token-bridge"); + + await postVaa( + connection, + wallet, + SOL_BRIDGE_ADDRESS, + payerAddress, + Buffer.from(signedVAA) + ); + console.log(Buffer.from(signedVAA).toString("hex")); + const ix = ixFromRust( + complete_transfer_wrapped_ix( + SOL_TOKEN_BRIDGE_ADDRESS, + SOL_BRIDGE_ADDRESS, + payerAddress, + signedVAA + ) + ); + const transaction = new Transaction().add(ix); + const { blockhash } = await connection.getRecentBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.feePayer = new PublicKey(payerAddress); + // Sign transaction, broadcast, and confirm + const signed = await wallet.signTransaction(transaction); + console.log("SIGNED", signed); + const txid = await connection.sendRawTransaction(signed.serialize()); + console.log("SENT", txid); + const conf = await connection.confirmTransaction(txid); + console.log("CONFIRMED", conf); + const info = await connection.getTransaction(txid); + console.log("INFO", info); +} + +const redeemOn = { + [CHAIN_ID_ETH]: redeemOnEth, + [CHAIN_ID_SOLANA]: redeemOnSolana, +}; + +export default redeemOn; diff --git a/bridge_ui/src/utils/transferFrom.ts b/bridge_ui/src/utils/transferFrom.ts index 8d4e08d6..a34073da 100644 --- a/bridge_ui/src/utils/transferFrom.ts +++ b/bridge_ui/src/utils/transferFrom.ts @@ -1,227 +1,228 @@ -import Wallet from "@project-serum/sol-wallet-adapter"; -import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { - Connection, Keypair, - PublicKey, - SystemProgram, - Transaction, -} from "@solana/web3.js"; -import { ethers } from "ethers"; -import { arrayify, formatUnits, parseUnits, zeroPad } from "ethers/lib/utils"; -import { - Bridge__factory, - Implementation__factory, - TokenImplementation__factory, -} from "../ethers-contracts"; -import { getSignedVAA, ixFromRust } from "../sdk"; -import { - ChainId, - CHAIN_ID_ETH, - CHAIN_ID_SOLANA, - ETH_BRIDGE_ADDRESS, - ETH_TOKEN_BRIDGE_ADDRESS, - SOLANA_HOST, - SOL_BRIDGE_ADDRESS, - SOL_TOKEN_BRIDGE_ADDRESS, -} from "./consts"; - -// TODO: allow for / handle cancellation? -// TODO: overall better input checking and error handling -export async function transferFromEth( - provider: ethers.providers.Web3Provider | undefined, - signer: ethers.Signer | undefined, - tokenAddress: string, - decimals: number, - amount: string, - recipientChain: ChainId, - recipientAddress: Uint8Array | undefined -) { - if (!provider || !signer || !recipientAddress) return; - //TODO: check if token attestation exists on the target chain - //TODO: don't hardcode, fetch decimals / share them with balance, how do we determine recipient chain? - //TODO: more catches - const amountParsed = parseUnits(amount, decimals); - const signerAddress = await signer.getAddress(); - console.log("Signer:", signerAddress); - console.log("Token:", tokenAddress); - const token = TokenImplementation__factory.connect(tokenAddress, signer); - const allowance = await token.allowance( - signerAddress, - ETH_TOKEN_BRIDGE_ADDRESS - ); - console.log("Allowance", allowance.toString()); //TODO: should we check that this is zero and warn if it isn't? - const transaction = await token.approve( - ETH_TOKEN_BRIDGE_ADDRESS, - amountParsed - ); - console.log(transaction); - const fee = 0; // for now, this won't do anything, we may add later - const nonceConst = Math.random() * 100000; - const nonceBuffer = Buffer.alloc(4); - nonceBuffer.writeUInt32LE(nonceConst, 0); - console.log("Initiating transfer"); - console.log("Amount:", formatUnits(amountParsed, decimals)); - console.log("To chain:", recipientChain); - console.log("To address:", recipientAddress); - console.log("Fees:", fee); - console.log("Nonce:", nonceBuffer); - const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer); - const v = await bridge.transferTokens( - tokenAddress, - amountParsed, - recipientChain, - recipientAddress, - fee, - nonceBuffer - ); - const receipt = await v.wait(); - // TODO: log parsing should be part of a utility - // TODO: dangerous!(?) - const bridgeLog = receipt.logs.filter((l) => { - console.log(l.address, ETH_BRIDGE_ADDRESS); - return l.address === ETH_BRIDGE_ADDRESS; - })[0]; - const { - args: { sender, sequence }, - } = Implementation__factory.createInterface().parseLog(bridgeLog); - console.log(sender, sequence); - const emitterAddress = Buffer.from( - zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32) - ).toString("hex"); - const { vaaBytes } = await getSignedVAA( - CHAIN_ID_ETH, - emitterAddress, - sequence - ); - console.log("SIGNED VAA:", vaaBytes); - return vaaBytes; -} - -// TODO: need to check transfer native vs transfer wrapped -// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts) -export async function transferFromSolana( - wallet: Wallet | undefined, - payerAddress: string | undefined, //TODO: we may not need this since we have wallet - fromAddress: string | undefined, - mintAddress: string, - amount: string, - decimals: number, - targetAddressStr: string | undefined, - targetChain: ChainId -) { - if ( - !wallet || - !wallet.publicKey || - !payerAddress || - !fromAddress || - !targetAddressStr - ) - return; - const targetAddress = zeroPad(arrayify(targetAddressStr), 32); - const nonceConst = Math.random() * 100000; - const nonceBuffer = Buffer.alloc(4); - nonceBuffer.writeUInt32LE(nonceConst, 0); - const nonce = nonceBuffer.readUInt32LE(0); - const amountParsed = parseUnits(amount, decimals).toBigInt(); - const fee = BigInt(0); // for now, this won't do anything, we may add later - console.log("program:", SOL_TOKEN_BRIDGE_ADDRESS); - console.log("bridge:", SOL_BRIDGE_ADDRESS); - console.log("payer:", payerAddress); - console.log("from:", fromAddress); - console.log("token:", mintAddress); - console.log("nonce:", nonce); - console.log("amount:", amountParsed); - console.log("fee:", fee); - console.log("target:", targetAddressStr, targetAddress); - console.log("chain:", targetChain); - const bridge = await import("bridge"); - const feeAccount = await bridge.fee_collector_address(SOL_BRIDGE_ADDRESS); - const bridgeStatePK = new PublicKey(bridge.state_address(SOL_BRIDGE_ADDRESS)); - // TODO: share connection in context? - const connection = new Connection(SOLANA_HOST, "confirmed"); - const bridgeStateAccountInfo = await connection.getAccountInfo(bridgeStatePK); - if (bridgeStateAccountInfo?.data === undefined) { - throw new Error("bridge state not found"); - } - const bridgeState = bridge.parse_state( - new Uint8Array(bridgeStateAccountInfo?.data) - ); - const transferIx = SystemProgram.transfer({ - fromPubkey: new PublicKey(payerAddress), - toPubkey: new PublicKey(feeAccount), - lamports: bridgeState.config.fee, - }); - // TODO: pass in connection - // Add transfer instruction to transaction - const { transfer_native_ix, approval_authority_address, emitter_address } = - await import("token-bridge"); - const approvalIx = Token.createApproveInstruction( - TOKEN_PROGRAM_ID, - new PublicKey(fromAddress), - new PublicKey(approval_authority_address(SOL_TOKEN_BRIDGE_ADDRESS)), - new PublicKey(payerAddress), - [], - Number(amountParsed) - ); - - let messageKey = Keypair.generate(); - const ix = ixFromRust( - transfer_native_ix( - SOL_TOKEN_BRIDGE_ADDRESS, - SOL_BRIDGE_ADDRESS, - payerAddress, - messageKey.publicKey.toString(), - fromAddress, - mintAddress, - nonce, - amountParsed, - fee, - targetAddress, - targetChain - ) - ); - const transaction = new Transaction().add(transferIx, approvalIx, ix); - const { blockhash } = await connection.getRecentBlockhash(); - transaction.recentBlockhash = blockhash; - transaction.feePayer = new PublicKey(payerAddress); - transaction.partialSign(messageKey); - // Sign transaction, broadcast, and confirm - const signed = await wallet.signTransaction(transaction); - console.log("SIGNED", signed); - const txid = await connection.sendRawTransaction(signed.serialize()); - console.log("SENT", txid); - const conf = await connection.confirmTransaction(txid); - console.log("CONFIRMED", conf); - const info = await connection.getTransaction(txid); - console.log("INFO", info); - // TODO: log parsing should be part of a utility - // TODO: better parsing, safer - const SEQ_LOG = "Program log: Sequence: "; - const sequence = info?.meta?.logMessages - ?.filter((msg) => msg.startsWith(SEQ_LOG))[0] - .replace(SEQ_LOG, ""); - if (!sequence) { - throw new Error("sequence not found"); - } - console.log("SEQ", sequence); - const emitterAddress = Buffer.from( - zeroPad( - new PublicKey(emitter_address(SOL_TOKEN_BRIDGE_ADDRESS)).toBytes(), - 32 - ) - ).toString("hex"); - const { vaaBytes } = await getSignedVAA( - CHAIN_ID_SOLANA, - emitterAddress, - sequence - ); - console.log("SIGNED VAA:", vaaBytes); - return vaaBytes; -} - -const transferFrom = { - [CHAIN_ID_ETH]: transferFromEth, - [CHAIN_ID_SOLANA]: transferFromSolana, -}; - -export default transferFrom; +import Wallet from "@project-serum/sol-wallet-adapter"; +import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { + Connection, + Keypair, + PublicKey, + SystemProgram, + Transaction, +} from "@solana/web3.js"; +import { ethers } from "ethers"; +import { arrayify, formatUnits, parseUnits, zeroPad } from "ethers/lib/utils"; +import { + Bridge__factory, + Implementation__factory, + TokenImplementation__factory, +} from "../ethers-contracts"; +import { getSignedVAA, ixFromRust } from "../sdk"; +import { + ChainId, + CHAIN_ID_ETH, + CHAIN_ID_SOLANA, + ETH_BRIDGE_ADDRESS, + ETH_TOKEN_BRIDGE_ADDRESS, + SOLANA_HOST, + SOL_BRIDGE_ADDRESS, + SOL_TOKEN_BRIDGE_ADDRESS, +} from "./consts"; + +// TODO: allow for / handle cancellation? +// TODO: overall better input checking and error handling +export async function transferFromEth( + provider: ethers.providers.Web3Provider | undefined, + signer: ethers.Signer | undefined, + tokenAddress: string, + decimals: number, + amount: string, + recipientChain: ChainId, + recipientAddress: Uint8Array | undefined +) { + if (!provider || !signer || !recipientAddress) return; + //TODO: check if token attestation exists on the target chain + //TODO: don't hardcode, fetch decimals / share them with balance, how do we determine recipient chain? + //TODO: more catches + const amountParsed = parseUnits(amount, decimals); + const signerAddress = await signer.getAddress(); + console.log("Signer:", signerAddress); + console.log("Token:", tokenAddress); + const token = TokenImplementation__factory.connect(tokenAddress, signer); + const allowance = await token.allowance( + signerAddress, + ETH_TOKEN_BRIDGE_ADDRESS + ); + console.log("Allowance", allowance.toString()); //TODO: should we check that this is zero and warn if it isn't? + const transaction = await token.approve( + ETH_TOKEN_BRIDGE_ADDRESS, + amountParsed + ); + console.log(transaction); + const fee = 0; // for now, this won't do anything, we may add later + const nonceConst = Math.random() * 100000; + const nonceBuffer = Buffer.alloc(4); + nonceBuffer.writeUInt32LE(nonceConst, 0); + console.log("Initiating transfer"); + console.log("Amount:", formatUnits(amountParsed, decimals)); + console.log("To chain:", recipientChain); + console.log("To address:", recipientAddress); + console.log("Fees:", fee); + console.log("Nonce:", nonceBuffer); + const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer); + const v = await bridge.transferTokens( + tokenAddress, + amountParsed, + recipientChain, + recipientAddress, + fee, + nonceBuffer + ); + const receipt = await v.wait(); + // TODO: log parsing should be part of a utility + // TODO: dangerous!(?) + const bridgeLog = receipt.logs.filter((l) => { + console.log(l.address, ETH_BRIDGE_ADDRESS); + return l.address === ETH_BRIDGE_ADDRESS; + })[0]; + const { + args: { sender, sequence }, + } = Implementation__factory.createInterface().parseLog(bridgeLog); + console.log(sender, sequence); + const emitterAddress = Buffer.from( + zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32) + ).toString("hex"); + const { vaaBytes } = await getSignedVAA( + CHAIN_ID_ETH, + emitterAddress, + sequence.toString() + ); + console.log("SIGNED VAA:", vaaBytes); + return vaaBytes; +} + +// TODO: need to check transfer native vs transfer wrapped +// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts) +export async function transferFromSolana( + wallet: Wallet | undefined, + payerAddress: string | undefined, //TODO: we may not need this since we have wallet + fromAddress: string | undefined, + mintAddress: string, + amount: string, + decimals: number, + targetAddressStr: string | undefined, + targetChain: ChainId +) { + if ( + !wallet || + !wallet.publicKey || + !payerAddress || + !fromAddress || + !targetAddressStr + ) + return; + const targetAddress = zeroPad(arrayify(targetAddressStr), 32); + const nonceConst = Math.random() * 100000; + const nonceBuffer = Buffer.alloc(4); + nonceBuffer.writeUInt32LE(nonceConst, 0); + const nonce = nonceBuffer.readUInt32LE(0); + const amountParsed = parseUnits(amount, decimals).toBigInt(); + const fee = BigInt(0); // for now, this won't do anything, we may add later + console.log("program:", SOL_TOKEN_BRIDGE_ADDRESS); + console.log("bridge:", SOL_BRIDGE_ADDRESS); + console.log("payer:", payerAddress); + console.log("from:", fromAddress); + console.log("token:", mintAddress); + console.log("nonce:", nonce); + console.log("amount:", amountParsed); + console.log("fee:", fee); + console.log("target:", targetAddressStr, targetAddress); + console.log("chain:", targetChain); + const bridge = await import("bridge"); + const feeAccount = await bridge.fee_collector_address(SOL_BRIDGE_ADDRESS); + const bridgeStatePK = new PublicKey(bridge.state_address(SOL_BRIDGE_ADDRESS)); + // TODO: share connection in context? + const connection = new Connection(SOLANA_HOST, "confirmed"); + const bridgeStateAccountInfo = await connection.getAccountInfo(bridgeStatePK); + if (bridgeStateAccountInfo?.data === undefined) { + throw new Error("bridge state not found"); + } + const bridgeState = bridge.parse_state( + new Uint8Array(bridgeStateAccountInfo?.data) + ); + const transferIx = SystemProgram.transfer({ + fromPubkey: new PublicKey(payerAddress), + toPubkey: new PublicKey(feeAccount), + lamports: bridgeState.config.fee, + }); + // TODO: pass in connection + // Add transfer instruction to transaction + const { transfer_native_ix, approval_authority_address, emitter_address } = + await import("token-bridge"); + const approvalIx = Token.createApproveInstruction( + TOKEN_PROGRAM_ID, + new PublicKey(fromAddress), + new PublicKey(approval_authority_address(SOL_TOKEN_BRIDGE_ADDRESS)), + new PublicKey(payerAddress), + [], + Number(amountParsed) + ); + + let messageKey = Keypair.generate(); + const ix = ixFromRust( + transfer_native_ix( + SOL_TOKEN_BRIDGE_ADDRESS, + SOL_BRIDGE_ADDRESS, + payerAddress, + messageKey.publicKey.toString(), + fromAddress, + mintAddress, + nonce, + amountParsed, + fee, + targetAddress, + targetChain + ) + ); + const transaction = new Transaction().add(transferIx, approvalIx, ix); + const { blockhash } = await connection.getRecentBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.feePayer = new PublicKey(payerAddress); + transaction.partialSign(messageKey); + // Sign transaction, broadcast, and confirm + const signed = await wallet.signTransaction(transaction); + console.log("SIGNED", signed); + const txid = await connection.sendRawTransaction(signed.serialize()); + console.log("SENT", txid); + const conf = await connection.confirmTransaction(txid); + console.log("CONFIRMED", conf); + const info = await connection.getTransaction(txid); + console.log("INFO", info); + // TODO: log parsing should be part of a utility + // TODO: better parsing, safer + const SEQ_LOG = "Program log: Sequence: "; + const sequence = info?.meta?.logMessages + ?.filter((msg) => msg.startsWith(SEQ_LOG))[0] + .replace(SEQ_LOG, ""); + if (!sequence) { + throw new Error("sequence not found"); + } + console.log("SEQ", sequence); + const emitterAddress = Buffer.from( + zeroPad( + new PublicKey(emitter_address(SOL_TOKEN_BRIDGE_ADDRESS)).toBytes(), + 32 + ) + ).toString("hex"); + const { vaaBytes } = await getSignedVAA( + CHAIN_ID_SOLANA, + emitterAddress, + sequence + ); + console.log("SIGNED VAA:", vaaBytes); + return vaaBytes; +} + +const transferFrom = { + [CHAIN_ID_ETH]: transferFromEth, + [CHAIN_ID_SOLANA]: transferFromSolana, +}; + +export default transferFrom;