bridge_ui: tx-based recovery for eth and solana
Change-Id: I20a0df101caaa956d3bd0330ffbb1898461e4f72
This commit is contained in:
parent
98e05e39cb
commit
74701adab2
|
@ -1,3 +1,14 @@
|
|||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_BSC,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
getSignedVAA,
|
||||
parseSequenceFromLogEth,
|
||||
parseSequenceFromLogSolana,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
|
@ -8,21 +19,40 @@ import {
|
|||
Divider,
|
||||
Fab,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import { Restore } from "@material-ui/icons";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
|
||||
import {
|
||||
selectTransferSignedVAAHex,
|
||||
selectTransferSourceChain,
|
||||
} from "../../store/selectors";
|
||||
import {
|
||||
setSignedVAAHex,
|
||||
setStep,
|
||||
setTargetChain,
|
||||
} from "../../store/transferSlice";
|
||||
import { selectTransferSignedVAAHex } from "../../store/selectors";
|
||||
import { hexToNativeString, hexToUint8Array } from "../../utils/array";
|
||||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import { BigNumber } from "ethers";
|
||||
import {
|
||||
hexToNativeString,
|
||||
hexToUint8Array,
|
||||
uint8ArrayToHex,
|
||||
} from "../../utils/array";
|
||||
import {
|
||||
CHAINS,
|
||||
ETH_BRIDGE_ADDRESS,
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
WORMHOLE_RPC_HOST,
|
||||
} from "../../utils/consts";
|
||||
import KeyAndBalance from "../KeyAndBalance";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
fab: {
|
||||
|
@ -32,6 +62,48 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
async function eth(provider: ethers.providers.Web3Provider, tx: string) {
|
||||
try {
|
||||
const receipt = await provider.getTransactionReceipt(tx);
|
||||
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
|
||||
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
WORMHOLE_RPC_HOST,
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence.toString()
|
||||
);
|
||||
return uint8ArrayToHex(vaaBytes);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
async function solana(tx: string) {
|
||||
try {
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const info = await connection.getTransaction(tx);
|
||||
if (!info) {
|
||||
throw new Error("An error occurred while fetching the transaction info");
|
||||
}
|
||||
const sequence = parseSequenceFromLogSolana(info);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
WORMHOLE_RPC_HOST,
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence.toString()
|
||||
);
|
||||
return uint8ArrayToHex(vaaBytes);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// 0 u256 amount
|
||||
// 32 [u8; 32] token_address
|
||||
// 64 u16 token_chain
|
||||
|
@ -50,9 +122,53 @@ const parsePayload = (arr: Buffer) => ({
|
|||
|
||||
function RecoveryDialogContent({ onClose }: { onClose: () => void }) {
|
||||
const dispatch = useDispatch();
|
||||
const { provider } = useEthereumProvider();
|
||||
const currentSourceChain = useSelector(selectTransferSourceChain);
|
||||
const [recoverySourceChain, setRecoverySourceChain] =
|
||||
useState(currentSourceChain);
|
||||
const [recoverySourceTx, setRecoverySourceTx] = useState("");
|
||||
const currentSignedVAA = useSelector(selectTransferSignedVAAHex);
|
||||
const [recoverySignedVAA, setRecoverySignedVAA] = useState(currentSignedVAA);
|
||||
const [recoveryParsedVAA, setRecoveryParsedVAA] = useState<any>(null);
|
||||
useEffect(() => {
|
||||
if (!recoverySignedVAA) {
|
||||
setRecoverySourceTx("");
|
||||
setRecoverySourceChain(currentSourceChain);
|
||||
}
|
||||
}, [recoverySignedVAA, currentSourceChain]);
|
||||
useEffect(() => {
|
||||
if (recoverySourceTx) {
|
||||
let cancelled = false;
|
||||
if (recoverySourceChain === CHAIN_ID_ETH && provider) {
|
||||
(async () => {
|
||||
const vaa = await eth(provider, recoverySourceTx);
|
||||
if (!cancelled) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
})();
|
||||
} else if (recoverySourceChain === CHAIN_ID_SOLANA) {
|
||||
(async () => {
|
||||
const vaa = await solana(recoverySourceTx);
|
||||
if (!cancelled) {
|
||||
setRecoverySignedVAA(vaa);
|
||||
}
|
||||
})();
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}
|
||||
}, [recoverySourceChain, recoverySourceTx, provider]);
|
||||
useEffect(() => {
|
||||
setRecoverySignedVAA(currentSignedVAA);
|
||||
}, [currentSignedVAA]);
|
||||
const handleSourceChainChange = useCallback((event) => {
|
||||
setRecoverySourceTx("");
|
||||
setRecoverySourceChain(event.target.value);
|
||||
}, []);
|
||||
const handleSourceTxChange = useCallback((event) => {
|
||||
setRecoverySourceTx(event.target.value);
|
||||
}, []);
|
||||
const handleSignedVAAChange = useCallback((event) => {
|
||||
setRecoverySignedVAA(event.target.value);
|
||||
}, []);
|
||||
|
@ -111,6 +227,38 @@ function RecoveryDialogContent({ onClose }: { onClose: () => void }) {
|
|||
If you have sent your tokens but have not redeemed them, you may paste
|
||||
the signed VAA here to resume from the redeem step.
|
||||
</Alert>
|
||||
<TextField
|
||||
select
|
||||
label="Source Chain"
|
||||
disabled={!!recoverySignedVAA}
|
||||
value={recoverySourceChain}
|
||||
onChange={handleSourceChainChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
>
|
||||
{CHAINS.filter(
|
||||
(x) => x.id === CHAIN_ID_ETH || x.id === CHAIN_ID_SOLANA
|
||||
).map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{recoverySourceChain === CHAIN_ID_ETH ||
|
||||
recoverySourceChain === CHAIN_ID_BSC ? (
|
||||
<KeyAndBalance chainId={recoverySourceChain} />
|
||||
) : null}
|
||||
<TextField
|
||||
label="Source Tx"
|
||||
disabled={!!recoverySignedVAA}
|
||||
value={recoverySourceTx}
|
||||
onChange={handleSourceTxChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<Box mt={4}>
|
||||
<Typography>or</Typography>
|
||||
</Box>
|
||||
<TextField
|
||||
label="Signed VAA (Hex)"
|
||||
value={recoverySignedVAA || ""}
|
||||
|
|
Loading…
Reference in New Issue