diff --git a/bridge_ui/package-lock.json b/bridge_ui/package-lock.json
index b326bd57a..69f264e2e 100644
--- a/bridge_ui/package-lock.json
+++ b/bridge_ui/package-lock.json
@@ -29,6 +29,7 @@
"@solana/wallet-base": "^0.0.1",
"@solana/web3.js": "^1.22.0",
"@terra-money/wallet-provider": "^1.4.0-alpha.1",
+ "bech32": "^1.1.4",
"ethers": "^5.4.1",
"js-base64": "^3.6.1",
"notistack": "^1.0.10",
@@ -57,6 +58,9 @@
"@improbable-eng/grpc-web": "^0.14.0",
"@solana/spl-token": "^0.1.8",
"@solana/web3.js": "^1.24.0",
+ "@terra-money/terra.js": "^1.8.10",
+ "@terra-money/wallet-provider": "^1.2.4",
+ "js-base64": "^3.6.1",
"protobufjs": "^6.11.2",
"rxjs": "^7.3.0"
},
@@ -65,6 +69,7 @@
"@typechain/ethers-v5": "^7.0.1",
"@types/long": "^4.0.1",
"@types/node": "^16.6.1",
+ "@types/react": "^17.0.19",
"copy-dir": "^1.3.0",
"ethers": "^5.4.4",
"prettier": "^2.3.2",
@@ -36739,6 +36744,7 @@
"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",
@@ -36752,6 +36758,7 @@
"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"
},
@@ -37587,6 +37594,7 @@
"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": "*"
}
@@ -37596,6 +37604,7 @@
"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",
@@ -37615,6 +37624,7 @@
"resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
"integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=0.6"
}
@@ -41022,11 +41032,15 @@
"@openzeppelin/contracts": "^4.2.0",
"@solana/spl-token": "^0.1.8",
"@solana/web3.js": "^1.24.0",
+ "@terra-money/terra.js": "^1.8.10",
+ "@terra-money/wallet-provider": "^1.2.4",
"@typechain/ethers-v5": "^7.0.1",
"@types/long": "^4.0.1",
"@types/node": "^16.6.1",
+ "@types/react": "^17.0.19",
"copy-dir": "^1.3.0",
"ethers": "^5.4.4",
+ "js-base64": "^3.6.1",
"prettier": "^2.3.2",
"protobufjs": "^6.11.2",
"rxjs": "^7.3.0",
@@ -48382,7 +48396,6 @@
"dev": true,
"optional": true,
"requires": {
- "bitcore-lib": "^8.25.10",
"unorm": "^1.4.1"
}
},
@@ -69246,6 +69259,7 @@
"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",
@@ -69258,7 +69272,8 @@
"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
+ "dev": true,
+ "peer": true
}
}
},
@@ -69268,8 +69283,7 @@
"integrity": "sha512-R4s75XH+o8qM+WaRrAU9S2rbAMDzob18/S3V8R9ZoFpZkPWLAohWWlzWAp1ybeTkOuuku/X1zJtxiV0pBYxZww==",
"dev": true,
"requires": {
- "loader-utils": "^1.1.0",
- "wasm-dce": "^1.0.0"
+ "loader-utils": "^1.1.0"
},
"dependencies": {
"json5": {
@@ -69991,13 +70005,15 @@
"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
+ "dev": true,
+ "peer": 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",
@@ -70008,7 +70024,8 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
"integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=",
- "dev": true
+ "dev": true,
+ "peer": true
}
}
},
diff --git a/bridge_ui/package.json b/bridge_ui/package.json
index ea33caa5c..006cd88b2 100644
--- a/bridge_ui/package.json
+++ b/bridge_ui/package.json
@@ -23,6 +23,7 @@
"@solana/wallet-base": "^0.0.1",
"@solana/web3.js": "^1.22.0",
"@terra-money/wallet-provider": "^1.4.0-alpha.1",
+ "bech32": "^1.1.4",
"ethers": "^5.4.1",
"js-base64": "^3.6.1",
"notistack": "^1.0.10",
diff --git a/bridge_ui/src/components/Attest/Source.tsx b/bridge_ui/src/components/Attest/Source.tsx
index 4630ac835..1d496b7b1 100644
--- a/bridge_ui/src/components/Attest/Source.tsx
+++ b/bridge_ui/src/components/Attest/Source.tsx
@@ -63,7 +63,7 @@ function Source() {
{/* TODO: token list for eth, check own */}
{
dispatch(setTargetChain(event.target.value));
@@ -76,7 +80,14 @@ function Target() {
+ (undefined);
- useEffect(() => {
- (async () => {
- if (targetChain === CHAIN_ID_SOLANA) {
- tpkRef.current = targetTokenAccountPublicKey
- ? zeroPad(new PublicKey(targetTokenAccountPublicKey).toBytes(), 32) // use the target's TokenAccount if it exists
- : solPK && targetAsset // otherwise, use the associated token account (which we create in the case it doesn't exist)
- ? zeroPad(
- (
- await Token.getAssociatedTokenAddress(
- ASSOCIATED_TOKEN_PROGRAM_ID,
- TOKEN_PROGRAM_ID,
- new PublicKey(targetAsset),
- solPK
- )
- ).toBytes(),
- 32
- )
- : undefined;
- } else tpkRef.current = undefined;
- })();
- }, [targetChain, solPK, targetAsset, targetTokenAccountPublicKey]);
- // 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
@@ -272,7 +235,7 @@ export function useHandleTransfer() {
sourceChain === CHAIN_ID_ETH &&
!!signer &&
decimals !== undefined &&
- !!tpkRef.current
+ !!targetAddress
) {
eth(
dispatch,
@@ -282,14 +245,14 @@ export function useHandleTransfer() {
decimals,
amount,
targetChain,
- tpkRef.current
+ targetAddress
);
} else if (
sourceChain === CHAIN_ID_SOLANA &&
!!solanaWallet &&
!!solPK &&
!!sourceTokenPublicKey &&
- !!signerAddress &&
+ !!targetAddress &&
decimals !== undefined
) {
solana(
@@ -299,10 +262,10 @@ export function useHandleTransfer() {
solPK.toString(),
sourceTokenPublicKey,
sourceAsset,
- amount, //TODO: avoid decimals, pass in parsed amount
+ amount,
decimals,
- signerAddress,
targetChain,
+ targetAddress,
originAsset,
originChain
);
@@ -310,7 +273,7 @@ export function useHandleTransfer() {
sourceChain === CHAIN_ID_TERRA &&
!!terraWallet &&
decimals !== undefined &&
- !!signerAddress
+ !!targetAddress
) {
terra(
dispatch,
@@ -318,8 +281,8 @@ export function useHandleTransfer() {
terraWallet,
sourceAsset,
amount,
- signerAddress, // TODO: only works for Eth
- targetChain
+ targetChain,
+ targetAddress
);
} else {
// enqueueSnackbar("Transfers from this chain are not yet supported", {
@@ -331,7 +294,6 @@ export function useHandleTransfer() {
enqueueSnackbar,
sourceChain,
signer,
- signerAddress,
solanaWallet,
solPK,
terraWallet,
@@ -340,6 +302,7 @@ export function useHandleTransfer() {
amount,
decimals,
targetChain,
+ targetAddress,
originAsset,
originChain,
]);
diff --git a/bridge_ui/src/hooks/useSyncTargetAddress.ts b/bridge_ui/src/hooks/useSyncTargetAddress.ts
new file mode 100644
index 000000000..0e05ff10a
--- /dev/null
+++ b/bridge_ui/src/hooks/useSyncTargetAddress.ts
@@ -0,0 +1,110 @@
+import {
+ CHAIN_ID_ETH,
+ CHAIN_ID_SOLANA,
+ CHAIN_ID_TERRA,
+} from "@certusone/wormhole-sdk";
+import { arrayify, zeroPad } from "@ethersproject/bytes";
+import {
+ ASSOCIATED_TOKEN_PROGRAM_ID,
+ Token,
+ TOKEN_PROGRAM_ID,
+} from "@solana/spl-token";
+import { PublicKey } from "@solana/web3.js";
+import { useConnectedWallet } from "@terra-money/wallet-provider";
+import { useEffect } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { useEthereumProvider } from "../contexts/EthereumProviderContext";
+import { useSolanaWallet } from "../contexts/SolanaWalletContext";
+import {
+ selectTransferTargetAsset,
+ selectTransferTargetChain,
+ selectTransferTargetParsedTokenAccount,
+} from "../store/selectors";
+import { setTargetAddressHex } from "../store/transferSlice";
+import { uint8ArrayToHex } from "../utils/array";
+import bech32 from "bech32";
+
+function useSyncTargetAddress(shouldFire: boolean) {
+ const dispatch = useDispatch();
+ const targetChain = useSelector(selectTransferTargetChain);
+ const { signerAddress } = useEthereumProvider();
+ const solanaWallet = useSolanaWallet();
+ const solPK = solanaWallet?.publicKey;
+ const targetAsset = useSelector(selectTransferTargetAsset);
+ const targetParsedTokenAccount = useSelector(
+ selectTransferTargetParsedTokenAccount
+ );
+ const targetTokenAccountPublicKey = targetParsedTokenAccount?.publicKey;
+ const terraWallet = useConnectedWallet();
+ useEffect(() => {
+ if (shouldFire) {
+ let cancelled = false;
+ if (targetChain === CHAIN_ID_ETH && signerAddress) {
+ dispatch(
+ setTargetAddressHex(
+ uint8ArrayToHex(zeroPad(arrayify(signerAddress), 32))
+ )
+ );
+ }
+ // TODO: have the user explicitly select an account on solana
+ else if (targetChain === CHAIN_ID_SOLANA && targetTokenAccountPublicKey) {
+ // use the target's TokenAccount if it exists
+ dispatch(
+ setTargetAddressHex(
+ uint8ArrayToHex(
+ zeroPad(new PublicKey(targetTokenAccountPublicKey).toBytes(), 32)
+ )
+ )
+ );
+ } else if (targetChain === CHAIN_ID_SOLANA && solPK && targetAsset) {
+ // otherwise, use the associated token account (which we create in the case it doesn't exist)
+ (async () => {
+ const associatedTokenAccount = await Token.getAssociatedTokenAddress(
+ ASSOCIATED_TOKEN_PROGRAM_ID,
+ TOKEN_PROGRAM_ID,
+ new PublicKey(targetAsset),
+ solPK
+ );
+ if (!cancelled) {
+ dispatch(
+ setTargetAddressHex(
+ uint8ArrayToHex(zeroPad(associatedTokenAccount.toBytes(), 32))
+ )
+ );
+ }
+ })();
+ } else if (
+ targetChain === CHAIN_ID_TERRA &&
+ terraWallet &&
+ terraWallet.walletAddress
+ ) {
+ dispatch(
+ setTargetAddressHex(
+ uint8ArrayToHex(
+ zeroPad(
+ new Uint8Array(bech32.decode(terraWallet.walletAddress).words),
+ 32
+ )
+ )
+ )
+ );
+ } else {
+ dispatch(setTargetAddressHex(undefined));
+ }
+ return () => {
+ cancelled = true;
+ };
+ }
+ }, [
+ dispatch,
+ shouldFire,
+ targetChain,
+ signerAddress,
+ solPK,
+ targetAsset,
+ targetTokenAccountPublicKey,
+ terraWallet,
+ ]);
+}
+
+export default useSyncTargetAddress;
diff --git a/bridge_ui/src/hooks/useTransferTargetAddress.ts b/bridge_ui/src/hooks/useTransferTargetAddress.ts
new file mode 100644
index 000000000..ef3bdd0a6
--- /dev/null
+++ b/bridge_ui/src/hooks/useTransferTargetAddress.ts
@@ -0,0 +1,13 @@
+import { useMemo } from "react";
+import { useSelector } from "react-redux";
+import { selectTransferTargetAddressHex } from "../store/selectors";
+import { hexToUint8Array } from "../utils/array";
+
+export default function useTransferTargetAddressHex() {
+ const targetAddressHex = useSelector(selectTransferTargetAddressHex);
+ const targetAddress = useMemo(
+ () => (targetAddressHex ? hexToUint8Array(targetAddressHex) : undefined),
+ [targetAddressHex]
+ );
+ return targetAddress;
+}
diff --git a/bridge_ui/src/store/selectors.ts b/bridge_ui/src/store/selectors.ts
index f643ae5d3..2792676d9 100644
--- a/bridge_ui/src/store/selectors.ts
+++ b/bridge_ui/src/store/selectors.ts
@@ -57,6 +57,8 @@ export const selectTransferSourceBalanceString = (state: RootState) =>
export const selectTransferAmount = (state: RootState) => state.transfer.amount;
export const selectTransferTargetChain = (state: RootState) =>
state.transfer.targetChain;
+export const selectTransferTargetAddressHex = (state: RootState) =>
+ state.transfer.targetAddressHex;
export const selectTransferTargetAsset = (state: RootState) =>
state.transfer.targetAsset;
export const selectTransferTargetParsedTokenAccount = (state: RootState) =>
@@ -96,11 +98,8 @@ export const selectTransferIsTargetComplete = (state: RootState) =>
!!state.transfer.targetChain &&
!!state.transfer.targetAsset &&
(state.transfer.targetChain !== CHAIN_ID_ETH ||
- state.transfer.targetAsset !== ethers.constants.AddressZero); //&&
-// Associated Token Account exists
-// (state.transfer.targetChain !== CHAIN_ID_SOLANA ||
-// (!!state.transfer.targetParsedTokenAccount &&
-// !!state.transfer.targetParsedTokenAccount.publicKey));
+ state.transfer.targetAsset !== ethers.constants.AddressZero) &&
+ !!state.transfer.targetAddressHex;
export const selectTransferIsSendComplete = (state: RootState) =>
!!selectTransferSignedVAAHex(state);
export const selectTransferShouldLockFields = (state: RootState) =>
diff --git a/bridge_ui/src/store/transferSlice.ts b/bridge_ui/src/store/transferSlice.ts
index 92d7e3b89..570aa9001 100644
--- a/bridge_ui/src/store/transferSlice.ts
+++ b/bridge_ui/src/store/transferSlice.ts
@@ -34,6 +34,7 @@ export interface TransferState {
sourceParsedTokenAccount: ParsedTokenAccount | undefined;
amount: string;
targetChain: ChainId;
+ targetAddressHex: string | undefined;
targetAsset: string | null | undefined;
targetParsedTokenAccount: ParsedTokenAccount | undefined;
signedVAAHex: string | undefined;
@@ -51,6 +52,7 @@ const initialState: TransferState = {
originAsset: undefined,
amount: "",
targetChain: CHAIN_ID_ETH,
+ targetAddressHex: undefined,
targetAsset: undefined,
targetParsedTokenAccount: undefined,
signedVAAHex: undefined,
@@ -86,6 +88,7 @@ export const transferSlice = createSlice({
}
if (state.targetChain === action.payload) {
state.targetChain = prevSourceChain;
+ state.targetAddressHex = undefined;
}
},
setSourceAsset: (state, action: PayloadAction) => {
@@ -117,6 +120,8 @@ export const transferSlice = createSlice({
setTargetChain: (state, action: PayloadAction) => {
const prevTargetChain = state.targetChain;
state.targetChain = action.payload;
+ state.targetAddressHex = undefined;
+ // targetAsset is handled by useFetchTargetAsset
if (state.sourceChain === action.payload) {
state.sourceChain = prevTargetChain;
state.activeStep = 0;
@@ -132,6 +137,9 @@ export const transferSlice = createSlice({
}
}
},
+ setTargetAddressHex: (state, action: PayloadAction) => {
+ state.targetAddressHex = action.payload;
+ },
setTargetAsset: (
state,
action: PayloadAction
@@ -169,6 +177,7 @@ export const {
setSourceParsedTokenAccount,
setAmount,
setTargetChain,
+ setTargetAddressHex,
setTargetAsset,
setTargetParsedTokenAccount,
setSignedVAAHex,