UI - native swap and relayer support
This commit is contained in:
parent
237d06b558
commit
c1ae41d6d8
|
@ -43,7 +43,7 @@ import {
|
||||||
swapExactOutFromVaaNativeV3,
|
swapExactOutFromVaaNativeV3,
|
||||||
swapExactOutFromVaaTokenV2,
|
swapExactOutFromVaaTokenV2,
|
||||||
swapExactOutFromVaaTokenV3,
|
swapExactOutFromVaaTokenV3,
|
||||||
} from "util";
|
} from "./util";
|
||||||
import { abi as SWAP_CONTRACT_V2_ABI } from "../abi/contracts/CrossChainSwapV2.json";
|
import { abi as SWAP_CONTRACT_V2_ABI } from "../abi/contracts/CrossChainSwapV2.json";
|
||||||
import { abi as SWAP_CONTRACT_V3_ABI } from "../abi/contracts/CrossChainSwapV3.json";
|
import { abi as SWAP_CONTRACT_V3_ABI } from "../abi/contracts/CrossChainSwapV3.json";
|
||||||
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../addresses/goerli";
|
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../addresses/goerli";
|
||||||
|
|
|
@ -7,30 +7,51 @@ import ethIcon from "../icons/eth.svg";
|
||||||
import polygonIcon from "../icons/polygon.svg";
|
import polygonIcon from "../icons/polygon.svg";
|
||||||
|
|
||||||
export interface TokenInfo {
|
export interface TokenInfo {
|
||||||
id: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
chainId: ChainId;
|
chainId: ChainId;
|
||||||
logo: string;
|
logo: string;
|
||||||
|
isNative: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MATIC_TOKEN_INFO: TokenInfo = {
|
||||||
|
name: "MATIC",
|
||||||
|
address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", // used to compute quote
|
||||||
|
chainId: CHAIN_ID_POLYGON,
|
||||||
|
logo: polygonIcon,
|
||||||
|
isNative: true,
|
||||||
|
};
|
||||||
|
|
||||||
export const WMATIC_TOKEN_INFO: TokenInfo = {
|
export const WMATIC_TOKEN_INFO: TokenInfo = {
|
||||||
id: "WMATIC",
|
|
||||||
name: "WMATIC",
|
name: "WMATIC",
|
||||||
address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889",
|
address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889",
|
||||||
chainId: CHAIN_ID_POLYGON,
|
chainId: CHAIN_ID_POLYGON,
|
||||||
logo: polygonIcon,
|
logo: polygonIcon,
|
||||||
|
isNative: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ETH_TOKEN_INFO: TokenInfo = {
|
||||||
|
name: "ETH",
|
||||||
|
address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // used to compute quote
|
||||||
|
chainId: CHAIN_ID_ETH,
|
||||||
|
logo: ethIcon,
|
||||||
|
isNative: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WETH_TOKEN_INFO: TokenInfo = {
|
export const WETH_TOKEN_INFO: TokenInfo = {
|
||||||
id: "WETH",
|
|
||||||
name: "WETH",
|
name: "WETH",
|
||||||
address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
|
address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
|
||||||
chainId: CHAIN_ID_ETH,
|
chainId: CHAIN_ID_ETH,
|
||||||
logo: ethIcon,
|
logo: ethIcon,
|
||||||
|
isNative: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TOKEN_INFOS = [WMATIC_TOKEN_INFO, WETH_TOKEN_INFO];
|
export const TOKEN_INFOS = [
|
||||||
|
MATIC_TOKEN_INFO,
|
||||||
|
WMATIC_TOKEN_INFO,
|
||||||
|
ETH_TOKEN_INFO,
|
||||||
|
WETH_TOKEN_INFO,
|
||||||
|
];
|
||||||
|
|
||||||
export const ETH_NETWORK_CHAIN_ID = 5;
|
export const ETH_NETWORK_CHAIN_ID = 5;
|
||||||
|
|
||||||
|
@ -43,7 +64,7 @@ export const getEvmChainId = (chainId: ChainId) =>
|
||||||
? POLYGON_NETWORK_CHAIN_ID
|
? POLYGON_NETWORK_CHAIN_ID
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
export const RELAYER_FEE_UST = "0.0001";
|
export const RELAYER_FEE_UST = "0.25";
|
||||||
|
|
||||||
export const WORMHOLE_RPC_HOSTS = [
|
export const WORMHOLE_RPC_HOSTS = [
|
||||||
"https://wormhole-v2-testnet-api.certus.one",
|
"https://wormhole-v2-testnet-api.certus.one",
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { getIsTransferCompletedEth } from "@certusone/wormhole-sdk";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
|
||||||
|
export default async function getIsTransferCompletedEvmWithRetry(
|
||||||
|
tokenBridgeAddress: string,
|
||||||
|
provider: ethers.providers.Provider,
|
||||||
|
signedVAA: Uint8Array,
|
||||||
|
retryTimeoutMs: number,
|
||||||
|
retryAttempts: number
|
||||||
|
) {
|
||||||
|
let result = false;
|
||||||
|
let attempts = 0;
|
||||||
|
while (attempts < retryAttempts) {
|
||||||
|
try {
|
||||||
|
result = await getIsTransferCompletedEth(
|
||||||
|
tokenBridgeAddress,
|
||||||
|
provider,
|
||||||
|
signedVAA
|
||||||
|
);
|
||||||
|
console.log("getIsTransferCompletedEth", result);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, retryTimeoutMs));
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -1,22 +1,26 @@
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
|
Link,
|
||||||
makeStyles,
|
makeStyles,
|
||||||
Paper,
|
Paper,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { ChainId } from "@certusone/wormhole-sdk";
|
import { ChainId, getSignedVAAWithRetry } from "@certusone/wormhole-sdk";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import ButtonWithLoader from "../components/ButtonWithLoader";
|
import ButtonWithLoader from "../components/ButtonWithLoader";
|
||||||
import EthereumSignerKey from "../components/EthereumSignerKey";
|
import EthereumSignerKey from "../components/EthereumSignerKey";
|
||||||
import TokenSelect from "../components/TokenSelect";
|
import TokenSelect from "../components/TokenSelect";
|
||||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||||
import {
|
import {
|
||||||
|
ETH_TOKEN_INFO,
|
||||||
getEvmChainId,
|
getEvmChainId,
|
||||||
|
MATIC_TOKEN_INFO,
|
||||||
RELAYER_FEE_UST,
|
RELAYER_FEE_UST,
|
||||||
TOKEN_INFOS,
|
TOKEN_INFOS,
|
||||||
WETH_TOKEN_INFO,
|
WETH_TOKEN_INFO,
|
||||||
WMATIC_TOKEN_INFO,
|
WMATIC_TOKEN_INFO,
|
||||||
|
WORMHOLE_RPC_HOSTS,
|
||||||
} from "../utils/consts";
|
} from "../utils/consts";
|
||||||
import { COLORS } from "../muiTheme";
|
import { COLORS } from "../muiTheme";
|
||||||
import Wormhole from "../icons/wormhole-network.svg";
|
import Wormhole from "../icons/wormhole-network.svg";
|
||||||
|
@ -28,6 +32,7 @@ import { useSnackbar } from "notistack";
|
||||||
import { Alert } from "@material-ui/lab";
|
import { Alert } from "@material-ui/lab";
|
||||||
import parseError from "../utils/parseError";
|
import parseError from "../utils/parseError";
|
||||||
import Settings from "../components/Settings";
|
import Settings from "../components/Settings";
|
||||||
|
import getIsTransferCompletedEvmWithRetry from "../utils/getIsTransferCompletedWithRetry";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
bg: {
|
bg: {
|
||||||
|
@ -160,7 +165,8 @@ export default function Home() {
|
||||||
const executor = new UniswapToUniswapExecutor();
|
const executor = new UniswapToUniswapExecutor();
|
||||||
await executor.initialize(
|
await executor.initialize(
|
||||||
sourceTokenInfo.address,
|
sourceTokenInfo.address,
|
||||||
targetTokenInfo.address
|
targetTokenInfo.address,
|
||||||
|
sourceTokenInfo.isNative
|
||||||
);
|
);
|
||||||
await executor.computeAndVerifySrcPoolAddress().catch((e) => {
|
await executor.computeAndVerifySrcPoolAddress().catch((e) => {
|
||||||
throw new Error("failed to verify source pool address");
|
throw new Error("failed to verify source pool address");
|
||||||
|
@ -225,12 +231,19 @@ export default function Home() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSourceChange = useCallback((event) => {
|
const handleSourceChange = useCallback((event) => {
|
||||||
|
// NOTE: only native-to-native or wrapped-to-wrapped swaps are currently supported
|
||||||
if (event.target.value === WMATIC_TOKEN_INFO.name) {
|
if (event.target.value === WMATIC_TOKEN_INFO.name) {
|
||||||
setSourceTokenInfo(WMATIC_TOKEN_INFO);
|
setSourceTokenInfo(WMATIC_TOKEN_INFO);
|
||||||
setTargetTokenInfo(WETH_TOKEN_INFO);
|
setTargetTokenInfo(WETH_TOKEN_INFO);
|
||||||
} else {
|
} else if (event.target.value === WETH_TOKEN_INFO.name) {
|
||||||
setSourceTokenInfo(WETH_TOKEN_INFO);
|
setSourceTokenInfo(WETH_TOKEN_INFO);
|
||||||
setTargetTokenInfo(WMATIC_TOKEN_INFO);
|
setTargetTokenInfo(WMATIC_TOKEN_INFO);
|
||||||
|
} else if (event.target.value === ETH_TOKEN_INFO.name) {
|
||||||
|
setSourceTokenInfo(ETH_TOKEN_INFO);
|
||||||
|
setTargetTokenInfo(MATIC_TOKEN_INFO);
|
||||||
|
} else {
|
||||||
|
setSourceTokenInfo(MATIC_TOKEN_INFO);
|
||||||
|
setTargetTokenInfo(ETH_TOKEN_INFO);
|
||||||
}
|
}
|
||||||
setAmountIn("0.0");
|
setAmountIn("0.0");
|
||||||
setAmountOut("0.0");
|
setAmountOut("0.0");
|
||||||
|
@ -242,13 +255,53 @@ export default function Home() {
|
||||||
setIsSwapping(true);
|
setIsSwapping(true);
|
||||||
await switchProviderNetwork(provider, sourceTokenInfo.chainId);
|
await switchProviderNetwork(provider, sourceTokenInfo.chainId);
|
||||||
const sourceReceipt = await executor.approveAndSwap(signer);
|
const sourceReceipt = await executor.approveAndSwap(signer);
|
||||||
console.info(`src transaction: ${sourceReceipt.transactionHash}`);
|
console.info(
|
||||||
|
"firstSwapTransactionHash:",
|
||||||
|
sourceReceipt.transactionHash
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the guardian network to reach consensus and emit the signedVAA
|
||||||
|
enqueueSnackbar(null, {
|
||||||
|
content: <Alert severity="info">Fetching VAA</Alert>,
|
||||||
|
});
|
||||||
|
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||||
|
WORMHOLE_RPC_HOSTS,
|
||||||
|
executor.srcExecutionParams.wormhole.chainId,
|
||||||
|
executor.vaaSearchParams.emitterAddress,
|
||||||
|
executor.vaaSearchParams.sequence
|
||||||
|
);
|
||||||
|
// Check if the signedVAA has redeemed by the relayer
|
||||||
|
enqueueSnackbar(null, {
|
||||||
|
content: (
|
||||||
|
<Alert severity="info">
|
||||||
|
Fetched the Signed VAA, waiting for relayer to redeem it
|
||||||
|
</Alert>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const isCompleted = await getIsTransferCompletedEvmWithRetry(
|
||||||
|
executor.dstExecutionParams.wormhole.tokenBridgeAddress,
|
||||||
|
executor.quoter.dstProvider, //provider,
|
||||||
|
vaaBytes,
|
||||||
|
// retry for two minutes
|
||||||
|
3000,
|
||||||
|
40
|
||||||
|
);
|
||||||
|
if (isCompleted) {
|
||||||
|
enqueueSnackbar(null, {
|
||||||
|
content: <Alert severity="success">Swap completed</Alert>,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If the relayer hasn't redeemed the signedVAA, then manually redeem it ourselves
|
||||||
await switchProviderNetwork(provider, targetTokenInfo.chainId);
|
await switchProviderNetwork(provider, targetTokenInfo.chainId);
|
||||||
const targetReceipt = await executor.fetchVaaAndSwap(signer);
|
const targetReceipt = await executor.fetchVaaAndSwap(signer);
|
||||||
console.info(`dst transaction: ${targetReceipt.transactionHash}`);
|
|
||||||
enqueueSnackbar(null, {
|
enqueueSnackbar(null, {
|
||||||
content: <Alert severity="success">Success!</Alert>,
|
content: <Alert severity="success">Swap completed</Alert>,
|
||||||
});
|
});
|
||||||
|
console.info(
|
||||||
|
"secondSwapTransactionHash:",
|
||||||
|
targetReceipt.transactionHash
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
enqueueSnackbar(null, {
|
enqueueSnackbar(null, {
|
||||||
|
@ -329,6 +382,12 @@ export default function Home() {
|
||||||
{"powered by wormhole"}
|
{"powered by wormhole"}
|
||||||
</Typography>
|
</Typography>
|
||||||
<img src={Wormhole} alt="Wormhole" className={classes.wormholeIcon} />
|
<img src={Wormhole} alt="Wormhole" className={classes.wormholeIcon} />
|
||||||
|
<div className={classes.spacer} />
|
||||||
|
<Link variant="subtitle2" href="https://goerli-faucet.slock.it/">
|
||||||
|
Goerli faucet
|
||||||
|
</Link>
|
||||||
|
<div />
|
||||||
|
<Link href="https://faucet.polygon.technology/">Mumbai faucet</Link>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue