bridge_ui: wrapped to wrapped

Change-Id: Icb12978ce7be6cc468d650039f508fc8ad19babe
This commit is contained in:
Evan Gray 2021-09-07 16:14:08 -04:00
parent a79ef81721
commit 42702dfbe8
8 changed files with 92 additions and 87 deletions

View File

@ -10,29 +10,32 @@ import {
} from "../../store/attestSlice"; } from "../../store/attestSlice";
import { import {
selectAttestSignedVAAHex, selectAttestSignedVAAHex,
selectTransferSourceAsset, selectTransferOriginAsset,
selectTransferSourceChain, selectTransferOriginChain,
selectTransferTargetChain, selectTransferTargetChain,
} from "../../store/selectors"; } from "../../store/selectors";
import { hexToNativeString } from "../../utils/array";
export default function RegisterNowButton() { export default function RegisterNowButton() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
const sourceChain = useSelector(selectTransferSourceChain); const originChain = useSelector(selectTransferOriginChain);
const sourceAsset = useSelector(selectTransferSourceAsset); const originAsset = useSelector(selectTransferOriginAsset);
const targetChain = useSelector(selectTransferTargetChain); const targetChain = useSelector(selectTransferTargetChain);
// user might be in the middle of a different attest // user might be in the middle of a different attest
const signedVAAHex = useSelector(selectAttestSignedVAAHex); const signedVAAHex = useSelector(selectAttestSignedVAAHex);
const canSwitch = sourceAsset && !signedVAAHex; const canSwitch = originChain && originAsset && !signedVAAHex;
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
if (sourceAsset && canSwitch) { const nativeAsset =
dispatch(setSourceChain(sourceChain)); originChain && hexToNativeString(originAsset, originChain);
dispatch(setSourceAsset(sourceAsset)); if (originChain && originAsset && nativeAsset && canSwitch) {
dispatch(setSourceChain(originChain));
dispatch(setSourceAsset(nativeAsset));
dispatch(setTargetChain(targetChain)); dispatch(setTargetChain(targetChain));
dispatch(setStep(2)); dispatch(setStep(2));
history.push("/register"); history.push("/register");
} }
}, [dispatch, canSwitch, sourceChain, sourceAsset, targetChain, history]); }, [dispatch, canSwitch, originChain, originAsset, targetChain, history]);
if (!canSwitch) return null; if (!canSwitch) return null;
return ( return (
<Button <Button

View File

@ -24,7 +24,7 @@ export const SolanaWalletProvider: FC = (props) => {
}, []); }, []);
return ( return (
<WalletProvider wallets={wallets} autoConnect> <WalletProvider wallets={wallets}>
<WalletDialogProvider>{props.children}</WalletDialogProvider> <WalletDialogProvider>{props.children}</WalletDialogProvider>
</WalletProvider> </WalletProvider>
); );

View File

@ -10,8 +10,6 @@ import {
selectTransferIsSourceAssetWormholeWrapped, selectTransferIsSourceAssetWormholeWrapped,
selectTransferOriginAsset, selectTransferOriginAsset,
selectTransferOriginChain, selectTransferOriginChain,
selectTransferSourceAsset,
selectTransferSourceChain,
selectTransferTargetChain, selectTransferTargetChain,
} from "../store/selectors"; } from "../store/selectors";
import { setTargetAsset } from "../store/transferSlice"; import { setTargetAsset } from "../store/transferSlice";
@ -24,8 +22,6 @@ import {
function useFetchTargetAsset() { function useFetchTargetAsset() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const sourceChain = useSelector(selectTransferSourceChain);
const sourceAsset = useSelector(selectTransferSourceAsset);
const isSourceAssetWormholeWrapped = useSelector( const isSourceAssetWormholeWrapped = useSelector(
selectTransferIsSourceAssetWormholeWrapped selectTransferIsSourceAssetWormholeWrapped
); );
@ -33,7 +29,6 @@ function useFetchTargetAsset() {
const originAsset = useSelector(selectTransferOriginAsset); const originAsset = useSelector(selectTransferOriginAsset);
const targetChain = useSelector(selectTransferTargetChain); const targetChain = useSelector(selectTransferTargetChain);
const { provider } = useEthereumProvider(); const { provider } = useEthereumProvider();
// TODO: this may not cover wrapped to wrapped, should always use origin?
useEffect(() => { useEffect(() => {
if (isSourceAssetWormholeWrapped && originChain === targetChain) { if (isSourceAssetWormholeWrapped && originChain === targetChain) {
dispatch(setTargetAsset(hexToNativeString(originAsset, originChain))); dispatch(setTargetAsset(hexToNativeString(originAsset, originChain)));
@ -43,19 +38,24 @@ function useFetchTargetAsset() {
dispatch(setTargetAsset(undefined)); dispatch(setTargetAsset(undefined));
let cancelled = false; let cancelled = false;
(async () => { (async () => {
if (targetChain === CHAIN_ID_ETH && provider && sourceAsset) { if (
targetChain === CHAIN_ID_ETH &&
provider &&
originChain &&
originAsset
) {
const asset = await getForeignAssetEth( const asset = await getForeignAssetEth(
provider, provider,
sourceChain, originChain,
sourceAsset originAsset
); );
if (!cancelled) { if (!cancelled) {
dispatch(setTargetAsset(asset)); dispatch(setTargetAsset(asset));
} }
} }
if (targetChain === CHAIN_ID_SOLANA && sourceAsset) { if (targetChain === CHAIN_ID_SOLANA && originChain && originAsset) {
try { try {
const asset = await getForeignAssetSol(sourceChain, sourceAsset); const asset = await getForeignAssetSol(originChain, originAsset);
if (!cancelled) { if (!cancelled) {
dispatch(setTargetAsset(asset)); dispatch(setTargetAsset(asset));
} }
@ -65,9 +65,9 @@ function useFetchTargetAsset() {
} }
} }
} }
if (targetChain === CHAIN_ID_TERRA && sourceAsset) { if (targetChain === CHAIN_ID_TERRA && originChain && originAsset) {
try { try {
const asset = await getForeignAssetTerra(sourceChain, sourceAsset); const asset = await getForeignAssetTerra(originChain, originAsset);
if (!cancelled) { if (!cancelled) {
dispatch(setTargetAsset(asset)); dispatch(setTargetAsset(asset));
} }
@ -87,8 +87,6 @@ function useFetchTargetAsset() {
originChain, originChain,
originAsset, originAsset,
targetChain, targetChain,
sourceChain,
sourceAsset,
provider, provider,
]); ]);
} }

View File

@ -96,6 +96,7 @@ export const transferSlice = createSlice({
state.targetAddressHex = undefined; state.targetAddressHex = undefined;
// clear targetAsset so that components that fire before useFetchTargetAsset don't get stale data // clear targetAsset so that components that fire before useFetchTargetAsset don't get stale data
state.targetAsset = undefined; state.targetAsset = undefined;
state.targetParsedTokenAccount = undefined;
} }
}, },
setSourceWormholeWrappedInfo: ( setSourceWormholeWrappedInfo: (
@ -158,6 +159,7 @@ export const transferSlice = createSlice({
state.targetAddressHex = undefined; state.targetAddressHex = undefined;
// clear targetAsset so that components that fire before useFetchTargetAsset don't get stale data // clear targetAsset so that components that fire before useFetchTargetAsset don't get stale data
state.targetAsset = undefined; state.targetAsset = undefined;
state.targetParsedTokenAccount = undefined;
if (state.sourceChain === action.payload) { if (state.sourceChain === action.payload) {
state.sourceChain = prevTargetChain; state.sourceChain = prevTargetChain;
state.activeStep = 0; state.activeStep = 0;
@ -196,6 +198,7 @@ export const transferSlice = createSlice({
}, },
setRedeemTx: (state, action: PayloadAction<Transaction>) => { setRedeemTx: (state, action: PayloadAction<Transaction>) => {
state.redeemTx = action.payload; state.redeemTx = action.payload;
state.isRedeeming = false;
}, },
reset: (state) => ({ reset: (state) => ({
...initialState, ...initialState,

View File

@ -12,13 +12,17 @@ export const uint8ArrayToHex = (a: Uint8Array) =>
Buffer.from(a).toString("hex"); Buffer.from(a).toString("hex");
export const hexToUint8Array = (h: string) => export const hexToUint8Array = (h: string) =>
new Uint8Array(Buffer.from(h, "hex")); new Uint8Array(Buffer.from(h, "hex"));
export const hexToNativeString = (h: string | undefined, c: ChainId) => export const hexToNativeString = (h: string | undefined, c: ChainId) => {
!h try {
? undefined return !h
: c === CHAIN_ID_SOLANA ? undefined
? new PublicKey(hexToUint8Array(h)).toString() : c === CHAIN_ID_SOLANA
: c === CHAIN_ID_ETH ? new PublicKey(hexToUint8Array(h)).toString()
? hexValue(hexToUint8Array(h)) : c === CHAIN_ID_ETH
: c === CHAIN_ID_TERRA ? hexValue(hexToUint8Array(h))
? humanAddress(hexToUint8Array(h.substr(24))) // terra expects 20 bytes, not 32 : c === CHAIN_ID_TERRA
: h; ? humanAddress(hexToUint8Array(h.substr(24))) // terra expects 20 bytes, not 32
: h;
} catch (e) {}
return undefined;
};

View File

@ -1,19 +1,18 @@
import { import {
ChainId, ChainId,
CHAIN_ID_SOLANA,
getForeignAssetEth as getForeignAssetEthTx, getForeignAssetEth as getForeignAssetEthTx,
getForeignAssetSolana as getForeignAssetSolanaTx, getForeignAssetSolana as getForeignAssetSolanaTx,
getForeignAssetTerra as getForeignAssetTerraTx, getForeignAssetTerra as getForeignAssetTerraTx,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import { Connection, PublicKey } from "@solana/web3.js"; import { Connection } from "@solana/web3.js";
import { ethers } from "ethers";
import { arrayify, isHexString, zeroPad } from "ethers/lib/utils";
import { LCDClient } from "@terra-money/terra.js"; import { LCDClient } from "@terra-money/terra.js";
import { ethers } from "ethers";
import { hexToUint8Array } from "./array";
import { import {
ETH_TOKEN_BRIDGE_ADDRESS, ETH_TOKEN_BRIDGE_ADDRESS,
SOLANA_HOST, SOLANA_HOST,
TERRA_HOST,
SOL_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_HOST,
TERRA_TOKEN_BRIDGE_ADDRESS, TERRA_TOKEN_BRIDGE_ADDRESS,
} from "./consts"; } from "./consts";
@ -23,21 +22,14 @@ export async function getForeignAssetEth(
originAsset: string originAsset: string
) { ) {
try { 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 getForeignAssetEthTx( return await getForeignAssetEthTx(
ETH_TOKEN_BRIDGE_ADDRESS, ETH_TOKEN_BRIDGE_ADDRESS,
provider, provider,
originChain, originChain,
originAssetBytes hexToUint8Array(originAsset)
); );
} catch (e) { } catch (e) {
return ethers.constants.AddressZero; return null;
} }
} }
@ -45,18 +37,12 @@ export async function getForeignAssetSol(
originChain: ChainId, originChain: ChainId,
originAsset: string originAsset: string
) { ) {
if (!isHexString(originAsset)) return null;
// TODO: address conversion may be more complex than this
const originAssetBytes = zeroPad(
arrayify(originAsset, { hexPad: "left" }),
32
);
const connection = new Connection(SOLANA_HOST, "confirmed"); const connection = new Connection(SOLANA_HOST, "confirmed");
return await getForeignAssetSolanaTx( return await getForeignAssetSolanaTx(
connection, connection,
SOL_TOKEN_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS,
originChain, originChain,
originAssetBytes hexToUint8Array(originAsset)
); );
} }
@ -71,21 +57,14 @@ export async function getForeignAssetTerra(
originAsset: string originAsset: string
) { ) {
try { try {
const originAssetBytes = zeroPad(
originChain === CHAIN_ID_SOLANA
? new PublicKey(originAsset).toBytes()
: arrayify(originAsset),
32
);
const lcd = new LCDClient(TERRA_HOST); const lcd = new LCDClient(TERRA_HOST);
return await getForeignAssetTerraTx( return await getForeignAssetTerraTx(
TERRA_TOKEN_BRIDGE_ADDRESS, TERRA_TOKEN_BRIDGE_ADDRESS,
lcd, lcd,
originChain, originChain,
originAssetBytes hexToUint8Array(originAsset)
); );
} catch (e) { } catch (e) {
// TODO: better return for this return null;
return ethers.constants.AddressZero;
} }
} }

View File

@ -23,7 +23,7 @@ export async function getForeignAssetEth(
try { try {
return await tokenBridge.wrappedAsset(originChain, originAsset); return await tokenBridge.wrappedAsset(originChain, originAsset);
} catch (e) { } catch (e) {
return ethers.constants.AddressZero; return null;
} }
} }
@ -33,13 +33,20 @@ export async function getForeignAssetTerra(
originChain: ChainId, originChain: ChainId,
originAsset: Uint8Array originAsset: Uint8Array
) { ) {
const result: { address: string } = await client.wasm.contractQuery(tokenBridgeAddress, { try {
wrapped_registry: { const result: { address: string } = await client.wasm.contractQuery(
chain: originChain, tokenBridgeAddress,
address: fromUint8Array(originAsset), {
}, wrapped_registry: {
}); chain: originChain,
return result.address; address: fromUint8Array(originAsset),
},
}
);
return result.address;
} catch (e) {
return null;
}
} }
/** /**

View File

@ -51,7 +51,7 @@ export async function getOriginalAssetEth(
return { return {
isWrapped: false, isWrapped: false,
chainId: CHAIN_ID_ETH, chainId: CHAIN_ID_ETH,
assetAddress: arrayify(wrappedAddress), assetAddress: zeroPad(arrayify(wrappedAddress), 32),
}; };
} }
@ -59,20 +59,24 @@ export async function getOriginalAssetTerra(
client: LCDClient, client: LCDClient,
wrappedAddress: string wrappedAddress: string
): Promise<WormholeWrappedInfo> { ): Promise<WormholeWrappedInfo> {
const result: { try {
asset_address: string; const result: {
asset_chain: ChainId; asset_address: string;
bridge: string; asset_chain: ChainId;
} = await client.wasm.contractQuery(wrappedAddress, { bridge: string;
wrapped_asset_info: {}, } = await client.wasm.contractQuery(wrappedAddress, {
}); wrapped_asset_info: {},
if (result) { });
return { if (result) {
isWrapped: true, return {
chainId: result.asset_chain, isWrapped: true,
assetAddress: new Uint8Array(Buffer.from(result.asset_address, "base64")), chainId: result.asset_chain,
}; assetAddress: new Uint8Array(
} Buffer.from(result.asset_address, "base64")
),
};
}
} catch (e) {}
return { return {
isWrapped: false, isWrapped: false,
chainId: CHAIN_ID_TERRA, chainId: CHAIN_ID_TERRA,
@ -114,6 +118,13 @@ export async function getOriginalAssetSol(
}; };
} }
} }
try {
return {
isWrapped: false,
chainId: CHAIN_ID_SOLANA,
assetAddress: new PublicKey(mintAddress).toBytes(),
};
} catch (e) {}
return { return {
isWrapped: false, isWrapped: false,
chainId: CHAIN_ID_SOLANA, chainId: CHAIN_ID_SOLANA,