wormhole-nativeswap-example/swap_relayer/src/evm.ts

335 lines
9.4 KiB
TypeScript

import { getIsTransferCompletedEth, hexToUint8Array } from "@certusone/wormhole-sdk";
import { ethers } from "ethers";
import { abi as SWAP_CONTRACT_V2_ABI } from "../../react/src/abi/contracts/CrossChainSwapV2.json";
import { abi as SWAP_CONTRACT_V3_ABI } from "../../react/src/abi/contracts/CrossChainSwapV3.json";
import * as swap from "../../react/src/swapper/helpers";
import { logger, OurEnvironment, Type3Payload } from "./index";
export type EvmEnvironment = {
name: string;
chain_id: number;
provider_url: string;
contract_address: string;
token_bridge_address: string;
wallet_private_key: string;
abi_version: string;
};
type EvmContractData = {
chain_id: number;
name: string;
contractAddress: string;
tokenBridgeAddress: string;
contract: ethers.Contract;
provider: ethers.providers.StaticJsonRpcProvider;
wallet: ethers.Wallet;
contractWithSigner: ethers.Contract;
};
let evmContractData = new Map<number, EvmContractData>();
export function loadEvmConfig(): EvmEnvironment[] {
let evm_configs: EvmEnvironment[] = [];
let evms = process.env.EVM_CHAINS.split(",");
for (const evm of evms) {
let key_chain_id: string = evm + "_CHAIN_ID";
let val_chain_id: string = eval("process.env." + key_chain_id);
if (!val_chain_id) {
logger.error("Missing environment variable " + key_chain_id);
return undefined;
}
let key_provider: string = evm + "_PROVIDER";
let val_provider: string = eval("process.env." + key_provider);
if (!val_provider) {
logger.error("Missing environment variable " + key_provider);
return undefined;
}
let key_contract_address: string = evm + "_CONTRACT_ADDRESS";
let val_contract_address: string = eval("process.env." + key_contract_address);
if (!val_contract_address) {
logger.error("Missing environment variable " + key_contract_address);
return undefined;
}
let key_token_bridge_address: string = evm + "_TOKEN_BRIDGE_ADDRESS";
let val_token_bridge_address: string = eval("process.env." + key_token_bridge_address);
if (!val_token_bridge_address) {
logger.error("Missing environment variable " + key_token_bridge_address);
return undefined;
}
let key_wallet_private_key: string = evm + "_WALLET_PRIVATE_KEY";
let val_wallet_private_key: string = eval("process.env." + key_wallet_private_key);
if (!val_wallet_private_key) val_wallet_private_key = process.env.WALLET_PRIVATE_KEY;
if (!val_wallet_private_key) {
logger.error("Missing environment variable " + key_wallet_private_key + " or WALLET_PRIVATE_KEY");
return undefined;
}
let key_abi_version: string = evm + "_ABI";
let val_abi_version: string = eval("process.env." + key_abi_version);
if (!val_abi_version) {
logger.error("Missing environment variable " + key_abi_version);
return undefined;
}
if (val_abi_version !== "V2" && val_abi_version !== "V3") {
logger.error(
"Invalid value of environment variable " +
key_abi_version +
", is [" +
val_abi_version +
"], must be either V2 or V3"
);
return undefined;
}
evm_configs.push({
name: evm,
chain_id: parseInt(val_chain_id),
provider_url: val_provider,
contract_address: val_contract_address,
token_bridge_address: val_token_bridge_address,
wallet_private_key: val_wallet_private_key,
abi_version: val_abi_version,
});
}
return evm_configs;
}
export function makeEvmContractData(envs: EvmEnvironment[]) {
if (!envs) return;
for (const evm of envs) {
evmContractData.set(evm.chain_id, makeContractDataForEvm(evm));
}
}
function makeContractDataForEvm(env: EvmEnvironment): EvmContractData {
let contractAddress: string = env.contract_address.toLowerCase();
if (contractAddress.search("0x") === 0) {
contractAddress = contractAddress.substring(2);
}
logger.info(
"Connecting to " +
env.name +
": chain_id: " +
env.chain_id +
", contract address: [" +
contractAddress +
"], node: [" +
env.provider_url +
"], token bridge address: [" +
env.token_bridge_address +
"], abi version: [" +
env.abi_version +
"]"
);
const provider = new ethers.providers.StaticJsonRpcProvider(env.provider_url);
const contract = new ethers.Contract(
contractAddress,
env.abi_version == "V2" ? SWAP_CONTRACT_V2_ABI : SWAP_CONTRACT_V3_ABI,
provider
);
const wallet = new ethers.Wallet(env.wallet_private_key, provider);
const contractWithSigner = contract.connect(wallet);
return {
chain_id: env.chain_id,
name: env.name,
contractAddress: contractAddress,
tokenBridgeAddress: env.token_bridge_address,
contract: contract,
provider: provider,
wallet: wallet,
contractWithSigner: contractWithSigner,
};
}
export function isEvmContract(contractAddress: string, chain_id: number): boolean {
let ecd = evmContractData.get(chain_id);
return ecd && ecd.contractAddress === contractAddress;
}
export async function relayVaaToEvm(vaaBytes: string, t3Payload: Type3Payload) {
let ecd = evmContractData.get(t3Payload.targetChainId);
if (!ecd) {
logger.error("relayVaaToEvm: chain id " + t3Payload.targetChainId + " does not exist!");
}
let exactIn: boolean = false;
let error: boolean = false;
if (t3Payload.swapFunctionType === 1) {
exactIn = true;
} else if (t3Payload.swapFunctionType !== 2) {
error = true;
logger.error("relayVaaTo" + ecd.name + ": unsupported swapFunctionType: [" + t3Payload.swapFunctionType + "]");
}
if (error) return;
logger.debug(
"relayVaaTo" + ecd.name + ": chain_id: " + ecd.chain_id + ", contractAddress: [" + t3Payload.contractAddress + "]"
);
const signedVaaArray = hexToUint8Array(vaaBytes);
await relayVaaToEvmChain(t3Payload, ecd, signedVaaArray, exactIn);
}
async function relayVaaToEvmChain(
t3Payload: Type3Payload,
tcd: EvmContractData,
signedVaaArray: Uint8Array,
exactIn: boolean
) {
logger.debug(
"relayVaaTo" +
tcd.name +
": checking if already redeemed on " +
tcd.name +
" using tokenBridgeAddress [" +
tcd.tokenBridgeAddress +
"]"
);
if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
logger.info(
"relayVaaTo" +
tcd.name +
": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress +
"], exactIn: " +
exactIn +
": completed: already transferred"
);
return;
}
logger.info(
"relayVaaTo" +
tcd.name +
": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress +
"], exactIn: " +
exactIn +
": submitting redeem request"
);
try {
let receipt: any = null;
if (exactIn) {
logger.debug("relayVaaTo: calling evmSwapExactInFromVaaNative()");
receipt = await swap.evmSwapExactInFromVaaNative(tcd.contractWithSigner, signedVaaArray);
} else {
logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaNative()");
receipt = await swap.evmSwapExactOutFromVaaNative(tcd.contractWithSigner, signedVaaArray);
}
logger.info(
"relayVaaTo" +
tcd.name +
": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress +
"], exactIn: " +
exactIn +
": completed: success, txHash: " +
receipt.transactionHash
);
} catch (e: any) {
if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
logger.info(
"relayVaaTo" +
tcd.name +
": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress +
"], exactIn: " +
exactIn +
": completed: relay failed because the vaa has already been redeemed"
);
return;
}
logger.error(
"relayVaaTo" +
tcd.name +
": contract: [" +
t3Payload.contractAddress +
"], exactIn: " +
exactIn +
": transaction failed: %o",
e
);
}
if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
logger.info(
"relayVaaTo" +
tcd.name +
": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress +
"], exactIn: " +
exactIn +
": redeem confirmed"
);
} else {
logger.error(
"relayVaaTo" +
tcd.name +
": srcChain: " +
t3Payload.sourceChainId +
", targetChain: " +
t3Payload.targetChainId +
", contract: [" +
t3Payload.contractAddress +
"], exactIn: " +
exactIn +
": completed: failed to confirm redeem!"
);
}
}
async function isRedeemedOnEvm(tcd: EvmContractData, signedVaaArray: Uint8Array): Promise<boolean> {
let redeemed: boolean = false;
try {
redeemed = await getIsTransferCompletedEth(tcd.tokenBridgeAddress, tcd.provider, signedVaaArray);
} catch (e) {
logger.error(
"relayVaaTo" + tcd.name + ": failed to check if transfer is already complete, will attempt the transfer, e: %o",
e
);
}
return redeemed;
}