2022-07-01 13:36:07 -07:00
|
|
|
import { getIsTransferCompletedEth, hexToUint8Array } from "@certusone/wormhole-sdk";
|
2022-01-25 08:19:02 -08:00
|
|
|
|
|
|
|
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";
|
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
import * as swap from "../../react/src/swapper/helpers";
|
2022-01-25 08:19:02 -08:00
|
|
|
|
|
|
|
import { logger, OurEnvironment, Type3Payload } from "./index";
|
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2022-01-25 08:19:02 -08:00
|
|
|
type EvmContractData = {
|
2022-01-28 09:59:48 -08:00
|
|
|
chain_id: number;
|
2022-01-25 08:19:02 -08:00
|
|
|
name: string;
|
|
|
|
contractAddress: string;
|
|
|
|
tokenBridgeAddress: string;
|
|
|
|
contract: ethers.Contract;
|
|
|
|
provider: ethers.providers.StaticJsonRpcProvider;
|
|
|
|
wallet: ethers.Wallet;
|
|
|
|
contractWithSigner: ethers.Contract;
|
|
|
|
};
|
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
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";
|
2022-07-01 13:36:07 -07:00
|
|
|
let val_contract_address: string = eval("process.env." + key_contract_address);
|
2022-01-28 09:59:48 -08:00
|
|
|
if (!val_contract_address) {
|
|
|
|
logger.error("Missing environment variable " + key_contract_address);
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
let key_token_bridge_address: string = evm + "_TOKEN_BRIDGE_ADDRESS";
|
2022-07-01 13:36:07 -07:00
|
|
|
let val_token_bridge_address: string = eval("process.env." + key_token_bridge_address);
|
2022-01-28 09:59:48 -08:00
|
|
|
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";
|
2022-07-01 13:36:07 -07:00
|
|
|
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;
|
2022-01-28 09:59:48 -08:00
|
|
|
if (!val_wallet_private_key) {
|
2022-07-01 13:36:07 -07:00
|
|
|
logger.error("Missing environment variable " + key_wallet_private_key + " or WALLET_PRIVATE_KEY");
|
2022-01-28 09:59:48 -08:00
|
|
|
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,
|
|
|
|
});
|
|
|
|
}
|
2022-01-25 08:19:02 -08:00
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
return evm_configs;
|
2022-01-25 08:19:02 -08:00
|
|
|
}
|
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
export function makeEvmContractData(envs: EvmEnvironment[]) {
|
|
|
|
if (!envs) return;
|
|
|
|
for (const evm of envs) {
|
|
|
|
evmContractData.set(evm.chain_id, makeContractDataForEvm(evm));
|
2022-01-25 08:19:02 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
function makeContractDataForEvm(env: EvmEnvironment): EvmContractData {
|
|
|
|
let contractAddress: string = env.contract_address.toLowerCase();
|
|
|
|
if (contractAddress.search("0x") === 0) {
|
2022-01-25 08:19:02 -08:00
|
|
|
contractAddress = contractAddress.substring(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info(
|
2022-01-28 09:59:48 -08:00
|
|
|
"Connecting to " +
|
|
|
|
env.name +
|
|
|
|
": chain_id: " +
|
|
|
|
env.chain_id +
|
|
|
|
", contract address: [" +
|
2022-01-25 08:19:02 -08:00
|
|
|
contractAddress +
|
|
|
|
"], node: [" +
|
2022-01-28 09:59:48 -08:00
|
|
|
env.provider_url +
|
2022-01-25 08:19:02 -08:00
|
|
|
"], token bridge address: [" +
|
2022-01-28 09:59:48 -08:00
|
|
|
env.token_bridge_address +
|
|
|
|
"], abi version: [" +
|
|
|
|
env.abi_version +
|
2022-01-25 08:19:02 -08:00
|
|
|
"]"
|
|
|
|
);
|
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
const provider = new ethers.providers.StaticJsonRpcProvider(env.provider_url);
|
2022-01-25 08:19:02 -08:00
|
|
|
|
|
|
|
const contract = new ethers.Contract(
|
|
|
|
contractAddress,
|
2022-01-28 09:59:48 -08:00
|
|
|
env.abi_version == "V2" ? SWAP_CONTRACT_V2_ABI : SWAP_CONTRACT_V3_ABI,
|
2022-01-25 08:19:02 -08:00
|
|
|
provider
|
|
|
|
);
|
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
const wallet = new ethers.Wallet(env.wallet_private_key, provider);
|
2022-01-25 08:19:02 -08:00
|
|
|
const contractWithSigner = contract.connect(wallet);
|
|
|
|
|
|
|
|
return {
|
2022-01-28 09:59:48 -08:00
|
|
|
chain_id: env.chain_id,
|
|
|
|
name: env.name,
|
2022-01-25 08:19:02 -08:00
|
|
|
contractAddress: contractAddress,
|
2022-01-28 09:59:48 -08:00
|
|
|
tokenBridgeAddress: env.token_bridge_address,
|
2022-01-25 08:19:02 -08:00
|
|
|
contract: contract,
|
|
|
|
provider: provider,
|
|
|
|
wallet: wallet,
|
|
|
|
contractWithSigner: contractWithSigner,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-07-01 13:36:07 -07:00
|
|
|
export function isEvmContract(contractAddress: string, chain_id: number): boolean {
|
2022-01-28 09:59:48 -08:00
|
|
|
let ecd = evmContractData.get(chain_id);
|
|
|
|
return ecd && ecd.contractAddress === contractAddress;
|
2022-01-25 08:19:02 -08:00
|
|
|
}
|
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
export async function relayVaaToEvm(vaaBytes: string, t3Payload: Type3Payload) {
|
|
|
|
let ecd = evmContractData.get(t3Payload.targetChainId);
|
|
|
|
if (!ecd) {
|
2022-07-01 13:36:07 -07:00
|
|
|
logger.error("relayVaaToEvm: chain id " + t3Payload.targetChainId + " does not exist!");
|
2022-01-28 09:59:48 -08:00
|
|
|
}
|
|
|
|
|
2022-01-25 08:19:02 -08:00
|
|
|
let exactIn: boolean = false;
|
2022-01-28 09:59:48 -08:00
|
|
|
let error: boolean = false;
|
2022-01-25 08:19:02 -08:00
|
|
|
if (t3Payload.swapFunctionType === 1) {
|
|
|
|
exactIn = true;
|
|
|
|
} else if (t3Payload.swapFunctionType !== 2) {
|
2022-01-28 09:59:48 -08:00
|
|
|
error = true;
|
2022-07-01 13:36:07 -07:00
|
|
|
logger.error("relayVaaTo" + ecd.name + ": unsupported swapFunctionType: [" + t3Payload.swapFunctionType + "]");
|
2022-01-25 08:19:02 -08:00
|
|
|
}
|
2022-01-28 09:59:48 -08:00
|
|
|
if (error) return;
|
|
|
|
|
2022-01-25 08:19:02 -08:00
|
|
|
logger.debug(
|
2022-07-01 13:36:07 -07:00
|
|
|
"relayVaaTo" + ecd.name + ": chain_id: " + ecd.chain_id + ", contractAddress: [" + t3Payload.contractAddress + "]"
|
2022-01-25 08:19:02 -08:00
|
|
|
);
|
|
|
|
|
2022-01-28 09:59:48 -08:00
|
|
|
const signedVaaArray = hexToUint8Array(vaaBytes);
|
2022-07-01 13:36:07 -07:00
|
|
|
await relayVaaToEvmChain(t3Payload, ecd, signedVaaArray, exactIn);
|
2022-01-25 08:19:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
async function relayVaaToEvmChain(
|
|
|
|
t3Payload: Type3Payload,
|
|
|
|
tcd: EvmContractData,
|
|
|
|
signedVaaArray: Uint8Array,
|
2022-07-01 13:36:07 -07:00
|
|
|
exactIn: boolean
|
2022-01-25 08:19:02 -08:00
|
|
|
) {
|
|
|
|
logger.debug(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
|
|
|
": checking if already redeemed on " +
|
|
|
|
tcd.name +
|
|
|
|
" using tokenBridgeAddress [" +
|
|
|
|
tcd.tokenBridgeAddress +
|
|
|
|
"]"
|
|
|
|
);
|
|
|
|
|
2022-01-28 12:00:56 -08:00
|
|
|
if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
|
2022-01-25 08:19:02 -08:00
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
2022-01-28 12:00:56 -08:00
|
|
|
": srcChain: " +
|
|
|
|
t3Payload.sourceChainId +
|
|
|
|
", targetChain: " +
|
|
|
|
t3Payload.targetChainId +
|
|
|
|
", contract: [" +
|
2022-01-25 08:19:02 -08:00
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], exactIn: " +
|
|
|
|
exactIn +
|
2022-01-28 12:00:56 -08:00
|
|
|
": completed: already transferred"
|
2022-01-25 08:19:02 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
2022-01-28 12:00:56 -08:00
|
|
|
": srcChain: " +
|
|
|
|
t3Payload.sourceChainId +
|
|
|
|
", targetChain: " +
|
|
|
|
t3Payload.targetChainId +
|
|
|
|
", contract: [" +
|
2022-01-25 08:19:02 -08:00
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], exactIn: " +
|
|
|
|
exactIn +
|
|
|
|
": submitting redeem request"
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
let receipt: any = null;
|
|
|
|
if (exactIn) {
|
2022-07-01 13:36:07 -07:00
|
|
|
logger.debug("relayVaaTo: calling evmSwapExactInFromVaaNative()");
|
|
|
|
receipt = await swap.evmSwapExactInFromVaaNative(tcd.contractWithSigner, signedVaaArray);
|
2022-01-25 08:19:02 -08:00
|
|
|
} else {
|
2022-07-01 13:36:07 -07:00
|
|
|
logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaNative()");
|
|
|
|
receipt = await swap.evmSwapExactOutFromVaaNative(tcd.contractWithSigner, signedVaaArray);
|
2022-01-25 08:19:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
2022-01-28 12:00:56 -08:00
|
|
|
": srcChain: " +
|
|
|
|
t3Payload.sourceChainId +
|
|
|
|
", targetChain: " +
|
|
|
|
t3Payload.targetChainId +
|
|
|
|
", contract: [" +
|
2022-01-25 08:19:02 -08:00
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], exactIn: " +
|
|
|
|
exactIn +
|
2022-01-28 12:00:56 -08:00
|
|
|
": completed: success, txHash: " +
|
2022-01-25 08:19:02 -08:00
|
|
|
receipt.transactionHash
|
|
|
|
);
|
|
|
|
} catch (e: any) {
|
2022-01-28 12:00:56 -08:00
|
|
|
if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
|
2022-01-25 08:19:02 -08:00
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
2022-01-28 12:00:56 -08:00
|
|
|
": srcChain: " +
|
|
|
|
t3Payload.sourceChainId +
|
|
|
|
", targetChain: " +
|
|
|
|
t3Payload.targetChainId +
|
|
|
|
", contract: [" +
|
2022-01-25 08:19:02 -08:00
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], exactIn: " +
|
|
|
|
exactIn +
|
2022-01-28 12:00:56 -08:00
|
|
|
": completed: relay failed because the vaa has already been redeemed"
|
2022-01-25 08:19:02 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.error(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
|
|
|
": contract: [" +
|
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], exactIn: " +
|
|
|
|
exactIn +
|
|
|
|
": transaction failed: %o",
|
|
|
|
e
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-28 12:00:56 -08:00
|
|
|
if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
|
2022-01-25 08:19:02 -08:00
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
2022-01-28 12:00:56 -08:00
|
|
|
": srcChain: " +
|
|
|
|
t3Payload.sourceChainId +
|
|
|
|
", targetChain: " +
|
|
|
|
t3Payload.targetChainId +
|
|
|
|
", contract: [" +
|
2022-01-25 08:19:02 -08:00
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], exactIn: " +
|
|
|
|
exactIn +
|
2022-01-28 12:00:56 -08:00
|
|
|
": redeem confirmed"
|
2022-01-25 08:19:02 -08:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
logger.error(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
2022-01-28 12:00:56 -08:00
|
|
|
": srcChain: " +
|
|
|
|
t3Payload.sourceChainId +
|
|
|
|
", targetChain: " +
|
|
|
|
t3Payload.targetChainId +
|
|
|
|
", contract: [" +
|
2022-01-25 08:19:02 -08:00
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], exactIn: " +
|
|
|
|
exactIn +
|
2022-01-28 12:00:56 -08:00
|
|
|
": completed: failed to confirm redeem!"
|
2022-01-25 08:19:02 -08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-01 13:36:07 -07:00
|
|
|
async function isRedeemedOnEvm(tcd: EvmContractData, signedVaaArray: Uint8Array): Promise<boolean> {
|
2022-01-25 08:19:02 -08:00
|
|
|
let redeemed: boolean = false;
|
|
|
|
try {
|
2022-07-01 13:36:07 -07:00
|
|
|
redeemed = await getIsTransferCompletedEth(tcd.tokenBridgeAddress, tcd.provider, signedVaaArray);
|
2022-01-25 08:19:02 -08:00
|
|
|
} catch (e) {
|
|
|
|
logger.error(
|
2022-07-01 13:36:07 -07:00
|
|
|
"relayVaaTo" + tcd.name + ": failed to check if transfer is already complete, will attempt the transfer, e: %o",
|
2022-01-25 08:19:02 -08:00
|
|
|
e
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return redeemed;
|
|
|
|
}
|