bridge_ui: add ability to update attestations
Change-Id: Iedb0418d2a3b24a979af99107ef8a4ca8c3a4619
This commit is contained in:
parent
10663cd72e
commit
78cdcb13ae
|
@ -1,27 +1,71 @@
|
|||
import { CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
||||
import { CircularProgress, makeStyles } from "@material-ui/core";
|
||||
import { useSelector } from "react-redux";
|
||||
import useFetchForeignAsset from "../../hooks/useFetchForeignAsset";
|
||||
import { useHandleCreateWrapped } from "../../hooks/useHandleCreateWrapped";
|
||||
import useIsWalletReady from "../../hooks/useIsWalletReady";
|
||||
import { selectAttestTargetChain } from "../../store/selectors";
|
||||
import {
|
||||
selectAttestSourceAsset,
|
||||
selectAttestSourceChain,
|
||||
selectAttestTargetChain,
|
||||
} from "../../store/selectors";
|
||||
import ButtonWithLoader from "../ButtonWithLoader";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
import WaitingForWalletMessage from "./WaitingForWalletMessage";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
alignCenter: {
|
||||
margin: "0 auto",
|
||||
display: "block",
|
||||
textAlign: "center",
|
||||
},
|
||||
spacer: {
|
||||
height: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
function Create() {
|
||||
const { handleClick, disabled, showLoader } = useHandleCreateWrapped();
|
||||
const classes = useStyles();
|
||||
const targetChain = useSelector(selectAttestTargetChain);
|
||||
const originAsset = useSelector(selectAttestSourceAsset);
|
||||
const originChain = useSelector(selectAttestSourceChain);
|
||||
const { isReady, statusMessage } = useIsWalletReady(targetChain);
|
||||
const foreignAssetInfo = useFetchForeignAsset(
|
||||
originChain,
|
||||
originAsset,
|
||||
targetChain
|
||||
);
|
||||
const shouldUpdate =
|
||||
targetChain !== CHAIN_ID_SOLANA && foreignAssetInfo.data?.doesExist;
|
||||
const error = foreignAssetInfo.error || statusMessage;
|
||||
const { handleClick, disabled, showLoader } = useHandleCreateWrapped(
|
||||
shouldUpdate || false
|
||||
);
|
||||
|
||||
console.log("foreign asset info", foreignAssetInfo);
|
||||
|
||||
return (
|
||||
<>
|
||||
<KeyAndBalance chainId={targetChain} />
|
||||
<ButtonWithLoader
|
||||
disabled={!isReady || disabled}
|
||||
onClick={handleClick}
|
||||
showLoader={showLoader}
|
||||
error={statusMessage}
|
||||
>
|
||||
Create
|
||||
</ButtonWithLoader>
|
||||
<WaitingForWalletMessage />
|
||||
|
||||
{foreignAssetInfo.isFetching ? (
|
||||
<>
|
||||
<div className={classes.spacer} />
|
||||
<CircularProgress className={classes.alignCenter} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ButtonWithLoader
|
||||
disabled={!isReady || disabled}
|
||||
onClick={handleClick}
|
||||
showLoader={showLoader}
|
||||
error={error}
|
||||
>
|
||||
{shouldUpdate ? "Update" : "Create"}
|
||||
</ButtonWithLoader>
|
||||
<WaitingForWalletMessage />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_TERRA,
|
||||
getForeignAssetEth,
|
||||
getForeignAssetSolana,
|
||||
getForeignAssetTerra,
|
||||
nativeToHexString,
|
||||
hexToUint8Array,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { LCDClient } from "@terra-money/terra.js";
|
||||
import { ethers } from "ethers";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import { DataWrapper } from "../store/helpers";
|
||||
import {
|
||||
getEvmChainId,
|
||||
getTokenBridgeAddressForChain,
|
||||
SOLANA_HOST,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_HOST,
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "../utils/consts";
|
||||
import { isEVMChain } from "../utils/ethereum";
|
||||
import useIsWalletReady from "./useIsWalletReady";
|
||||
|
||||
export type ForeignAssetInfo = {
|
||||
doesExist: boolean;
|
||||
address: string | null;
|
||||
};
|
||||
|
||||
function useFetchForeignAsset(
|
||||
originChain: ChainId,
|
||||
originAsset: string,
|
||||
foreignChain: ChainId
|
||||
): DataWrapper<ForeignAssetInfo> {
|
||||
const { provider, chainId: evmChainId } = useEthereumProvider();
|
||||
const { isReady, statusMessage } = useIsWalletReady(foreignChain);
|
||||
const correctEvmNetwork = getEvmChainId(foreignChain);
|
||||
const hasCorrectEvmNetwork = evmChainId === correctEvmNetwork;
|
||||
|
||||
const [assetAddress, setAssetAddress] = useState<string | null>(null);
|
||||
const [doesExist, setDoesExist] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const originAssetHex = useMemo(
|
||||
() => nativeToHexString(originAsset, originChain),
|
||||
[originAsset, originChain]
|
||||
);
|
||||
|
||||
const argumentError = useMemo(
|
||||
() =>
|
||||
!foreignChain ||
|
||||
!originAssetHex ||
|
||||
foreignChain === originChain ||
|
||||
(isEVMChain(foreignChain) && !isReady) ||
|
||||
(isEVMChain(foreignChain) && !hasCorrectEvmNetwork),
|
||||
[isReady, foreignChain, originChain, hasCorrectEvmNetwork, originAssetHex]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (argumentError || !originAssetHex) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
setIsLoading(true);
|
||||
setAssetAddress(null);
|
||||
setError("");
|
||||
setDoesExist(false);
|
||||
const getterFunc: () => Promise<string | null> = isEVMChain(foreignChain)
|
||||
? () =>
|
||||
getForeignAssetEth(
|
||||
getTokenBridgeAddressForChain(foreignChain),
|
||||
provider as any, //why does this typecheck work elsewhere?
|
||||
originChain,
|
||||
hexToUint8Array(originAssetHex)
|
||||
)
|
||||
: foreignChain === CHAIN_ID_TERRA
|
||||
? () => {
|
||||
const lcd = new LCDClient(TERRA_HOST);
|
||||
return getForeignAssetTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
lcd,
|
||||
originChain,
|
||||
hexToUint8Array(originAssetHex)
|
||||
);
|
||||
}
|
||||
: () => {
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
return getForeignAssetSolana(
|
||||
connection,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
originChain,
|
||||
hexToUint8Array(originAssetHex)
|
||||
);
|
||||
};
|
||||
|
||||
const promise = getterFunc();
|
||||
|
||||
promise
|
||||
.then((result) => {
|
||||
if (!cancelled) {
|
||||
if (
|
||||
result &&
|
||||
!(
|
||||
isEVMChain(foreignChain) &&
|
||||
result === ethers.constants.AddressZero
|
||||
)
|
||||
) {
|
||||
setDoesExist(true);
|
||||
setIsLoading(false);
|
||||
setAssetAddress(result);
|
||||
} else {
|
||||
setDoesExist(false);
|
||||
setIsLoading(false);
|
||||
setAssetAddress(null);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
if (!cancelled) {
|
||||
setError("Could not retrieve the foreign asset.");
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
}, [argumentError, foreignChain, originAssetHex, originChain, provider]);
|
||||
|
||||
const compoundError = useMemo(() => {
|
||||
return error
|
||||
? error
|
||||
: !isReady
|
||||
? statusMessage
|
||||
: argumentError
|
||||
? "Invalid arguments."
|
||||
: "";
|
||||
}, [error, isReady, statusMessage, argumentError]);
|
||||
|
||||
const output: DataWrapper<ForeignAssetInfo> = useMemo(
|
||||
() => ({
|
||||
error: compoundError,
|
||||
isFetching: isLoading,
|
||||
data: { address: assetAddress, doesExist },
|
||||
receivedAt: null,
|
||||
}),
|
||||
[compoundError, isLoading, assetAddress, doesExist]
|
||||
);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export default useFetchForeignAsset;
|
|
@ -5,6 +5,8 @@ import {
|
|||
createWrappedOnEth,
|
||||
createWrappedOnSolana,
|
||||
createWrappedOnTerra,
|
||||
updateWrappedOnEth,
|
||||
updateWrappedOnTerra,
|
||||
postVaaSolana,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { WalletContextState } from "@solana/wallet-adapter-react";
|
||||
|
@ -43,15 +45,22 @@ async function evm(
|
|||
enqueueSnackbar: any,
|
||||
signer: Signer,
|
||||
signedVAA: Uint8Array,
|
||||
chainId: ChainId
|
||||
chainId: ChainId,
|
||||
shouldUpdate: boolean
|
||||
) {
|
||||
dispatch(setIsCreating(true));
|
||||
try {
|
||||
const receipt = await createWrappedOnEth(
|
||||
getTokenBridgeAddressForChain(chainId),
|
||||
signer,
|
||||
signedVAA
|
||||
);
|
||||
const receipt = shouldUpdate
|
||||
? await updateWrappedOnEth(
|
||||
getTokenBridgeAddressForChain(chainId),
|
||||
signer,
|
||||
signedVAA
|
||||
)
|
||||
: await createWrappedOnEth(
|
||||
getTokenBridgeAddressForChain(chainId),
|
||||
signer,
|
||||
signedVAA
|
||||
);
|
||||
dispatch(
|
||||
setCreateTx({ id: receipt.transactionHash, block: receipt.blockNumber })
|
||||
);
|
||||
|
@ -71,7 +80,8 @@ async function solana(
|
|||
enqueueSnackbar: any,
|
||||
wallet: WalletContextState,
|
||||
payerAddress: string, // TODO: we may not need this since we have wallet
|
||||
signedVAA: Uint8Array
|
||||
signedVAA: Uint8Array,
|
||||
shouldUpdate: boolean //TODO utilize
|
||||
) {
|
||||
dispatch(setIsCreating(true));
|
||||
try {
|
||||
|
@ -111,15 +121,22 @@ async function terra(
|
|||
dispatch: any,
|
||||
enqueueSnackbar: any,
|
||||
wallet: ConnectedWallet,
|
||||
signedVAA: Uint8Array
|
||||
signedVAA: Uint8Array,
|
||||
shouldUpdate: boolean
|
||||
) {
|
||||
dispatch(setIsCreating(true));
|
||||
try {
|
||||
const msg = await createWrappedOnTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
wallet.terraAddress,
|
||||
signedVAA
|
||||
);
|
||||
const msg = shouldUpdate
|
||||
? await updateWrappedOnTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
wallet.terraAddress,
|
||||
signedVAA
|
||||
)
|
||||
: await createWrappedOnTerra(
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
wallet.terraAddress,
|
||||
signedVAA
|
||||
);
|
||||
const result = await postWithFees(
|
||||
wallet,
|
||||
[msg],
|
||||
|
@ -139,7 +156,7 @@ async function terra(
|
|||
}
|
||||
}
|
||||
|
||||
export function useHandleCreateWrapped() {
|
||||
export function useHandleCreateWrapped(shouldUpdate: boolean) {
|
||||
const dispatch = useDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const targetChain = useSelector(selectAttestTargetChain);
|
||||
|
@ -151,7 +168,14 @@ export function useHandleCreateWrapped() {
|
|||
const terraWallet = useConnectedWallet();
|
||||
const handleCreateClick = useCallback(() => {
|
||||
if (isEVMChain(targetChain) && !!signer && !!signedVAA) {
|
||||
evm(dispatch, enqueueSnackbar, signer, signedVAA, targetChain);
|
||||
evm(
|
||||
dispatch,
|
||||
enqueueSnackbar,
|
||||
signer,
|
||||
signedVAA,
|
||||
targetChain,
|
||||
shouldUpdate
|
||||
);
|
||||
} else if (
|
||||
targetChain === CHAIN_ID_SOLANA &&
|
||||
!!solanaWallet &&
|
||||
|
@ -163,10 +187,11 @@ export function useHandleCreateWrapped() {
|
|||
enqueueSnackbar,
|
||||
solanaWallet,
|
||||
solPK.toString(),
|
||||
signedVAA
|
||||
signedVAA,
|
||||
shouldUpdate
|
||||
);
|
||||
} else if (targetChain === CHAIN_ID_TERRA && !!terraWallet && !!signedVAA) {
|
||||
terra(dispatch, enqueueSnackbar, terraWallet, signedVAA);
|
||||
terra(dispatch, enqueueSnackbar, terraWallet, signedVAA, shouldUpdate);
|
||||
} else {
|
||||
// enqueueSnackbar(
|
||||
// "Creating wrapped tokens on this chain is not yet supported",
|
||||
|
@ -184,6 +209,7 @@ export function useHandleCreateWrapped() {
|
|||
terraWallet,
|
||||
signedVAA,
|
||||
signer,
|
||||
shouldUpdate,
|
||||
]);
|
||||
return useMemo(
|
||||
() => ({
|
||||
|
|
|
@ -5,3 +5,4 @@ export * from "./getIsWrappedAsset";
|
|||
export * from "./getOriginalAsset";
|
||||
export * from "./redeem";
|
||||
export * from "./transfer";
|
||||
export * from "./updateWrapped";
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { MsgExecuteContract } from "@terra-money/terra.js";
|
||||
import { ethers } from "ethers";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
import { Bridge__factory } from "../ethers-contracts";
|
||||
|
||||
export async function updateWrappedOnEth(
|
||||
tokenBridgeAddress: string,
|
||||
signer: ethers.Signer,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
|
||||
const v = await bridge.updateWrapped(signedVAA);
|
||||
const receipt = await v.wait();
|
||||
return receipt;
|
||||
}
|
||||
|
||||
export async function updateWrappedOnTerra(
|
||||
tokenBridgeAddress: string,
|
||||
walletAddress: string,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
return new MsgExecuteContract(walletAddress, tokenBridgeAddress, {
|
||||
submit_vaa: {
|
||||
data: fromUint8Array(signedVAA),
|
||||
},
|
||||
});
|
||||
}
|
|
@ -6,9 +6,15 @@ import {
|
|||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_POLYGON,
|
||||
} from "./consts";
|
||||
import { humanAddress } from "../terra";
|
||||
import { humanAddress, canonicalAddress } from "../terra";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { hexValue, hexZeroPad, stripZeros } from "ethers/lib/utils";
|
||||
import { arrayify, zeroPad } from "@ethersproject/bytes";
|
||||
|
||||
export const isEVMChain = (chainId: ChainId) =>
|
||||
chainId === CHAIN_ID_ETH ||
|
||||
chainId === CHAIN_ID_BSC ||
|
||||
chainId === CHAIN_ID_POLYGON;
|
||||
|
||||
export const isHexNativeTerra = (h: string) => h.startsWith("01");
|
||||
export const nativeTerraHexToDenom = (h: string) =>
|
||||
|
@ -33,3 +39,22 @@ export const hexToNativeString = (h: string | undefined, c: ChainId) => {
|
|||
} catch (e) {}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const nativeToHexString = (
|
||||
address: string | undefined,
|
||||
chain: ChainId
|
||||
) => {
|
||||
if (!address || !chain) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isEVMChain(chain)) {
|
||||
return uint8ArrayToHex(zeroPad(arrayify(address), 32));
|
||||
} else if (chain === CHAIN_ID_SOLANA) {
|
||||
return uint8ArrayToHex(zeroPad(new PublicKey(address).toBytes(), 32));
|
||||
} else if (chain === CHAIN_ID_TERRA) {
|
||||
return uint8ArrayToHex(zeroPad(canonicalAddress(address), 32));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue