2022-01-21 12:08:47 -08:00
|
|
|
import { Mutex } from "async-mutex";
|
|
|
|
let CondVar = require("condition-variable");
|
|
|
|
|
2022-01-20 08:52:49 -08:00
|
|
|
import {
|
|
|
|
ChainId,
|
|
|
|
CHAIN_ID_SOLANA,
|
|
|
|
CHAIN_ID_TERRA,
|
|
|
|
hexToUint8Array,
|
|
|
|
uint8ArrayToHex,
|
|
|
|
getEmitterAddressEth,
|
|
|
|
getEmitterAddressSolana,
|
|
|
|
getEmitterAddressTerra,
|
2022-01-21 12:08:47 -08:00
|
|
|
getIsTransferCompletedEth,
|
2022-01-20 08:52:49 -08:00
|
|
|
} from "@certusone/wormhole-sdk";
|
|
|
|
|
|
|
|
import {
|
|
|
|
importCoreWasm,
|
|
|
|
setDefaultWasm,
|
|
|
|
} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
|
|
|
|
|
|
|
|
import {
|
|
|
|
createSpyRPCServiceClient,
|
|
|
|
subscribeSignedVAA,
|
|
|
|
} from "@certusone/wormhole-spydk";
|
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
import { ethers } from "ethers";
|
|
|
|
|
|
|
|
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../../react/src/addresses/goerli";
|
|
|
|
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../../react/src/addresses/mumbai";
|
|
|
|
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/util";
|
|
|
|
|
2022-01-20 08:52:49 -08:00
|
|
|
let logger: any;
|
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
let configFile: string = ".env";
|
2022-01-20 08:52:49 -08:00
|
|
|
if (process.env.SWAP_RELAY_CONFIG) {
|
|
|
|
configFile = process.env.SWAP_RELAY_CONFIG;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("Loading config file [%s]", configFile);
|
|
|
|
require("dotenv").config({ path: configFile });
|
|
|
|
|
|
|
|
initLogger();
|
|
|
|
|
|
|
|
type OurEnvironment = {
|
|
|
|
spy_host: string;
|
|
|
|
spy_filters: string;
|
2022-01-20 16:22:40 -08:00
|
|
|
eth_provider_url: string;
|
|
|
|
polygon_provider_url: string;
|
|
|
|
wallet_private_key: string;
|
|
|
|
eth_contract_address: string;
|
|
|
|
polygon_contract_address: string;
|
2022-01-21 12:08:47 -08:00
|
|
|
eth_token_bridge_address: string;
|
|
|
|
polygon_token_bridge_address: string;
|
2022-01-20 16:22:40 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
type TargetContractData = {
|
2022-01-21 12:08:47 -08:00
|
|
|
name: string;
|
2022-01-20 16:22:40 -08:00
|
|
|
contractAddress: string;
|
2022-01-21 12:08:47 -08:00
|
|
|
tokenBridgeAddress: string;
|
2022-01-20 16:22:40 -08:00
|
|
|
contract: ethers.Contract;
|
|
|
|
provider: ethers.providers.StaticJsonRpcProvider;
|
|
|
|
wallet: ethers.Wallet;
|
|
|
|
contractWithSigner: ethers.Contract;
|
2022-01-20 08:52:49 -08:00
|
|
|
};
|
|
|
|
|
2022-01-21 12:08:47 -08:00
|
|
|
type Type3Payload = {
|
|
|
|
contractAddress: string;
|
|
|
|
relayerFee: ethers.BigNumber;
|
|
|
|
swapFunctionType: number;
|
|
|
|
swapCurrencyType: number;
|
|
|
|
};
|
|
|
|
|
|
|
|
type PendingEvent = {
|
|
|
|
vaaBytes: string;
|
|
|
|
t3Payload: Type3Payload;
|
|
|
|
receiveTime: Date;
|
|
|
|
};
|
|
|
|
|
2022-01-20 08:52:49 -08:00
|
|
|
setDefaultWasm("node");
|
|
|
|
|
|
|
|
let success: boolean;
|
|
|
|
let env: OurEnvironment;
|
|
|
|
[success, env] = loadConfig();
|
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
let ethContractData: TargetContractData = null;
|
|
|
|
let polygonContractData: TargetContractData = null;
|
2022-01-21 12:08:47 -08:00
|
|
|
let seqMap = new Map<string, number>();
|
|
|
|
|
|
|
|
const mutex = new Mutex();
|
|
|
|
let condition = new CondVar();
|
|
|
|
let pendingQueue = new Array<PendingEvent>();
|
2022-01-20 16:22:40 -08:00
|
|
|
|
2022-01-20 08:52:49 -08:00
|
|
|
if (success) {
|
|
|
|
logger.info(
|
|
|
|
"swap_relay starting up, will listen for signed VAAs from [" +
|
|
|
|
env.spy_host +
|
|
|
|
"]"
|
|
|
|
);
|
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
try {
|
|
|
|
ethContractData = makeEthContractData();
|
|
|
|
polygonContractData = makePolygonContractData();
|
|
|
|
} catch (e: any) {
|
|
|
|
logger.error("failed to connect to target contracts: %o", e);
|
|
|
|
success = false;
|
|
|
|
}
|
2022-01-20 08:52:49 -08:00
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
if (success) {
|
2022-01-21 12:08:47 -08:00
|
|
|
run_worker();
|
2022-01-20 16:22:40 -08:00
|
|
|
spy_listen();
|
|
|
|
}
|
2022-01-20 08:52:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
function loadConfig(): [boolean, OurEnvironment] {
|
|
|
|
if (!process.env.SPY_SERVICE_HOST) {
|
|
|
|
logger.error("Missing environment variable SPY_SERVICE_HOST");
|
|
|
|
return [false, undefined];
|
|
|
|
}
|
2022-01-20 16:22:40 -08:00
|
|
|
if (!process.env.ETH_PROVIDER) {
|
|
|
|
logger.error("Missing environment variable ETH_PROVIDER");
|
2022-01-20 08:52:49 -08:00
|
|
|
return [false, undefined];
|
|
|
|
}
|
2022-01-20 16:22:40 -08:00
|
|
|
if (!process.env.POLYGON_PROVIDER) {
|
|
|
|
logger.error("Missing environment variable POLYGON_PROVIDER");
|
2022-01-20 08:52:49 -08:00
|
|
|
return [false, undefined];
|
|
|
|
}
|
2022-01-20 16:22:40 -08:00
|
|
|
if (!process.env.WALLET_PRIVATE_KEY) {
|
|
|
|
logger.error("Missing environment variable WALLET_PRIVATE_KEY");
|
2022-01-20 08:52:49 -08:00
|
|
|
return [false, undefined];
|
|
|
|
}
|
2022-01-21 12:08:47 -08:00
|
|
|
if (!process.env.ETH_TOKEN_BRIDGE_ADDRESS) {
|
|
|
|
logger.error("Missing environment variable ETH_TOKEN_BRIDGE_ADDRESS");
|
|
|
|
return [false, undefined];
|
|
|
|
}
|
|
|
|
if (!process.env.POLYGON_TOKEN_BRIDGE_ADDRESS) {
|
|
|
|
logger.error("Missing environment variable POLYGON_TOKEN_BRIDGE_ADDRESS");
|
|
|
|
return [false, undefined];
|
|
|
|
}
|
2022-01-20 08:52:49 -08:00
|
|
|
|
|
|
|
return [
|
|
|
|
true,
|
|
|
|
{
|
|
|
|
spy_host: process.env.SPY_SERVICE_HOST,
|
|
|
|
spy_filters: process.env.SPY_SERVICE_FILTERS,
|
2022-01-20 16:22:40 -08:00
|
|
|
eth_provider_url: process.env.ETH_PROVIDER,
|
|
|
|
polygon_provider_url: process.env.POLYGON_PROVIDER,
|
|
|
|
wallet_private_key: process.env.WALLET_PRIVATE_KEY,
|
|
|
|
eth_contract_address: process.env.ETH_CONTRACT_ADDRESS,
|
|
|
|
polygon_contract_address: process.env.POLYGON_CONTRACT_ADDRESS,
|
2022-01-21 12:08:47 -08:00
|
|
|
eth_token_bridge_address: process.env.ETH_TOKEN_BRIDGE_ADDRESS,
|
|
|
|
polygon_token_bridge_address: process.env.POLYGON_TOKEN_BRIDGE_ADDRESS,
|
2022-01-20 08:52:49 -08:00
|
|
|
},
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
async function spy_listen() {
|
|
|
|
(async () => {
|
|
|
|
var filter = {};
|
|
|
|
if (env.spy_filters) {
|
|
|
|
const parsedJsonFilters = eval(env.spy_filters);
|
|
|
|
|
|
|
|
var myFilters = [];
|
|
|
|
for (var i = 0; i < parsedJsonFilters.length; i++) {
|
|
|
|
var myChainId = parseInt(parsedJsonFilters[i].chain_id) as ChainId;
|
|
|
|
var myEmitterAddress = await encodeEmitterAddress(
|
|
|
|
myChainId,
|
|
|
|
parsedJsonFilters[i].emitter_address
|
|
|
|
);
|
|
|
|
var myEmitterFilter = {
|
|
|
|
emitterFilter: {
|
|
|
|
chainId: myChainId,
|
|
|
|
emitterAddress: myEmitterAddress,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
logger.info(
|
|
|
|
"adding filter: chainId: [" +
|
|
|
|
myEmitterFilter.emitterFilter.chainId +
|
|
|
|
"], emitterAddress: [" +
|
|
|
|
myEmitterFilter.emitterFilter.emitterAddress +
|
|
|
|
"]"
|
|
|
|
);
|
|
|
|
myFilters.push(myEmitterFilter);
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info("setting " + myFilters.length + " filters");
|
|
|
|
filter = {
|
|
|
|
filters: myFilters,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
logger.info("processing all signed VAAs");
|
|
|
|
}
|
|
|
|
|
|
|
|
const client = createSpyRPCServiceClient(env.spy_host);
|
|
|
|
const stream = await subscribeSignedVAA(client, filter);
|
|
|
|
|
|
|
|
stream.on("data", ({ vaaBytes }) => {
|
|
|
|
processVaa(vaaBytes);
|
|
|
|
});
|
|
|
|
|
|
|
|
logger.info("swap_relay waiting for transfer signed VAAs");
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
async function encodeEmitterAddress(
|
|
|
|
myChainId,
|
|
|
|
emitterAddressStr
|
|
|
|
): Promise<string> {
|
|
|
|
if (myChainId === CHAIN_ID_SOLANA) {
|
|
|
|
return await getEmitterAddressSolana(emitterAddressStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (myChainId === CHAIN_ID_TERRA) {
|
|
|
|
return await getEmitterAddressTerra(emitterAddressStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return getEmitterAddressEth(emitterAddressStr);
|
|
|
|
}
|
|
|
|
|
2022-01-21 12:08:47 -08:00
|
|
|
async function processVaa(vaaBytes: string) {
|
|
|
|
let receiveTime = new Date();
|
2022-01-20 08:52:49 -08:00
|
|
|
logger.debug("processVaa");
|
|
|
|
const { parse_vaa } = await importCoreWasm();
|
|
|
|
const parsedVAA = parse_vaa(hexToUint8Array(vaaBytes));
|
|
|
|
logger.debug("processVaa: parsedVAA: %o", parsedVAA);
|
|
|
|
|
|
|
|
let emitter_address: string = uint8ArrayToHex(parsedVAA.emitter_address);
|
2022-01-21 12:08:47 -08:00
|
|
|
|
|
|
|
let seqNumKey: string =
|
|
|
|
parsedVAA.emitter_chain.toString() + ":" + emitter_address;
|
|
|
|
let lastSeqNum = seqMap.get(seqNumKey);
|
|
|
|
if (lastSeqNum) {
|
|
|
|
if (lastSeqNum >= parsedVAA.sequence) {
|
|
|
|
logger.debug(
|
|
|
|
"ignoring duplicate: emitter: [" +
|
|
|
|
seqNumKey +
|
|
|
|
"], seqNum: " +
|
|
|
|
parsedVAA.sequence
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
seqMap.set(seqNumKey, parsedVAA.sequence);
|
|
|
|
|
2022-01-20 08:52:49 -08:00
|
|
|
let payload_type: number = parsedVAA.payload[0];
|
|
|
|
|
|
|
|
let t3Payload = decodeSignedVAAPayloadType3(parsedVAA);
|
|
|
|
if (t3Payload) {
|
2022-01-21 12:08:47 -08:00
|
|
|
if (isOurContract(t3Payload.contractAddress)) {
|
|
|
|
logger.info(
|
|
|
|
"enqueuing type 3 vaa: emitter: [" +
|
|
|
|
parsedVAA.emitter_chain +
|
|
|
|
":" +
|
|
|
|
emitter_address +
|
|
|
|
"], seqNum: " +
|
|
|
|
parsedVAA.sequence +
|
|
|
|
", contractAddress: [" +
|
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], relayerFee: [" +
|
|
|
|
t3Payload.relayerFee +
|
|
|
|
"], swapFunctionType: [" +
|
|
|
|
t3Payload.swapFunctionType +
|
|
|
|
"], swapCurrencyType: [" +
|
|
|
|
t3Payload.swapCurrencyType +
|
|
|
|
"]"
|
|
|
|
);
|
|
|
|
|
|
|
|
await postVaa(vaaBytes, t3Payload, receiveTime);
|
|
|
|
} else {
|
|
|
|
logger.debug(
|
|
|
|
"dropping type 3 vaa for unsupported contract: emitter: [" +
|
|
|
|
parsedVAA.emitter_chain +
|
|
|
|
":" +
|
|
|
|
emitter_address +
|
|
|
|
"], seqNum: " +
|
|
|
|
parsedVAA.sequence +
|
|
|
|
", contractAddress: [" +
|
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], relayerFee: [" +
|
|
|
|
t3Payload.relayerFee +
|
|
|
|
"], swapFunctionType: [" +
|
|
|
|
t3Payload.swapFunctionType +
|
|
|
|
"], swapCurrencyType: [" +
|
|
|
|
t3Payload.swapCurrencyType +
|
|
|
|
"]"
|
|
|
|
);
|
2022-01-20 08:52:49 -08:00
|
|
|
}
|
|
|
|
} else {
|
2022-01-21 12:08:47 -08:00
|
|
|
logger.debug(
|
2022-01-20 08:52:49 -08:00
|
|
|
"dropping vaa: emitter: [" +
|
2022-01-21 12:08:47 -08:00
|
|
|
parsedVAA.emitter_chain +
|
2022-01-20 08:52:49 -08:00
|
|
|
":" +
|
|
|
|
emitter_address +
|
|
|
|
"], seqNum: " +
|
2022-01-21 12:08:47 -08:00
|
|
|
parsedVAA.sequence +
|
2022-01-20 08:52:49 -08:00
|
|
|
" payloadType: " +
|
|
|
|
payload_type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload {
|
|
|
|
const payload = Buffer.from(new Uint8Array(parsedVAA.payload));
|
|
|
|
const version = payload.readUInt8(0);
|
|
|
|
|
|
|
|
if (version !== 3) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
contractAddress: payload.slice(79, 79 + 20).toString("hex"),
|
|
|
|
relayerFee: ethers.BigNumber.from(payload.slice(101, 101 + 32)),
|
|
|
|
swapFunctionType: payload.readUInt8(260),
|
|
|
|
swapCurrencyType: payload.readUInt8(261),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-01-21 12:08:47 -08:00
|
|
|
function isOurContract(contractAddress: string): boolean {
|
|
|
|
return (
|
|
|
|
contractAddress === ethContractData.contractAddress ||
|
|
|
|
contractAddress === polygonContractData.contractAddress
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function postVaa(
|
|
|
|
vaaBytes: any,
|
|
|
|
t3Payload: Type3Payload,
|
|
|
|
receiveTime: Date
|
|
|
|
) {
|
|
|
|
let event: PendingEvent = {
|
|
|
|
vaaBytes: vaaBytes,
|
|
|
|
t3Payload: t3Payload,
|
|
|
|
receiveTime: receiveTime,
|
|
|
|
};
|
|
|
|
|
|
|
|
await mutex.runExclusive(() => {
|
|
|
|
pendingQueue.push(event);
|
|
|
|
logger.debug(
|
|
|
|
"posting event, there are now " + pendingQueue.length + " enqueued events"
|
|
|
|
);
|
|
|
|
if (condition) {
|
|
|
|
logger.debug("hitting condition variable.");
|
|
|
|
condition.complete(true);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const COND_VAR_TIMEOUT = 10000;
|
|
|
|
|
|
|
|
async function run_worker() {
|
|
|
|
await mutex.runExclusive(async () => {
|
|
|
|
await condition.wait(COND_VAR_TIMEOUT, callBack);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async function callBack(err: any, result: any) {
|
|
|
|
// logger.debug(
|
|
|
|
// "entering callback, pendingEvents: " +
|
|
|
|
// pendingQueue.length +
|
|
|
|
// ", err: %o, result: %o",
|
|
|
|
// err,
|
|
|
|
// result
|
|
|
|
// );
|
|
|
|
|
|
|
|
let done = false;
|
|
|
|
do {
|
|
|
|
let currEvent: PendingEvent = null;
|
|
|
|
|
|
|
|
await mutex.runExclusive(async () => {
|
|
|
|
condition = null;
|
|
|
|
if (pendingQueue.length !== 0) {
|
|
|
|
currEvent = pendingQueue[0];
|
|
|
|
pendingQueue.pop();
|
|
|
|
} else {
|
|
|
|
done = true;
|
|
|
|
condition = new CondVar();
|
|
|
|
await condition.wait(COND_VAR_TIMEOUT, callBack);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (currEvent) {
|
|
|
|
logger.debug("in callback, relaying event.");
|
|
|
|
try {
|
|
|
|
await relayVaa(currEvent.vaaBytes, currEvent.t3Payload);
|
|
|
|
} catch (e) {
|
|
|
|
logger.error("failed to relay type 3 vaa: %o", e);
|
|
|
|
}
|
|
|
|
|
|
|
|
await mutex.runExclusive(async () => {
|
|
|
|
if (pendingQueue.length === 0) {
|
|
|
|
logger.debug(
|
|
|
|
"in callback, no more pending events, rearming the condition."
|
|
|
|
);
|
|
|
|
done = true;
|
|
|
|
condition = new CondVar();
|
|
|
|
await condition.wait(COND_VAR_TIMEOUT, callBack);
|
|
|
|
} else {
|
|
|
|
logger.debug(
|
|
|
|
"in callback, there are " + pendingQueue.length + " pending events."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} while (!done);
|
|
|
|
|
|
|
|
// logger.debug("leaving callback.");
|
|
|
|
}
|
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
// Ethereum (Goerli) set up
|
|
|
|
function makeEthContractData(): TargetContractData {
|
|
|
|
let overridden: boolean = false;
|
|
|
|
let contractAddress: string = CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM;
|
|
|
|
if (env.eth_contract_address) {
|
|
|
|
contractAddress = env.eth_contract_address;
|
|
|
|
overridden = true;
|
|
|
|
}
|
2022-01-20 08:52:49 -08:00
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
contractAddress = contractAddress.toLowerCase();
|
|
|
|
if (contractAddress.search("0x") == 0) {
|
|
|
|
contractAddress = contractAddress.substring(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (overridden) {
|
|
|
|
logger.info(
|
2022-01-21 12:08:47 -08:00
|
|
|
"Connecting to Ethereum: overriding contract address: [" +
|
2022-01-20 16:22:40 -08:00
|
|
|
contractAddress +
|
2022-01-21 12:08:47 -08:00
|
|
|
"], node: [" +
|
|
|
|
env.eth_provider_url +
|
|
|
|
"], token bridge address: [" +
|
|
|
|
env.eth_token_bridge_address +
|
2022-01-20 16:22:40 -08:00
|
|
|
"]"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
logger.info(
|
2022-01-21 12:08:47 -08:00
|
|
|
"Connecting to Ethereum: contract address: [" +
|
2022-01-20 16:22:40 -08:00
|
|
|
contractAddress +
|
2022-01-21 12:08:47 -08:00
|
|
|
"], node: [" +
|
|
|
|
env.eth_provider_url +
|
|
|
|
"], token bridge address: [" +
|
|
|
|
env.eth_token_bridge_address +
|
2022-01-20 16:22:40 -08:00
|
|
|
"]"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const provider = new ethers.providers.StaticJsonRpcProvider(
|
|
|
|
env.eth_provider_url
|
|
|
|
);
|
|
|
|
|
|
|
|
const contract = new ethers.Contract(
|
|
|
|
contractAddress,
|
|
|
|
SWAP_CONTRACT_V3_ABI,
|
|
|
|
provider
|
|
|
|
);
|
|
|
|
|
|
|
|
const wallet = new ethers.Wallet(env.wallet_private_key, provider);
|
|
|
|
const contractWithSigner = contract.connect(wallet);
|
|
|
|
|
|
|
|
return {
|
2022-01-21 12:08:47 -08:00
|
|
|
name: "Ethereum",
|
2022-01-20 16:22:40 -08:00
|
|
|
contractAddress: contractAddress,
|
2022-01-21 12:08:47 -08:00
|
|
|
tokenBridgeAddress: env.eth_token_bridge_address,
|
2022-01-20 16:22:40 -08:00
|
|
|
contract: contract,
|
|
|
|
provider: provider,
|
|
|
|
wallet: wallet,
|
|
|
|
contractWithSigner: contractWithSigner,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Polygon (Mumbai) set up
|
|
|
|
function makePolygonContractData(): TargetContractData {
|
|
|
|
let overridden: boolean = false;
|
|
|
|
let contractAddress: string = CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON;
|
|
|
|
if (env.polygon_contract_address) {
|
|
|
|
contractAddress = env.polygon_contract_address;
|
|
|
|
overridden = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
contractAddress = contractAddress.toLowerCase();
|
|
|
|
if (contractAddress.search("0x") == 0) {
|
|
|
|
contractAddress = contractAddress.substring(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (overridden) {
|
|
|
|
logger.info(
|
2022-01-21 12:08:47 -08:00
|
|
|
"Connecting to Polygon: overriding contract address: [" +
|
2022-01-20 16:22:40 -08:00
|
|
|
contractAddress +
|
2022-01-21 12:08:47 -08:00
|
|
|
"], node: [" +
|
|
|
|
env.polygon_provider_url +
|
|
|
|
"], token bridge address: [" +
|
|
|
|
env.polygon_token_bridge_address +
|
2022-01-20 16:22:40 -08:00
|
|
|
"]"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
logger.info(
|
2022-01-21 12:08:47 -08:00
|
|
|
"Connecting to Polygon: contract address: [" +
|
2022-01-20 16:22:40 -08:00
|
|
|
contractAddress +
|
2022-01-21 12:08:47 -08:00
|
|
|
"], node: [" +
|
|
|
|
env.polygon_provider_url +
|
|
|
|
"], token bridge address: [" +
|
|
|
|
env.polygon_token_bridge_address +
|
2022-01-20 16:22:40 -08:00
|
|
|
"]"
|
|
|
|
);
|
|
|
|
}
|
2022-01-20 08:52:49 -08:00
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
const provider = new ethers.providers.StaticJsonRpcProvider(
|
|
|
|
env.polygon_provider_url
|
2022-01-20 08:52:49 -08:00
|
|
|
);
|
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
const contract = new ethers.Contract(
|
|
|
|
contractAddress,
|
|
|
|
SWAP_CONTRACT_V2_ABI,
|
|
|
|
provider
|
2022-01-20 08:52:49 -08:00
|
|
|
);
|
|
|
|
|
2022-01-20 16:22:40 -08:00
|
|
|
const wallet = new ethers.Wallet(env.wallet_private_key, provider);
|
|
|
|
const contractWithSigner = contract.connect(wallet);
|
|
|
|
|
|
|
|
return {
|
2022-01-21 12:08:47 -08:00
|
|
|
name: "Polygon",
|
2022-01-20 16:22:40 -08:00
|
|
|
contractAddress: contractAddress,
|
2022-01-21 12:08:47 -08:00
|
|
|
tokenBridgeAddress: env.polygon_token_bridge_address,
|
2022-01-20 16:22:40 -08:00
|
|
|
contract: contract,
|
|
|
|
provider: provider,
|
|
|
|
wallet: wallet,
|
|
|
|
contractWithSigner: contractWithSigner,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
// GOERLI_PROVIDER = Ethereum
|
|
|
|
// MUMBAI_PROVIDER = Polygon
|
|
|
|
|
|
|
|
if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM) {
|
|
|
|
// Use one of the V3 swap methods.
|
|
|
|
} else if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON) {
|
|
|
|
// Use one of the V2 swap methods.
|
|
|
|
} else {
|
|
|
|
// Error
|
|
|
|
}
|
|
|
|
|
|
|
|
if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 1) {
|
|
|
|
// swapExactInFromVaaNative
|
|
|
|
} else if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 2) {
|
|
|
|
// swapExactInFromVaaToken
|
|
|
|
} else if (
|
|
|
|
t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 1) {
|
|
|
|
// swapExactOutFromVaaNative
|
|
|
|
} else if (t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 2) {
|
|
|
|
// swapExactOutFromVaaToken
|
|
|
|
} else {
|
|
|
|
// error
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function relayVaa(vaaBytes: string, t3Payload: Type3Payload) {
|
|
|
|
const signedVaaArray = hexToUint8Array(vaaBytes);
|
|
|
|
|
|
|
|
let exactIn: boolean = false;
|
|
|
|
if (t3Payload.swapFunctionType === 1) {
|
|
|
|
exactIn = true;
|
|
|
|
} else if (t3Payload.swapFunctionType !== 2) {
|
|
|
|
logger.error(
|
2022-01-21 12:08:47 -08:00
|
|
|
"relayVaa: unsupported swapFunctionType: [" +
|
2022-01-20 16:22:40 -08:00
|
|
|
t3Payload.swapFunctionType +
|
|
|
|
"]"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let native: boolean = false;
|
|
|
|
if (t3Payload.swapCurrencyType === 1) {
|
|
|
|
native = true;
|
|
|
|
} else if (t3Payload.swapCurrencyType !== 2) {
|
|
|
|
logger.error(
|
2022-01-21 12:08:47 -08:00
|
|
|
"relayVaa: unsupported swapCurrencyType: [" +
|
2022-01-20 16:22:40 -08:00
|
|
|
t3Payload.swapCurrencyType +
|
|
|
|
"]"
|
|
|
|
);
|
|
|
|
}
|
2022-01-20 08:52:49 -08:00
|
|
|
|
2022-01-21 12:08:47 -08:00
|
|
|
logger.debug(
|
2022-01-20 16:22:40 -08:00
|
|
|
"relayVaa: contractAddress: [" +
|
|
|
|
t3Payload.contractAddress +
|
|
|
|
"], ethContract: [" +
|
|
|
|
ethContractData.contractAddress +
|
|
|
|
"], polygonContract[" +
|
|
|
|
polygonContractData.contractAddress +
|
|
|
|
"]"
|
2022-01-20 08:52:49 -08:00
|
|
|
);
|
2022-01-20 16:22:40 -08:00
|
|
|
|
|
|
|
if (t3Payload.contractAddress === ethContractData.contractAddress) {
|
2022-01-21 12:08:47 -08:00
|
|
|
await relayVaaToChain(ethContractData, signedVaaArray, exactIn, native);
|
2022-01-20 16:22:40 -08:00
|
|
|
} else if (
|
|
|
|
t3Payload.contractAddress === polygonContractData.contractAddress
|
|
|
|
) {
|
2022-01-21 12:08:47 -08:00
|
|
|
await relayVaaToChain(polygonContractData, signedVaaArray, exactIn, native);
|
2022-01-20 16:22:40 -08:00
|
|
|
} else {
|
|
|
|
logger.error(
|
2022-01-21 12:08:47 -08:00
|
|
|
"relayVaa: unexpected contract: [" +
|
2022-01-20 16:22:40 -08:00
|
|
|
t3Payload.contractAddress +
|
2022-01-21 12:08:47 -08:00
|
|
|
"], this should not happen!"
|
2022-01-20 16:22:40 -08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-21 12:08:47 -08:00
|
|
|
async function relayVaaToChain(
|
|
|
|
tcd: TargetContractData,
|
2022-01-20 16:22:40 -08:00
|
|
|
signedVaaArray: Uint8Array,
|
|
|
|
exactIn: boolean,
|
|
|
|
native: boolean
|
|
|
|
) {
|
2022-01-21 12:08:47 -08:00
|
|
|
try {
|
|
|
|
logger.debug(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
|
|
|
": checking if already redeemed on " +
|
|
|
|
tcd.name +
|
|
|
|
" using tokenBridgeAddress [" +
|
|
|
|
tcd.tokenBridgeAddress +
|
|
|
|
"]"
|
|
|
|
);
|
|
|
|
|
|
|
|
const alreadyRedeemed = await getIsTransferCompletedEth(
|
|
|
|
tcd.tokenBridgeAddress,
|
|
|
|
tcd.provider,
|
|
|
|
signedVaaArray
|
|
|
|
);
|
|
|
|
|
|
|
|
if (alreadyRedeemed) {
|
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
|
|
|
": exactIn: " +
|
|
|
|
exactIn +
|
|
|
|
", native: " +
|
|
|
|
native +
|
|
|
|
": already transferred!"
|
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
2022-01-20 16:22:40 -08:00
|
|
|
}
|
2022-01-21 12:08:47 -08:00
|
|
|
} catch (e) {
|
|
|
|
logger.error(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
|
|
|
": failed to check if transfer is already complete, will attempt the transfer, e: %o",
|
|
|
|
e
|
|
|
|
);
|
2022-01-20 16:22:40 -08:00
|
|
|
}
|
|
|
|
|
2022-01-21 12:08:47 -08:00
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" + tcd.name + ": exactIn: " + exactIn + ", native: " + native
|
|
|
|
);
|
2022-01-20 16:22:40 -08:00
|
|
|
if (exactIn) {
|
|
|
|
if (native) {
|
|
|
|
await swap
|
2022-01-21 12:08:47 -08:00
|
|
|
.swapExactInFromVaaNative(tcd.contractWithSigner, signedVaaArray)
|
2022-01-20 16:22:40 -08:00
|
|
|
.then((receipt) => {
|
2022-01-21 12:08:47 -08:00
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" + tcd.name + ": %o",
|
|
|
|
receipt.transactionHash
|
|
|
|
);
|
2022-01-20 16:22:40 -08:00
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
logger.error(
|
2022-01-21 12:08:47 -08:00
|
|
|
"relayVaaTo" + tcd.name + ": transaction failed: %o",
|
2022-01-20 16:22:40 -08:00
|
|
|
error.transactionHash
|
|
|
|
);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
await swap
|
2022-01-21 12:08:47 -08:00
|
|
|
.swapExactInFromVaaToken(tcd.contractWithSigner, signedVaaArray)
|
2022-01-20 16:22:40 -08:00
|
|
|
.then((receipt) => {
|
2022-01-21 12:08:47 -08:00
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" + tcd.name + ": %o",
|
|
|
|
receipt.transactionHash
|
|
|
|
);
|
2022-01-20 16:22:40 -08:00
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
logger.error(
|
2022-01-21 12:08:47 -08:00
|
|
|
"relayVaaTo" + tcd.name + ": transaction failed: %o",
|
2022-01-20 16:22:40 -08:00
|
|
|
error.transactionHash
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (native) {
|
|
|
|
await swap
|
2022-01-21 12:08:47 -08:00
|
|
|
.swapExactOutFromVaaNative(tcd.contractWithSigner, signedVaaArray)
|
2022-01-20 16:22:40 -08:00
|
|
|
.then((receipt) => {
|
2022-01-21 12:08:47 -08:00
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" + tcd.name + ": %o",
|
|
|
|
receipt.transactionHash
|
|
|
|
);
|
2022-01-20 16:22:40 -08:00
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
logger.error(
|
2022-01-21 12:08:47 -08:00
|
|
|
"relayVaaTo" + tcd.name + ": transaction failed: %o",
|
2022-01-20 16:22:40 -08:00
|
|
|
error.transactionHash
|
|
|
|
);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
await swap
|
2022-01-21 12:08:47 -08:00
|
|
|
.swapExactOutFromVaaToken(tcd.contractWithSigner, signedVaaArray)
|
2022-01-20 16:22:40 -08:00
|
|
|
.then((receipt) => {
|
2022-01-21 12:08:47 -08:00
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" + tcd.name + ": %o",
|
|
|
|
receipt.transactionHash
|
|
|
|
);
|
2022-01-20 16:22:40 -08:00
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
logger.error(
|
2022-01-21 12:08:47 -08:00
|
|
|
"relayVaaTo" + tcd.name + ": transaction failed: %o",
|
2022-01-20 16:22:40 -08:00
|
|
|
error.transactionHash
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-01-21 12:08:47 -08:00
|
|
|
|
|
|
|
try {
|
|
|
|
logger.debug(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
|
|
|
": checking if redeemed on " +
|
|
|
|
tcd.name +
|
|
|
|
" using tokenBridgeAddress [" +
|
|
|
|
tcd.tokenBridgeAddress +
|
|
|
|
"]"
|
|
|
|
);
|
|
|
|
const redeemed = await getIsTransferCompletedEth(
|
|
|
|
tcd.tokenBridgeAddress,
|
|
|
|
tcd.provider,
|
|
|
|
signedVaaArray
|
|
|
|
);
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
|
|
|
": exactIn: " +
|
|
|
|
exactIn +
|
|
|
|
", native: " +
|
|
|
|
native +
|
|
|
|
": redeemed: " +
|
|
|
|
redeemed
|
|
|
|
);
|
|
|
|
} catch (e) {
|
|
|
|
logger.error(
|
|
|
|
"relayVaaTo" +
|
|
|
|
tcd.name +
|
|
|
|
": failed to check if transfer completed, e: %o",
|
|
|
|
e
|
|
|
|
);
|
|
|
|
}
|
2022-01-20 08:52:49 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////// Start of logger stuff ///////////////////////////////////////////
|
|
|
|
|
|
|
|
function initLogger() {
|
|
|
|
const winston = require("winston");
|
|
|
|
|
|
|
|
let useConsole: boolean = true;
|
|
|
|
let logFileName: string = "";
|
|
|
|
if (process.env.LOG_DIR) {
|
|
|
|
useConsole = false;
|
|
|
|
logFileName =
|
|
|
|
process.env.LOG_DIR + "/swap_relay." + new Date().toISOString() + ".log";
|
|
|
|
}
|
|
|
|
|
|
|
|
let logLevel = "info";
|
|
|
|
if (process.env.LOG_LEVEL) {
|
|
|
|
logLevel = process.env.LOG_LEVEL;
|
|
|
|
}
|
|
|
|
|
|
|
|
let transport: any;
|
|
|
|
if (useConsole) {
|
|
|
|
console.log("swap_relay is logging to the console at level [%s]", logLevel);
|
|
|
|
|
|
|
|
transport = new winston.transports.Console({
|
|
|
|
level: logLevel,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
console.log(
|
|
|
|
"swap_relay is logging to [%s] at level [%s]",
|
|
|
|
logFileName,
|
|
|
|
logLevel
|
|
|
|
);
|
|
|
|
|
|
|
|
transport = new winston.transports.File({
|
|
|
|
filename: logFileName,
|
|
|
|
level: logLevel,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const logConfiguration = {
|
|
|
|
transports: [transport],
|
|
|
|
format: winston.format.combine(
|
|
|
|
winston.format.splat(),
|
|
|
|
winston.format.simple(),
|
|
|
|
winston.format.timestamp({
|
|
|
|
format: "YYYY-MM-DD HH:mm:ss.SSS",
|
|
|
|
}),
|
|
|
|
winston.format.printf(
|
|
|
|
(info: any) => `${[info.timestamp]}|${info.level}|${info.message}`
|
|
|
|
)
|
|
|
|
),
|
|
|
|
};
|
|
|
|
|
|
|
|
logger = winston.createLogger(logConfiguration);
|
|
|
|
}
|