From 29704b701de6ffd92da50867ddbca6b3a1d98302 Mon Sep 17 00:00:00 2001 From: Bruce Riley Date: Tue, 25 Jan 2022 16:19:02 +0000 Subject: [PATCH 01/38] Start of relayer support for terra --- swap_relayer/.env.sample | 21 +- swap_relayer/package.json | 13 +- swap_relayer/src/evm.ts | 408 +++++++++++++++++++++++++++ swap_relayer/src/index.ts | 560 +++++++++++--------------------------- swap_relayer/src/terra.ts | 179 ++++++++++++ 5 files changed, 768 insertions(+), 413 deletions(-) create mode 100644 swap_relayer/src/evm.ts create mode 100644 swap_relayer/src/terra.ts diff --git a/swap_relayer/.env.sample b/swap_relayer/.env.sample index 7216c95..aa91f7a 100644 --- a/swap_relayer/.env.sample +++ b/swap_relayer/.env.sample @@ -1,18 +1,25 @@ # TestNet SPY_SERVICE_HOST=localhost:7073 -SPY_SERVICE_FILTERS=[{"chain_id":2,"emitter_address":"0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7"},{"chain_id":5,"emitter_address":"0x000000000000000000000000377d55a7928c046e18eebb61977e714d2a76472a"}] +SPY_SERVICE_FILTERS=[{"chain_id":2,"emitter_address":"0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7"},{"chain_id":3,"emitter_address":"terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a"},{"chain_id":5,"emitter_address":"0x000000000000000000000000377d55a7928c046e18eebb61977e714d2a76472a"}] ETH_PROVIDER=https://goerli.infura.io/v3/your_project_id -POLYGON_PROVIDER=https://polygon-mumbai.infura.io/v3/your_project_id - ETH_TOKEN_BRIDGE_ADDRESS=0xF890982f9310df57d00f659cf4fd87e65adEd8d7 -POLYGON_TOKEN_BRIDGE_ADDRESS=0x377D55a7928c046E18eEbb61977e714d2a76472a -# If these are defined, use them instead of the ones defined in the code. -ETH_CONTRACT_ADDRESS=0x61D26732B190bdc5771e2a2b3ADB295e1b5A88BF -POLYGON_CONTRACT_ADDRESS=0xc5Ba16A974a0c0E7935285d99F496Ee65eDFB8BA +POLYGON_PROVIDER=https://polygon-mumbai.infura.io/v3/your_project_id +POLYGON_TOKEN_BRIDGE_ADDRESS=0x377D55a7928c046E18eEbb61977e714d2a76472a WALLET_PRIVATE_KEY=your_key_here +TERRA_PROVIDER=https://bombay-lcd.terra.dev +TERRA_CHAIN_ID=bombay-12 +TERRA_NAME=testnet +TERRA_WALLET_PRIVATE_KEY=your_key_here +TERRA_GAS_PRICE_URL=https://bombay-fcd.terra.dev/v1/txs/gas_prices +TERRA_TOKEN_BRIDGE_ADDRESS=terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a + #LOG_DIR=/var/logs LOG_LEVEL=debug + +ETH_CONTRACT_ADDRESS=0x61D26732B190bdc5771e2a2b3ADB295e1b5A88BF +POLYGON_CONTRACT_ADDRESS=0xc5Ba16A974a0c0E7935285d99F496Ee65eDFB8BA +TERRA_CONTRACT_ADDRESS=terra_contract_address \ No newline at end of file diff --git a/swap_relayer/package.json b/swap_relayer/package.json index 328ba6f..0720959 100644 --- a/swap_relayer/package.json +++ b/swap_relayer/package.json @@ -14,24 +14,25 @@ "@types/jest": "^27.0.2", "@types/long": "^4.0.1", "@types/node": "^16.6.1", - "async-mutex": "^0.3.2", "axios": "^0.24.0", - "condition-variable": "^1.0.0", "esm": "^3.2.25", - "ethers": "^5.5.3", "jest": "^27.3.1", "prettier": "^2.3.2", "ts-jest": "^27.0.7", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", - "typescript": "^4.3.5", - "winston": "^3.3.3" + "typescript": "^4.3.5" }, "dependencies": { "@certusone/wormhole-sdk": "^0.1.4", "@certusone/wormhole-spydk": "^0.0.1", + "async-mutex": "^0.3.2", + "condition-variable": "^1.0.0", "body-parser": "^1.19.0", "cors": "^2.8.5", - "dotenv": "^10.0.0" + "dotenv": "^10.0.0", + "ethers": "^5.5.3", + "@terra-money/terra.js": "^2.0.14", + "winston": "^3.3.3" } } diff --git a/swap_relayer/src/evm.ts b/swap_relayer/src/evm.ts new file mode 100644 index 0000000..470bdc5 --- /dev/null +++ b/swap_relayer/src/evm.ts @@ -0,0 +1,408 @@ +import { Mutex } from "async-mutex"; +let CondVar = require("condition-variable"); + +import { getIsTransferCompletedEth } from "@certusone/wormhole-sdk"; + +import { + importCoreWasm, + setDefaultWasm, +} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; + +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/util"; + +import { logger, OurEnvironment, Type3Payload } from "./index"; + +type EvmContractData = { + name: string; + contractAddress: string; + tokenBridgeAddress: string; + contract: ethers.Contract; + provider: ethers.providers.StaticJsonRpcProvider; + wallet: ethers.Wallet; + contractWithSigner: ethers.Contract; +}; + +let ethContractData: EvmContractData = null; +let polygonContractData: EvmContractData = null; + +export function makeEvmContractData(env: OurEnvironment) { + ethContractData = makeEthContractData(env); + polygonContractData = makePolygonContractData(env); +} + +// Ethereum (Goerli) set up +function makeEthContractData(env: OurEnvironment): EvmContractData { + let contractAddress: string = env.eth_contract_address.toLowerCase(); + if (contractAddress.search("0x") == 0) { + contractAddress = contractAddress.substring(2); + } + + logger.info( + "Connecting to Ethereum: contract address: [" + + contractAddress + + "], node: [" + + env.eth_provider_url + + "], token bridge address: [" + + env.eth_token_bridge_address + + "]" + ); + + 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.evm_wallet_private_key, provider); + const contractWithSigner = contract.connect(wallet); + + return { + name: "Ethereum", + contractAddress: contractAddress, + tokenBridgeAddress: env.eth_token_bridge_address, + contract: contract, + provider: provider, + wallet: wallet, + contractWithSigner: contractWithSigner, + }; +} + +// Polygon (Mumbai) set up +function makePolygonContractData(env: OurEnvironment): EvmContractData { + let contractAddress: string = env.polygon_contract_address.toLowerCase(); + if (contractAddress.search("0x") == 0) { + contractAddress = contractAddress.substring(2); + } + + logger.info( + "Connecting to Polygon: contract address: [" + + contractAddress + + "], node: [" + + env.polygon_provider_url + + "], token bridge address: [" + + env.polygon_token_bridge_address + + "]" + ); + + const provider = new ethers.providers.StaticJsonRpcProvider( + env.polygon_provider_url + ); + + const contract = new ethers.Contract( + contractAddress, + SWAP_CONTRACT_V2_ABI, + provider + ); + + const wallet = new ethers.Wallet(env.evm_wallet_private_key, provider); + const contractWithSigner = contract.connect(wallet); + + return { + name: "Polygon", + contractAddress: contractAddress, + tokenBridgeAddress: env.polygon_token_bridge_address, + contract: contract, + provider: provider, + wallet: wallet, + contractWithSigner: contractWithSigner, + }; +} + +export function isEvmContract(contractAddress: string): boolean { + return ( + (ethContractData && contractAddress === ethContractData.contractAddress) || + (polygonContractData && + contractAddress === polygonContractData.contractAddress) + ); +} + +/* + // 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 + } +*/ + +/* + // 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 + } +*/ + +export async function relayVaaToEvm( + signedVaaArray: Uint8Array, + t3Payload: Type3Payload +) { + let exactIn: boolean = false; + if (t3Payload.swapFunctionType === 1) { + exactIn = true; + } else if (t3Payload.swapFunctionType !== 2) { + logger.error( + "relayVaa: unsupported swapFunctionType: [" + + t3Payload.swapFunctionType + + "]" + ); + } + + let native: boolean = false; + if (t3Payload.swapCurrencyType === 1) { + native = true; + } else if (t3Payload.swapCurrencyType !== 2) { + logger.error( + "relayVaa: unsupported swapCurrencyType: [" + + t3Payload.swapCurrencyType + + "]" + ); + } + + logger.debug( + "relayVaa: contractAddress: [" + + t3Payload.contractAddress + + "], ethContract: [" + + ethContractData.contractAddress + + "], polygonContract[" + + polygonContractData.contractAddress + + "]" + ); + + if (t3Payload.contractAddress === ethContractData.contractAddress) { + await relayVaaToEvmChain( + t3Payload, + ethContractData, + signedVaaArray, + exactIn, + native + ); + } else if ( + t3Payload.contractAddress === polygonContractData.contractAddress + ) { + await relayVaaToEvmChain( + t3Payload, + polygonContractData, + signedVaaArray, + exactIn, + native + ); + } else { + logger.error( + "relayVaa: unexpected contract: [" + + t3Payload.contractAddress + + "], this should not happen!" + ); + } +} + +async function relayVaaToEvmChain( + t3Payload: Type3Payload, + tcd: EvmContractData, + signedVaaArray: Uint8Array, + exactIn: boolean, + native: boolean +) { + logger.debug( + "relayVaaTo" + + tcd.name + + ": checking if already redeemed on " + + tcd.name + + " using tokenBridgeAddress [" + + tcd.tokenBridgeAddress + + "]" + ); + + if (await isRedeemedOnEvm(t3Payload, tcd, signedVaaArray)) { + logger.info( + "relayVaaTo" + + tcd.name + + ": contract: [" + + t3Payload.contractAddress + + "], exactIn: " + + exactIn + + ", native: " + + native + + ": already transferred" + ); + + return; + } + + logger.info( + "relayVaaTo" + + tcd.name + + ": contract: [" + + t3Payload.contractAddress + + "], exactIn: " + + exactIn + + ", native: " + + native + + ": submitting redeem request" + ); + + try { + let receipt: any = null; + if (exactIn) { + if (native) { + receipt = await swap.swapExactInFromVaaNative( + tcd.contractWithSigner, + signedVaaArray + ); + } else { + receipt = await swap.swapExactInFromVaaToken( + tcd.contractWithSigner, + signedVaaArray + ); + } + } else { + if (native) { + receipt = await swap.swapExactOutFromVaaNative( + tcd.contractWithSigner, + signedVaaArray + ); + } else { + receipt = await swap.swapExactOutFromVaaToken( + tcd.contractWithSigner, + signedVaaArray + ); + } + } + + logger.info( + "relayVaaTo" + + tcd.name + + ": contract: [" + + t3Payload.contractAddress + + "], exactIn: " + + exactIn + + ", native: " + + native + + ": success, txHash: " + + receipt.transactionHash + ); + } catch (e: any) { + if (await isRedeemedOnEvm(t3Payload, tcd, signedVaaArray)) { + logger.info( + "relayVaaTo" + + tcd.name + + ": contract: [" + + t3Payload.contractAddress + + "], exactIn: " + + exactIn + + ", native: " + + native + + ": relay failed because the vaa has already been redeemed" + ); + + return; + } + + logger.error( + "relayVaaTo" + + tcd.name + + ": contract: [" + + t3Payload.contractAddress + + "], exactIn: " + + exactIn + + ", native: " + + native + + ": transaction failed: %o", + e + ); + } + + if (await isRedeemedOnEvm(t3Payload, tcd, signedVaaArray)) { + logger.info( + "relayVaaTo" + + tcd.name + + ": contract: [" + + t3Payload.contractAddress + + "], exactIn: " + + exactIn + + ", native: " + + native + + ": redeem succeeded" + ); + } else { + logger.error( + "relayVaaTo" + + tcd.name + + ": contract: [" + + t3Payload.contractAddress + + "], exactIn: " + + exactIn + + ", native: " + + native + + ": redeem failed!" + ); + } +} + +async function isRedeemedOnEvm( + t3Payload: Type3Payload, + tcd: EvmContractData, + signedVaaArray: Uint8Array +): Promise { + 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; +} diff --git a/swap_relayer/src/index.ts b/swap_relayer/src/index.ts index 3f3e75b..2a4b675 100644 --- a/swap_relayer/src/index.ts +++ b/swap_relayer/src/index.ts @@ -25,14 +25,20 @@ import { 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"; -let logger: any; +import { isEvmContract, makeEvmContractData, relayVaaToEvm } from "./evm"; + +import { + isTerraContract, + makeTerraContractData, + relayVaaToTerra, +} from "./terra"; + +export let logger: any; let configFile: string = ".env"; if (process.env.SWAP_RELAY_CONFIG) { @@ -44,19 +50,31 @@ require("dotenv").config({ path: configFile }); initLogger(); -type OurEnvironment = { +export type OurEnvironment = { spy_host: string; spy_filters: string; + eth_provider_url: string; - polygon_provider_url: string; - wallet_private_key: string; eth_contract_address: string; - polygon_contract_address: string; eth_token_bridge_address: string; + + polygon_provider_url: string; + polygon_contract_address: string; polygon_token_bridge_address: string; + + evm_wallet_private_key: string; + + terraEnabled: boolean; + terra_provider_url: string; + terra_chain_id: string; + terra_name: string; + terra_contract_address: string; + terra_token_bridge_address: string; + terra_wallet_private_key: string; + terra_gas_price_url: string; }; -type TargetContractData = { +type EvmContractData = { name: string; contractAddress: string; tokenBridgeAddress: string; @@ -66,7 +84,8 @@ type TargetContractData = { contractWithSigner: ethers.Contract; }; -type Type3Payload = { +export type Type3Payload = { + targetChainId: number; contractAddress: string; relayerFee: ethers.BigNumber; swapFunctionType: number; @@ -85,8 +104,6 @@ let success: boolean; let env: OurEnvironment; [success, env] = loadConfig(); -let ethContractData: TargetContractData = null; -let polygonContractData: TargetContractData = null; let seqMap = new Map(); const mutex = new Mutex(); @@ -101,8 +118,10 @@ if (success) { ); try { - ethContractData = makeEthContractData(); - polygonContractData = makePolygonContractData(); + makeEvmContractData(env); + if (env.terraEnabled) { + makeTerraContractData(env); + } } catch (e: any) { logger.error("failed to connect to target contracts: %o", e); success = false; @@ -119,39 +138,99 @@ function loadConfig(): [boolean, OurEnvironment] { logger.error("Missing environment variable SPY_SERVICE_HOST"); return [false, undefined]; } + if (!process.env.ETH_PROVIDER) { logger.error("Missing environment variable ETH_PROVIDER"); return [false, undefined]; } - if (!process.env.POLYGON_PROVIDER) { - logger.error("Missing environment variable POLYGON_PROVIDER"); - return [false, undefined]; - } - if (!process.env.WALLET_PRIVATE_KEY) { - logger.error("Missing environment variable WALLET_PRIVATE_KEY"); + if (!process.env.ETH_CONTRACT_ADDRESS) { + logger.error("Missing environment variable ETH_CONTRACT_ADDRESS"); return [false, undefined]; } if (!process.env.ETH_TOKEN_BRIDGE_ADDRESS) { logger.error("Missing environment variable ETH_TOKEN_BRIDGE_ADDRESS"); return [false, undefined]; } + + if (!process.env.POLYGON_PROVIDER) { + logger.error("Missing environment variable POLYGON_PROVIDER"); + return [false, undefined]; + } + if (!process.env.POLYGON_CONTRACT_ADDRESS) { + logger.error("Missing environment variable POLYGON_CONTRACT_ADDRESS"); + return [false, undefined]; + } if (!process.env.POLYGON_TOKEN_BRIDGE_ADDRESS) { logger.error("Missing environment variable POLYGON_TOKEN_BRIDGE_ADDRESS"); return [false, undefined]; } + if (!process.env.WALLET_PRIVATE_KEY) { + logger.error("Missing environment variable WALLET_PRIVATE_KEY"); + return [false, undefined]; + } + + let terraEnabled: boolean = false; + if (process.env.TERRA_PROVIDER) { + terraEnabled = true; + + if (!process.env.TERRA_CHAIN_ID) { + logger.error("Missing environment variable WALLET_PRIVATE_KEY"); + return [false, undefined]; + throw "Missing environment variable TERRA_CHAIN_ID"; + } + + if (!process.env.TERRA_NAME) { + logger.error("Missing environment variable WALLET_PRIVATE_KEY"); + return [false, undefined]; + throw "Missing environment variable TERRA_NAME"; + } + + if (!process.env.TERRA_WALLET_PRIVATE_KEY) { + logger.error("Missing environment variable TERRA_WALLET_PRIVATE_KEY"); + return [false, undefined]; + } + + if (!process.env.TERRA_GAS_PRICE_URL) { + logger.error("Missing environment variable TERRA_GAS_PRICE_URL"); + return [false, undefined]; + } + + if (!process.env.TERRA_CONTRACT_ADDRESS) { + logger.error("Missing environment variable TERRA_CONTRACT_ADDRESS"); + return [false, undefined]; + } + + if (!process.env.TERRA_TOKEN_BRIDGE_ADDRESS) { + logger.error("Missing environment variable TERRA_TOKEN_BRIDGE_ADDRESS"); + return [false, undefined]; + } + } + return [ true, { spy_host: process.env.SPY_SERVICE_HOST, spy_filters: process.env.SPY_SERVICE_FILTERS, + 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, eth_token_bridge_address: process.env.ETH_TOKEN_BRIDGE_ADDRESS, + + polygon_provider_url: process.env.POLYGON_PROVIDER, + polygon_contract_address: process.env.POLYGON_CONTRACT_ADDRESS, polygon_token_bridge_address: process.env.POLYGON_TOKEN_BRIDGE_ADDRESS, + + evm_wallet_private_key: process.env.WALLET_PRIVATE_KEY, + + terraEnabled: terraEnabled, + terra_provider_url: process.env.TERRA_PROVIDER, + terra_chain_id: process.env.TERRA_CHAIN_ID, + terra_name: process.env.TERRA_NAME, + terra_contract_address: process.env.TERRA_CONTRACT_ADDRESS, + terra_token_bridge_address: process.env.TERRA_TOKEN_BRIDGE_ADDRESS, + terra_wallet_private_key: process.env.TERRA_WALLET_PRIVATE_KEY, + terra_gas_price_url: process.env.TERRA_GAS_PRICE_URL, }, ]; } @@ -245,9 +324,14 @@ async function processVaa(vaaBytes: string) { seqMap.set(seqNumKey, parsedVAA.sequence); - let payload_type: number = parsedVAA.payload[0]; + let t3Payload: Type3Payload = null; + try { + t3Payload = decodeSignedVAAPayloadType3(parsedVAA); + } catch (e) { + logger.error("failed to parse type 3 vaa: %o", e); + return; + } - let t3Payload = decodeSignedVAAPayloadType3(parsedVAA); if (t3Payload) { if (isOurContract(t3Payload.contractAddress)) { logger.info( @@ -297,31 +381,71 @@ async function processVaa(vaaBytes: string) { "], seqNum: " + parsedVAA.sequence + " payloadType: " + - payload_type + parsedVAA.payload[0] ); } } function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload { const payload = Buffer.from(new Uint8Array(parsedVAA.payload)); - const version = payload.readUInt8(0); + if (payload[0] !== 3) return undefined; - if (version !== 3) { + logger.info("decodeSignedVAAPayloadType3: length: " + payload.length); + if (payload.length < 101) { + logger.error( + "decodeSignedVAAPayloadType3: dropping type 3 vaa because the payload is too short to determine the target chain id, length: " + + payload.length + ); return undefined; } + + const targetChainId = payload.readUInt16BE(99); + logger.info("decodeSignedVAAPayloadType3: target ChainId: " + targetChainId); + + let contractAddress: string = ""; + let swapFunctionType: number = 0; + let swapCurrencyType: number = 0; + + if (targetChainId === 3) { + logger.info( + "decodeSignedVAAPayloadType3: terraContractAddr: [" + + payload.slice(67, 67 + 32).toString("hex") + + "]" + ); + logger.info( + "decodeSignedVAAPayloadType3: terraContractAddr: [" + + payload.slice(67, 67 + 32).toString() + + "]" + ); + + contractAddress = payload.slice(67, 67 + 32).toString("hex"); + } else { + if (payload.length < 262) { + logger.error( + "decodeSignedVAAPayloadType3: dropping type 3 vaa because the payload is too short to extract the contract fields, length: " + + payload.length + + ", target chain id: " + + targetChainId + ); + return undefined; + } + + contractAddress = payload.slice(79, 79 + 20).toString("hex"); + swapFunctionType = payload.readUInt8(260); + swapCurrencyType = payload.readUInt8(261); + } + return { + targetChainId: targetChainId, 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), + swapFunctionType: swapFunctionType, + swapCurrencyType: swapCurrencyType, }; } function isOurContract(contractAddress: string): boolean { - return ( - contractAddress === ethContractData.contractAddress || - contractAddress === polygonContractData.contractAddress - ); + return isEvmContract(contractAddress) || isTerraContract(contractAddress); } async function postVaa( @@ -408,379 +532,15 @@ async function callBack(err: any, result: any) { // logger.debug("leaving callback."); } -// 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; - } - - contractAddress = contractAddress.toLowerCase(); - if (contractAddress.search("0x") == 0) { - contractAddress = contractAddress.substring(2); - } - - if (overridden) { - logger.info( - "Connecting to Ethereum: overriding contract address: [" + - contractAddress + - "], node: [" + - env.eth_provider_url + - "], token bridge address: [" + - env.eth_token_bridge_address + - "]" - ); - } else { - logger.info( - "Connecting to Ethereum: contract address: [" + - contractAddress + - "], node: [" + - env.eth_provider_url + - "], token bridge address: [" + - env.eth_token_bridge_address + - "]" - ); - } - - 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 { - name: "Ethereum", - contractAddress: contractAddress, - tokenBridgeAddress: env.eth_token_bridge_address, - 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( - "Connecting to Polygon: overriding contract address: [" + - contractAddress + - "], node: [" + - env.polygon_provider_url + - "], token bridge address: [" + - env.polygon_token_bridge_address + - "]" - ); - } else { - logger.info( - "Connecting to Polygon: contract address: [" + - contractAddress + - "], node: [" + - env.polygon_provider_url + - "], token bridge address: [" + - env.polygon_token_bridge_address + - "]" - ); - } - - const provider = new ethers.providers.StaticJsonRpcProvider( - env.polygon_provider_url - ); - - const contract = new ethers.Contract( - contractAddress, - SWAP_CONTRACT_V2_ABI, - provider - ); - - const wallet = new ethers.Wallet(env.wallet_private_key, provider); - const contractWithSigner = contract.connect(wallet); - - return { - name: "Polygon", - contractAddress: contractAddress, - tokenBridgeAddress: env.polygon_token_bridge_address, - 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( - "relayVaa: unsupported swapFunctionType: [" + - t3Payload.swapFunctionType + - "]" - ); - } - - let native: boolean = false; - if (t3Payload.swapCurrencyType === 1) { - native = true; - } else if (t3Payload.swapCurrencyType !== 2) { - logger.error( - "relayVaa: unsupported swapCurrencyType: [" + - t3Payload.swapCurrencyType + - "]" - ); - } - - logger.debug( - "relayVaa: contractAddress: [" + - t3Payload.contractAddress + - "], ethContract: [" + - ethContractData.contractAddress + - "], polygonContract[" + - polygonContractData.contractAddress + - "]" - ); - - if (t3Payload.contractAddress === ethContractData.contractAddress) { - await relayVaaToChain( - t3Payload, - ethContractData, - signedVaaArray, - exactIn, - native - ); - } else if ( - t3Payload.contractAddress === polygonContractData.contractAddress - ) { - await relayVaaToChain( - t3Payload, - polygonContractData, - signedVaaArray, - exactIn, - native - ); - } else { - logger.error( - "relayVaa: unexpected contract: [" + - t3Payload.contractAddress + - "], this should not happen!" - ); - } -} - -async function relayVaaToChain( - t3Payload: Type3Payload, - tcd: TargetContractData, - signedVaaArray: Uint8Array, - exactIn: boolean, - native: boolean -) { - logger.debug( - "relayVaaTo" + - tcd.name + - ": checking if already redeemed on " + - tcd.name + - " using tokenBridgeAddress [" + - tcd.tokenBridgeAddress + - "]" - ); - - if (await isRedeemed(t3Payload, tcd, signedVaaArray)) { - logger.info( - "relayVaaTo" + - tcd.name + - ": contract: [" + - t3Payload.contractAddress + - "], exactIn: " + - exactIn + - ", native: " + - native + - ": already transferred" - ); - + if (t3Payload.targetChainId === 3) { + relayVaaToTerra(t3Payload, signedVaaArray); return; } - logger.info( - "relayVaaTo" + - tcd.name + - ": contract: [" + - t3Payload.contractAddress + - "], exactIn: " + - exactIn + - ", native: " + - native + - ": submitting redeem request" - ); - - try { - let receipt: any = null; - if (exactIn) { - if (native) { - receipt = await swap.swapExactInFromVaaNative( - tcd.contractWithSigner, - signedVaaArray - ); - } else { - receipt = await swap.swapExactInFromVaaToken( - tcd.contractWithSigner, - signedVaaArray - ); - } - } else { - if (native) { - receipt = await swap.swapExactOutFromVaaNative( - tcd.contractWithSigner, - signedVaaArray - ); - } else { - receipt = await swap.swapExactOutFromVaaToken( - tcd.contractWithSigner, - signedVaaArray - ); - } - } - - logger.info( - "relayVaaTo" + - tcd.name + - ": contract: [" + - t3Payload.contractAddress + - "], exactIn: " + - exactIn + - ", native: " + - native + - ": success, txHash: " + - receipt.transactionHash - ); - } catch (e: any) { - if (await isRedeemed(t3Payload, tcd, signedVaaArray)) { - logger.info( - "relayVaaTo" + - tcd.name + - ": contract: [" + - t3Payload.contractAddress + - "], exactIn: " + - exactIn + - ", native: " + - native + - ": relay failed because the vaa has already been redeemed" - ); - - return; - } - - logger.error( - "relayVaaTo" + - tcd.name + - ": contract: [" + - t3Payload.contractAddress + - "], exactIn: " + - exactIn + - ", native: " + - native + - ": transaction failed: %o", - e - ); - } - - if (await isRedeemed(t3Payload, tcd, signedVaaArray)) { - logger.info( - "relayVaaTo" + - tcd.name + - ": contract: [" + - t3Payload.contractAddress + - "], exactIn: " + - exactIn + - ", native: " + - native + - ": redeem succeeded" - ); - } else { - logger.error( - "relayVaaTo" + - tcd.name + - ": contract: [" + - t3Payload.contractAddress + - "], exactIn: " + - exactIn + - ", native: " + - native + - ": redeem failed!" - ); - } -} - -async function isRedeemed( - t3Payload: Type3Payload, - tcd: TargetContractData, - signedVaaArray: Uint8Array -): Promise { - 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; + relayVaaToEvm(signedVaaArray, t3Payload); } ///////////////////////////////// Start of logger stuff /////////////////////////////////////////// diff --git a/swap_relayer/src/terra.ts b/swap_relayer/src/terra.ts new file mode 100644 index 0000000..692c96b --- /dev/null +++ b/swap_relayer/src/terra.ts @@ -0,0 +1,179 @@ +import { + getIsTransferCompletedTerra, + redeemOnTerra, +} from "@certusone/wormhole-sdk"; + +import { + importCoreWasm, + setDefaultWasm, +} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; + +import * as Terra from "@terra-money/terra.js"; + +import { logger, OurEnvironment, Type3Payload } from "./index"; + +type TerraContractData = { + name: string; + contractAddress: string; + tokenBridgeAddress: string; + lcdConfig: Terra.LCDClientConfig; + lcdClient: Terra.LCDClient; + wallet: Terra.Wallet; + gasPriceUrl: string; +}; + +let terraContractData: TerraContractData = null; + +export function makeTerraContractData(env: OurEnvironment) { + let contractAddress: string = env.terra_contract_address.toLowerCase(); + if (contractAddress.search("0x") == 0) { + contractAddress = contractAddress.substring(2); + } + + logger.info( + "Connecting to Terra: contract address: [" + + contractAddress + + "], node: [" + + env.terra_provider_url + + "], token bridge address: [" + + env.terra_token_bridge_address + + "], terra chain id: [" + + env.terra_chain_id + + "], terra name: [" + + env.terra_name + + "]" + ); + + const lcdConfig = { + URL: env.terra_provider_url, + chainID: env.terra_chain_id, + name: env.terra_name, + }; + + const lcdClient = new Terra.LCDClient(lcdConfig); + + const mk = new Terra.MnemonicKey({ + mnemonic: env.terra_wallet_private_key, + }); + + const wallet = lcdClient.wallet(mk); + + terraContractData = { + name: "Terra", + contractAddress: contractAddress, + tokenBridgeAddress: env.terra_token_bridge_address, + lcdConfig: lcdConfig, + lcdClient: lcdClient, + wallet: wallet, + gasPriceUrl: env.terra_gas_price_url, + }; +} + +export function isTerraContract(contractAddress: string): boolean { + return ( + terraContractData && contractAddress === terraContractData.contractAddress + ); +} + +export async function relayVaaToTerra( + t3Payload: Type3Payload, + signedVaaArray: Uint8Array +) { + if (!terraContractData) return; + + logger.debug( + "relayVaaToTerra: checking if already redeemed using tokenBridgeAddress [" + + terraContractData.tokenBridgeAddress + + "]" + ); + + if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { + logger.info( + "relayVaaToTerra: contract: [" + + t3Payload.contractAddress + + "]: already transferred" + ); + + return; + } + + logger.info( + "relayVaaToTerra: contract: [" + + t3Payload.contractAddress + + "]: submitting redeem request" + ); + + try { + const msg = await redeemOnTerra( + terraContractData.contractAddress, + terraContractData.wallet.key.accAddress, + signedVaaArray + ); + + let receipt: any = null; + + logger.info( + "relayVaaToTerra: contract: [" + + t3Payload.contractAddress + + "]: success, txHash: " + + receipt.transactionHash + ); + } catch (e: any) { + if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { + logger.info( + "relayVaaToTerra: contract: [" + + t3Payload.contractAddress + + "]: relay failed because the vaa has already been redeemed" + ); + + return; + } + + logger.error( + "relayVaaToTerra: contract: [" + + t3Payload.contractAddress + + "]: transaction failed: %o", + e + ); + } + + if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { + logger.info( + "relayVaaToTerra: contract: [" + + t3Payload.contractAddress + + "]: redeem succeeded" + ); + } else { + logger.error( + "relayVaaToTerra: contract: [" + + t3Payload.contractAddress + + "]: redeem failed!" + ); + } +} + +async function isRedeemedOnTerra( + t3Payload: Type3Payload, + terraContractData: TerraContractData, + signedVaaArray: Uint8Array +): Promise { + let redeemed: boolean = false; + try { + redeemed = await await getIsTransferCompletedTerra( + terraContractData.tokenBridgeAddress, + signedVaaArray, + terraContractData.wallet.key.accAddress, + terraContractData.lcdClient, + terraContractData.gasPriceUrl + ); + } catch (e) { + logger.error( + "relayVaaTo" + + terraContractData.name + + ": failed to check if transfer is already complete, will attempt the transfer, e: %o", + e + ); + } + + return redeemed; +} From 0c50e93dc0a15d741a9493c73e2114d533fc7441 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Wed, 26 Jan 2022 17:06:26 +0000 Subject: [PATCH 02/38] Add terra ust for quoting --- react/src/route/cross-quote.ts | 414 ++++++++++++++------------ react/src/route/evm.ts | 2 +- react/src/route/generic.ts | 54 ++-- react/src/route/quickswap.ts | 12 +- react/src/route/terra-ust-transfer.ts | 67 +++++ react/src/route/uniswap-core.ts | 154 ++++++++-- react/src/route/uniswap-v2.ts | 69 +++-- react/src/route/uniswap-v3.ts | 80 ++--- react/src/swapper/swapper.ts | 189 ++++++++---- react/src/utils/consts.ts | 62 +++- react/src/utils/math.ts | 19 ++ 11 files changed, 750 insertions(+), 372 deletions(-) create mode 100644 react/src/route/terra-ust-transfer.ts create mode 100644 react/src/utils/math.ts diff --git a/react/src/route/cross-quote.ts b/react/src/route/cross-quote.ts index b9db9c6..3d8f2d4 100644 --- a/react/src/route/cross-quote.ts +++ b/react/src/route/cross-quote.ts @@ -1,41 +1,92 @@ import { ethers } from "ethers"; -import { UniEvmToken } from "./uniswap-core"; -import { QuickswapRouter } from "./quickswap"; -import { SingleAmmSwapRouter as UniswapV3Router } from "./uniswap-v3"; + +import { QuickswapRouter as MaticRouter } from "./quickswap"; +import { UniswapV3Router as EthRouter } from "./uniswap-v3"; +import { TerraUstTransfer as UstRouter } from "./terra-ust-transfer"; import { - ETH_NETWORK_CHAIN_ID, - POLYGON_NETWORK_CHAIN_ID, + WETH_TOKEN_INFO, + WMATIC_TOKEN_INFO, + UST_TOKEN_INFO, + WORMHOLE_CHAIN_ID_ETHEREUM, + WORMHOLE_CHAIN_ID_POLYGON, + WORMHOLE_CHAIN_ID_TERRA, } from "../utils/consts"; +import { addFixedAmounts, subtractFixedAmounts } from "../utils/math"; +import { UstLocation } from "./generic"; +import { + ExactInParameters, + ExactOutParameters, + makeExactInParameters, + makeExactOutParameters, +} from "./uniswap-core"; +import { ChainId } from "@certusone/wormhole-sdk"; export { PROTOCOL as PROTOCOL_UNISWAP_V2 } from "./uniswap-v2"; export { PROTOCOL as PROTOCOL_UNISWAP_V3 } from "./uniswap-v3"; +export { PROTOCOL as PROTOCOL_TERRA_UST_TRANSFER } from "./terra-ust-transfer"; + +export const TERRA_UST = UST_TOKEN_INFO.address; export enum QuoteType { ExactIn = 1, ExactOut, } -function makeRouter(provider: ethers.providers.Provider, id: number) { - switch (id) { - case ETH_NETWORK_CHAIN_ID: { - return new UniswapV3Router(provider); +export function makeEvmProviderFromAddress(tokenAddress: string) { + switch (tokenAddress) { + case WETH_TOKEN_INFO.address: { + const url = process.env.REACT_APP_GOERLI_PROVIDER; + if (!url) { + throw new Error("Could not find REACT_APP_GOERLI_PROVIDER"); + } + return new ethers.providers.StaticJsonRpcProvider(url); } - case POLYGON_NETWORK_CHAIN_ID: { - return new QuickswapRouter(provider); + case WMATIC_TOKEN_INFO.address: { + const url = process.env.REACT_APP_MUMBAI_PROVIDER; + if (!url) { + throw new Error("Could not find REACT_APP_MUMBAI_PROVIDER"); + } + return new ethers.providers.StaticJsonRpcProvider(url); } default: { - throw Error("unrecognized chain id"); + throw Error("unrecognized evm token address"); } } } -export function getUstAddress(id: number): string { - switch (id) { - case ETH_NETWORK_CHAIN_ID: { - return "0x36Ed51Afc79619b299b238898E72ce482600568a"; +export function getChainIdFromAddress(tokenAddress: string) { + switch (tokenAddress) { + case WETH_TOKEN_INFO.address: { + return WORMHOLE_CHAIN_ID_ETHEREUM; } - case POLYGON_NETWORK_CHAIN_ID: { - return "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c"; + case WMATIC_TOKEN_INFO.address: { + return WORMHOLE_CHAIN_ID_POLYGON; + } + case UST_TOKEN_INFO.address: { + return WORMHOLE_CHAIN_ID_TERRA; + } + default: { + throw Error("unrecognized evm token address"); + } + } +} + +async function makeRouter(tokenAddress: string, loc: UstLocation) { + switch (tokenAddress) { + case WETH_TOKEN_INFO.address: { + const provider = makeEvmProviderFromAddress(tokenAddress); + const router = new EthRouter(provider); + await router.initialize(loc); + return router; + } + case WMATIC_TOKEN_INFO.address: { + const provider = makeEvmProviderFromAddress(tokenAddress); + const router = new MaticRouter(provider); + await router.initialize(loc); + return router; + } + case UST_TOKEN_INFO.address: { + return new UstRouter(); } default: { throw Error("unrecognized chain id"); @@ -51,123 +102,105 @@ function splitSlippageInHalf(totalSlippage: string): string { .toString(); } -interface RelayerFee { - amount: ethers.BigNumber; +export interface RelayerFee { + amount: string; tokenAddress: string; } -export interface ExactInParameters { - protocol: string; - amountIn: ethers.BigNumber; - minAmountOut: ethers.BigNumber; - deadline: ethers.BigNumber; - poolFee: string; - path: [string, string]; -} - export interface ExactInCrossParameters { + amountIn: string; + minAmountOut: string; src: ExactInParameters; dst: ExactInParameters; relayerFee: RelayerFee; } -export interface ExactOutParameters { - protocol: string; - amountOut: ethers.BigNumber; - maxAmountIn: ethers.BigNumber; - deadline: ethers.BigNumber; - poolFee: string; - path: [string, string]; -} - export interface ExactOutCrossParameters { + amountOut: string; + maxAmountIn: string; src: ExactOutParameters; dst: ExactOutParameters; relayerFee: RelayerFee; } export class UniswapToUniswapQuoter { - // providers - srcProvider: ethers.providers.Provider; - dstProvider: ethers.providers.Provider; - - // networks - srcNetwork: ethers.providers.Network; - dstNetwork: ethers.providers.Network; + // tokens + tokenInAddress: string; + tokenOutAddress: string; // routers - srcRouter: UniswapV3Router | QuickswapRouter; - dstRouter: UniswapV3Router | QuickswapRouter; + srcRouter: UstRouter | EthRouter | MaticRouter; + dstRouter: UstRouter | EthRouter | MaticRouter; - // tokens - srcTokenIn: UniEvmToken; - srcTokenOut: UniEvmToken; - dstTokenIn: UniEvmToken; - dstTokenOut: UniEvmToken; + constructor() {} - constructor( - srcProvider: ethers.providers.Provider, - dstProvider: ethers.providers.Provider - ) { - this.srcProvider = srcProvider; - this.dstProvider = dstProvider; - } + async initialize( + tokenInAddress: string, + tokenOutAddress: string + ): Promise { + if (tokenInAddress !== this.tokenInAddress) { + this.tokenInAddress = tokenInAddress; + this.srcRouter = await makeRouter(tokenInAddress, UstLocation.Out); + } - async initialize(): Promise { - [this.srcNetwork, this.dstNetwork] = await Promise.all([ - this.srcProvider.getNetwork(), - this.dstProvider.getNetwork(), - ]); - - this.srcRouter = makeRouter(this.srcProvider, this.srcNetwork.chainId); - this.dstRouter = makeRouter(this.dstProvider, this.dstNetwork.chainId); + if (tokenOutAddress != this.tokenOutAddress) { + this.tokenOutAddress = tokenOutAddress; + this.dstRouter = await makeRouter(tokenOutAddress, UstLocation.In); + } return; } - sameChain(): boolean { - return this.srcNetwork.chainId === this.dstNetwork.chainId; - } - - async makeSrcTokens( - tokenInAddress: string - ): Promise<[UniEvmToken, UniEvmToken]> { - const ustOutAddress = getUstAddress(this.srcNetwork.chainId); - - const router = this.srcRouter; - - [this.srcTokenIn, this.srcTokenOut] = await Promise.all([ - router.makeToken(tokenInAddress), - router.makeToken(ustOutAddress), - ]); - return [this.srcTokenIn, this.srcTokenOut]; - } - - async makeDstTokens( - tokenOutAddress: string - ): Promise<[UniEvmToken, UniEvmToken]> { - const ustInAddress = getUstAddress(this.dstNetwork.chainId); - - const router = this.dstRouter; - - [this.dstTokenIn, this.dstTokenOut] = await Promise.all([ - router.makeToken(ustInAddress), - router.makeToken(tokenOutAddress), - ]); - return [this.dstTokenIn, this.dstTokenOut]; - } - async computeAndVerifySrcPoolAddress(): Promise { - return this.srcRouter.computeAndVerifyPoolAddress( - this.srcTokenIn, - this.srcTokenOut - ); + return this.srcRouter.computeAndVerifyPoolAddress(); } async computeAndVerifyDstPoolAddress(): Promise { - return this.dstRouter.computeAndVerifyPoolAddress( - this.dstTokenIn, - this.dstTokenOut - ); + return this.dstRouter.computeAndVerifyPoolAddress(); + } + + computeSwapSlippage(slippage): string { + if (this.isSrcUst() || this.isDstUst()) { + return slippage; + } + + return splitSlippageInHalf(slippage); + } + + getRelayerFee(amount: string): RelayerFee { + if (this.isSrcUst()) { + return { + amount: this.srcRouter.computeUnitAmountOut(amount), + tokenAddress: TERRA_UST, // TODO: make sure this is the right address for bridge transfer? + }; + } + + const relayerFee: RelayerFee = { + amount: this.srcRouter.computeUnitAmountOut(amount), + tokenAddress: this.srcRouter.getTokenOutAddress(), + }; + return relayerFee; + } + + makeSrcExactInParameters( + amountIn: string, + minAmountOut: string + ): ExactInParameters { + if (this.isSrcUst()) { + return undefined; + } + // @ts-ignore + return makeExactInParameters(this.srcRouter, amountIn, minAmountOut); + } + + makeDstExactInParameters( + amountIn: string, + minAmountOut: string + ): ExactInParameters { + if (this.isDstUst()) { + return undefined; + } + // @ts-ignore + return makeExactInParameters(this.dstRouter, amountIn, minAmountOut); } async computeExactInParameters( @@ -175,71 +208,68 @@ export class UniswapToUniswapQuoter { slippage: string, relayerFeeUst: string ): Promise { - const singleSlippage = splitSlippageInHalf(slippage); + const singleSlippage = this.computeSwapSlippage(slippage); // src quote const srcRouter = this.srcRouter; - const srcTokenIn = this.srcTokenIn; - const srcTokenOut = this.srcTokenOut; - const srcMinAmountOut = await srcRouter.fetchQuoteAmountOut( - srcTokenIn, - srcTokenOut, + const srcMinAmountOut = await srcRouter.fetchExactInQuote( amountIn, singleSlippage ); // dst quote const dstRouter = this.dstRouter; - const dstAmountIn = this.srcTokenOut.formatAmount(srcMinAmountOut); + const dstAmountIn = srcMinAmountOut; //srcRouter.formatAmountOut(srcMinAmountOut); if (Number(dstAmountIn) < Number(relayerFeeUst)) { throw Error( `srcAmountOut <= relayerFeeUst. ${dstAmountIn} vs ${relayerFeeUst}` ); } - const dstTokenIn = this.dstTokenIn; - const dstTokenOut = this.dstTokenOut; - const dstAmountInAfterFee = dstTokenIn.subtractAmounts( + const dstAmountInAfterFee = subtractFixedAmounts( dstAmountIn, - relayerFeeUst + relayerFeeUst, + dstRouter.getTokenInDecimals() ); - const dstMinAmountOut = await dstRouter.fetchQuoteAmountOut( - dstTokenIn, - dstTokenOut, + const dstMinAmountOut = await dstRouter.fetchExactInQuote( dstAmountInAfterFee, singleSlippage ); - const srcParameters: ExactInParameters = { - protocol: srcRouter.getProtocol(), - amountIn: srcTokenIn.computeUnitAmount(amountIn), - minAmountOut: srcMinAmountOut, - poolFee: srcRouter.getPoolFee(), - deadline: srcRouter.getTradeDeadline(), - path: [srcTokenIn.getAddress(), srcTokenOut.getAddress()], - }; - - const dstParameters: ExactInParameters = { - protocol: dstRouter.getProtocol(), - amountIn: dstTokenIn.computeUnitAmount(dstAmountInAfterFee), - minAmountOut: dstMinAmountOut, - poolFee: dstRouter.getPoolFee(), - deadline: dstRouter.getTradeDeadline(), - path: [dstTokenIn.getAddress(), dstTokenOut.getAddress()], - }; - + // organize parameters const params: ExactInCrossParameters = { - src: srcParameters, - dst: dstParameters, - relayerFee: { - amount: dstTokenIn.computeUnitAmount(relayerFeeUst), - tokenAddress: this.dstTokenIn.getAddress(), - }, + amountIn: amountIn, + minAmountOut: dstMinAmountOut, + src: this.makeSrcExactInParameters(amountIn, srcMinAmountOut), + dst: this.makeDstExactInParameters(dstAmountInAfterFee, dstMinAmountOut), + relayerFee: this.getRelayerFee(relayerFeeUst), }; return params; } + makeSrcExactOutParameters( + amountOut: string, + maxAmountIn: string + ): ExactOutParameters { + if (this.isSrcUst()) { + return null; + } + // @ts-ignore + return makeExactOutParameters(this.srcRouter, amountOut, maxAmountIn); + } + + makeDstExactOutParameters( + amountOut: string, + maxAmountIn: string + ): ExactOutParameters { + if (this.isDstUst()) { + return null; + } + // @ts-ignore + return makeExactOutParameters(this.dstRouter, amountOut, maxAmountIn); + } + async computeExactOutParameters( amountOut: string, slippage: string, @@ -249,69 +279,85 @@ export class UniswapToUniswapQuoter { // dst quote first const dstRouter = this.dstRouter; - const dstTokenIn = this.dstTokenIn; - const dstTokenOut = this.dstTokenOut; - const dstMaxAmountIn = await dstRouter.fetchQuoteAmountIn( - dstTokenIn, - dstTokenOut, + const dstMaxAmountIn = await dstRouter.fetchExactOutQuote( amountOut, singleSlippage ); // src quote const srcRouter = this.srcRouter; - const srcAmountOut = this.dstTokenIn.formatAmount(dstMaxAmountIn); + const srcAmountOut = dstMaxAmountIn; if (Number(srcAmountOut) < Number(relayerFeeUst)) { throw Error( `dstAmountIn <= relayerFeeUst. ${srcAmountOut} vs ${relayerFeeUst}` ); } - const srcTokenIn = this.srcTokenIn; - const srcTokenOut = this.srcTokenOut; - const srcAmountOutBeforeFee = srcTokenOut.addAmounts( + const srcAmountOutBeforeFee = addFixedAmounts( srcAmountOut, - relayerFeeUst + relayerFeeUst, + srcRouter.getTokenOutDecimals() ); - const srcMaxAmountIn = await srcRouter.fetchQuoteAmountIn( - srcTokenIn, - srcTokenOut, + const srcMaxAmountIn = await srcRouter.fetchExactOutQuote( srcAmountOutBeforeFee, singleSlippage ); - const srcParameters: ExactOutParameters = { - protocol: srcRouter.getProtocol(), - amountOut: srcTokenOut.computeUnitAmount(srcAmountOutBeforeFee), - maxAmountIn: srcMaxAmountIn, - poolFee: srcRouter.getPoolFee(), - deadline: srcRouter.getTradeDeadline(), - path: [srcTokenIn.getAddress(), srcTokenOut.getAddress()], - }; - - const dstParameters: ExactOutParameters = { - protocol: dstRouter.getProtocol(), - amountOut: dstTokenOut.computeUnitAmount(amountOut), - maxAmountIn: dstMaxAmountIn, - poolFee: dstRouter.getPoolFee(), - deadline: dstRouter.getTradeDeadline(), - path: [dstTokenIn.getAddress(), dstTokenOut.getAddress()], - }; - + // organize parameters const params: ExactOutCrossParameters = { - src: srcParameters, - dst: dstParameters, - relayerFee: { - amount: dstTokenIn.computeUnitAmount(relayerFeeUst), - tokenAddress: this.dstTokenIn.getAddress(), - }, + amountOut: amountOut, + maxAmountIn: srcMaxAmountIn, + src: this.makeSrcExactOutParameters( + srcAmountOutBeforeFee, + srcMaxAmountIn + ), + dst: this.makeDstExactOutParameters(amountOut, dstMaxAmountIn), + relayerFee: this.getRelayerFee(relayerFeeUst), }; return params; } setDeadlines(deadline: string): void { - this.srcRouter.setDeadline(deadline); - this.dstRouter.setDeadline(deadline); + if (!this.isSrcUst()) { + // @ts-ignore + this.srcRouter.setDeadline(deadline); + } + if (!this.isDstUst()) { + // @ts-ignore + this.dstRouter.setDeadline(deadline); + } + } + + isSrcUst(): boolean { + return this.tokenInAddress === TERRA_UST; + } + + isDstUst(): boolean { + return this.tokenOutAddress === TERRA_UST; + } + + getSrcEvmProvider(): ethers.providers.Provider { + if (this.isSrcUst()) { + return undefined; + } + // @ts-ignore + return this.srcRouter.getProvider(); + } + + getDstEvmProvider(): ethers.providers.Provider { + if (this.isDstUst()) { + return undefined; + } + // @ts-ignore + return this.dstRouter.getProvider(); + } + + getSrcChainId(): ChainId { + return getChainIdFromAddress(this.tokenInAddress); + } + + getDstChainId(): ChainId { + return getChainIdFromAddress(this.tokenOutAddress); } } diff --git a/react/src/route/evm.ts b/react/src/route/evm.ts index 53ee755..2da4d30 100644 --- a/react/src/route/evm.ts +++ b/react/src/route/evm.ts @@ -3,7 +3,7 @@ import { ethers } from "ethers"; import { GenericToken } from "./generic"; // erc20 spec -import { abi as Erc20Abi } from "../abi/erc20.json"; +import { abi as Erc20Abi } from "../../abi/erc20.json"; import { TransactionReceipt, TransactionRequest, diff --git a/react/src/route/generic.ts b/react/src/route/generic.ts index 151edf5..7d2392d 100644 --- a/react/src/route/generic.ts +++ b/react/src/route/generic.ts @@ -1,7 +1,40 @@ -export abstract class DexRouter { - abstract makeToken(tokenAddress: string): any; - abstract quoteLot(tokenA: any, tokenB: any, amount: string): Promise; - abstract setSlippage(slippage: string): void; +import { FixedNumber } from "ethers"; + +export enum UstLocation { + In = 1, + Out, +} + +export abstract class RouterCore { + abstract computeAndVerifyPoolAddress(): Promise; + + abstract computePoolAddress(): string; + + //abstract computeUnitAmountIn(amount: string): string; + + abstract computeUnitAmountOut(amount: string): string; + + abstract fetchExactInQuote( + amountOut: string, + slippage: string + ): Promise; + + abstract fetchExactOutQuote( + amountOut: string, + slippage: string + ): Promise; + + abstract formatAmountIn(amount: string): string; + + abstract formatAmountOut(amount: string): string; + + abstract getProtocol(): string; + + abstract getTokenInDecimals(): number; + + abstract getTokenOutDecimals(): number; + + abstract getTokenOutAddress(): string; } export abstract class GenericToken { @@ -9,16 +42,3 @@ export abstract class GenericToken { abstract getDecimals(): number; } - -// TODO: wrap SwapRoute and other routes -export class GenericRoute { - route: any; - - constructor(route: any) { - this.route = route; - } - - getRoute(): any { - return this.route; - } -} diff --git a/react/src/route/quickswap.ts b/react/src/route/quickswap.ts index a36c4c1..ff92e9a 100644 --- a/react/src/route/quickswap.ts +++ b/react/src/route/quickswap.ts @@ -1,12 +1,18 @@ import { ethers } from "ethers"; -import { QUICKSWAP_FACTORY_ADDRESS } from "../utils/consts"; -import { SingleAmmSwapRouter } from "./uniswap-v2"; +import { QUICKSWAP_FACTORY_ADDRESS, WMATIC_TOKEN_INFO } from "../utils/consts"; +import { UstLocation } from "./generic"; +import { UniswapV2Router } from "./uniswap-v2"; export { PROTOCOL } from "./uniswap-v2"; -export class QuickswapRouter extends SingleAmmSwapRouter { +export class QuickswapRouter extends UniswapV2Router { constructor(provider: ethers.providers.Provider) { super(provider); super.setFactoryAddress(QUICKSWAP_FACTORY_ADDRESS); } + + async initialize(ustLocation: UstLocation): Promise { + await super.initializeTokens(WMATIC_TOKEN_INFO, ustLocation); + return; + } } diff --git a/react/src/route/terra-ust-transfer.ts b/react/src/route/terra-ust-transfer.ts new file mode 100644 index 0000000..687d2df --- /dev/null +++ b/react/src/route/terra-ust-transfer.ts @@ -0,0 +1,67 @@ +import { Dec, Int } from "@terra-money/terra.js"; + +import { UST_TOKEN_INFO } from "../utils/consts"; +import { RouterCore } from "./generic"; + +export const PROTOCOL = "TerraUstTransfer"; + +const UST_DECIMALS = 6; + +const UST_AMOUNT_MULTIPLIER = "1000000"; + +export class TerraUstTransfer extends RouterCore { + computePoolAddress(): string { + return UST_TOKEN_INFO.address; + } + + computeAndVerifyPoolAddress(): Promise { + return new Promise((resolve) => { + return resolve(this.computePoolAddress()); + }); + } + + formatAmountIn(amount: string): string { + const formatted = new Dec(amount).div(UST_AMOUNT_MULTIPLIER); + return formatted.toString(); + } + + formatAmountOut(amount: string): string { + return this.formatAmountIn(amount); + } + + computeUnitAmountIn(amount: string): string { + const unitified = new Dec(amount).mul(UST_AMOUNT_MULTIPLIER); + return new Int(unitified.toString()).toString(); + } + + computeUnitAmountOut(amount: string): string { + return this.computeUnitAmountIn(amount); + } + + getProtocol(): string { + return PROTOCOL; + } + + async fetchExactInQuote(amountIn: string, slippage: string): Promise { + return amountIn; + } + + async fetchExactOutQuote( + amountOut: string, + slippage: string + ): Promise { + return amountOut; + } + + getTokenInDecimals(): number { + return UST_DECIMALS; + } + + getTokenOutDecimals(): number { + return UST_DECIMALS; + } + + getTokenOutAddress(): string { + return this.computePoolAddress(); + } +} diff --git a/react/src/route/uniswap-core.ts b/react/src/route/uniswap-core.ts index 54e63a7..511686c 100644 --- a/react/src/route/uniswap-core.ts +++ b/react/src/route/uniswap-core.ts @@ -2,6 +2,8 @@ import { ethers } from "ethers"; import { CurrencyAmount, Token } from "@uniswap/sdk-core"; import { EvmToken } from "./evm"; +import { RouterCore, UstLocation } from "./generic"; +import { TokenInfo } from "../utils/consts"; export function computeTradeDeadline(deadline: string): ethers.BigNumber { return ethers.BigNumber.from(Math.floor(Date.now() / 1000)).add(deadline); @@ -78,46 +80,112 @@ export async function makeUniEvmToken( return new UniEvmToken(chainId, erc20); } -export abstract class UniswapRouterCore { +function stringToBigNumber(value: string): ethers.BigNumber { + return ethers.BigNumber.from(value); +} + +export interface ExactInParameters { + protocol: string; + amountIn: ethers.BigNumber; + minAmountOut: ethers.BigNumber; + deadline: ethers.BigNumber; + poolFee: string; + path: [string, string]; +} + +export interface ExactOutParameters { + protocol: string; + amountOut: ethers.BigNumber; + maxAmountIn: ethers.BigNumber; + deadline: ethers.BigNumber; + poolFee: string; + path: [string, string]; +} + +export function makeExactInParameters( + router: UniswapRouterCore, + amountIn: string, + minAmountOut: string +): ExactInParameters { + const params: ExactInParameters = { + protocol: router.getProtocol(), + amountIn: router.tokenIn.computeUnitAmount(amountIn), + minAmountOut: router.tokenOut.computeUnitAmount(minAmountOut), + poolFee: router.getPoolFee(), + deadline: router.getTradeDeadline(), + path: [router.tokenIn.getAddress(), router.tokenOut.getAddress()], + }; + return params; +} + +export function makeExactOutParameters( + router: UniswapRouterCore, + amountOut: string, + maxAmountIn: string +): ExactOutParameters { + const params: ExactOutParameters = { + protocol: router.getProtocol(), + amountOut: router.tokenOut.computeUnitAmount(amountOut), + maxAmountIn: router.tokenIn.computeUnitAmount(maxAmountIn), + poolFee: router.getPoolFee(), + deadline: router.getTradeDeadline(), + path: [router.tokenIn.getAddress(), router.tokenOut.getAddress()], + }; + return params; +} + +export abstract class UniswapRouterCore extends RouterCore { provider: ethers.providers.Provider; + network: ethers.providers.Network; + + // wormhole + chainId: number; + + // tokens + tokenIn: UniEvmToken; + tokenOut: UniEvmToken; // params deadline: string = ""; constructor(provider: ethers.providers.Provider) { + super(); this.provider = provider; } - public async makeToken(tokenAddress: string): Promise { - const network = await this.provider.getNetwork(); - return makeUniEvmToken(this.provider, network.chainId, tokenAddress); + public getProvider(): ethers.providers.Provider { + return this.provider; } - abstract computePoolAddress( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken - ): string; + public async initializeTokens( + tokenInfo: TokenInfo, + ustLocation: UstLocation + ): Promise { + this.network = await this.provider.getNetwork(); - abstract computeAndVerifyPoolAddress( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken - ): Promise; + const network = this.network; - abstract fetchQuoteAmountOut( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken, - amountOut: string, - slippage: string - ): Promise; - - abstract fetchQuoteAmountIn( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken, - amountOut: string, - slippage: string - ): Promise; - - abstract getProtocol(): string; + if (ustLocation == UstLocation.Out) { + [this.tokenIn, this.tokenOut] = await Promise.all([ + makeUniEvmToken(this.provider, network.chainId, tokenInfo.address), + makeUniEvmToken( + this.provider, + network.chainId, + tokenInfo.ustPairedAddress + ), + ]); + } else { + [this.tokenIn, this.tokenOut] = await Promise.all([ + makeUniEvmToken( + this.provider, + network.chainId, + tokenInfo.ustPairedAddress + ), + makeUniEvmToken(this.provider, network.chainId, tokenInfo.address), + ]); + } + return; + } public getPoolFee(): string { return ""; @@ -130,4 +198,36 @@ export abstract class UniswapRouterCore { public getTradeDeadline(): ethers.BigNumber { return computeTradeDeadline(this.deadline); } + + /* + public computeUnitAmountIn(amount: string): string { + return this.tokenIn.computeUnitAmount(amount).toString(); + } + */ + + public computeUnitAmountOut(amount: string): string { + return this.tokenOut.computeUnitAmount(amount).toString(); + } + + public formatAmountIn(amount: string): string { + return this.tokenIn.formatAmount(stringToBigNumber(amount)); + } + + public formatAmountOut(amount: string): string { + return this.tokenOut.formatAmount(stringToBigNumber(amount)); + } + + public getTokenInDecimals(): number { + return this.tokenIn.getDecimals(); + } + + public getTokenOutDecimals(): number { + return this.tokenOut.getDecimals(); + } + + public getTokenOutAddress(): string { + return this.tokenOut.getAddress(); + } + + abstract getProtocol(): string; } diff --git a/react/src/route/uniswap-v2.ts b/react/src/route/uniswap-v2.ts index 77d3d90..19bbb8a 100644 --- a/react/src/route/uniswap-v2.ts +++ b/react/src/route/uniswap-v2.ts @@ -3,11 +3,19 @@ import { CurrencyAmount, TradeType } from "@uniswap/sdk-core"; import { abi as IUniswapV2PairABI } from "@uniswap/v2-core/build/UniswapV2Pair.json"; import { computePairAddress, Pair, Route, Trade } from "@uniswap/v2-sdk"; -import { UniEvmToken, UniswapRouterCore } from "./uniswap-core"; +import { UniswapRouterCore } from "./uniswap-core"; export const PROTOCOL = "UniswapV2"; -export class SingleAmmSwapRouter extends UniswapRouterCore { +// uniswap v3 (ethereum) +//export const UNISWAP_V3_FACTORY_ADDRESS = '0x1F98431c8aD98523631AE4a59f267346ea31F984'; +//export const UNISWAP_V3_ROUTER_ADDRESS = '0xE592427A0AEce92De3Edee1F18E0157C05861564'; + +// quickswap (polygon) +export const QUICKSWAP_V2_ROUTER_ADDRESS = + "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff"; + +export class UniswapV2Router extends UniswapRouterCore { factoryAddress: string; pairContract: ethers.Contract; pair: Pair; @@ -17,23 +25,20 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { return; } - computePoolAddress(tokenIn: UniEvmToken, tokenOut: UniEvmToken): string { + computePoolAddress(): string { if (this.factoryAddress === undefined) { throw Error("factoryAddress is undefined. use setFactoryAddress"); } return computePairAddress({ factoryAddress: this.factoryAddress, - tokenA: tokenIn.getUniToken(), - tokenB: tokenOut.getUniToken(), + tokenA: this.tokenIn.getUniToken(), + tokenB: this.tokenOut.getUniToken(), }); } - async computeAndVerifyPoolAddress( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken - ): Promise { - const pairAddress = this.computePoolAddress(tokenIn, tokenOut); + async computeAndVerifyPoolAddress(): Promise { + const pairAddress = this.computePoolAddress(); // verify by attempting to call factory() const poolContract = new ethers.Contract( @@ -46,8 +51,8 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { return pairAddress; } - async createPool(tokenIn: UniEvmToken, tokenOut: UniEvmToken): Promise { - const pairAddress = this.computePoolAddress(tokenIn, tokenOut); + async createPool(): Promise { + const pairAddress = this.computePoolAddress(); const pairContract = new ethers.Contract( pairAddress, @@ -63,6 +68,9 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { const reserve0 = reserves._reserve0.toString(); const reserve1 = reserves._reserve1.toString(); + const tokenIn = this.tokenIn; + const tokenOut = this.tokenOut; + if (token0.toLowerCase() === tokenIn.getAddress().toLowerCase()) { return new Pair( CurrencyAmount.fromRawAmount(tokenIn.getUniToken(), reserve0), @@ -76,15 +84,13 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { ); } - async fetchQuoteAmountOut( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken, - amountIn: string, - slippage: string - ): Promise { + async fetchExactInQuote(amountIn: string, slippage: string): Promise { // create pool - const pair = await this.createPool(tokenIn, tokenOut); + const pair = await this.createPool(); + // let's get that quote + const tokenIn = this.tokenIn; + const tokenOut = this.tokenOut; const route = new Route( [pair], @@ -108,18 +114,24 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { .mulUnsafe(slippageMultiplier) .round(decimals); - return tokenOut.computeUnitAmount(minAmountOutWithSlippage.toString()); + /* + return tokenOut + .computeUnitAmount(minAmountOutWithSlippage.toString()) + .toString(); + */ + return minAmountOutWithSlippage.toString(); } - async fetchQuoteAmountIn( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken, + async fetchExactOutQuote( amountOut: string, slippage: string - ): Promise { + ): Promise { // create pool - const pair = await this.createPool(tokenIn, tokenOut); + const pair = await this.createPool(); + // let's get that quote + const tokenIn = this.tokenIn; + const tokenOut = this.tokenOut; const route = new Route( [pair], @@ -142,7 +154,12 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { .divUnsafe(slippageDivisor) .round(decimals); - return tokenIn.computeUnitAmount(maxAmountInWithSlippage.toString()); + /* + return tokenIn + .computeUnitAmount(maxAmountInWithSlippage.toString()) + .toString(); + */ + return maxAmountInWithSlippage.toString(); } getProtocol(): string { diff --git a/react/src/route/uniswap-v3.ts b/react/src/route/uniswap-v3.ts index 8855300..d5e118a 100644 --- a/react/src/route/uniswap-v3.ts +++ b/react/src/route/uniswap-v3.ts @@ -14,11 +14,12 @@ import { } from "@uniswap/v3-sdk"; import { UniEvmToken, UniswapRouterCore } from "./uniswap-core"; -import { UNISWAP_V3_FACTORY_ADDRESS } from "../utils/consts"; +import { WETH_TOKEN_INFO, UNISWAP_V3_FACTORY_ADDRESS } from "../utils/consts"; +import { UstLocation } from "./generic"; export const PROTOCOL = "UniswapV3"; -export class SingleAmmSwapRouter extends UniswapRouterCore { +export class UniswapV3Router extends UniswapRouterCore { poolContract: ethers.Contract; pool: Pool; poolFee: FeeAmount; @@ -30,24 +31,26 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { this.poolFee = FeeAmount.MEDIUM; } + async initialize(ustLocation: UstLocation): Promise { + await this.initializeTokens(WETH_TOKEN_INFO, ustLocation); + return; + } + getPoolFee(): string { return this.poolFee.toString(); } - computePoolAddress(tokenIn: UniEvmToken, tokenOut: UniEvmToken): string { + computePoolAddress(): string { return computePoolAddress({ factoryAddress: UNISWAP_V3_FACTORY_ADDRESS, fee: this.poolFee, - tokenA: tokenIn.getUniToken(), - tokenB: tokenOut.getUniToken(), + tokenA: this.tokenIn.getUniToken(), + tokenB: this.tokenOut.getUniToken(), }); } - async computeAndVerifyPoolAddress( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken - ): Promise { - const pairAddress = this.computePoolAddress(tokenIn, tokenOut); + async computeAndVerifyPoolAddress(): Promise { + const pairAddress = this.computePoolAddress(); // verify by attempting to call factory() const poolContract = new ethers.Contract( @@ -60,8 +63,8 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { return pairAddress; } - async createPool(tokenIn: UniEvmToken, tokenOut: UniEvmToken): Promise { - const poolAddress = this.computePoolAddress(tokenIn, tokenOut); + async createPool(): Promise { + const poolAddress = this.computePoolAddress(); const poolContract = new ethers.Contract( poolAddress, @@ -103,8 +106,8 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { ]; return new Pool( - tokenIn.getUniToken(), - tokenOut.getUniToken(), + this.tokenIn.getUniToken(), + this.tokenOut.getUniToken(), this.poolFee, sqrtPriceX96.toString(), //note the description discrepancy - sqrtPriceX96 and sqrtRatioX96 are interchangable values liquidity, @@ -114,13 +117,15 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { } async computeTradeExactIn( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken, amount: string ): Promise> { // create pool - const pool = await this.createPool(tokenIn, tokenOut); + const pool = await this.createPool(); + // let's get that quote + const tokenIn = this.tokenIn; + const tokenOut = this.tokenOut; + const amountIn = tokenIn.computeUnitAmount(amount); const route = new Route( @@ -136,13 +141,15 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { } async computeTradeExactOut( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken, amount: string ): Promise> { // create pool - const pool = await this.createPool(tokenIn, tokenOut); + const pool = await this.createPool(); + // let's get that quote + const tokenIn = this.tokenIn; + const tokenOut = this.tokenOut; + const amountOut = tokenOut.computeUnitAmount(amount); const route = new Route( @@ -160,15 +167,11 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { ); } - async fetchQuoteAmountOut( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken, - amountIn: string, - slippage: string - ): Promise { + async fetchExactInQuote(amountIn: string, slippage: string): Promise { // get the quote - const trade = await this.computeTradeExactIn(tokenIn, tokenOut, amountIn); + const trade = await this.computeTradeExactIn(amountIn); + const tokenOut = this.tokenOut; const decimals = tokenOut.getDecimals(); // calculate output amount with slippage @@ -183,18 +186,22 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { .mulUnsafe(slippageMultiplier) .round(decimals); - return tokenOut.computeUnitAmount(minAmountOutWithSlippage.toString()); + /* + return tokenOut + .computeUnitAmount(minAmountOutWithSlippage.toString()) + .toString(); + */ + return minAmountOutWithSlippage.toString(); } - async fetchQuoteAmountIn( - tokenIn: UniEvmToken, - tokenOut: UniEvmToken, + async fetchExactOutQuote( amountOut: string, slippage: string - ): Promise { + ): Promise { // get the quote - const trade = await this.computeTradeExactOut(tokenIn, tokenOut, amountOut); + const trade = await this.computeTradeExactOut(amountOut); + const tokenIn = this.tokenIn; const decimals = tokenIn.getDecimals(); // calculate output amount with slippage @@ -209,7 +216,12 @@ export class SingleAmmSwapRouter extends UniswapRouterCore { .divUnsafe(slippageDivisor) .round(decimals); - return tokenIn.computeUnitAmount(maxAmountInWithSlippage.toString()); + /* + return tokenIn + .computeUnitAmount(maxAmountInWithSlippage.toString()) + .toString(); + */ + return maxAmountInWithSlippage.toString(); } getProtocol(): string { diff --git a/react/src/swapper/swapper.ts b/react/src/swapper/swapper.ts index 7bc639d..4a9203a 100644 --- a/react/src/swapper/swapper.ts +++ b/react/src/swapper/swapper.ts @@ -1,15 +1,14 @@ import { ethers } from "ethers"; import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { - CHAIN_ID_POLYGON as WORMHOLE_CHAIN_ID_POLYGON, - CHAIN_ID_ETH as WORMHOLE_CHAIN_ID_ETHEREUM, ChainId, getEmitterAddressEth, hexToUint8Array, nativeToHexString, parseSequenceFromLogEth, - getSignedVAAWithRetry, + //getSignedVAAWithRetry, } from "@certusone/wormhole-sdk"; +import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/cjs/rpc/getSignedVAAWithRetry"; import { grpc } from "@improbable-eng/grpc-web"; import { UniEvmToken } from "../route/uniswap-core"; import { @@ -21,15 +20,22 @@ import { UniswapToUniswapQuoter, } from "../route/cross-quote"; import { + TOKEN_BRIDGE_ADDRESS_ETHEREUM, TOKEN_BRIDGE_ADDRESS_POLYGON, + TOKEN_BRIDGE_ADDRESS_TERRA, CORE_BRIDGE_ADDRESS_ETHEREUM, CORE_BRIDGE_ADDRESS_POLYGON, - TOKEN_BRIDGE_ADDRESS_ETHEREUM, + CORE_BRIDGE_ADDRESS_TERRA, + WORMHOLE_CHAIN_ID_ETHEREUM, + WORMHOLE_CHAIN_ID_POLYGON, + WORMHOLE_CHAIN_ID_TERRA, WORMHOLE_RPC_HOSTS, - POLYGON_NETWORK_CHAIN_ID, - ETH_NETWORK_CHAIN_ID, + //ETH_NETWORK_CHAIN_ID, + //POLYGON_NETWORK_CHAIN_ID, + //TERRA_NETWORK_CHAIN_ID, WETH_TOKEN_INFO, WMATIC_TOKEN_INFO, + UST_TOKEN_INFO, } from "../utils/consts"; import { CROSSCHAINSWAP_GAS_PARAMETERS, @@ -38,10 +44,13 @@ import { swapExactOutFromVaaNative, swapExactOutFromVaaToken, } from "./util"; -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 { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../addresses/goerli"; -import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../addresses/mumbai"; +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 { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../../scripts/contract-addresses/goerli"; +import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../../scripts/contract-addresses/mumbai"; + +// placeholders +const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA = ""; interface SwapContractParameters { address: string; @@ -80,21 +89,35 @@ const EXECUTION_PARAMETERS_POLYGON: ExecutionParameters = { }, }; -function makeExecutionParameters(id: number): ExecutionParameters { - switch (id) { - case ETH_NETWORK_CHAIN_ID: { +const EXECUTION_PARAMETERS_TERRA: ExecutionParameters = { + crossChainSwap: { + address: CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA, + }, + wormhole: { + chainId: WORMHOLE_CHAIN_ID_TERRA, + coreBridgeAddress: CORE_BRIDGE_ADDRESS_TERRA, + tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_TERRA, + }, +}; + +function makeExecutionParameters(chainId: ChainId): ExecutionParameters { + switch (chainId) { + case WORMHOLE_CHAIN_ID_ETHEREUM: { return EXECUTION_PARAMETERS_ETHEREUM; } - case POLYGON_NETWORK_CHAIN_ID: { + case WORMHOLE_CHAIN_ID_POLYGON: { return EXECUTION_PARAMETERS_POLYGON; } + case WORMHOLE_CHAIN_ID_TERRA: { + return EXECUTION_PARAMETERS_TERRA; + } default: { throw Error("unrecognized chain id"); } } } -async function approveContractTokenSpend( +async function evmApproveContractTokenSpend( provider: ethers.providers.Provider, signer: ethers.Signer, tokenContract: ethers.Contract, @@ -140,7 +163,7 @@ function makeCrossChainSwapV2Contract( return new ethers.Contract(contractAddress, SWAP_CONTRACT_V2_ABI, provider); } -function makeCrossChainSwapContract( +function makeCrossChainSwapEvmContract( provider: ethers.providers.Provider, protocol: string, contractAddress: string @@ -163,7 +186,7 @@ function addressToBytes32( return hexToUint8Array(hexString); } -async function approveAndSwapExactIn( +async function evmApproveAndSwapExactIn( srcProvider: ethers.providers.Provider, srcWallet: ethers.Signer, srcTokenIn: UniEvmToken, @@ -175,7 +198,7 @@ async function approveAndSwapExactIn( const swapContractParams = srcExecutionParams.crossChainSwap; const protocol = quoteParams.src.protocol; - const swapContract = makeCrossChainSwapContract( + const swapContract = makeCrossChainSwapEvmContract( srcProvider, protocol, swapContractParams.address @@ -187,18 +210,19 @@ async function approveAndSwapExactIn( const address = await srcWallet.getAddress(); + const dstWormholeChainId = dstExecutionParams.wormhole.chainId; + const swapParams = [ amountIn, quoteParams.src.minAmountOut, quoteParams.dst.minAmountOut, - address, + addressToBytes32(address, dstWormholeChainId), quoteParams.src.deadline, quoteParams.dst.poolFee || quoteParams.src.poolFee, ]; const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); - const dstWormholeChainId = dstExecutionParams.wormhole.chainId; const dstContractAddress = addressToBytes32( dstExecutionParams.crossChainSwap.address, dstWormholeChainId @@ -227,7 +251,7 @@ async function approveAndSwapExactIn( return tx.wait(); } else { console.info("approving contract to spend token in"); - await approveContractTokenSpend( + await evmApproveContractTokenSpend( srcProvider, srcWallet, srcTokenIn.getContract(), @@ -249,7 +273,7 @@ async function approveAndSwapExactIn( } } -async function approveAndSwapExactOut( +async function evmApproveAndSwapExactOut( srcProvider: ethers.providers.Provider, srcWallet: ethers.Signer, srcTokenIn: UniEvmToken, @@ -261,7 +285,7 @@ async function approveAndSwapExactOut( const swapContractParams = srcExecutionParams.crossChainSwap; const protocol = quoteParams.src.protocol; - const swapContract = makeCrossChainSwapContract( + const swapContract = makeCrossChainSwapEvmContract( srcProvider, protocol, swapContractParams.address @@ -274,17 +298,18 @@ async function approveAndSwapExactOut( const address = await srcWallet.getAddress(); + const dstWormholeChainId = dstExecutionParams.wormhole.chainId; + const swapParams = [ amountOut, maxAmountIn, quoteParams.dst.amountOut, - address, + addressToBytes32(address, dstWormholeChainId), quoteParams.src.deadline, quoteParams.dst.poolFee || quoteParams.src.poolFee, ]; const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); - const dstWormholeChainId = dstExecutionParams.wormhole.chainId; const dstContractAddress = addressToBytes32( dstExecutionParams.crossChainSwap.address, dstWormholeChainId @@ -313,7 +338,7 @@ async function approveAndSwapExactOut( return tx.wait(); } else { console.info("approving contract to spend token in"); - await approveContractTokenSpend( + await evmApproveContractTokenSpend( srcProvider, srcWallet, srcTokenIn.getContract(), @@ -345,7 +370,7 @@ async function swapExactInFromVaa( ): Promise { const swapContractParams = dstExecutionParams.crossChainSwap; - const swapContract = makeCrossChainSwapContract( + const swapContract = makeCrossChainSwapEvmContract( dstProvider, dstProtocol, swapContractParams.address @@ -371,7 +396,7 @@ async function swapExactOutFromVaa( ): Promise { const swapContractParams = dstExecutionParams.crossChainSwap; - const swapContract = makeCrossChainSwapContract( + const swapContract = makeCrossChainSwapEvmContract( dstProvider, dstProtocol, swapContractParams.address @@ -399,7 +424,7 @@ interface VaaSearchParams { emitterAddress: string; } -export function makeProvider(tokenAddress: string) { +export function makeEvmProvider(tokenAddress: string) { switch (tokenAddress) { case WETH_TOKEN_INFO.address: { const url = process.env.REACT_APP_GOERLI_PROVIDER; @@ -416,6 +441,7 @@ export function makeProvider(tokenAddress: string) { return new ethers.providers.StaticJsonRpcProvider(url); } default: { + console.log("huh?", tokenAddress); throw Error("unrecognized token address"); } } @@ -440,8 +466,16 @@ export class UniswapToUniswapExecutor { transportFactory: grpc.TransportFactory; vaaSearchParams: VaaSearchParams; vaaBytes: Uint8Array; - srcReceipt: TransactionReceipt; - dstReceipt: TransactionReceipt; + + // receipts + srcEvmReceipt: TransactionReceipt; + dstEvmReceipt: TransactionReceipt; + srcTerraReceipt: any; + dstTerraReceipt: any; + + constructor() { + this.quoter = new UniswapToUniswapQuoter(); + } async initialize( tokenInAddress: string, @@ -450,20 +484,14 @@ export class UniswapToUniswapExecutor { ): Promise { this.isNative = isNative; - const srcProvider = makeProvider(tokenInAddress); - const dstProvider = makeProvider(tokenOutAddress); - - this.quoter = new UniswapToUniswapQuoter(srcProvider, dstProvider); - await this.quoter.initialize(); - - await this.makeTokens(tokenInAddress, tokenOutAddress); + await this.quoter.initialize(tokenInAddress, tokenOutAddress); // now that we have a chain id for each network, get contract info for each chain this.srcExecutionParams = makeExecutionParameters( - this.quoter.srcNetwork.chainId + this.quoter.getSrcChainId() ); this.dstExecutionParams = makeExecutionParameters( - this.quoter.dstNetwork.chainId + this.quoter.getDstChainId() ); } @@ -483,6 +511,7 @@ export class UniswapToUniswapExecutor { this.quoter.setDeadlines(deadline); } + /* async makeTokens( tokenInAddress: string, tokenOutAddress: string @@ -507,7 +536,7 @@ export class UniswapToUniswapExecutor { getTokens(): CrossChainSwapTokens { return this.tokens; } - +*/ async computeAndVerifySrcPoolAddress(): Promise { return this.quoter.computeAndVerifySrcPoolAddress(); } @@ -546,19 +575,19 @@ export class UniswapToUniswapExecutor { return this.cachedExactOutParams; } - getSrcProvider(): ethers.providers.Provider { - return this.quoter.srcProvider; + getSrcEvmProvider(): ethers.providers.Provider { + return this.quoter.getSrcEvmProvider(); } - getDstProvider(): ethers.providers.Provider { - return this.quoter.dstProvider; + getDstEvmProvider(): ethers.providers.Provider { + return this.quoter.getDstEvmProvider(); } - async approveAndSwapExactIn( + async evmApproveAndSwapExactIn( wallet: ethers.Signer ): Promise { - return approveAndSwapExactIn( - this.getSrcProvider(), + return evmApproveAndSwapExactIn( + this.getSrcEvmProvider(), wallet, this.tokens.srcIn, this.cachedExactInParams, @@ -568,11 +597,11 @@ export class UniswapToUniswapExecutor { ); } - async approveAndSwapExactOut( + async evmApproveAndSwapExactOut( wallet: ethers.Signer ): Promise { - return approveAndSwapExactOut( - this.getSrcProvider(), + return evmApproveAndSwapExactOut( + this.getSrcEvmProvider(), wallet, this.tokens.srcIn, this.cachedExactOutParams, @@ -582,23 +611,40 @@ export class UniswapToUniswapExecutor { ); } - async approveAndSwap(wallet: ethers.Signer): Promise { + srcIsUst(): boolean { + return ( + this.quoter.tokenInAddress === UST_TOKEN_INFO.address && + this.cachedExactInParams.src === undefined + ); + } + + async evmApproveAndSwap(wallet: ethers.Signer): Promise { const quoteType = this.quoteType; if (quoteType === QuoteType.ExactIn) { - this.srcReceipt = await this.approveAndSwapExactIn(wallet); + this.srcEvmReceipt = await this.evmApproveAndSwapExactIn(wallet); } else if (quoteType === QuoteType.ExactOut) { - this.srcReceipt = await this.approveAndSwapExactOut(wallet); + this.srcEvmReceipt = await this.evmApproveAndSwapExactOut(wallet); } else { throw Error("no quote found"); } this.fetchAndSetEmitterAndSequence(); - return this.srcReceipt; + return this.srcEvmReceipt; } fetchAndSetEmitterAndSequence(): void { - const receipt = this.srcReceipt; + // TODO + return; + } + + fetchAndSetTerraEmitterAndSequence(): void { + // TODO + return; + } + + fetchAndSetEvmEmitterAndSequence(): void { + const receipt = this.srcEvmReceipt; if (receipt === undefined) { throw Error("no swap receipt found"); } @@ -623,11 +669,15 @@ export class UniswapToUniswapExecutor { const emitterAddress = vaaSearchParams.emitterAddress; console.info(`sequence: ${sequence}, emitterAddress: ${emitterAddress}`); // wait for VAA to be signed + const vaaResponse = await getSignedVAAWithRetry( WORMHOLE_RPC_HOSTS, this.srcExecutionParams.wormhole.chainId, vaaSearchParams.emitterAddress, - vaaSearchParams.sequence + vaaSearchParams.sequence, + { + transport: this.transportFactory, + } ); // grab vaaBytes this.vaaBytes = vaaResponse.vaaBytes; @@ -636,22 +686,27 @@ export class UniswapToUniswapExecutor { async fetchVaaAndSwap(wallet: ethers.Signer): Promise { await this.fetchSignedVaaFromSwap(); + // check if Terra transaction + // TODO: change return as something else (not evm TransactionReceipt) + const quoteType = this.quoteType; if (quoteType === QuoteType.ExactIn) { - this.dstReceipt = await this.swapExactInFromVaa(wallet); + this.dstEvmReceipt = await this.evmSwapExactInFromVaa(wallet); } else if (quoteType === QuoteType.ExactOut) { - this.dstReceipt = await this.swapExactOutFromVaa(wallet); + this.dstEvmReceipt = await this.evmSwapExactOutFromVaa(wallet); } else { throw Error("no quote found"); } - return this.dstReceipt; + return this.dstEvmReceipt; } - async swapExactInFromVaa(wallet: ethers.Signer): Promise { + async evmSwapExactInFromVaa( + wallet: ethers.Signer + ): Promise { return swapExactInFromVaa( - this.getDstProvider(), + this.getDstEvmProvider(), wallet, this.dstExecutionParams, this.cachedExactInParams.dst.protocol, @@ -660,11 +715,11 @@ export class UniswapToUniswapExecutor { ); } - async swapExactOutFromVaa( + async evmSwapExactOutFromVaa( wallet: ethers.Signer ): Promise { return swapExactOutFromVaa( - this.getDstProvider(), + this.getDstEvmProvider(), wallet, this.dstExecutionParams, this.cachedExactOutParams.dst.protocol, @@ -673,6 +728,10 @@ export class UniswapToUniswapExecutor { ); } + setTransport(transportFactory: grpc.TransportFactory) { + this.transportFactory = transportFactory; + } + //getSwapResult( // walletAddress: string, // onSwapResult: (result: boolean) => void @@ -680,7 +739,7 @@ export class UniswapToUniswapExecutor { // console.log(this.cachedExactInParams.dst.protocol); // console.log(this.dstExecutionParams.crossChainSwap.address); // const contract = makeCrossChainSwapContract( - // this.getDstProvider(), + // this.getDstEvmProvider(), // this.quoteType === QuoteType.ExactIn // ? this.cachedExactInParams.dst.protocol // : this.cachedExactOutParams.dst.protocol, diff --git a/react/src/utils/consts.ts b/react/src/utils/consts.ts index e9b818f..710d25c 100644 --- a/react/src/utils/consts.ts +++ b/react/src/utils/consts.ts @@ -1,10 +1,21 @@ import { ChainId, - CHAIN_ID_ETH, - CHAIN_ID_POLYGON, + CHAIN_ID_ETH as WORMHOLE_CHAIN_ID_ETHEREUM, + CHAIN_ID_POLYGON as WORMHOLE_CHAIN_ID_POLYGON, + CHAIN_ID_TERRA as WORMHOLE_CHAIN_ID_TERRA, } from "@certusone/wormhole-sdk"; -import ethIcon from "../icons/eth.svg"; -import polygonIcon from "../icons/polygon.svg"; +//import ethIcon from "../icons/eth.svg"; +//import polygonIcon from "../icons/polygon.svg"; + +const ethIcon = undefined; +const polygonIcon = undefined; +const ustIcon = undefined; + +export { + WORMHOLE_CHAIN_ID_ETHEREUM, + WORMHOLE_CHAIN_ID_POLYGON, + WORMHOLE_CHAIN_ID_TERRA, +}; export interface TokenInfo { name: string; @@ -13,42 +24,57 @@ export interface TokenInfo { logo: string; isNative: boolean; maxAmount: number; + ustPairedAddress: string; } export const MATIC_TOKEN_INFO: TokenInfo = { name: "MATIC", address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", // used to compute quote - chainId: CHAIN_ID_POLYGON, + chainId: WORMHOLE_CHAIN_ID_POLYGON, logo: polygonIcon, isNative: true, maxAmount: 0.1, + ustPairedAddress: "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c", }; export const WMATIC_TOKEN_INFO: TokenInfo = { name: "WMATIC", address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", - chainId: CHAIN_ID_POLYGON, + chainId: WORMHOLE_CHAIN_ID_POLYGON, logo: polygonIcon, isNative: false, maxAmount: 0.1, + ustPairedAddress: "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c", }; export const ETH_TOKEN_INFO: TokenInfo = { name: "ETH", address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // used to compute quote - chainId: CHAIN_ID_ETH, + chainId: WORMHOLE_CHAIN_ID_ETHEREUM, logo: ethIcon, isNative: true, maxAmount: 0.01, + ustPairedAddress: "0x36Ed51Afc79619b299b238898E72ce482600568a", }; export const WETH_TOKEN_INFO: TokenInfo = { name: "WETH", address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", - chainId: CHAIN_ID_ETH, + chainId: WORMHOLE_CHAIN_ID_ETHEREUM, logo: ethIcon, isNative: false, maxAmount: 0.01, + ustPairedAddress: "0x36Ed51Afc79619b299b238898E72ce482600568a", +}; + +export const UST_TOKEN_INFO: TokenInfo = { + name: "UST", + address: "uusd", + chainId: WORMHOLE_CHAIN_ID_TERRA, + logo: ustIcon, + isNative: false, + maxAmount: 10.0, + ustPairedAddress: undefined, }; export const TOKEN_INFOS = [ @@ -56,19 +82,21 @@ export const TOKEN_INFOS = [ WMATIC_TOKEN_INFO, ETH_TOKEN_INFO, WETH_TOKEN_INFO, + UST_TOKEN_INFO, ]; -export const ETH_NETWORK_CHAIN_ID = 5; - -export const POLYGON_NETWORK_CHAIN_ID = 80001; +// evm handling +export const EVM_ETH_NETWORK_CHAIN_ID = 5; +export const EVM_POLYGON_NETWORK_CHAIN_ID = 80001; export const getEvmChainId = (chainId: ChainId) => - chainId === CHAIN_ID_ETH - ? ETH_NETWORK_CHAIN_ID - : chainId === CHAIN_ID_POLYGON - ? POLYGON_NETWORK_CHAIN_ID + chainId === WORMHOLE_CHAIN_ID_ETHEREUM + ? EVM_ETH_NETWORK_CHAIN_ID + : chainId === WORMHOLE_CHAIN_ID_POLYGON + ? EVM_POLYGON_NETWORK_CHAIN_ID : undefined; +// misc export const RELAYER_FEE_UST = "0.25"; export const WORMHOLE_RPC_HOSTS = [ @@ -81,12 +109,16 @@ export const CORE_BRIDGE_ADDRESS_ETHEREUM = export const CORE_BRIDGE_ADDRESS_POLYGON = "0x0CBE91CF822c73C2315FB05100C2F714765d5c20"; +export const CORE_BRIDGE_ADDRESS_TERRA = undefined; + export const TOKEN_BRIDGE_ADDRESS_ETHEREUM = "0xF890982f9310df57d00f659cf4fd87e65adEd8d7"; export const TOKEN_BRIDGE_ADDRESS_POLYGON = "0x377D55a7928c046E18eEbb61977e714d2a76472a"; +export const TOKEN_BRIDGE_ADDRESS_TERRA = undefined; + export const QUICKSWAP_FACTORY_ADDRESS = "0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32"; diff --git a/react/src/utils/math.ts b/react/src/utils/math.ts new file mode 100644 index 0000000..a3a4937 --- /dev/null +++ b/react/src/utils/math.ts @@ -0,0 +1,19 @@ +import { FixedNumber } from "ethers"; + +export function addFixedAmounts( + left: string, + right: string, + decimals: number +): string { + const sum = FixedNumber.from(left).addUnsafe(FixedNumber.from(right)); + return sum.round(this.getDecimals()).toString(); +} + +export function subtractFixedAmounts( + left: string, + right: string, + decimals: number +): string { + const diff = FixedNumber.from(left).subUnsafe(FixedNumber.from(right)); + return diff.round(decimals).toString(); +} From 1c3b6908a9e8dd55e4b3247d46ea0bd70c5fc783 Mon Sep 17 00:00:00 2001 From: Kevin Peters Date: Wed, 26 Jan 2022 17:07:33 +0000 Subject: [PATCH 03/38] UI - terra support --- react/package-lock.json | 103 ++++------ react/package.json | 1 + react/src/components/SwapProgress.tsx | 44 ++--- react/src/components/TerraWalletKey.tsx | 22 +++ react/src/contexts/TerraWalletContext.tsx | 97 ++++++++++ react/src/hooks/useIsWalletReady.ts | 41 +++- react/src/icons/terra.svg | 23 +++ react/src/index.js | 9 +- react/src/route/cross-quote.ts | 52 +++-- react/src/route/evm.ts | 2 +- react/src/route/uniswap-core.ts | 1 + react/src/swapper/swapper.ts | 30 +-- react/src/utils/consts.ts | 68 ++++--- react/src/utils/math.ts | 2 +- react/src/views/Home.tsx | 225 ++++++++++++++++------ 15 files changed, 489 insertions(+), 231 deletions(-) create mode 100644 react/src/components/TerraWalletKey.tsx create mode 100644 react/src/contexts/TerraWalletContext.tsx create mode 100644 react/src/icons/terra.svg diff --git a/react/package-lock.json b/react/package-lock.json index e6c20d5..9bf83e2 100644 --- a/react/package-lock.json +++ b/react/package-lock.json @@ -14,6 +14,7 @@ "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", "@metamask/detect-provider": "^1.2.0", + "@terra-money/wallet-provider": "^2.2.0", "@types/node": "^16.11.19", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", @@ -4464,34 +4465,17 @@ "react-dom": "^17.0.0" } }, - "node_modules/@terra-dev/web-connector-controller": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@terra-dev/web-connector-controller/-/web-connector-controller-0.8.1.tgz", - "integrity": "sha512-TIwFtta7vN2GdDUy8SbIIsfTd9XWiU+U6yjizd82yUAhlOJNAGHshG0r1j0irkA5MycYG1duAlr7foeKRu4PGA==", + "node_modules/@terra-dev/web-extension": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@terra-dev/web-extension/-/web-extension-0.6.0.tgz", + "integrity": "sha512-IyIWHLfweZCb5nHuMyzavnMYposnZMvpsA/89zZPIgIooxhxE//uZD+Ty+ptt4nvkbOgEFKdKIKe5rIHqgVLpA==", "dependencies": { - "@terra-dev/web-connector-interface": "^0.8.1", + "@terra-money/terra.js": "^1.8.0 || ^2.0.0", "bowser": "^2.11.0", - "rxjs": "^7.4.0" + "rxjs": "^7.3.0" }, "engines": { "node": ">=12" - }, - "peerDependencies": { - "@terra-money/terra.js": "^2.0.0" - } - }, - "node_modules/@terra-dev/web-connector-interface": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@terra-dev/web-connector-interface/-/web-connector-interface-0.8.1.tgz", - "integrity": "sha512-ryA3xtTFJ7OkAF6pTlrsuqxtSUp0DxHhyxvzwRPbT3h8VqlkFStknvYjRwNRspN2LOpi4/F1TNFzcUBNHPCo2g==", - "dependencies": { - "rxjs": "^7.4.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@terra-money/terra.js": "^2.0.0" } }, "node_modules/@terra-money/terra.js": { @@ -4536,27 +4520,26 @@ } }, "node_modules/@terra-money/wallet-provider": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.5.3.tgz", - "integrity": "sha512-v/5Z35gCo4nZyZCu3nYDFvhwuvlyDeNSSYmN9KUc9ewoIO9K/2fi3vxcOLcvqq5PYowwwod21vgaQ9QHFV+8eA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.2.0.tgz", + "integrity": "sha512-K8NLpJ/yak8Pq6jQpjVr7yWDIbxjTp42OXaAS+xlTufqQwWbCR7coAGbm2FpYX43j4uymuSAICZvsOS1qrFeYA==", "dependencies": { - "@terra-dev/browser-check": "^2.5.3", - "@terra-dev/chrome-extension": "^2.5.3", - "@terra-dev/readonly-wallet": "^2.5.3", - "@terra-dev/readonly-wallet-modal": "^2.5.3", - "@terra-dev/use-wallet": "^2.5.3", - "@terra-dev/wallet-types": "^2.5.3", - "@terra-dev/walletconnect": "^2.5.3", - "@terra-dev/web-connector-controller": "^0.8.1", - "@terra-dev/web-connector-interface": "^0.8.1", + "@terra-dev/browser-check": "^2.2.0", + "@terra-dev/chrome-extension": "^2.2.0", + "@terra-dev/readonly-wallet": "^2.2.0", + "@terra-dev/readonly-wallet-modal": "^2.2.0", + "@terra-dev/use-wallet": "^2.2.0", + "@terra-dev/wallet-types": "^2.2.0", + "@terra-dev/walletconnect": "^2.2.0", + "@terra-dev/web-extension": "^0.6.0", + "@terra-money/terra.js": "^2.0.0", "fast-deep-equal": "^3.1.3", - "rxjs": "^7.4.0" + "rxjs": "^7.3.0" }, "engines": { "node": ">=12" }, "peerDependencies": { - "@terra-money/terra.js": "^2.0.0", "react": "^17.0.0" }, "peerDependenciesMeta": { @@ -29130,22 +29113,14 @@ "styled-components": "^5.0.0" } }, - "@terra-dev/web-connector-controller": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@terra-dev/web-connector-controller/-/web-connector-controller-0.8.1.tgz", - "integrity": "sha512-TIwFtta7vN2GdDUy8SbIIsfTd9XWiU+U6yjizd82yUAhlOJNAGHshG0r1j0irkA5MycYG1duAlr7foeKRu4PGA==", + "@terra-dev/web-extension": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@terra-dev/web-extension/-/web-extension-0.6.0.tgz", + "integrity": "sha512-IyIWHLfweZCb5nHuMyzavnMYposnZMvpsA/89zZPIgIooxhxE//uZD+Ty+ptt4nvkbOgEFKdKIKe5rIHqgVLpA==", "requires": { - "@terra-dev/web-connector-interface": "^0.8.1", + "@terra-money/terra.js": "^1.8.0 || ^2.0.0", "bowser": "^2.11.0", - "rxjs": "^7.4.0" - } - }, - "@terra-dev/web-connector-interface": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@terra-dev/web-connector-interface/-/web-connector-interface-0.8.1.tgz", - "integrity": "sha512-ryA3xtTFJ7OkAF6pTlrsuqxtSUp0DxHhyxvzwRPbT3h8VqlkFStknvYjRwNRspN2LOpi4/F1TNFzcUBNHPCo2g==", - "requires": { - "rxjs": "^7.4.0" + "rxjs": "^7.3.0" } }, "@terra-money/terra.js": { @@ -29189,21 +29164,21 @@ } }, "@terra-money/wallet-provider": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.5.3.tgz", - "integrity": "sha512-v/5Z35gCo4nZyZCu3nYDFvhwuvlyDeNSSYmN9KUc9ewoIO9K/2fi3vxcOLcvqq5PYowwwod21vgaQ9QHFV+8eA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.2.0.tgz", + "integrity": "sha512-K8NLpJ/yak8Pq6jQpjVr7yWDIbxjTp42OXaAS+xlTufqQwWbCR7coAGbm2FpYX43j4uymuSAICZvsOS1qrFeYA==", "requires": { - "@terra-dev/browser-check": "^2.5.3", - "@terra-dev/chrome-extension": "^2.5.3", - "@terra-dev/readonly-wallet": "^2.5.3", - "@terra-dev/readonly-wallet-modal": "^2.5.3", - "@terra-dev/use-wallet": "^2.5.3", - "@terra-dev/wallet-types": "^2.5.3", - "@terra-dev/walletconnect": "^2.5.3", - "@terra-dev/web-connector-controller": "^0.8.1", - "@terra-dev/web-connector-interface": "^0.8.1", + "@terra-dev/browser-check": "^2.2.0", + "@terra-dev/chrome-extension": "^2.2.0", + "@terra-dev/readonly-wallet": "^2.2.0", + "@terra-dev/readonly-wallet-modal": "^2.2.0", + "@terra-dev/use-wallet": "^2.2.0", + "@terra-dev/wallet-types": "^2.2.0", + "@terra-dev/walletconnect": "^2.2.0", + "@terra-dev/web-extension": "^0.6.0", + "@terra-money/terra.js": "^2.0.0", "fast-deep-equal": "^3.1.3", - "rxjs": "^7.4.0" + "rxjs": "^7.3.0" } }, "@tootallnate/once": { diff --git a/react/package.json b/react/package.json index 639f0ef..101dc96 100644 --- a/react/package.json +++ b/react/package.json @@ -10,6 +10,7 @@ "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", "@metamask/detect-provider": "^1.2.0", + "@terra-money/wallet-provider": "^2.2.0", "@types/node": "^16.11.19", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", diff --git a/react/src/components/SwapProgress.tsx b/react/src/components/SwapProgress.tsx index 8087fab..2b42ff6 100644 --- a/react/src/components/SwapProgress.tsx +++ b/react/src/components/SwapProgress.tsx @@ -16,17 +16,21 @@ const useStyles = makeStyles((theme) => ({ export default function TransactionProgress({ chainId, txBlockNumber, - step, + isSourceSwapComplete, + hasSignedVAA, + isTargetSwapComplete, }: { chainId: ChainId; txBlockNumber: number | undefined; - step: number; + isSourceSwapComplete: boolean; + hasSignedVAA: boolean; + isTargetSwapComplete: boolean; }) { const classes = useStyles(); const { provider } = useEthereumProvider(); const [currentBlock, setCurrentBlock] = useState(0); useEffect(() => { - if (step !== 1 || !txBlockNumber) return; + if (hasSignedVAA || !txBlockNumber) return; if (isEVMChain(chainId) && provider) { let cancelled = false; (async () => { @@ -46,7 +50,7 @@ export default function TransactionProgress({ cancelled = true; }; } - }, [step, chainId, provider, txBlockNumber]); + }, [hasSignedVAA, chainId, provider, txBlockNumber]); const blockDiff = txBlockNumber !== undefined && txBlockNumber && currentBlock ? currentBlock - txBlockNumber @@ -55,24 +59,20 @@ export default function TransactionProgress({ let value; let valueBuffer; let message; - switch (step) { - case 1: - value = (blockDiff / expectedBlocks) * 50; - valueBuffer = 50; - message = `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${ - chainId === CHAIN_ID_POLYGON ? "Polygon" : "Ethereum" - }...`; - break; - case 2: - value = 50; - valueBuffer = 100; - message = "Waiting for relayer to complete swap..."; - break; - case 3: - value = 100; - valueBuffer = 100; - message = ""; - break; + if (!hasSignedVAA) { + value = (blockDiff / expectedBlocks) * 50; + valueBuffer = 50; + message = `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${ + chainId === CHAIN_ID_POLYGON ? "Polygon" : "Ethereum" + }...`; + } else if (!isTargetSwapComplete) { + value = 50; + valueBuffer = 100; + message = "Waiting for relayer to complete swap..."; + } else { + value = 100; + valueBuffer = 100; + message = "Success!"; } return (
diff --git a/react/src/components/TerraWalletKey.tsx b/react/src/components/TerraWalletKey.tsx new file mode 100644 index 0000000..38456c3 --- /dev/null +++ b/react/src/components/TerraWalletKey.tsx @@ -0,0 +1,22 @@ +import { useTerraWallet } from "../contexts/TerraWalletContext"; +import ToggleConnectedButton from "./ToggleConnectedButton"; + +const TerraWalletKey = () => { + const { connect, disconnect, connected, wallet } = useTerraWallet(); + const pk = + (wallet && + wallet.wallets && + wallet.wallets.length > 0 && + wallet.wallets[0].terraAddress) || + ""; + return ( + + ); +}; + +export default TerraWalletKey; diff --git a/react/src/contexts/TerraWalletContext.tsx b/react/src/contexts/TerraWalletContext.tsx new file mode 100644 index 0000000..5787d59 --- /dev/null +++ b/react/src/contexts/TerraWalletContext.tsx @@ -0,0 +1,97 @@ +import { + NetworkInfo, + Wallet, + WalletProvider, + useWallet, +} from "@terra-money/wallet-provider"; +import React, { + ReactChildren, + useCallback, + useContext, + useMemo, + useState, +} from "react"; + +const testnet: NetworkInfo = { + name: "testnet", + chainID: "bombay-12", + lcd: "https://bombay-lcd.terra.dev", +}; + +const walletConnectChainIds: Record = { + 0: testnet, +}; + +interface ITerraWalletContext { + connect(): void; + disconnect(): void; + connected: boolean; + wallet: any; +} + +const TerraWalletContext = React.createContext({ + connect: () => {}, + disconnect: () => {}, + connected: false, + wallet: null, +}); + +export const TerraWalletWrapper = ({ + children, +}: { + children: ReactChildren; +}) => { + // TODO: Use wallet instead of useConnectedWallet. + const terraWallet = useWallet(); + const [, setWallet] = useState(undefined); + const [connected, setConnected] = useState(false); + + const connect = useCallback(() => { + const CHROME_EXTENSION = 1; + if (terraWallet) { + terraWallet.connect(terraWallet.availableConnectTypes[CHROME_EXTENSION]); + setWallet(terraWallet); + setConnected(true); + } + }, [terraWallet]); + + const disconnect = useCallback(() => { + setConnected(false); + setWallet(undefined); + }, []); + + const contextValue = useMemo( + () => ({ + connect, + disconnect, + connected, + wallet: terraWallet, + }), + [connect, disconnect, connected, terraWallet] + ); + + return ( + + {children} + + ); +}; + +export const TerraWalletProvider = ({ + children, +}: { + children: ReactChildren; +}) => { + return ( + + {children} + + ); +}; + +export const useTerraWallet = () => { + return useContext(TerraWalletContext); +}; diff --git a/react/src/hooks/useIsWalletReady.ts b/react/src/hooks/useIsWalletReady.ts index f722dee..06e4fb8 100644 --- a/react/src/hooks/useIsWalletReady.ts +++ b/react/src/hooks/useIsWalletReady.ts @@ -1,5 +1,11 @@ -import { ChainId, CHAIN_ID_SOLANA, isEVMChain } from "@certusone/wormhole-sdk"; +import { + ChainId, + CHAIN_ID_SOLANA, + CHAIN_ID_TERRA, + isEVMChain, +} from "@certusone/wormhole-sdk"; import { hexlify, hexStripZeros } from "@ethersproject/bytes"; +import { useConnectedWallet } from "@terra-money/wallet-provider"; import { useCallback, useMemo } from "react"; import { useEthereumProvider } from "../contexts/EthereumProviderContext"; // import { useSolanaWallet } from "../contexts/SolanaWalletContext"; @@ -31,6 +37,8 @@ function useIsWalletReady( const autoSwitch = enableNetworkAutoswitch; // const solanaWallet = useSolanaWallet(); // const solPK = solanaWallet?.publicKey; + const terraWallet = useConnectedWallet(); + const hasTerraWallet = !!terraWallet; const { provider, signerAddress, @@ -54,15 +62,28 @@ function useIsWalletReady( }, [provider, correctEvmNetwork, chainId]); return useMemo(() => { - //if (chainId === CHAIN_ID_SOLANA && solPK) { - // return createWalletStatus( - // true, - // undefined, - // forceNetworkSwitch, - // solPK.toString() - // ); - //} + if ( + chainId === CHAIN_ID_TERRA && + hasTerraWallet && + terraWallet?.walletAddress + ) { + // TODO: terraWallet does not update on wallet changes + return createWalletStatus( + true, + undefined, + forceNetworkSwitch, + terraWallet.walletAddress + ); + } if (isEVMChain(chainId) && hasEthInfo && signerAddress) { + //if (chainId === CHAIN_ID_SOLANA && solPK) { + // return createWalletStatus( + // true, + // undefined, + // forceNetworkSwitch, + // solPK.toString() + // ); + //} if (hasCorrectEvmNetwork) { return createWalletStatus( true, @@ -93,12 +114,14 @@ function useIsWalletReady( chainId, autoSwitch, forceNetworkSwitch, + hasTerraWallet, // solPK, hasEthInfo, correctEvmNetwork, hasCorrectEvmNetwork, provider, signerAddress, + terraWallet, ]); } diff --git a/react/src/icons/terra.svg b/react/src/icons/terra.svg new file mode 100644 index 0000000..f1c7602 --- /dev/null +++ b/react/src/icons/terra.svg @@ -0,0 +1,23 @@ + + + + + + + diff --git a/react/src/index.js b/react/src/index.js index ef588ed..2c4cdf8 100644 --- a/react/src/index.js +++ b/react/src/index.js @@ -5,6 +5,7 @@ import ReactDOM from "react-dom"; import App from "./App"; import ErrorBoundary from "./components/ErrorBoundary"; import { EthereumProviderProvider } from "./contexts/EthereumProviderContext"; +import { TerraWalletProvider } from "./contexts/TerraWalletContext"; import { theme } from "./muiTheme"; ReactDOM.render( @@ -12,9 +13,11 @@ ReactDOM.render( - - - + + + + + diff --git a/react/src/route/cross-quote.ts b/react/src/route/cross-quote.ts index 3d8f2d4..81220e2 100644 --- a/react/src/route/cross-quote.ts +++ b/react/src/route/cross-quote.ts @@ -7,9 +7,6 @@ import { WETH_TOKEN_INFO, WMATIC_TOKEN_INFO, UST_TOKEN_INFO, - WORMHOLE_CHAIN_ID_ETHEREUM, - WORMHOLE_CHAIN_ID_POLYGON, - WORMHOLE_CHAIN_ID_TERRA, } from "../utils/consts"; import { addFixedAmounts, subtractFixedAmounts } from "../utils/math"; import { UstLocation } from "./generic"; @@ -19,7 +16,12 @@ import { makeExactInParameters, makeExactOutParameters, } from "./uniswap-core"; -import { ChainId } from "@certusone/wormhole-sdk"; +import { + ChainId, + CHAIN_ID_ETH, + CHAIN_ID_POLYGON, + CHAIN_ID_TERRA, +} from "@certusone/wormhole-sdk"; export { PROTOCOL as PROTOCOL_UNISWAP_V2 } from "./uniswap-v2"; export { PROTOCOL as PROTOCOL_UNISWAP_V3 } from "./uniswap-v3"; @@ -57,13 +59,13 @@ export function makeEvmProviderFromAddress(tokenAddress: string) { export function getChainIdFromAddress(tokenAddress: string) { switch (tokenAddress) { case WETH_TOKEN_INFO.address: { - return WORMHOLE_CHAIN_ID_ETHEREUM; + return CHAIN_ID_ETH; } case WMATIC_TOKEN_INFO.address: { - return WORMHOLE_CHAIN_ID_POLYGON; + return CHAIN_ID_POLYGON; } case UST_TOKEN_INFO.address: { - return WORMHOLE_CHAIN_ID_TERRA; + return CHAIN_ID_TERRA; } default: { throw Error("unrecognized evm token address"); @@ -110,16 +112,16 @@ export interface RelayerFee { export interface ExactInCrossParameters { amountIn: string; minAmountOut: string; - src: ExactInParameters; - dst: ExactInParameters; + src: ExactInParameters | undefined; + dst: ExactInParameters | undefined; relayerFee: RelayerFee; } export interface ExactOutCrossParameters { amountOut: string; maxAmountIn: string; - src: ExactOutParameters; - dst: ExactOutParameters; + src: ExactOutParameters | undefined; + dst: ExactOutParameters | undefined; relayerFee: RelayerFee; } @@ -132,22 +134,16 @@ export class UniswapToUniswapQuoter { srcRouter: UstRouter | EthRouter | MaticRouter; dstRouter: UstRouter | EthRouter | MaticRouter; - constructor() {} - - async initialize( - tokenInAddress: string, - tokenOutAddress: string - ): Promise { + async initialize(tokenInAddress: string, tokenOutAddress: string) { if (tokenInAddress !== this.tokenInAddress) { this.tokenInAddress = tokenInAddress; this.srcRouter = await makeRouter(tokenInAddress, UstLocation.Out); } - if (tokenOutAddress != this.tokenOutAddress) { + if (tokenOutAddress !== this.tokenOutAddress) { this.tokenOutAddress = tokenOutAddress; this.dstRouter = await makeRouter(tokenOutAddress, UstLocation.In); } - return; } async computeAndVerifySrcPoolAddress(): Promise { @@ -158,7 +154,7 @@ export class UniswapToUniswapQuoter { return this.dstRouter.computeAndVerifyPoolAddress(); } - computeSwapSlippage(slippage): string { + computeSwapSlippage(slippage: string): string { if (this.isSrcUst() || this.isDstUst()) { return slippage; } @@ -184,7 +180,7 @@ export class UniswapToUniswapQuoter { makeSrcExactInParameters( amountIn: string, minAmountOut: string - ): ExactInParameters { + ): ExactInParameters | undefined { if (this.isSrcUst()) { return undefined; } @@ -195,7 +191,7 @@ export class UniswapToUniswapQuoter { makeDstExactInParameters( amountIn: string, minAmountOut: string - ): ExactInParameters { + ): ExactInParameters | undefined { if (this.isDstUst()) { return undefined; } @@ -251,9 +247,9 @@ export class UniswapToUniswapQuoter { makeSrcExactOutParameters( amountOut: string, maxAmountIn: string - ): ExactOutParameters { + ): ExactOutParameters | undefined { if (this.isSrcUst()) { - return null; + return undefined; } // @ts-ignore return makeExactOutParameters(this.srcRouter, amountOut, maxAmountIn); @@ -262,9 +258,9 @@ export class UniswapToUniswapQuoter { makeDstExactOutParameters( amountOut: string, maxAmountIn: string - ): ExactOutParameters { + ): ExactOutParameters | undefined { if (this.isDstUst()) { - return null; + return undefined; } // @ts-ignore return makeExactOutParameters(this.dstRouter, amountOut, maxAmountIn); @@ -337,7 +333,7 @@ export class UniswapToUniswapQuoter { return this.tokenOutAddress === TERRA_UST; } - getSrcEvmProvider(): ethers.providers.Provider { + getSrcEvmProvider(): ethers.providers.Provider | undefined { if (this.isSrcUst()) { return undefined; } @@ -345,7 +341,7 @@ export class UniswapToUniswapQuoter { return this.srcRouter.getProvider(); } - getDstEvmProvider(): ethers.providers.Provider { + getDstEvmProvider(): ethers.providers.Provider | undefined { if (this.isDstUst()) { return undefined; } diff --git a/react/src/route/evm.ts b/react/src/route/evm.ts index 2da4d30..53ee755 100644 --- a/react/src/route/evm.ts +++ b/react/src/route/evm.ts @@ -3,7 +3,7 @@ import { ethers } from "ethers"; import { GenericToken } from "./generic"; // erc20 spec -import { abi as Erc20Abi } from "../../abi/erc20.json"; +import { abi as Erc20Abi } from "../abi/erc20.json"; import { TransactionReceipt, TransactionRequest, diff --git a/react/src/route/uniswap-core.ts b/react/src/route/uniswap-core.ts index 511686c..0479eff 100644 --- a/react/src/route/uniswap-core.ts +++ b/react/src/route/uniswap-core.ts @@ -1,3 +1,4 @@ +//@ts-nocheck import { ethers } from "ethers"; import { CurrencyAmount, Token } from "@uniswap/sdk-core"; diff --git a/react/src/swapper/swapper.ts b/react/src/swapper/swapper.ts index 4a9203a..3f1f870 100644 --- a/react/src/swapper/swapper.ts +++ b/react/src/swapper/swapper.ts @@ -1,14 +1,17 @@ +//@ts-nocheck import { ethers } from "ethers"; import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { ChainId, + CHAIN_ID_ETH, + CHAIN_ID_POLYGON, + CHAIN_ID_TERRA, getEmitterAddressEth, hexToUint8Array, nativeToHexString, parseSequenceFromLogEth, - //getSignedVAAWithRetry, + getSignedVAAWithRetry, } from "@certusone/wormhole-sdk"; -import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/cjs/rpc/getSignedVAAWithRetry"; import { grpc } from "@improbable-eng/grpc-web"; import { UniEvmToken } from "../route/uniswap-core"; import { @@ -26,9 +29,6 @@ import { CORE_BRIDGE_ADDRESS_ETHEREUM, CORE_BRIDGE_ADDRESS_POLYGON, CORE_BRIDGE_ADDRESS_TERRA, - WORMHOLE_CHAIN_ID_ETHEREUM, - WORMHOLE_CHAIN_ID_POLYGON, - WORMHOLE_CHAIN_ID_TERRA, WORMHOLE_RPC_HOSTS, //ETH_NETWORK_CHAIN_ID, //POLYGON_NETWORK_CHAIN_ID, @@ -44,10 +44,10 @@ import { swapExactOutFromVaaNative, swapExactOutFromVaaToken, } from "./util"; -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 { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../../scripts/contract-addresses/goerli"; -import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../../scripts/contract-addresses/mumbai"; +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 { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../addresses/goerli"; +import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../addresses/mumbai"; // placeholders const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA = ""; @@ -72,7 +72,7 @@ const EXECUTION_PARAMETERS_ETHEREUM: ExecutionParameters = { address: CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM, }, wormhole: { - chainId: WORMHOLE_CHAIN_ID_ETHEREUM, + chainId: CHAIN_ID_ETH, coreBridgeAddress: CORE_BRIDGE_ADDRESS_ETHEREUM, tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_ETHEREUM, }, @@ -83,7 +83,7 @@ const EXECUTION_PARAMETERS_POLYGON: ExecutionParameters = { address: CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON, }, wormhole: { - chainId: WORMHOLE_CHAIN_ID_POLYGON, + chainId: CHAIN_ID_POLYGON, coreBridgeAddress: CORE_BRIDGE_ADDRESS_POLYGON, tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_POLYGON, }, @@ -94,7 +94,7 @@ const EXECUTION_PARAMETERS_TERRA: ExecutionParameters = { address: CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA, }, wormhole: { - chainId: WORMHOLE_CHAIN_ID_TERRA, + chainId: CHAIN_ID_TERRA, coreBridgeAddress: CORE_BRIDGE_ADDRESS_TERRA, tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_TERRA, }, @@ -102,13 +102,13 @@ const EXECUTION_PARAMETERS_TERRA: ExecutionParameters = { function makeExecutionParameters(chainId: ChainId): ExecutionParameters { switch (chainId) { - case WORMHOLE_CHAIN_ID_ETHEREUM: { + case CHAIN_ID_ETH: { return EXECUTION_PARAMETERS_ETHEREUM; } - case WORMHOLE_CHAIN_ID_POLYGON: { + case CHAIN_ID_POLYGON: { return EXECUTION_PARAMETERS_POLYGON; } - case WORMHOLE_CHAIN_ID_TERRA: { + case CHAIN_ID_TERRA: { return EXECUTION_PARAMETERS_TERRA; } default: { diff --git a/react/src/utils/consts.ts b/react/src/utils/consts.ts index 710d25c..dd5e3e6 100644 --- a/react/src/utils/consts.ts +++ b/react/src/utils/consts.ts @@ -1,21 +1,12 @@ import { ChainId, - CHAIN_ID_ETH as WORMHOLE_CHAIN_ID_ETHEREUM, - CHAIN_ID_POLYGON as WORMHOLE_CHAIN_ID_POLYGON, - CHAIN_ID_TERRA as WORMHOLE_CHAIN_ID_TERRA, + CHAIN_ID_ETH, + CHAIN_ID_POLYGON, + CHAIN_ID_TERRA, } from "@certusone/wormhole-sdk"; -//import ethIcon from "../icons/eth.svg"; -//import polygonIcon from "../icons/polygon.svg"; - -const ethIcon = undefined; -const polygonIcon = undefined; -const ustIcon = undefined; - -export { - WORMHOLE_CHAIN_ID_ETHEREUM, - WORMHOLE_CHAIN_ID_POLYGON, - WORMHOLE_CHAIN_ID_TERRA, -}; +import ethIcon from "../icons/eth.svg"; +import polygonIcon from "../icons/polygon.svg"; +import terraIcon from "../icons/terra.svg"; export interface TokenInfo { name: string; @@ -24,13 +15,13 @@ export interface TokenInfo { logo: string; isNative: boolean; maxAmount: number; - ustPairedAddress: string; + ustPairedAddress: string | undefined; } export const MATIC_TOKEN_INFO: TokenInfo = { name: "MATIC", address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", // used to compute quote - chainId: WORMHOLE_CHAIN_ID_POLYGON, + chainId: CHAIN_ID_POLYGON, logo: polygonIcon, isNative: true, maxAmount: 0.1, @@ -40,7 +31,7 @@ export const MATIC_TOKEN_INFO: TokenInfo = { export const WMATIC_TOKEN_INFO: TokenInfo = { name: "WMATIC", address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", - chainId: WORMHOLE_CHAIN_ID_POLYGON, + chainId: CHAIN_ID_POLYGON, logo: polygonIcon, isNative: false, maxAmount: 0.1, @@ -50,7 +41,7 @@ export const WMATIC_TOKEN_INFO: TokenInfo = { export const ETH_TOKEN_INFO: TokenInfo = { name: "ETH", address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // used to compute quote - chainId: WORMHOLE_CHAIN_ID_ETHEREUM, + chainId: CHAIN_ID_ETH, logo: ethIcon, isNative: true, maxAmount: 0.01, @@ -60,7 +51,7 @@ export const ETH_TOKEN_INFO: TokenInfo = { export const WETH_TOKEN_INFO: TokenInfo = { name: "WETH", address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", - chainId: WORMHOLE_CHAIN_ID_ETHEREUM, + chainId: CHAIN_ID_ETH, logo: ethIcon, isNative: false, maxAmount: 0.01, @@ -70,9 +61,9 @@ export const WETH_TOKEN_INFO: TokenInfo = { export const UST_TOKEN_INFO: TokenInfo = { name: "UST", address: "uusd", - chainId: WORMHOLE_CHAIN_ID_TERRA, - logo: ustIcon, - isNative: false, + chainId: CHAIN_ID_TERRA, + logo: terraIcon, + isNative: true, // TODO: change? maxAmount: 10.0, ustPairedAddress: undefined, }; @@ -85,18 +76,33 @@ export const TOKEN_INFOS = [ UST_TOKEN_INFO, ]; -// evm handling -export const EVM_ETH_NETWORK_CHAIN_ID = 5; -export const EVM_POLYGON_NETWORK_CHAIN_ID = 80001; +export const getSupportedSwaps = (tokenInfo: TokenInfo) => { + switch (tokenInfo) { + case MATIC_TOKEN_INFO: + return [ETH_TOKEN_INFO, UST_TOKEN_INFO]; + case WMATIC_TOKEN_INFO: + return [WETH_TOKEN_INFO]; + case ETH_TOKEN_INFO: + return [MATIC_TOKEN_INFO, UST_TOKEN_INFO]; + case WETH_TOKEN_INFO: + return [WMATIC_TOKEN_INFO]; + case UST_TOKEN_INFO: + return [ETH_TOKEN_INFO, MATIC_TOKEN_INFO]; + } + return []; +}; + +export const ETH_NETWORK_CHAIN_ID = 5; + +export const POLYGON_NETWORK_CHAIN_ID = 80001; export const getEvmChainId = (chainId: ChainId) => - chainId === WORMHOLE_CHAIN_ID_ETHEREUM - ? EVM_ETH_NETWORK_CHAIN_ID - : chainId === WORMHOLE_CHAIN_ID_POLYGON - ? EVM_POLYGON_NETWORK_CHAIN_ID + chainId === CHAIN_ID_ETH + ? ETH_NETWORK_CHAIN_ID + : chainId === CHAIN_ID_POLYGON + ? POLYGON_NETWORK_CHAIN_ID : undefined; -// misc export const RELAYER_FEE_UST = "0.25"; export const WORMHOLE_RPC_HOSTS = [ diff --git a/react/src/utils/math.ts b/react/src/utils/math.ts index a3a4937..7cadd4f 100644 --- a/react/src/utils/math.ts +++ b/react/src/utils/math.ts @@ -6,7 +6,7 @@ export function addFixedAmounts( decimals: number ): string { const sum = FixedNumber.from(left).addUnsafe(FixedNumber.from(right)); - return sum.round(this.getDecimals()).toString(); + return sum.round(decimals).toString(); } export function subtractFixedAmounts( diff --git a/react/src/views/Home.tsx b/react/src/views/Home.tsx index 63db034..a73a2d1 100644 --- a/react/src/views/Home.tsx +++ b/react/src/views/Home.tsx @@ -7,7 +7,12 @@ import { TextField, Typography, } from "@material-ui/core"; -import { ChainId, getSignedVAAWithRetry } from "@certusone/wormhole-sdk"; +import { + ChainId, + CHAIN_ID_TERRA, + getSignedVAAWithRetry, + isEVMChain, +} from "@certusone/wormhole-sdk"; import { useCallback, useEffect, useState } from "react"; import ButtonWithLoader from "../components/ButtonWithLoader"; import EthereumSignerKey from "../components/EthereumSignerKey"; @@ -16,11 +21,11 @@ import { useEthereumProvider } from "../contexts/EthereumProviderContext"; import { ETH_TOKEN_INFO, getEvmChainId, + getSupportedSwaps, MATIC_TOKEN_INFO, RELAYER_FEE_UST, + TokenInfo, TOKEN_INFOS, - WETH_TOKEN_INFO, - WMATIC_TOKEN_INFO, WORMHOLE_RPC_HOSTS, } from "../utils/consts"; import { COLORS } from "../muiTheme"; @@ -34,9 +39,15 @@ import parseError from "../utils/parseError"; import Settings from "../components/Settings"; import getIsTransferCompletedEvmWithRetry from "../utils/getIsTransferCompletedWithRetry"; import CircleLoader from "../components/CircleLoader"; -import { ArrowForward, CheckCircleOutlineRounded } from "@material-ui/icons"; +import { + ArrowForward, + CheckCircleOutlineRounded, + QueueTwoTone, +} from "@material-ui/icons"; import SwapProgress from "../components/SwapProgress"; import Footer from "../components/Footer"; +import TerraWalletKey from "../components/TerraWalletKey"; +import useIsWalletReady from "../hooks/useIsWalletReady"; const useStyles = makeStyles((theme) => ({ bg: { @@ -135,7 +146,7 @@ const useStyles = makeStyles((theme) => ({ }, })); -const switchProviderNetwork = async ( +const switchEvmProviderNetwork = async ( provider: Web3Provider, chainId: ChainId ) => { @@ -152,6 +163,74 @@ const switchProviderNetwork = async ( } }; +const ConnectedWalletAddress = ({ chainId }: { chainId: ChainId }) => { + const { walletAddress } = useIsWalletReady(chainId, false); + if (walletAddress) { + const is0x = walletAddress.startsWith("0x"); + return ( + + {walletAddress?.substring(0, is0x ? 6 : 3)}... + {walletAddress?.substring(walletAddress.length - (is0x ? 4 : 3))} + + ); + } + return null; +}; + +const SwapButton = ({ + source, + target, + disabled, + showLoader, + onClick, +}: { + source: TokenInfo; + target: TokenInfo; + disabled: boolean; + showLoader: boolean; + onClick: () => void; +}) => { + const { isReady: isSourceWalletReady, walletAddress: sourceWalletAddress } = + useIsWalletReady(source.chainId, false); + const { isReady: isTargetWalletReady, walletAddress: targetWalletAddress } = + useIsWalletReady(target.chainId, false); + + console.log( + "sourceWalletAddress", + sourceWalletAddress, + "targetWalletAddress", + targetWalletAddress + ); + + if (!isSourceWalletReady) { + return isEVMChain(source.chainId) ? ( + + ) : source.chainId === CHAIN_ID_TERRA ? ( + + ) : null; + } + + if ( + !isTargetWalletReady && + (!isEVMChain(source.chainId) || !isEVMChain(target.chainId)) + ) { + return isEVMChain(target.chainId) ? ( + + ) : source.chainId === CHAIN_ID_TERRA ? ( + + ) : null; + } + return ( + + Swap + + ); +}; + export default function Home() { const classes = useStyles(); const [sourceTokenInfo, setSourceTokenInfo] = useState(MATIC_TOKEN_INFO); @@ -169,14 +248,16 @@ export default function Home() { const [hasQuote, setHasQuote] = useState(false); const { provider, signer } = useEthereumProvider(); const { enqueueSnackbar } = useSnackbar(); - const [isFirstSwapComplete, setIsFirstSwapComplete] = useState(false); - const [isSecondSwapComplete, setIsSecondSwapComplete] = useState(false); + const [isSourceSwapComplete, setIsSourceSwapComplete] = useState(false); + const [isTargetSwapComplete, setIsTargetSwapComplete] = useState(false); const [sourceTxBlockNumber, setSourceTxBlockNumber] = useState< number | undefined >(undefined); const [hasSignedVAA, setHasSignedVAA] = useState(false); const [relayerTimeoutString, setRelayerTimeoutString] = useState(""); + const foo = useIsWalletReady(sourceTokenInfo.chainId); + const computeQuote = useCallback(() => { (async () => { setHasQuote(false); @@ -205,10 +286,15 @@ export default function Home() { executor.setSlippage((parseFloat(slippage) / 100).toString()); executor.setRelayerFee(RELAYER_FEE_UST); const quote = await executor.computeQuoteExactIn(amountIn); + // TODO: FIX + if (!quote || !quote.dst) { + throw new Error("failed to compute quote"); + } setExecutor(executor); setAmountOut( parseFloat( - executor.tokens.dstOut.formatAmount(quote.dst.minAmountOut) + // executor.tokens.dstOut.formatAmount(quote.dst.minAmountOut) + quote.minAmountOut ).toFixed(8) ); setAmountInUST( @@ -260,30 +346,38 @@ export default function Home() { setDeadline(deadline); }, []); - 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) { - setSourceTokenInfo(WMATIC_TOKEN_INFO); - setTargetTokenInfo(WETH_TOKEN_INFO); - } else if (event.target.value === WETH_TOKEN_INFO.name) { - setSourceTokenInfo(WETH_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); + const handleSourceChange = useCallback( + (event) => { + const tokenInfo = TOKEN_INFOS.find((x) => x.name === event.target.value); + if (tokenInfo) { + const supportedSwaps = getSupportedSwaps(tokenInfo); + console.log(supportedSwaps); + if (supportedSwaps) { + setSourceTokenInfo(tokenInfo); + if (!supportedSwaps.find((x) => x.name === targetTokenInfo.name)) { + setTargetTokenInfo(supportedSwaps[0]); + } + setAmountIn(""); + setAmountOut(""); + } + } + }, + [targetTokenInfo] + ); + + const handleTargetChange = useCallback((event) => { + const tokenInfo = TOKEN_INFOS.find((x) => x.name === event.target.value); + if (tokenInfo) { + setTargetTokenInfo(tokenInfo); + setAmountOut(""); } - setAmountIn(""); - setAmountOut(""); }, []); const reset = useCallback(() => { setIsSwapping(false); setHasQuote(false); - setIsFirstSwapComplete(false); - setIsSecondSwapComplete(false); + setIsSourceSwapComplete(false); + setIsTargetSwapComplete(false); setAmountIn(""); setAmountOut(""); setSourceTxBlockNumber(undefined); @@ -294,18 +388,19 @@ export default function Home() { if (provider && signer && executor) { try { setIsSwapping(true); - setIsFirstSwapComplete(false); + setIsSourceSwapComplete(false); setHasSignedVAA(false); - setIsSecondSwapComplete(false); + setIsTargetSwapComplete(false); setRelayerTimeoutString(""); - await switchProviderNetwork(provider, sourceTokenInfo.chainId); + await switchEvmProviderNetwork(provider, sourceTokenInfo.chainId); - const sourceReceipt = await executor.approveAndSwap(signer); + // TODO: fix + const sourceReceipt = await executor.evmApproveAndSwap(signer); console.info( "firstSwapTransactionHash:", sourceReceipt.transactionHash ); - setIsFirstSwapComplete(true); + setIsSourceSwapComplete(true); setSourceTxBlockNumber(sourceReceipt.blockNumber); // Wait for the guardian network to reach consensus and emit the signedVAA @@ -319,7 +414,9 @@ export default function Home() { // Check if the signedVAA has redeemed by the relayer const isCompleted = await getIsTransferCompletedEvmWithRetry( executor.dstExecutionParams.wormhole.tokenBridgeAddress, - executor.quoter.dstProvider, + // TODO: fix + //@ts-ignore + executor.quoter.getDstEvmProvider(), vaaBytes, // retry for two minutes 3000, @@ -330,14 +427,14 @@ export default function Home() { setRelayerTimeoutString( "Timed out waiting for relayer to complete swap. You'll need to complete it yourself." ); - await switchProviderNetwork(provider, targetTokenInfo.chainId); + await switchEvmProviderNetwork(provider, targetTokenInfo.chainId); const targetReceipt = await executor.fetchVaaAndSwap(signer); console.info( "secondSwapTransactionHash:", targetReceipt.transactionHash ); } - setIsSecondSwapComplete(true); + setIsTargetSwapComplete(true); } catch (e: any) { reset(); console.error(e); @@ -357,6 +454,7 @@ export default function Home() { ]); const readyToSwap = provider && signer && hasQuote; + const disableSelect = isSwapping || isComputingQuote; return (
@@ -367,9 +465,9 @@ export default function Home() {
- + Send {`The max input amount is ${sourceTokenInfo.maxAmount} ${sourceTokenInfo.name}`} ) : null} +
{}} - disabled={true} + onChange={handleTargetChange} + disabled={disableSelect} > Receive (estimated) + +
{`Slippage tolerance: ${slippage}%`} {`Relayer fee: ${RELAYER_FEE_UST} UST`} - {!isSwapping && } - - Swap - + /> - +
@@ -438,14 +545,14 @@ export default function Home() {
- +
Swap completed! - reset()}> + Swap more tokens!
@@ -460,19 +567,23 @@ export default function Home() { {`${amountOut} ${targetTokenInfo.name}`} )} - {isFirstSwapComplete && - !isSecondSwapComplete && + {isSourceSwapComplete && + !isTargetSwapComplete && !relayerTimeoutString && ( - + <> + +
+ )} {relayerTimeoutString && ( {relayerTimeoutString} )} -
WARNING: this is a Testnet release only From b4804451b22507f560a355800162b6a42f5b5c1d Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 01:44:10 +0000 Subject: [PATCH 04/38] Add ustAmountIn to cross parameters interfaces --- react/src/route/cross-quote.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/react/src/route/cross-quote.ts b/react/src/route/cross-quote.ts index 81220e2..87f342a 100644 --- a/react/src/route/cross-quote.ts +++ b/react/src/route/cross-quote.ts @@ -111,6 +111,7 @@ export interface RelayerFee { export interface ExactInCrossParameters { amountIn: string; + ustAmountIn: string; minAmountOut: string; src: ExactInParameters | undefined; dst: ExactInParameters | undefined; @@ -119,6 +120,7 @@ export interface ExactInCrossParameters { export interface ExactOutCrossParameters { amountOut: string; + ustAmountIn: string; maxAmountIn: string; src: ExactOutParameters | undefined; dst: ExactOutParameters | undefined; @@ -236,6 +238,7 @@ export class UniswapToUniswapQuoter { // organize parameters const params: ExactInCrossParameters = { amountIn: amountIn, + ustAmountIn: dstAmountInAfterFee, minAmountOut: dstMinAmountOut, src: this.makeSrcExactInParameters(amountIn, srcMinAmountOut), dst: this.makeDstExactInParameters(dstAmountInAfterFee, dstMinAmountOut), @@ -303,6 +306,7 @@ export class UniswapToUniswapQuoter { // organize parameters const params: ExactOutCrossParameters = { amountOut: amountOut, + ustAmountIn: dstMaxAmountIn, maxAmountIn: srcMaxAmountIn, src: this.makeSrcExactOutParameters( srcAmountOutBeforeFee, From 4aa0d507fedf2c77c2129daa4275ad351b2d4b10 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 01:47:26 +0000 Subject: [PATCH 05/38] Fix using ustAmountIn from quote --- react/src/views/Home.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/react/src/views/Home.tsx b/react/src/views/Home.tsx index a73a2d1..042ab5a 100644 --- a/react/src/views/Home.tsx +++ b/react/src/views/Home.tsx @@ -299,7 +299,8 @@ export default function Home() { ); setAmountInUST( parseFloat( - executor.tokens.dstIn.formatAmount(quote.dst.amountIn) + //executor.tokens.dstIn.formatAmount(quote.dst.amountIn) + quote.ustAmountIn ).toFixed(2) ); setHasQuote(true); From cd548f6016581ae172a177354e0ae1a4723c6615 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 17:32:40 +0000 Subject: [PATCH 06/38] Add terra.js 2.0.14 dependency --- react/package-lock.json | 141 +++++++++++++++++++++++++-------- react/package.json | 1 + react/src/addresses/.gitignore | 1 + react/src/route/.gitignore | 1 + react/src/swapper/.gitignore | 1 + react/src/utils/.gitignore | 1 + 6 files changed, 115 insertions(+), 31 deletions(-) create mode 100644 react/src/route/.gitignore create mode 100644 react/src/swapper/.gitignore create mode 100644 react/src/utils/.gitignore diff --git a/react/package-lock.json b/react/package-lock.json index 9bf83e2..e394d26 100644 --- a/react/package-lock.json +++ b/react/package-lock.json @@ -14,6 +14,7 @@ "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", "@metamask/detect-provider": "^1.2.0", + "@terra-money/terra.js": "^2.0.14", "@terra-money/wallet-provider": "^2.2.0", "@types/node": "^16.11.19", "@types/react": "^17.0.38", @@ -1894,6 +1895,36 @@ "rxjs": "^7.3.0" } }, + "node_modules/@certusone/wormhole-sdk/node_modules/@terra-money/wallet-provider": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.5.3.tgz", + "integrity": "sha512-v/5Z35gCo4nZyZCu3nYDFvhwuvlyDeNSSYmN9KUc9ewoIO9K/2fi3vxcOLcvqq5PYowwwod21vgaQ9QHFV+8eA==", + "dependencies": { + "@terra-dev/browser-check": "^2.5.3", + "@terra-dev/chrome-extension": "^2.5.3", + "@terra-dev/readonly-wallet": "^2.5.3", + "@terra-dev/readonly-wallet-modal": "^2.5.3", + "@terra-dev/use-wallet": "^2.5.3", + "@terra-dev/wallet-types": "^2.5.3", + "@terra-dev/walletconnect": "^2.5.3", + "@terra-dev/web-connector-controller": "^0.8.1", + "@terra-dev/web-connector-interface": "^0.8.1", + "fast-deep-equal": "^3.1.3", + "rxjs": "^7.4.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@terra-money/terra.js": "^2.0.0", + "react": "^17.0.0" + }, + "peerDependenciesMeta": { + "react-router-dom": { + "optional": true + } + } + }, "node_modules/@cnakazawa/watch": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", @@ -4465,6 +4496,36 @@ "react-dom": "^17.0.0" } }, + "node_modules/@terra-dev/web-connector-controller": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@terra-dev/web-connector-controller/-/web-connector-controller-0.8.1.tgz", + "integrity": "sha512-TIwFtta7vN2GdDUy8SbIIsfTd9XWiU+U6yjizd82yUAhlOJNAGHshG0r1j0irkA5MycYG1duAlr7foeKRu4PGA==", + "dependencies": { + "@terra-dev/web-connector-interface": "^0.8.1", + "bowser": "^2.11.0", + "rxjs": "^7.4.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@terra-money/terra.js": "^2.0.0" + } + }, + "node_modules/@terra-dev/web-connector-interface": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@terra-dev/web-connector-interface/-/web-connector-interface-0.8.1.tgz", + "integrity": "sha512-ryA3xtTFJ7OkAF6pTlrsuqxtSUp0DxHhyxvzwRPbT3h8VqlkFStknvYjRwNRspN2LOpi4/F1TNFzcUBNHPCo2g==", + "dependencies": { + "rxjs": "^7.4.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@terra-money/terra.js": "^2.0.0" + } + }, "node_modules/@terra-dev/web-extension": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@terra-dev/web-extension/-/web-extension-0.6.0.tgz", @@ -4479,11 +4540,10 @@ } }, "node_modules/@terra-money/terra.js": { - "version": "2.1.23", - "resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-2.1.23.tgz", - "integrity": "sha512-nSAR35zqjKUn1Jzqevf30s47XRlW/VXU01YgK3n9ndmX15lkdlgFvqaV7UezK0xAmCpm+7xWIrtBTMmZpVBkMQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-2.0.14.tgz", + "integrity": "sha512-GeMadRIPaOedODa5a0pJ2+76l7MeFSIfSJZ2vvWPRco6MRIQLw/k0cZpPKMLm2Zo54li/oY1mrR+r3uxLM7q3Q==", "dependencies": { - "@terra-money/terra.proto": "^0.1.7", "axios": "^0.21.1", "bech32": "^2.0.0", "bip32": "^2.0.6", @@ -4498,7 +4558,7 @@ "ws": "^7.4.2" }, "engines": { - "node": ">=14" + "node": ">=12" } }, "node_modules/@terra-money/terra.js/node_modules/axios": { @@ -4509,16 +4569,6 @@ "follow-redirects": "^1.14.0" } }, - "node_modules/@terra-money/terra.proto": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz", - "integrity": "sha512-NXD7f6pQCulvo6+mv6MAPzhOkUzRjgYVuHZE/apih+lVnPG5hDBU0rRYnOGGofwvKT5/jQoOENnFn/gioWWnyQ==", - "dependencies": { - "google-protobuf": "^3.17.3", - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, "node_modules/@terra-money/wallet-provider": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.2.0.tgz", @@ -12307,7 +12357,8 @@ "node_modules/google-protobuf": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.19.3.tgz", - "integrity": "sha512-3GRDj8o9XjcALYjgxNKeD7Wm6w/V8r1Jo4sLYMic9+VaIMLBx8TQeHP9yaoRoDymNONhnkmmveDPyjw/Fpw8+A==" + "integrity": "sha512-3GRDj8o9XjcALYjgxNKeD7Wm6w/V8r1Jo4sLYMic9+VaIMLBx8TQeHP9yaoRoDymNONhnkmmveDPyjw/Fpw8+A==", + "peer": true }, "node_modules/graceful-fs": { "version": "4.2.9", @@ -27339,6 +27390,26 @@ "js-base64": "^3.6.1", "protobufjs": "^6.11.2", "rxjs": "^7.3.0" + }, + "dependencies": { + "@terra-money/wallet-provider": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.5.3.tgz", + "integrity": "sha512-v/5Z35gCo4nZyZCu3nYDFvhwuvlyDeNSSYmN9KUc9ewoIO9K/2fi3vxcOLcvqq5PYowwwod21vgaQ9QHFV+8eA==", + "requires": { + "@terra-dev/browser-check": "^2.5.3", + "@terra-dev/chrome-extension": "^2.5.3", + "@terra-dev/readonly-wallet": "^2.5.3", + "@terra-dev/readonly-wallet-modal": "^2.5.3", + "@terra-dev/use-wallet": "^2.5.3", + "@terra-dev/wallet-types": "^2.5.3", + "@terra-dev/walletconnect": "^2.5.3", + "@terra-dev/web-connector-controller": "^0.8.1", + "@terra-dev/web-connector-interface": "^0.8.1", + "fast-deep-equal": "^3.1.3", + "rxjs": "^7.4.0" + } + } } }, "@cnakazawa/watch": { @@ -29113,6 +29184,24 @@ "styled-components": "^5.0.0" } }, + "@terra-dev/web-connector-controller": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@terra-dev/web-connector-controller/-/web-connector-controller-0.8.1.tgz", + "integrity": "sha512-TIwFtta7vN2GdDUy8SbIIsfTd9XWiU+U6yjizd82yUAhlOJNAGHshG0r1j0irkA5MycYG1duAlr7foeKRu4PGA==", + "requires": { + "@terra-dev/web-connector-interface": "^0.8.1", + "bowser": "^2.11.0", + "rxjs": "^7.4.0" + } + }, + "@terra-dev/web-connector-interface": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@terra-dev/web-connector-interface/-/web-connector-interface-0.8.1.tgz", + "integrity": "sha512-ryA3xtTFJ7OkAF6pTlrsuqxtSUp0DxHhyxvzwRPbT3h8VqlkFStknvYjRwNRspN2LOpi4/F1TNFzcUBNHPCo2g==", + "requires": { + "rxjs": "^7.4.0" + } + }, "@terra-dev/web-extension": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@terra-dev/web-extension/-/web-extension-0.6.0.tgz", @@ -29124,11 +29213,10 @@ } }, "@terra-money/terra.js": { - "version": "2.1.23", - "resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-2.1.23.tgz", - "integrity": "sha512-nSAR35zqjKUn1Jzqevf30s47XRlW/VXU01YgK3n9ndmX15lkdlgFvqaV7UezK0xAmCpm+7xWIrtBTMmZpVBkMQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-2.0.14.tgz", + "integrity": "sha512-GeMadRIPaOedODa5a0pJ2+76l7MeFSIfSJZ2vvWPRco6MRIQLw/k0cZpPKMLm2Zo54li/oY1mrR+r3uxLM7q3Q==", "requires": { - "@terra-money/terra.proto": "^0.1.7", "axios": "^0.21.1", "bech32": "^2.0.0", "bip32": "^2.0.6", @@ -29153,16 +29241,6 @@ } } }, - "@terra-money/terra.proto": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz", - "integrity": "sha512-NXD7f6pQCulvo6+mv6MAPzhOkUzRjgYVuHZE/apih+lVnPG5hDBU0rRYnOGGofwvKT5/jQoOENnFn/gioWWnyQ==", - "requires": { - "google-protobuf": "^3.17.3", - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, "@terra-money/wallet-provider": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.2.0.tgz", @@ -35397,7 +35475,8 @@ "google-protobuf": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.19.3.tgz", - "integrity": "sha512-3GRDj8o9XjcALYjgxNKeD7Wm6w/V8r1Jo4sLYMic9+VaIMLBx8TQeHP9yaoRoDymNONhnkmmveDPyjw/Fpw8+A==" + "integrity": "sha512-3GRDj8o9XjcALYjgxNKeD7Wm6w/V8r1Jo4sLYMic9+VaIMLBx8TQeHP9yaoRoDymNONhnkmmveDPyjw/Fpw8+A==", + "peer": true }, "graceful-fs": { "version": "4.2.9", diff --git a/react/package.json b/react/package.json index 101dc96..4dd492c 100644 --- a/react/package.json +++ b/react/package.json @@ -10,6 +10,7 @@ "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", "@metamask/detect-provider": "^1.2.0", + "@terra-money/terra.js": "^2.0.14", "@terra-money/wallet-provider": "^2.2.0", "@types/node": "^16.11.19", "@types/react": "^17.0.38", diff --git a/react/src/addresses/.gitignore b/react/src/addresses/.gitignore index b44d3a4..98b1c8c 100644 --- a/react/src/addresses/.gitignore +++ b/react/src/addresses/.gitignore @@ -1,2 +1,3 @@ goerli.ts mumbai.ts +*.js diff --git a/react/src/route/.gitignore b/react/src/route/.gitignore new file mode 100644 index 0000000..a6c7c28 --- /dev/null +++ b/react/src/route/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/react/src/swapper/.gitignore b/react/src/swapper/.gitignore new file mode 100644 index 0000000..a6c7c28 --- /dev/null +++ b/react/src/swapper/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/react/src/utils/.gitignore b/react/src/utils/.gitignore new file mode 100644 index 0000000..a6c7c28 --- /dev/null +++ b/react/src/utils/.gitignore @@ -0,0 +1 @@ +*.js From caa9a40f1e17722e2ede948dd305a3990f40c0be Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 17:35:52 +0000 Subject: [PATCH 07/38] Add misc scripts --- misc/.gitignore | 5 + misc/package.json | 40 ++++ misc/scripts/check-ust-quotes.ts | 394 +++++++++++++++++++++++++++++++ misc/scripts/src/custom.d.ts | 4 + misc/scripts/src/provider.ts | 21 ++ misc/scripts/swap-with-vaa.ts | 390 ++++++++++++++++++++++++++++++ misc/tsconfig.json | 13 + 7 files changed, 867 insertions(+) create mode 100644 misc/.gitignore create mode 100644 misc/package.json create mode 100644 misc/scripts/check-ust-quotes.ts create mode 100644 misc/scripts/src/custom.d.ts create mode 100644 misc/scripts/src/provider.ts create mode 100644 misc/scripts/swap-with-vaa.ts create mode 100644 misc/tsconfig.json diff --git a/misc/.gitignore b/misc/.gitignore new file mode 100644 index 0000000..72b3ff2 --- /dev/null +++ b/misc/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +scripts/*.js +scripts/src/*.js +./src +package-lock.json diff --git a/misc/package.json b/misc/package.json new file mode 100644 index 0000000..26bb5f5 --- /dev/null +++ b/misc/package.json @@ -0,0 +1,40 @@ +{ + "homepage": "https://certusone.github.io/wormhole-nativeswap-example", + "name": "NativeSwap", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "tsc" + }, + "dependencies": { + "@certusone/wormhole-sdk": "^0.1.6", + "@improbable-eng/grpc-web-node-http-transport": "^0.15.0", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.11.2", + "@material-ui/lab": "^4.0.0-alpha.60", + "@metamask/detect-provider": "^1.2.0", + "@terra-money/terra.js": "^2.0.14", + "@terra-money/wallet-provider": "^2.2.0", + "@types/node": "^16.11.19", + "@types/react": "^17.0.38", + "@types/react-dom": "^17.0.11", + "@uniswap/smart-order-router": "^2.1.1", + "@uniswap/v2-core": "^1.0.1", + "@uniswap/v2-sdk": "^3.0.1", + "@uniswap/v3-periphery": "1.3", + "@uniswap/v3-sdk": "^3.8.1", + "ethers": "^5.5.3", + "jsbi": "^3.2.5", + "notistack": "^1.0.10", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-scripts": "4.0.3", + "typescript": "^4.4.2", + "use-debounce": "^7.0.1" + }, + "devDependencies": { + "@craco/craco": "^6.3.0", + "gh-pages": "^3.2.3", + "wasm-loader": "^1.3.0" + } +} diff --git a/misc/scripts/check-ust-quotes.ts b/misc/scripts/check-ust-quotes.ts new file mode 100644 index 0000000..0a9b2fc --- /dev/null +++ b/misc/scripts/check-ust-quotes.ts @@ -0,0 +1,394 @@ +import { ethers } from "ethers"; + +import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport"; + +import { + ExactInCrossParameters, + ExactOutCrossParameters, + UniswapToUniswapQuoter, +} from "../src/route/cross-quote"; + +import { UniswapToUniswapExecutor } from "../src/swapper/swapper"; + +import { makeProvider } from "./src/provider"; +import { + ETH_TOKEN_INFO, + MATIC_TOKEN_INFO, + UST_TOKEN_INFO, +} from "../src/utils/consts"; + +require("dotenv").config({ path: ".env" }); + +// swap related parameters (configurable in UI) +const SWAP_AMOUNT_IN_WMATIC = "0.0069"; +const SWAP_AMOUNT_IN_WETH = "0.000907"; +const SWAP_AMOUNT_IN_UST = "3.40"; + +const SWAP_DEADLINE = "1800"; +const SWAP_SLIPPAGE = "0.01"; + +// token bridge things +const BRIDGE_RELAYER_FEE_UST = "0.25"; + +export function makeEvmWallet( + provider: ethers.providers.Provider +): ethers.Wallet { + return new ethers.Wallet(process.env.ETH_PRIVATE_KEY, provider); +} + +/* +async function fetchTokenBalance(signer, contract) { + const decimals = await contract.decimals(); + const balanceBeforeDecimals = (await contract.balanceOf(signer.address)).toString(); + const balance = ethers.utils.formatUnits(balanceBeforeDecimals, decimals); + return balance; +} +*/ + +// only exist as placeholder for actual wallet connection +function determineWalletFromToken(tokenAddress: string): ethers.Wallet { + if (tokenAddress === UST_TOKEN_INFO.address) { + return undefined; + } + return makeEvmWallet(makeProvider(tokenAddress)); +} + +function determineAmountFromToken(tokenAddress: string): string { + switch (tokenAddress) { + case ETH_TOKEN_INFO.address: { + return SWAP_AMOUNT_IN_WETH; + } + case MATIC_TOKEN_INFO.address: { + return SWAP_AMOUNT_IN_WMATIC; + } + case UST_TOKEN_INFO.address: { + return SWAP_AMOUNT_IN_UST; + } + default: { + throw Error("you suck"); + } + } +} + +function logExactInParameters( + quoter: UniswapToUniswapQuoter, + params: ExactInCrossParameters +): void { + console.info(`amountIn: ${params.amountIn}`); + console.info(`minAmountOut: ${params.minAmountOut}`); + + const src = params.src; + if (src === undefined) { + console.warn(` src is undefined (ust?)`); + } else { + console.info(`src`); + console.info(` protocol: ${src.protocol}`); + //console.info(` amountIn: ${quoter.srcTokenIn.formatAmount(src.amountIn)}`); + console.info( + ` amountIn: ${quoter.srcRouter.formatAmountIn( + src.amountIn.toString() + )}` + ); + console.info( + // ` minAmountOut: ${quoter.srcTokenOut.formatAmount(src.minAmountOut)}` + ` minAmountOut: ${quoter.srcRouter.formatAmountOut( + src.minAmountOut.toString() + )}` + ); + console.info(` poolFee: ${src.poolFee}`); + console.info(` deadline: ${src.deadline.toString()}`); + console.info(` path: ${src.path}`); + } + + const dst = params.dst; + if (dst === undefined) { + console.warn(` dst is undefined (ust?)`); + } else { + console.info(`dst`); + console.info(` protocol: ${dst.protocol}`); + //console.info(` amountIn: ${quoter.dstTokenIn.formatAmount(dst.amountIn)}`); + console.info( + ` amountIn: ${quoter.dstRouter.formatAmountIn( + dst.amountIn.toString() + )}` + ); + console.info( + // ` minAmountOut: ${quoter.dstTokenOut.formatAmount(dst.minAmountOut)}` + ` minAmountOut: ${quoter.dstRouter.formatAmountOut( + dst.minAmountOut.toString() + )}` + ); + console.info(` poolFee: ${dst.poolFee}`); + console.info(` deadline: ${dst.deadline.toString()}`); + console.info(` path: ${dst.path}`); + + const relayerFee = params.relayerFee; + console.info(`relayerFee`); + console.info(` tokenAddress: ${relayerFee.tokenAddress}`); + console.info( + ` amount: ${quoter.dstRouter.formatAmountIn(relayerFee.amount)}` + ); + } + + return; +} + +async function swapEverythingExactIn( + swapper: UniswapToUniswapExecutor, + tokenInAddress: string, + tokenOutAddress: string, + isNative: boolean, + amountIn: string +): Promise { + // connect src wallet + //const srcWallet = determineWalletFromToken(tokenInAddress); + //console.info(`wallet pubkey: ${await srcWallet.getAddress()}`); + + // tokens selected, let's initialize + await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); + console.info(`quoter initialized`); + + /* + const tokens = swapper.getTokens(); + + // display tokens on front-end? + console.info( + `srcTokenIn: ${tokens.srcIn.getAddress()} (${tokens.srcIn.getDecimals()})` + ); + console.info( + `srcTokenOut: ${tokens.srcOut.getAddress()} (${tokens.srcOut.getDecimals()})` + ); + console.info( + `dstTokenIn: ${tokens.dstIn.getAddress()} (${tokens.dstIn.getDecimals()})` + ); + console.info( + `dstTokenOut: ${tokens.dstOut.getAddress()} (${tokens.dstOut.getDecimals()})` + ); + */ + + // verify pool address on src and dst + await swapper + .computeAndVerifySrcPoolAddress() + .then((address) => { + console.info(`srcPool: ${address}`); + return address; + }) + .catch((response) => { + console.error( + `failed to find a pool address for src. how to handle in the front-end?` + ); + process.exit(1); + }); + + await swapper + .computeAndVerifyDstPoolAddress() + .then((address) => { + console.info(`dstPool: ${address}`); + return address; + }) + .catch((response) => { + console.error( + `failed to find a pool address for dst. how to handle in the front-end?` + ); + process.exit(1); + }); + + // set deadline + swapper.setDeadlines(SWAP_DEADLINE); + swapper.setSlippage(SWAP_SLIPPAGE); + swapper.setRelayerFee(BRIDGE_RELAYER_FEE_UST); + + const exactInParameters: ExactInCrossParameters = + await swapper.computeQuoteExactIn(amountIn); + + console.info("exactInParameters"); + logExactInParameters(swapper.quoter, exactInParameters); + + return; +} + +function logExactOutParameters( + quoter: UniswapToUniswapQuoter, + params: ExactOutCrossParameters +): void { + console.info(`amountIn: ${params.amountOut}`); + console.info(`minAmountOut: ${params.maxAmountIn}`); + + const src = params.src; + console.info(`src`); + if (src === undefined) { + } else { + } + console.info(` protocol: ${src.protocol}`); + console.info( + ` amountOut: ${quoter.srcRouter.formatAmountOut( + src.amountOut.toString() + )}` + ); + console.info( + ` maxAmountIn: ${quoter.srcRouter.formatAmountIn( + src.maxAmountIn.toString() + )}` + ); + console.info(` poolFee: ${src.poolFee}`); + console.info(` deadline: ${src.deadline.toString()}`); + console.info(` path: ${src.path}`); + + const dst = params.dst; + console.info(`dst`); + console.info(` protocol: ${dst.protocol}`); + console.info( + ` amountOut: ${quoter.dstRouter.formatAmountOut( + dst.amountOut.toString() + )}` + ); + console.info( + ` maxAmountIn: ${quoter.dstRouter.formatAmountIn( + dst.maxAmountIn.toString() + )}` + ); + console.info(` poolFee: ${dst.poolFee}`); + console.info(` deadline: ${dst.deadline.toString()}`); + console.info(` path: ${dst.path}`); + + const relayerFee = params.relayerFee; + console.info(`relayerFee`); + console.info(` tokenAddress: ${relayerFee.tokenAddress}`); + console.info( + ` amount: ${quoter.dstRouter.formatAmountIn( + relayerFee.amount.toString() + )}` + ); + return; +} + +async function swapEverythingExactOut( + swapper: UniswapToUniswapExecutor, + tokenInAddress: string, + tokenOutAddress: string, + isNative: boolean, + amountOut: string +): Promise { + // connect src wallet + //const srcWallet = determineWalletFromToken(tokenInAddress); + //console.info(`wallet pubkey: ${await srcWallet.getAddress()}`); + + // tokens selected, let's initialize + await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); + console.info(`quoter initialized`); + + /* + const tokens = swapper.getTokens(); + + // display tokens on front-end? + console.info( + `srcTokenIn: ${tokens.srcIn.getAddress()} (${tokens.srcIn.getDecimals()})` + ); + console.info( + `srcTokenOut: ${tokens.srcOut.getAddress()} (${tokens.srcOut.getDecimals()})` + ); + console.info( + `dstTokenIn: ${tokens.dstIn.getAddress()} (${tokens.dstIn.getDecimals()})` + ); + console.info( + `dstTokenOut: ${tokens.dstOut.getAddress()} (${tokens.dstOut.getDecimals()})` + ); + */ + + // verify pool address on src and dst + await swapper + .computeAndVerifySrcPoolAddress() + .then((address) => { + console.info(`srcPool: ${address}`); + return address; + }) + .catch((response) => { + console.error( + `failed to find a pool address for src. how to handle in the front-end?` + ); + process.exit(1); + }); + + await swapper + .computeAndVerifyDstPoolAddress() + .then((address) => { + console.info(`dstPool: ${address}`); + return address; + }) + .catch((response) => { + console.error( + `failed to find a pool address for dst. how to handle in the front-end?` + ); + process.exit(1); + }); + + // set deadline + swapper.setDeadlines(SWAP_DEADLINE); + swapper.setSlippage(SWAP_SLIPPAGE); + swapper.setRelayerFee(BRIDGE_RELAYER_FEE_UST); + + const exactOutParameters: ExactOutCrossParameters = + await swapper.computeQuoteExactOut(amountOut); + + console.info("exactOutParameters"); + logExactOutParameters(swapper.quoter, exactOutParameters); + + return; +} + +async function main() { + const testExactIn = true; + const isNative = true; + + const swapper = new UniswapToUniswapExecutor(); + swapper.setTransport(NodeHttpTransport()); + + //const tokenInAddress = MATIC_TOKEN_INFO.address; + const tokenInAddress = UST_TOKEN_INFO.address; + const tokenOutAddress = ETH_TOKEN_INFO.address; + + if (testExactIn) { + console.info(`testing exact in. native=${isNative}`); + + console.info("wmatic -> weth"); + await swapEverythingExactIn( + swapper, + tokenInAddress, + tokenOutAddress, + isNative, + determineAmountFromToken(tokenInAddress) + ); + + console.info("weth -> wmatic"); + await swapEverythingExactIn( + swapper, + tokenOutAddress, + tokenInAddress, + isNative, + determineAmountFromToken(tokenOutAddress) + ); + } else { + console.info(`testing exact out. native=${isNative}`); + + console.info("wmatic -> weth"); + await swapEverythingExactOut( + swapper, + tokenInAddress, + tokenOutAddress, + isNative, + determineAmountFromToken(tokenOutAddress) + ); + + console.info("weth -> wmatic"); + await swapEverythingExactOut( + swapper, + tokenOutAddress, + tokenInAddress, + isNative, + determineAmountFromToken(tokenInAddress) + ); + } + + return; +} +main(); diff --git a/misc/scripts/src/custom.d.ts b/misc/scripts/src/custom.d.ts new file mode 100644 index 0000000..1a3dd3c --- /dev/null +++ b/misc/scripts/src/custom.d.ts @@ -0,0 +1,4 @@ +declare module "*.svg" { + const content: any; + export default content; +} diff --git a/misc/scripts/src/provider.ts b/misc/scripts/src/provider.ts new file mode 100644 index 0000000..65937fc --- /dev/null +++ b/misc/scripts/src/provider.ts @@ -0,0 +1,21 @@ +import { ethers } from "ethers"; + +import { ETH_TOKEN_INFO, MATIC_TOKEN_INFO } from "../../src/utils/consts"; + +export function makeProvider(tokenAddress: string) { + switch (tokenAddress) { + case ETH_TOKEN_INFO.address: { + return new ethers.providers.StaticJsonRpcProvider( + process.env.GOERLI_PROVIDER + ); + } + case MATIC_TOKEN_INFO.address: { + return new ethers.providers.StaticJsonRpcProvider( + process.env.MUMBAI_PROVIDER + ); + } + default: { + throw Error("unrecognized token address"); + } + } +} diff --git a/misc/scripts/swap-with-vaa.ts b/misc/scripts/swap-with-vaa.ts new file mode 100644 index 0000000..31f6065 --- /dev/null +++ b/misc/scripts/swap-with-vaa.ts @@ -0,0 +1,390 @@ +import { ethers } from "ethers"; + +import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport"; + +import { + ExactInCrossParameters, + ExactOutCrossParameters, + UniswapToUniswapQuoter, +} from "../src/route/cross-quote"; + +import { UniswapToUniswapExecutor } from "../src/swapper/swapper"; + +import { makeProvider } from "./src/provider"; + +require("dotenv").config({ path: ".env" }); + +// quote using these +const POLYGON_TOKEN_WMATIC = "0x9c3c9283d3e44854697cd22d3faa240cfb032889"; +const ETHEREUM_TOKEN_WETH = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"; + +// swap related parameters (configurable in UI) +const SWAP_AMOUNT_IN_WMATIC = "0.0069"; +const SWAP_AMOUNT_IN_WETH = "0.000123"; +const SWAP_DEADLINE = "1800"; +const SWAP_SLIPPAGE = "0.01"; + +// token bridge things +const BRIDGE_RELAYER_FEE_UST = "0.25"; + +export function makeEvmWallet( + provider: ethers.providers.Provider +): ethers.Wallet { + return new ethers.Wallet(process.env.ETH_PRIVATE_KEY, provider); +} + +/* +async function fetchTokenBalance(signer, contract) { + const decimals = await contract.decimals(); + const balanceBeforeDecimals = (await contract.balanceOf(signer.address)).toString(); + const balance = ethers.utils.formatUnits(balanceBeforeDecimals, decimals); + return balance; +} +*/ + +// only exist as placeholder for actual wallet connection +function determineWalletFromToken(tokenAddress: string): ethers.Wallet { + return makeEvmWallet(makeProvider(tokenAddress)); +} + +function determineAmountFromToken(tokenAddress: string): string { + if (tokenAddress === ETHEREUM_TOKEN_WETH) { + return SWAP_AMOUNT_IN_WETH; + } else if (tokenAddress === POLYGON_TOKEN_WMATIC) { + return SWAP_AMOUNT_IN_WMATIC; + } else { + throw Error("you suck"); + } +} + +function logExactInParameters( + quoter: UniswapToUniswapQuoter, + params: ExactInCrossParameters +): void { + const src = params.src; + console.info(`src`); + console.info(` protocol: ${src.protocol}`); + //console.info(` amountIn: ${quoter.srcTokenIn.formatAmount(src.amountIn)}`); + console.info( + ` amountIn: ${quoter.srcRouter.formatAmountIn( + src.amountIn.toString() + )}` + ); + console.info( + // ` minAmountOut: ${quoter.srcTokenOut.formatAmount(src.minAmountOut)}` + ` minAmountOut: ${quoter.srcRouter.formatAmountOut( + src.minAmountOut.toString() + )}` + ); + console.info(` poolFee: ${src.poolFee}`); + console.info(` deadline: ${src.deadline.toString()}`); + console.info(` path: ${src.path}`); + + const dst = params.dst; + console.info(`dst`); + console.info(` protocol: ${dst.protocol}`); + //console.info(` amountIn: ${quoter.dstTokenIn.formatAmount(dst.amountIn)}`); + console.info( + ` amountIn: ${quoter.dstRouter.formatAmountIn( + dst.amountIn.toString() + )}` + ); + console.info( + // ` minAmountOut: ${quoter.dstTokenOut.formatAmount(dst.minAmountOut)}` + ` minAmountOut: ${quoter.dstRouter.formatAmountOut( + dst.minAmountOut.toString() + )}` + ); + console.info(` poolFee: ${dst.poolFee}`); + console.info(` deadline: ${dst.deadline.toString()}`); + console.info(` path: ${dst.path}`); + + const relayerFee = params.relayerFee; + console.info(`relayerFee`); + console.info(` tokenAddress: ${relayerFee.tokenAddress}`); + console.info( + ` amount: ${quoter.dstRouter.formatAmountIn(relayerFee.amount)}` + ); + return; +} + +async function swapEverythingExactIn( + swapper: UniswapToUniswapExecutor, + tokenInAddress: string, + tokenOutAddress: string, + isNative: boolean, + amountIn: string +): Promise { + // connect src wallet + const srcWallet = determineWalletFromToken(tokenInAddress); + console.info(`wallet pubkey: ${await srcWallet.getAddress()}`); + + // tokens selected, let's initialize + await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); + console.info(`quoter initialized`); + + /* + const tokens = swapper.getTokens(); + + // display tokens on front-end? + console.info( + `srcTokenIn: ${tokens.srcIn.getAddress()} (${tokens.srcIn.getDecimals()})` + ); + console.info( + `srcTokenOut: ${tokens.srcOut.getAddress()} (${tokens.srcOut.getDecimals()})` + ); + console.info( + `dstTokenIn: ${tokens.dstIn.getAddress()} (${tokens.dstIn.getDecimals()})` + ); + console.info( + `dstTokenOut: ${tokens.dstOut.getAddress()} (${tokens.dstOut.getDecimals()})` + ); + */ + + // verify pool address on src and dst + await swapper + .computeAndVerifySrcPoolAddress() + .then((address) => { + console.info(`srcPool: ${address}`); + return address; + }) + .catch((response) => { + console.error( + `failed to find a pool address for src. how to handle in the front-end?` + ); + process.exit(1); + }); + + await swapper + .computeAndVerifyDstPoolAddress() + .then((address) => { + console.info(`dstPool: ${address}`); + return address; + }) + .catch((response) => { + console.error( + `failed to find a pool address for dst. how to handle in the front-end?` + ); + process.exit(1); + }); + + // set deadline + swapper.setDeadlines(SWAP_DEADLINE); + swapper.setSlippage(SWAP_SLIPPAGE); + swapper.setRelayerFee(BRIDGE_RELAYER_FEE_UST); + + const exactInParameters: ExactInCrossParameters = + await swapper.computeQuoteExactIn(amountIn); + + console.info("exactInParameters"); + logExactInParameters(swapper.quoter, exactInParameters); + + // do the src swap + console.info("approveAndSwap"); + const srcSwapReceipt = await swapper.evmApproveAndSwap(srcWallet); + console.info(`src transaction: ${srcSwapReceipt.transactionHash}`); + + // do the dst swap after fetching vaa + // connect dst wallet + const dstWallet = determineWalletFromToken(tokenOutAddress); + + console.info("fetchVaaAndSwap"); + //const dstSwapReceipt = await swapper.fetchVaaAndSwap(dstWallet); + //console.info(`dst transaction: ${dstSwapReceipt.transactionHash}`); + console.warn("jk"); + + return; +} + +function logExactOutParameters( + quoter: UniswapToUniswapQuoter, + params: ExactOutCrossParameters +): void { + const src = params.src; + console.info(`src`); + console.info(` protocol: ${src.protocol}`); + console.info( + ` amountOut: ${quoter.srcRouter.formatAmountOut( + src.amountOut.toString() + )}` + ); + console.info( + ` maxAmountIn: ${quoter.srcRouter.formatAmountIn( + src.maxAmountIn.toString() + )}` + ); + console.info(` poolFee: ${src.poolFee}`); + console.info(` deadline: ${src.deadline.toString()}`); + console.info(` path: ${src.path}`); + + const dst = params.dst; + console.info(`dst`); + console.info(` protocol: ${dst.protocol}`); + console.info( + ` amountOut: ${quoter.dstRouter.formatAmountOut( + dst.amountOut.toString() + )}` + ); + console.info( + ` maxAmountIn: ${quoter.dstRouter.formatAmountIn( + dst.maxAmountIn.toString() + )}` + ); + console.info(` poolFee: ${dst.poolFee}`); + console.info(` deadline: ${dst.deadline.toString()}`); + console.info(` path: ${dst.path}`); + + const relayerFee = params.relayerFee; + console.info(`relayerFee`); + console.info(` tokenAddress: ${relayerFee.tokenAddress}`); + console.info( + ` amount: ${quoter.dstRouter.formatAmountIn( + relayerFee.amount.toString() + )}` + ); + return; +} + +async function swapEverythingExactOut( + swapper: UniswapToUniswapExecutor, + tokenInAddress: string, + tokenOutAddress: string, + isNative: boolean, + amountOut: string +): Promise { + // connect src wallet + const srcWallet = determineWalletFromToken(tokenInAddress); + console.info(`wallet pubkey: ${await srcWallet.getAddress()}`); + + // tokens selected, let's initialize + await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); + console.info(`quoter initialized`); + + /* + const tokens = swapper.getTokens(); + + // display tokens on front-end? + console.info( + `srcTokenIn: ${tokens.srcIn.getAddress()} (${tokens.srcIn.getDecimals()})` + ); + console.info( + `srcTokenOut: ${tokens.srcOut.getAddress()} (${tokens.srcOut.getDecimals()})` + ); + console.info( + `dstTokenIn: ${tokens.dstIn.getAddress()} (${tokens.dstIn.getDecimals()})` + ); + console.info( + `dstTokenOut: ${tokens.dstOut.getAddress()} (${tokens.dstOut.getDecimals()})` + ); + */ + + // verify pool address on src and dst + await swapper + .computeAndVerifySrcPoolAddress() + .then((address) => { + console.info(`srcPool: ${address}`); + return address; + }) + .catch((response) => { + console.error( + `failed to find a pool address for src. how to handle in the front-end?` + ); + process.exit(1); + }); + + await swapper + .computeAndVerifyDstPoolAddress() + .then((address) => { + console.info(`dstPool: ${address}`); + return address; + }) + .catch((response) => { + console.error( + `failed to find a pool address for dst. how to handle in the front-end?` + ); + process.exit(1); + }); + + // set deadline + swapper.setDeadlines(SWAP_DEADLINE); + swapper.setSlippage(SWAP_SLIPPAGE); + swapper.setRelayerFee(BRIDGE_RELAYER_FEE_UST); + + const exactOutParameters: ExactOutCrossParameters = + await swapper.computeQuoteExactOut(amountOut); + + console.info("exactOutParameters"); + logExactOutParameters(swapper.quoter, exactOutParameters); + + // do the src swap + console.info("approveAndSwap"); + const srcSwapReceipt = await swapper.evmApproveAndSwap(srcWallet); + console.info(`src transaction: ${srcSwapReceipt.transactionHash}`); + + // do the dst swap after fetching vaa + // connect dst wallet + const dstWallet = determineWalletFromToken(tokenOutAddress); + + console.info("fetchVaaAndSwap"); + //const dstSwapReceipt = await swapper.fetchVaaAndSwap(dstWallet); + //console.info(`dst transaction: ${dstSwapReceipt.transactionHash}`); + console.warn("jk"); + + return; +} + +async function main() { + const testExactIn = true; + const isNative = true; + + const swapper = new UniswapToUniswapExecutor(); + swapper.setTransport(NodeHttpTransport()); + + const tokenInAddress = POLYGON_TOKEN_WMATIC; + const tokenOutAddress = ETHEREUM_TOKEN_WETH; + + if (testExactIn) { + console.info(`testing exact in. native=${isNative}`); + + console.info("wmatic -> weth"); + await swapEverythingExactIn( + swapper, + tokenInAddress, + tokenOutAddress, + isNative, + determineAmountFromToken(tokenInAddress) + ); + + console.info("weth -> wmatic"); + await swapEverythingExactIn( + swapper, + tokenOutAddress, + tokenInAddress, + isNative, + determineAmountFromToken(tokenOutAddress) + ); + } else { + console.info(`testing exact out. native=${isNative}`); + + console.info("wmatic -> weth"); + await swapEverythingExactOut( + swapper, + tokenInAddress, + tokenOutAddress, + isNative, + determineAmountFromToken(tokenOutAddress) + ); + + console.info("weth -> wmatic"); + await swapEverythingExactOut( + swapper, + tokenOutAddress, + tokenInAddress, + isNative, + determineAmountFromToken(tokenInAddress) + ); + } + + return; +} +main(); diff --git a/misc/tsconfig.json b/misc/tsconfig.json new file mode 100644 index 0000000..fc2e218 --- /dev/null +++ b/misc/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "esModuleInterop": true + }, + "include": [ + "scripts/src/custom.d.ts" + ], + "files": [ + "scripts/swap-with-vaa.ts", + "scripts/check-ust-quotes.ts" + ] +} From e5bd5b11422fc2e5c3f95f415967407883783e9c Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 17:36:11 +0000 Subject: [PATCH 08/38] Update .gitignore --- misc/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/.gitignore b/misc/.gitignore index 72b3ff2..9b84438 100644 --- a/misc/.gitignore +++ b/misc/.gitignore @@ -1,5 +1,5 @@ node_modules/ scripts/*.js scripts/src/*.js -./src +src package-lock.json From 65c0c545b23f30ee4a8aa27c992f23d0b616a571 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 18:03:18 +0000 Subject: [PATCH 09/38] Add rm script; fix tsconfig.json --- misc/package.json | 3 ++- misc/rm_js_in_src.sh | 4 ++++ misc/scripts/src/custom.d.ts | 4 ---- misc/tsconfig.json | 3 --- 4 files changed, 6 insertions(+), 8 deletions(-) create mode 100755 misc/rm_js_in_src.sh delete mode 100644 misc/scripts/src/custom.d.ts diff --git a/misc/package.json b/misc/package.json index 26bb5f5..0ef1c3c 100644 --- a/misc/package.json +++ b/misc/package.json @@ -4,7 +4,8 @@ "version": "0.1.0", "private": true, "scripts": { - "build": "tsc" + "build": "tsc", + "clean": "rm_js_in_src.sh" }, "dependencies": { "@certusone/wormhole-sdk": "^0.1.6", diff --git a/misc/rm_js_in_src.sh b/misc/rm_js_in_src.sh new file mode 100755 index 0000000..25d1862 --- /dev/null +++ b/misc/rm_js_in_src.sh @@ -0,0 +1,4 @@ +rm src/addresses/*.js +rm src/route/*.js +rm src/swapper/*.js +rm src/utils/*.js diff --git a/misc/scripts/src/custom.d.ts b/misc/scripts/src/custom.d.ts deleted file mode 100644 index 1a3dd3c..0000000 --- a/misc/scripts/src/custom.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "*.svg" { - const content: any; - export default content; -} diff --git a/misc/tsconfig.json b/misc/tsconfig.json index fc2e218..c15b631 100644 --- a/misc/tsconfig.json +++ b/misc/tsconfig.json @@ -3,9 +3,6 @@ "resolveJsonModule": true, "esModuleInterop": true }, - "include": [ - "scripts/src/custom.d.ts" - ], "files": [ "scripts/swap-with-vaa.ts", "scripts/check-ust-quotes.ts" From 5629f2fae847035a45ea8a4d7b14eb5b2e4f2c46 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 18:04:23 +0000 Subject: [PATCH 10/38] Update .gitignore --- misc/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/misc/.gitignore b/misc/.gitignore index 9b84438..eead6f2 100644 --- a/misc/.gitignore +++ b/misc/.gitignore @@ -1,3 +1,4 @@ +.env node_modules/ scripts/*.js scripts/src/*.js From 3154c74c738a36f09ec3f4b1763ca61b2fcdc926 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 18:28:36 +0000 Subject: [PATCH 11/38] Update contracts; fix evm approve and swap --- contracts/contracts/CrossChainSwapV2.sol | 65 +++++++++++++++--------- contracts/contracts/CrossChainSwapV3.sol | 65 +++++++++++++++--------- contracts/contracts/SwapHelper.sol | 6 +-- react/src/swapper/swapper.ts | 31 +++++------ 4 files changed, 103 insertions(+), 64 deletions(-) diff --git a/contracts/contracts/CrossChainSwapV2.sol b/contracts/contracts/CrossChainSwapV2.sol index 1d53e0c..0edd059 100644 --- a/contracts/contracts/CrossChainSwapV2.sol +++ b/contracts/contracts/CrossChainSwapV2.sol @@ -44,7 +44,8 @@ contract CrossChainSwapV2 { uint8 public immutable typeExactOut = 2; uint8 public immutable typeNativeSwap = 1; uint8 public immutable typeTokenSwap = 2; - uint16 public immutable expectedVaaLength = 262; + uint16 public immutable expectedVaaLength = 274; + uint8 public immutable terraChainId = 3; IUniswapV2Router02 public immutable swapRouter; address public immutable feeTokenAddress; address public immutable tokenBridgeAddress; @@ -385,17 +386,26 @@ contract CrossChainSwapV2 { swapParams.deadline ); - // encode payload for second swap - bytes memory payload = abi.encodePacked( - swapParams.targetAmountOutMinimum, - swapParams.targetChainRecipient, - path[2], - path[3], - swapParams.deadline, - swapParams.poolFee, - typeExactIn, - typeNativeSwap - ); + // create payload variable + bytes memory payload; + + // UST is native to Terra - no need for swap instructions + if (targetChainId == terraChainId) { + payload = abi.encodePacked( + swapParams.targetChainRecipient + ); + } else { + payload = abi.encodePacked( + swapParams.targetAmountOutMinimum, + swapParams.targetChainRecipient, + path[2], + path[3], + swapParams.deadline, + swapParams.poolFee, + typeExactIn, + typeNativeSwap + ); + } // approve token bridge to spend feeTokens (UST) TransferHelper.safeApprove( @@ -761,17 +771,26 @@ contract CrossChainSwapV2 { typeNativeSwap ); - // encode payload for second swap - bytes memory payload = abi.encodePacked( - swapParams.targetAmountOut, - swapParams.targetChainRecipient, - path[2], - path[3], - swapParams.deadline, - swapParams.poolFee, - typeExactOut, - typeNativeSwap - ); + // create payload variable + bytes memory payload; + + // UST is native to Terra - no need for swap instructions + if (targetChainId == terraChainId) { + payload = abi.encodePacked( + swapParams.targetChainRecipient + ); + } else { + payload = abi.encodePacked( + swapParams.targetAmountOut, + swapParams.targetChainRecipient, + path[2], + path[3], + swapParams.deadline, + swapParams.poolFee, + typeExactOut, + typeNativeSwap + ); + } // approve token bridge to spend feeTokens (UST) TransferHelper.safeApprove( diff --git a/contracts/contracts/CrossChainSwapV3.sol b/contracts/contracts/CrossChainSwapV3.sol index 98510aa..373e119 100644 --- a/contracts/contracts/CrossChainSwapV3.sol +++ b/contracts/contracts/CrossChainSwapV3.sol @@ -48,7 +48,8 @@ contract CrossChainSwapV3 { uint8 public immutable typeExactOut = 2; uint8 public immutable typeNativeSwap = 1; uint8 public immutable typeTokenSwap = 2; - uint16 public immutable expectedVaaLength = 262; + uint16 public immutable expectedVaaLength = 274; + uint8 public immutable terraChainId = 3; IUniswapRouter public immutable swapRouter; address public immutable feeTokenAddress; address public immutable tokenBridgeAddress; @@ -403,17 +404,26 @@ contract CrossChainSwapV3 { typeNativeSwap ); - // encode payload for second swap - bytes memory payload = abi.encodePacked( - swapParams.targetAmountOutMinimum, - swapParams.targetChainRecipient, - path[2], - path[3], - swapParams.deadline, - swapParams.poolFee, - typeExactIn, - typeNativeSwap - ); + // create payload variable + bytes memory payload; + + // UST is native to Terra - no need for swap instructions + if (targetChainId == terraChainId) { + payload = abi.encodePacked( + swapParams.targetChainRecipient + ); + } else { + payload = abi.encodePacked( + swapParams.targetAmountOutMinimum, + swapParams.targetChainRecipient, + path[2], + path[3], + swapParams.deadline, + swapParams.poolFee, + typeExactIn, + typeNativeSwap + ); + } // approve token bridge to spend feeTokens (UST) TransferHelper.safeApprove( @@ -788,17 +798,26 @@ contract CrossChainSwapV3 { typeNativeSwap ); - // encode payload for second swap - bytes memory payload = abi.encodePacked( - swapParams.targetAmountOut, - swapParams.targetChainRecipient, - path[2], - path[3], - swapParams.deadline, - swapParams.poolFee, - typeExactOut, - typeNativeSwap - ); + // create payload variable + bytes memory payload; + + // UST is native to Terra - no need for swap instructions + if (targetChainId == terraChainId) { + payload = abi.encodePacked( + swapParams.targetChainRecipient + ); + } else { + payload = abi.encodePacked( + swapParams.targetAmountOut, + swapParams.targetChainRecipient, + path[2], + path[3], + swapParams.deadline, + swapParams.poolFee, + typeExactOut, + typeNativeSwap + ); + } // approve token bridge to spend feeTokens (UST) TransferHelper.safeApprove( diff --git a/contracts/contracts/SwapHelper.sol b/contracts/contracts/SwapHelper.sol index 4b20654..c9e925c 100644 --- a/contracts/contracts/SwapHelper.sol +++ b/contracts/contracts/SwapHelper.sol @@ -18,7 +18,7 @@ library SwapHelper { uint256 amountIn; uint256 amountOutMinimum; uint256 targetAmountOutMinimum; - address targetChainRecipient; + bytes32 targetChainRecipient; uint256 deadline; uint24 poolFee; } @@ -28,7 +28,7 @@ library SwapHelper { uint256 amountOut; uint256 amountInMaximum; uint256 targetAmountOut; - address targetChainRecipient; + bytes32 targetChainRecipient; uint256 deadline; uint24 poolFee; } @@ -75,7 +75,7 @@ library SwapHelper { index += 32; decoded.estimatedAmount = encodedVm.payload.toUint256(index); - index += 32; + index += 44; decoded.recipientAddress = encodedVm.payload.toAddress(index); index += 20; diff --git a/react/src/swapper/swapper.ts b/react/src/swapper/swapper.ts index 3f1f870..6749c97 100644 --- a/react/src/swapper/swapper.ts +++ b/react/src/swapper/swapper.ts @@ -13,7 +13,6 @@ import { getSignedVAAWithRetry, } from "@certusone/wormhole-sdk"; import { grpc } from "@improbable-eng/grpc-web"; -import { UniEvmToken } from "../route/uniswap-core"; import { PROTOCOL_UNISWAP_V2, // PROTOCOL_UNISWAP_V3, @@ -120,11 +119,12 @@ function makeExecutionParameters(chainId: ChainId): ExecutionParameters { async function evmApproveContractTokenSpend( provider: ethers.providers.Provider, signer: ethers.Signer, - tokenContract: ethers.Contract, + tokenAddress: string, //ethers.Contract, swapContractAddress: string, amount: ethers.BigNumber ): Promise { // build transaction for token spending + const tokenContract = makeEvmToken(provider, tokenAddress).getContract(); const unsignedTx = await tokenContract.populateTransaction.approve( swapContractAddress, amount @@ -189,7 +189,7 @@ function addressToBytes32( async function evmApproveAndSwapExactIn( srcProvider: ethers.providers.Provider, srcWallet: ethers.Signer, - srcTokenIn: UniEvmToken, + tokenInAddress: string, quoteParams: ExactInCrossParameters, srcExecutionParams: ExecutionParameters, dstExecutionParams: ExecutionParameters, @@ -254,7 +254,7 @@ async function evmApproveAndSwapExactIn( await evmApproveContractTokenSpend( srcProvider, srcWallet, - srcTokenIn.getContract(), + tokenInAddress, swapContract.address, amountIn ); @@ -276,7 +276,7 @@ async function evmApproveAndSwapExactIn( async function evmApproveAndSwapExactOut( srcProvider: ethers.providers.Provider, srcWallet: ethers.Signer, - srcTokenIn: UniEvmToken, + tokenInAddress: string, quoteParams: ExactOutCrossParameters, srcExecutionParams: ExecutionParameters, dstExecutionParams: ExecutionParameters, @@ -341,7 +341,7 @@ async function evmApproveAndSwapExactOut( await evmApproveContractTokenSpend( srcProvider, srcWallet, - srcTokenIn.getContract(), + tokenInAddress, swapContract.address, maxAmountIn ); @@ -412,13 +412,6 @@ async function swapExactOutFromVaa( } } -interface CrossChainSwapTokens { - srcIn: UniEvmToken; - srcOut: UniEvmToken; - dstIn: UniEvmToken; - dstOut: UniEvmToken; -} - interface VaaSearchParams { sequence: string; emitterAddress: string; @@ -583,13 +576,21 @@ export class UniswapToUniswapExecutor { return this.quoter.getDstEvmProvider(); } + getTokenInAddress(): string { + return this.quoter.tokenInAddress; + } + + getTokenOutAddress(): string { + return this.quoter.tokenOutAddress; + } + async evmApproveAndSwapExactIn( wallet: ethers.Signer ): Promise { return evmApproveAndSwapExactIn( this.getSrcEvmProvider(), wallet, - this.tokens.srcIn, + this.getTokenInAddress(), this.cachedExactInParams, this.srcExecutionParams, this.dstExecutionParams, @@ -603,7 +604,7 @@ export class UniswapToUniswapExecutor { return evmApproveAndSwapExactOut( this.getSrcEvmProvider(), wallet, - this.tokens.srcIn, + this.getTokenInAddress(), this.cachedExactOutParams, this.srcExecutionParams, this.dstExecutionParams, From 601546b9211e2e41248431d4c0e77a2d16e29fba Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 19:23:02 +0000 Subject: [PATCH 12/38] Add avalanche and bsc migrations; rename deploy scripts --- contracts/cfg/truffle-config.avalanche.js | 123 +++++++++++++++++ contracts/cfg/truffle-config.bsc.js | 124 ++++++++++++++++++ .../{ => cfg}/truffle-config.ethereum.js | 0 contracts/{ => cfg}/truffle-config.polygon.js | 0 contracts/deploy_to_mumbai.sh | 2 - contracts/deploy_v2.sh | 6 + .../{deploy_to_goerli.sh => deploy_v3.sh} | 4 +- .../avalanche/1_initial_migration.js | 5 + .../avalanche/2_deploy_contracts.js | 34 +++++ .../migrations/bsc/1_initial_migration.js | 5 + .../migrations/bsc/2_deploy_contracts.js | 34 +++++ react/src/addresses/.gitignore | 2 + 12 files changed, 336 insertions(+), 3 deletions(-) create mode 100644 contracts/cfg/truffle-config.avalanche.js create mode 100644 contracts/cfg/truffle-config.bsc.js rename contracts/{ => cfg}/truffle-config.ethereum.js (100%) rename contracts/{ => cfg}/truffle-config.polygon.js (100%) delete mode 100755 contracts/deploy_to_mumbai.sh create mode 100755 contracts/deploy_v2.sh rename contracts/{deploy_to_goerli.sh => deploy_v3.sh} (68%) create mode 100644 contracts/migrations/avalanche/1_initial_migration.js create mode 100644 contracts/migrations/avalanche/2_deploy_contracts.js create mode 100644 contracts/migrations/bsc/1_initial_migration.js create mode 100644 contracts/migrations/bsc/2_deploy_contracts.js diff --git a/contracts/cfg/truffle-config.avalanche.js b/contracts/cfg/truffle-config.avalanche.js new file mode 100644 index 0000000..5e32b35 --- /dev/null +++ b/contracts/cfg/truffle-config.avalanche.js @@ -0,0 +1,123 @@ +const HDWalletProvider = require('@truffle/hdwallet-provider'); + +require('dotenv').config({path:'.env'}); + +/** + * Use this file to configure your truffle project. It's seeded with some + * common settings for different networks and features like migrations, + * compilation and testing. Uncomment the ones you need or modify + * them to suit your project as necessary. + * + * More information about configuration can be found at: + * + * trufflesuite.com/docs/advanced/configuration + * + * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) + * to sign your transactions before they're sent to a remote public node. Infura accounts + * are available for free at: infura.io/register. + * + * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate + * public/private key pairs. If you're publishing your code to GitHub make sure you load this + * phrase from a file you've .gitignored so it doesn't accidentally become public. + * + */ + +// const HDWalletProvider = require('@truffle/hdwallet-provider'); +// +// const fs = require('fs'); +// const mnemonic = fs.readFileSync('.secret').toString().trim(); + +module.exports = { + contracts_directory: './contracts', + contracts_build_directory: './build/contracts', + migrations_directory: './migrations/avalanche', + /** + * Networks define how you connect to your ethereum client and let you set the + * defaults web3 uses to send transactions. If you don't specify one truffle + * will spin up a development blockchain for you on port 9545 when you + * run `develop` or `test`. You can ask a truffle command to use a specific + * network from the command line, e.g + * + * $ truffle test --network + */ + + networks: { + // Useful for testing. The `development` name is special - truffle uses it by default + // if it's defined here and no other network is specified at the command line. + // You should run a client (like ganache-cli, geth or parity) in a separate terminal + // tab if you use this network and you must also set the `host`, `port` and `network_id` + // options below to some value. + // + development: { + host: '127.0.0.1', // Localhost (default: none) + port: 8545, // Standard Ethereum port (default: none) + network_id: '*', // Any network (default: none) + }, + // Another network with more advanced options... + // advanced: { + // port: 8777, // Custom port + // network_id: 1342, // Custom network + // gas: 8500000, // Gas sent with each transaction (default: ~6700000) + // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) + // from:
, // Account to send txs from (default: accounts[0]) + // websocket: true // Enable EventEmitter interface for web3 (default: false) + // }, + // Useful for deploying to a public network. + // NB: It's important to wrap the provider as a function. + fuji: { + provider: () => new HDWalletProvider( + process.env.ETH_PRIVATE_KEY, + process.env.FUJI_PROVIDER + ), + network_id: 43113, + skipDryRun: true + }, + // Useful for private networks + // private: { + // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), + // network_id: 2111, // This network is yours, in the cloud. + // production: true // Treats this network as if it was a public net. (default: false) + // } + }, + + // Set default mocha options here, use special reporters etc. + mocha: { + // timeout: 100000 + }, + + // Configure your compilers + compilers: { + solc: { + version: '0.7.6', // Fetch exact version from solc-bin (default: truffle's version) + // docker: true, // Use '0.5.1' you've installed locally with docker (default: false) + // settings: { // See the solidity docs for advice about optimization and evmVersion + optimizer: { + enabled: false, + runs: 200 + }, + // evmVersion: 'byzantium' + // } + } + }, + + // Truffle DB is currently disabled by default; to enable it, change enabled: + // false to enabled: true. The default storage location can also be + // overridden by specifying the adapter settings, as shown in the commented code below. + // + // NOTE: It is not possible to migrate your contracts to truffle DB and you should + // make a backup of your artifacts to a safe location before enabling this feature. + // + // After you backed up your artifacts you can utilize db by running migrate as follows: + // $ truffle migrate --reset --compile-all + // + // db: { + // enabled: false, + // host: '127.0.0.1', + // adapter: { + // name: 'sqlite', + // settings: { + // directory: '.db' + // } + // } + // } +}; diff --git a/contracts/cfg/truffle-config.bsc.js b/contracts/cfg/truffle-config.bsc.js new file mode 100644 index 0000000..5f7526e --- /dev/null +++ b/contracts/cfg/truffle-config.bsc.js @@ -0,0 +1,124 @@ +const HDWalletProvider = require('@truffle/hdwallet-provider'); + +require('dotenv').config({path:'.env'}); + +/** + * Use this file to configure your truffle project. It's seeded with some + * common settings for different networks and features like migrations, + * compilation and testing. Uncomment the ones you need or modify + * them to suit your project as necessary. + * + * More information about configuration can be found at: + * + * trufflesuite.com/docs/advanced/configuration + * + * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) + * to sign your transactions before they're sent to a remote public node. Infura accounts + * are available for free at: infura.io/register. + * + * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate + * public/private key pairs. If you're publishing your code to GitHub make sure you load this + * phrase from a file you've .gitignored so it doesn't accidentally become public. + * + */ + +// const HDWalletProvider = require('@truffle/hdwallet-provider'); +// +// const fs = require('fs'); +// const mnemonic = fs.readFileSync('.secret').toString().trim(); + +module.exports = { + contracts_directory: './contracts', + contracts_build_directory: './build/contracts', + migrations_directory: './migrations/bsc', + /** + * Networks define how you connect to your ethereum client and let you set the + * defaults web3 uses to send transactions. If you don't specify one truffle + * will spin up a development blockchain for you on port 9545 when you + * run `develop` or `test`. You can ask a truffle command to use a specific + * network from the command line, e.g + * + * $ truffle test --network + */ + + networks: { + // Useful for testing. The `development` name is special - truffle uses it by default + // if it's defined here and no other network is specified at the command line. + // You should run a client (like ganache-cli, geth or parity) in a separate terminal + // tab if you use this network and you must also set the `host`, `port` and `network_id` + // options below to some value. + // + development: { + host: '127.0.0.1', // Localhost (default: none) + port: 8545, // Standard Ethereum port (default: none) + network_id: '*', // Any network (default: none) + }, + // Another network with more advanced options... + // advanced: { + // port: 8777, // Custom port + // network_id: 1342, // Custom network + // gas: 8500000, // Gas sent with each transaction (default: ~6700000) + // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) + // from:
, // Account to send txs from (default: accounts[0]) + // websocket: true // Enable EventEmitter interface for web3 (default: false) + // }, + // Useful for deploying to a public network. + // NB: It's important to wrap the provider as a function. + bsc: { + provider: () => new HDWalletProvider( + process.env.ETH_PRIVATE_KEY, + process.env.BSC_PROVIDER + ), + network_id: 97, + skipDryRun: true, + timeoutBlocks: 200 + }, + // Useful for private networks + // private: { + // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), + // network_id: 2111, // This network is yours, in the cloud. + // production: true // Treats this network as if it was a public net. (default: false) + // } + }, + + // Set default mocha options here, use special reporters etc. + mocha: { + // timeout: 100000 + }, + + // Configure your compilers + compilers: { + solc: { + version: '0.7.6', // Fetch exact version from solc-bin (default: truffle's version) + // docker: true, // Use '0.5.1' you've installed locally with docker (default: false) + // settings: { // See the solidity docs for advice about optimization and evmVersion + optimizer: { + enabled: false, + runs: 200 + }, + // evmVersion: 'byzantium' + // } + } + }, + + // Truffle DB is currently disabled by default; to enable it, change enabled: + // false to enabled: true. The default storage location can also be + // overridden by specifying the adapter settings, as shown in the commented code below. + // + // NOTE: It is not possible to migrate your contracts to truffle DB and you should + // make a backup of your artifacts to a safe location before enabling this feature. + // + // After you backed up your artifacts you can utilize db by running migrate as follows: + // $ truffle migrate --reset --compile-all + // + // db: { + // enabled: false, + // host: '127.0.0.1', + // adapter: { + // name: 'sqlite', + // settings: { + // directory: '.db' + // } + // } + // } +}; diff --git a/contracts/truffle-config.ethereum.js b/contracts/cfg/truffle-config.ethereum.js similarity index 100% rename from contracts/truffle-config.ethereum.js rename to contracts/cfg/truffle-config.ethereum.js diff --git a/contracts/truffle-config.polygon.js b/contracts/cfg/truffle-config.polygon.js similarity index 100% rename from contracts/truffle-config.polygon.js rename to contracts/cfg/truffle-config.polygon.js diff --git a/contracts/deploy_to_mumbai.sh b/contracts/deploy_to_mumbai.sh deleted file mode 100755 index 68ed2b5..0000000 --- a/contracts/deploy_to_mumbai.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -npx truffle migrate --config truffle-config.polygon.js --network mumbai --reset diff --git a/contracts/deploy_v2.sh b/contracts/deploy_v2.sh new file mode 100755 index 0000000..eedc682 --- /dev/null +++ b/contracts/deploy_v2.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail + +npx truffle migrate --config cfg/truffle-config.avalanche.js --network fuji --reset +npx truffle migrate --config cfg/truffle-config.bsc.js --network bsc --reset +npx truffle migrate --config cfg/truffle-config.polygon.js --network mumbai --reset \ No newline at end of file diff --git a/contracts/deploy_to_goerli.sh b/contracts/deploy_v3.sh similarity index 68% rename from contracts/deploy_to_goerli.sh rename to contracts/deploy_v3.sh index 42c498d..53fc86a 100755 --- a/contracts/deploy_to_goerli.sh +++ b/contracts/deploy_v3.sh @@ -1,2 +1,4 @@ #!/bin/bash -npx truffle migrate --config truffle-config.ethereum.js --network goerli --reset +set -euo pipefail + +npx truffle migrate --config truffle-config.ethereum.js --network goerli --reset \ No newline at end of file diff --git a/contracts/migrations/avalanche/1_initial_migration.js b/contracts/migrations/avalanche/1_initial_migration.js new file mode 100644 index 0000000..16a7ba5 --- /dev/null +++ b/contracts/migrations/avalanche/1_initial_migration.js @@ -0,0 +1,5 @@ +const Migrations = artifacts.require("Migrations"); + +module.exports = function (deployer) { + deployer.deploy(Migrations); +}; diff --git a/contracts/migrations/avalanche/2_deploy_contracts.js b/contracts/migrations/avalanche/2_deploy_contracts.js new file mode 100644 index 0000000..09d6a26 --- /dev/null +++ b/contracts/migrations/avalanche/2_deploy_contracts.js @@ -0,0 +1,34 @@ +const fsp = require("fs/promises"); + +const CrossChainSwapV2 = artifacts.require("CrossChainSwapV2"); +const SwapHelper = artifacts.require("SwapHelper"); + +const scriptsAddressPath = "../react/src/addresses"; + +module.exports = async function(deployer, network) { + const routerAddress = "0x7e3411b04766089cfaa52db688855356a12f05d1"; // hurricaneswap + const feeTokenAddress = "0xe09ed38e5cd1014444846f62376ac88c5232cde9"; // wUST + const tokenBridgeAddress = "0x61E44E506Ca5659E6c0bba9b678586fA2d729756"; + const wrappedAvaxAddress = "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3"; + + await deployer.deploy(SwapHelper); + await deployer.link(SwapHelper, CrossChainSwapV2); + await deployer.deploy( + CrossChainSwapV2, + routerAddress, + feeTokenAddress, + tokenBridgeAddress, + wrappedAvaxAddress + ); + + // save the contract address somewhere + await fsp.mkdir(scriptsAddressPath, { recursive: true }); + + await fsp.writeFile( + `${scriptsAddressPath}/${network}.ts`, + `export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';` + ); + + //deployer.link(ConvertLib, MetaCoin); + //deployer.deploy(MetaCoin); +}; diff --git a/contracts/migrations/bsc/1_initial_migration.js b/contracts/migrations/bsc/1_initial_migration.js new file mode 100644 index 0000000..16a7ba5 --- /dev/null +++ b/contracts/migrations/bsc/1_initial_migration.js @@ -0,0 +1,5 @@ +const Migrations = artifacts.require("Migrations"); + +module.exports = function (deployer) { + deployer.deploy(Migrations); +}; diff --git a/contracts/migrations/bsc/2_deploy_contracts.js b/contracts/migrations/bsc/2_deploy_contracts.js new file mode 100644 index 0000000..5219f85 --- /dev/null +++ b/contracts/migrations/bsc/2_deploy_contracts.js @@ -0,0 +1,34 @@ +const fsp = require("fs/promises"); + +const CrossChainSwapV2 = artifacts.require("CrossChainSwapV2"); +const SwapHelper = artifacts.require("SwapHelper"); + +const scriptsAddressPath = "../react/src/addresses"; + +module.exports = async function(deployer, network) { + const routerAddress = "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3"; // pancakeswap + const feeTokenAddress = "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1"; // wUST + const tokenBridgeAddress = "0x9dcF9D205C9De35334D646BeE44b2D2859712A09"; + const wrappedBnbAddress = "0xae13d989dac2f0debff460ac112a837c89baa7cd"; + + await deployer.deploy(SwapHelper); + await deployer.link(SwapHelper, CrossChainSwapV2); + await deployer.deploy( + CrossChainSwapV2, + routerAddress, + feeTokenAddress, + tokenBridgeAddress, + wrappedBnbAddress + ); + + // save the contract address somewhere + await fsp.mkdir(scriptsAddressPath, { recursive: true }); + + await fsp.writeFile( + `${scriptsAddressPath}/${network}.ts`, + `export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';` + ); + + //deployer.link(ConvertLib, MetaCoin); + //deployer.deploy(MetaCoin); +}; diff --git a/react/src/addresses/.gitignore b/react/src/addresses/.gitignore index 98b1c8c..680028e 100644 --- a/react/src/addresses/.gitignore +++ b/react/src/addresses/.gitignore @@ -1,3 +1,5 @@ +bsc.ts +fuji.ts goerli.ts mumbai.ts *.js From 01f1a7c971e6546e777a85c371ceaa246537ddc0 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 19:36:52 +0000 Subject: [PATCH 13/38] Fix tokenContract declaration --- react/src/swapper/swapper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react/src/swapper/swapper.ts b/react/src/swapper/swapper.ts index 6749c97..6b6775f 100644 --- a/react/src/swapper/swapper.ts +++ b/react/src/swapper/swapper.ts @@ -47,6 +47,7 @@ import { abi as SWAP_CONTRACT_V2_ABI } from "../abi/contracts/CrossChainSwapV2.j 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_POLYGON } from "../addresses/mumbai"; +import { makeErc20Contract } from "../route/evm"; // placeholders const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA = ""; @@ -124,7 +125,7 @@ async function evmApproveContractTokenSpend( amount: ethers.BigNumber ): Promise { // build transaction for token spending - const tokenContract = makeEvmToken(provider, tokenAddress).getContract(); + const tokenContract = await makeErc20Contract(provider, tokenAddress); const unsignedTx = await tokenContract.populateTransaction.approve( swapContractAddress, amount @@ -446,7 +447,6 @@ export class UniswapToUniswapExecutor { cachedExactInParams: ExactInCrossParameters; cachedExactOutParams: ExactOutCrossParameters; quoteType: QuoteType; - tokens: CrossChainSwapTokens; // swapping isNative: boolean; From 9441a3a11501d7f7803a2b93f40d9aecb33ac6a2 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Thu, 27 Jan 2022 22:06:13 +0000 Subject: [PATCH 14/38] Fix compile_contracts.sh --- contracts/compile_contracts.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/compile_contracts.sh b/contracts/compile_contracts.sh index 929d2ac..9311782 100755 --- a/contracts/compile_contracts.sh +++ b/contracts/compile_contracts.sh @@ -2,8 +2,8 @@ set -euo pipefail -npx truffle compile --config truffle-config.ethereum.js -npx truffle compile --config truffle-config.polygon.js +npx truffle compile --config cfg/truffle-config.ethereum.js +npx truffle compile --config cfg/truffle-config.polygon.js CONTRACTS="../react/src/abi/contracts" From 245d281a0d5c5c90aa605d32869767a5d524b62e Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Fri, 28 Jan 2022 16:23:49 +0000 Subject: [PATCH 15/38] Add avax and bsc support --- misc/scripts/src/provider.ts | 17 ++- misc/scripts/swap-with-vaa.ts | 207 +++++++++++++++---------------- react/src/route/cross-quote.ts | 58 +++++++-- react/src/route/hurricaneswap.ts | 26 ++++ react/src/route/pancakeswap.ts | 26 ++++ react/src/route/quickswap.ts | 5 +- react/src/route/uniswap-v3.ts | 6 +- react/src/swapper/helpers.ts | 108 ++++++++++++++++ react/src/swapper/swapper.ts | 95 +++++++++----- react/src/swapper/util.ts | 56 --------- react/src/utils/consts.ts | 113 ++++++++++++++--- 11 files changed, 489 insertions(+), 228 deletions(-) create mode 100644 react/src/route/hurricaneswap.ts create mode 100644 react/src/route/pancakeswap.ts create mode 100644 react/src/swapper/helpers.ts delete mode 100644 react/src/swapper/util.ts diff --git a/misc/scripts/src/provider.ts b/misc/scripts/src/provider.ts index 65937fc..54aaf49 100644 --- a/misc/scripts/src/provider.ts +++ b/misc/scripts/src/provider.ts @@ -1,6 +1,11 @@ import { ethers } from "ethers"; -import { ETH_TOKEN_INFO, MATIC_TOKEN_INFO } from "../../src/utils/consts"; +import { + ETH_TOKEN_INFO, + MATIC_TOKEN_INFO, + AVAX_TOKEN_INFO, + BNB_TOKEN_INFO, +} from "../../src/utils/consts"; export function makeProvider(tokenAddress: string) { switch (tokenAddress) { @@ -14,6 +19,16 @@ export function makeProvider(tokenAddress: string) { process.env.MUMBAI_PROVIDER ); } + case AVAX_TOKEN_INFO.address: { + return new ethers.providers.StaticJsonRpcProvider( + process.env.FUJI_PROVIDER + ); + } + case BNB_TOKEN_INFO.address: { + return new ethers.providers.StaticJsonRpcProvider( + process.env.BSC_PROVIDER + ); + } default: { throw Error("unrecognized token address"); } diff --git a/misc/scripts/swap-with-vaa.ts b/misc/scripts/swap-with-vaa.ts index 31f6065..990283f 100644 --- a/misc/scripts/swap-with-vaa.ts +++ b/misc/scripts/swap-with-vaa.ts @@ -7,20 +7,26 @@ import { ExactOutCrossParameters, UniswapToUniswapQuoter, } from "../src/route/cross-quote"; - import { UniswapToUniswapExecutor } from "../src/swapper/swapper"; +import { + ETH_TOKEN_INFO, + MATIC_TOKEN_INFO, + AVAX_TOKEN_INFO, + BNB_TOKEN_INFO, + UST_TOKEN_INFO, +} from "../src/utils/consts"; import { makeProvider } from "./src/provider"; require("dotenv").config({ path: ".env" }); -// quote using these -const POLYGON_TOKEN_WMATIC = "0x9c3c9283d3e44854697cd22d3faa240cfb032889"; -const ETHEREUM_TOKEN_WETH = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"; - // swap related parameters (configurable in UI) -const SWAP_AMOUNT_IN_WMATIC = "0.0069"; -const SWAP_AMOUNT_IN_WETH = "0.000123"; +const SWAP_AMOUNT_IN_MATIC = "0.0069"; +const SWAP_AMOUNT_IN_ETH = "0.000907"; +const SWAP_AMOUNT_IN_AVAX = "0.0075"; +const SWAP_AMOUNT_IN_BNB = "0.015"; +const SWAP_AMOUNT_IN_UST = "3.40"; + const SWAP_DEADLINE = "1800"; const SWAP_SLIPPAGE = "0.01"; @@ -48,12 +54,25 @@ function determineWalletFromToken(tokenAddress: string): ethers.Wallet { } function determineAmountFromToken(tokenAddress: string): string { - if (tokenAddress === ETHEREUM_TOKEN_WETH) { - return SWAP_AMOUNT_IN_WETH; - } else if (tokenAddress === POLYGON_TOKEN_WMATIC) { - return SWAP_AMOUNT_IN_WMATIC; - } else { - throw Error("you suck"); + switch (tokenAddress) { + case ETH_TOKEN_INFO.address: { + return SWAP_AMOUNT_IN_ETH; + } + case MATIC_TOKEN_INFO.address: { + return SWAP_AMOUNT_IN_MATIC; + } + case AVAX_TOKEN_INFO.address: { + return SWAP_AMOUNT_IN_AVAX; + } + case BNB_TOKEN_INFO.address: { + return SWAP_AMOUNT_IN_BNB; + } + case UST_TOKEN_INFO.address: { + return SWAP_AMOUNT_IN_UST; + } + default: { + throw Error("you suck"); + } } } @@ -61,50 +80,62 @@ function logExactInParameters( quoter: UniswapToUniswapQuoter, params: ExactInCrossParameters ): void { + console.info(`amountIn: ${params.amountIn}`); + console.info(`minAmountOut: ${params.minAmountOut}`); + const src = params.src; - console.info(`src`); - console.info(` protocol: ${src.protocol}`); - //console.info(` amountIn: ${quoter.srcTokenIn.formatAmount(src.amountIn)}`); - console.info( - ` amountIn: ${quoter.srcRouter.formatAmountIn( - src.amountIn.toString() - )}` - ); - console.info( - // ` minAmountOut: ${quoter.srcTokenOut.formatAmount(src.minAmountOut)}` - ` minAmountOut: ${quoter.srcRouter.formatAmountOut( - src.minAmountOut.toString() - )}` - ); - console.info(` poolFee: ${src.poolFee}`); - console.info(` deadline: ${src.deadline.toString()}`); - console.info(` path: ${src.path}`); + if (src === undefined) { + console.warn(` src is undefined (ust?)`); + } else { + console.info(`src`); + console.info(` protocol: ${src.protocol}`); + //console.info(` amountIn: ${quoter.srcTokenIn.formatAmount(src.amountIn)}`); + console.info( + ` amountIn: ${quoter.srcRouter.formatAmountIn( + src.amountIn.toString() + )}` + ); + console.info( + // ` minAmountOut: ${quoter.srcTokenOut.formatAmount(src.minAmountOut)}` + ` minAmountOut: ${quoter.srcRouter.formatAmountOut( + src.minAmountOut.toString() + )}` + ); + console.info(` poolFee: ${src.poolFee}`); + console.info(` deadline: ${src.deadline.toString()}`); + console.info(` path: ${src.path}`); + } const dst = params.dst; console.info(`dst`); - console.info(` protocol: ${dst.protocol}`); - //console.info(` amountIn: ${quoter.dstTokenIn.formatAmount(dst.amountIn)}`); - console.info( - ` amountIn: ${quoter.dstRouter.formatAmountIn( - dst.amountIn.toString() - )}` - ); - console.info( - // ` minAmountOut: ${quoter.dstTokenOut.formatAmount(dst.minAmountOut)}` - ` minAmountOut: ${quoter.dstRouter.formatAmountOut( - dst.minAmountOut.toString() - )}` - ); - console.info(` poolFee: ${dst.poolFee}`); - console.info(` deadline: ${dst.deadline.toString()}`); - console.info(` path: ${dst.path}`); + if (dst === undefined) { + console.warn(` dst is undefined (ust?)`); + } else { + console.info(` protocol: ${dst.protocol}`); + //console.info(` amountIn: ${quoter.dstTokenIn.formatAmount(dst.amountIn)}`); + console.info( + ` amountIn: ${quoter.dstRouter.formatAmountIn( + dst.amountIn.toString() + )}` + ); + console.info( + // ` minAmountOut: ${quoter.dstTokenOut.formatAmount(dst.minAmountOut)}` + ` minAmountOut: ${quoter.dstRouter.formatAmountOut( + dst.minAmountOut.toString() + )}` + ); + console.info(` poolFee: ${dst.poolFee}`); + console.info(` deadline: ${dst.deadline.toString()}`); + console.info(` path: ${dst.path}`); + + const relayerFee = params.relayerFee; + console.info(`relayerFee`); + console.info(` tokenAddress: ${relayerFee.tokenAddress}`); + console.info( + ` amount: ${quoter.dstRouter.formatAmountIn(relayerFee.amount)}` + ); + } - const relayerFee = params.relayerFee; - console.info(`relayerFee`); - console.info(` tokenAddress: ${relayerFee.tokenAddress}`); - console.info( - ` amount: ${quoter.dstRouter.formatAmountIn(relayerFee.amount)}` - ); return; } @@ -123,24 +154,6 @@ async function swapEverythingExactIn( await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); console.info(`quoter initialized`); - /* - const tokens = swapper.getTokens(); - - // display tokens on front-end? - console.info( - `srcTokenIn: ${tokens.srcIn.getAddress()} (${tokens.srcIn.getDecimals()})` - ); - console.info( - `srcTokenOut: ${tokens.srcOut.getAddress()} (${tokens.srcOut.getDecimals()})` - ); - console.info( - `dstTokenIn: ${tokens.dstIn.getAddress()} (${tokens.dstIn.getDecimals()})` - ); - console.info( - `dstTokenOut: ${tokens.dstOut.getAddress()} (${tokens.dstOut.getDecimals()})` - ); - */ - // verify pool address on src and dst await swapper .computeAndVerifySrcPoolAddress() @@ -260,24 +273,6 @@ async function swapEverythingExactOut( await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); console.info(`quoter initialized`); - /* - const tokens = swapper.getTokens(); - - // display tokens on front-end? - console.info( - `srcTokenIn: ${tokens.srcIn.getAddress()} (${tokens.srcIn.getDecimals()})` - ); - console.info( - `srcTokenOut: ${tokens.srcOut.getAddress()} (${tokens.srcOut.getDecimals()})` - ); - console.info( - `dstTokenIn: ${tokens.dstIn.getAddress()} (${tokens.dstIn.getDecimals()})` - ); - console.info( - `dstTokenOut: ${tokens.dstOut.getAddress()} (${tokens.dstOut.getDecimals()})` - ); - */ - // verify pool address on src and dst await swapper .computeAndVerifySrcPoolAddress() @@ -340,48 +335,48 @@ async function main() { const swapper = new UniswapToUniswapExecutor(); swapper.setTransport(NodeHttpTransport()); - const tokenInAddress = POLYGON_TOKEN_WMATIC; - const tokenOutAddress = ETHEREUM_TOKEN_WETH; + const tokenIn = ETH_TOKEN_INFO; + const tokenOut = AVAX_TOKEN_INFO; if (testExactIn) { console.info(`testing exact in. native=${isNative}`); - console.info("wmatic -> weth"); + console.info(`${tokenIn.name} -> ${tokenOut.name}`); await swapEverythingExactIn( swapper, - tokenInAddress, - tokenOutAddress, + tokenIn.address, + tokenOut.address, isNative, - determineAmountFromToken(tokenInAddress) + determineAmountFromToken(tokenIn.address) ); - console.info("weth -> wmatic"); + console.info(`${tokenOut.name} -> ${tokenIn.name}`); await swapEverythingExactIn( swapper, - tokenOutAddress, - tokenInAddress, + tokenOut.address, + tokenIn.address, isNative, - determineAmountFromToken(tokenOutAddress) + determineAmountFromToken(tokenOut.address) ); } else { console.info(`testing exact out. native=${isNative}`); - console.info("wmatic -> weth"); + console.info(`${tokenIn.name} -> ${tokenOut.name}`); await swapEverythingExactOut( swapper, - tokenInAddress, - tokenOutAddress, + tokenIn.address, + tokenOut.address, isNative, - determineAmountFromToken(tokenOutAddress) + determineAmountFromToken(tokenOut.address) ); - console.info("weth -> wmatic"); + console.info(`${tokenOut.name} -> ${tokenIn.name}`); await swapEverythingExactOut( swapper, - tokenOutAddress, - tokenInAddress, + tokenOut.address, + tokenIn.address, isNative, - determineAmountFromToken(tokenInAddress) + determineAmountFromToken(tokenIn.address) ); } diff --git a/react/src/route/cross-quote.ts b/react/src/route/cross-quote.ts index 87f342a..310b3cd 100644 --- a/react/src/route/cross-quote.ts +++ b/react/src/route/cross-quote.ts @@ -3,9 +3,13 @@ import { ethers } from "ethers"; import { QuickswapRouter as MaticRouter } from "./quickswap"; import { UniswapV3Router as EthRouter } from "./uniswap-v3"; import { TerraUstTransfer as UstRouter } from "./terra-ust-transfer"; +import { HurricaneswapRouter as AvaxRouter } from "./hurricaneswap"; +import { PancakeswapRouter as BnbRouter } from "./pancakeswap"; import { - WETH_TOKEN_INFO, - WMATIC_TOKEN_INFO, + ETH_TOKEN_INFO, + MATIC_TOKEN_INFO, + AVAX_TOKEN_INFO, + BNB_TOKEN_INFO, UST_TOKEN_INFO, } from "../utils/consts"; import { addFixedAmounts, subtractFixedAmounts } from "../utils/math"; @@ -20,6 +24,8 @@ import { ChainId, CHAIN_ID_ETH, CHAIN_ID_POLYGON, + CHAIN_ID_AVAX, + CHAIN_ID_BSC, CHAIN_ID_TERRA, } from "@certusone/wormhole-sdk"; @@ -36,20 +42,34 @@ export enum QuoteType { export function makeEvmProviderFromAddress(tokenAddress: string) { switch (tokenAddress) { - case WETH_TOKEN_INFO.address: { + case ETH_TOKEN_INFO.address: { const url = process.env.REACT_APP_GOERLI_PROVIDER; if (!url) { throw new Error("Could not find REACT_APP_GOERLI_PROVIDER"); } return new ethers.providers.StaticJsonRpcProvider(url); } - case WMATIC_TOKEN_INFO.address: { + case MATIC_TOKEN_INFO.address: { const url = process.env.REACT_APP_MUMBAI_PROVIDER; if (!url) { throw new Error("Could not find REACT_APP_MUMBAI_PROVIDER"); } return new ethers.providers.StaticJsonRpcProvider(url); } + case AVAX_TOKEN_INFO.address: { + const url = process.env.REACT_APP_FUJI_PROVIDER; + if (!url) { + throw new Error("Could not find REACT_APP_FUJI_PROVIDER"); + } + return new ethers.providers.StaticJsonRpcProvider(url); + } + case BNB_TOKEN_INFO.address: { + const url = process.env.REACT_APP_BSC_PROVIDER; + if (!url) { + throw new Error("Could not find REACT_APP_BSC_PROVIDER"); + } + return new ethers.providers.StaticJsonRpcProvider(url); + } default: { throw Error("unrecognized evm token address"); } @@ -58,12 +78,18 @@ export function makeEvmProviderFromAddress(tokenAddress: string) { export function getChainIdFromAddress(tokenAddress: string) { switch (tokenAddress) { - case WETH_TOKEN_INFO.address: { + case ETH_TOKEN_INFO.address: { return CHAIN_ID_ETH; } - case WMATIC_TOKEN_INFO.address: { + case MATIC_TOKEN_INFO.address: { return CHAIN_ID_POLYGON; } + case AVAX_TOKEN_INFO.address: { + return CHAIN_ID_AVAX; + } + case BNB_TOKEN_INFO.address: { + return CHAIN_ID_BSC; + } case UST_TOKEN_INFO.address: { return CHAIN_ID_TERRA; } @@ -75,18 +101,30 @@ export function getChainIdFromAddress(tokenAddress: string) { async function makeRouter(tokenAddress: string, loc: UstLocation) { switch (tokenAddress) { - case WETH_TOKEN_INFO.address: { + case ETH_TOKEN_INFO.address: { const provider = makeEvmProviderFromAddress(tokenAddress); const router = new EthRouter(provider); await router.initialize(loc); return router; } - case WMATIC_TOKEN_INFO.address: { + case MATIC_TOKEN_INFO.address: { const provider = makeEvmProviderFromAddress(tokenAddress); const router = new MaticRouter(provider); await router.initialize(loc); return router; } + case AVAX_TOKEN_INFO.address: { + const provider = makeEvmProviderFromAddress(tokenAddress); + const router = new AvaxRouter(provider); + await router.initialize(loc); + return router; + } + case BNB_TOKEN_INFO.address: { + const provider = makeEvmProviderFromAddress(tokenAddress); + const router = new BnbRouter(provider); + await router.initialize(loc); + return router; + } case UST_TOKEN_INFO.address: { return new UstRouter(); } @@ -133,8 +171,8 @@ export class UniswapToUniswapQuoter { tokenOutAddress: string; // routers - srcRouter: UstRouter | EthRouter | MaticRouter; - dstRouter: UstRouter | EthRouter | MaticRouter; + srcRouter: UstRouter | EthRouter | MaticRouter | AvaxRouter | BnbRouter; + dstRouter: UstRouter | EthRouter | MaticRouter | AvaxRouter | BnbRouter; async initialize(tokenInAddress: string, tokenOutAddress: string) { if (tokenInAddress !== this.tokenInAddress) { diff --git a/react/src/route/hurricaneswap.ts b/react/src/route/hurricaneswap.ts new file mode 100644 index 0000000..f696c81 --- /dev/null +++ b/react/src/route/hurricaneswap.ts @@ -0,0 +1,26 @@ +import { ethers } from "ethers"; + +import { WAVAX_TOKEN_INFO } from "../utils/consts"; +import { UstLocation } from "./generic"; +import { UniswapV2Router } from "./uniswap-v2"; + +export { PROTOCOL } from "./uniswap-v2"; + +const HURRICANESWAP_FACTORY_ADDRESS = null; + +export class HurricaneswapRouter extends UniswapV2Router { + constructor(provider: ethers.providers.Provider) { + super(provider); + super.setFactoryAddress(HURRICANESWAP_FACTORY_ADDRESS); + } + + async initialize(ustLocation: UstLocation): Promise { + await super.initializeTokens(WAVAX_TOKEN_INFO, ustLocation); + return; + } + + computePoolAddress(): string { + // cannot find factory address on testnet + return "0xD8087870E8869e45154189d434DF61C19e77ae30"; + } +} diff --git a/react/src/route/pancakeswap.ts b/react/src/route/pancakeswap.ts new file mode 100644 index 0000000..37ce319 --- /dev/null +++ b/react/src/route/pancakeswap.ts @@ -0,0 +1,26 @@ +import { ethers } from "ethers"; + +import { WBNB_TOKEN_INFO } from "../utils/consts"; +import { UstLocation } from "./generic"; +import { UniswapV2Router } from "./uniswap-v2"; + +export { PROTOCOL } from "./uniswap-v2"; + +const PANCAKESWAP_FACTORY_ADDRESS = null; + +export class PancakeswapRouter extends UniswapV2Router { + constructor(provider: ethers.providers.Provider) { + super(provider); + super.setFactoryAddress(PANCAKESWAP_FACTORY_ADDRESS); + } + + async initialize(ustLocation: UstLocation): Promise { + await super.initializeTokens(WBNB_TOKEN_INFO, ustLocation); + return; + } + + computePoolAddress(): string { + // cannot find factory address on testnet + return "0x8682096d4A2a2f3cd63147D05e4BAB47634e2AD1"; + } +} diff --git a/react/src/route/quickswap.ts b/react/src/route/quickswap.ts index ff92e9a..7c4d421 100644 --- a/react/src/route/quickswap.ts +++ b/react/src/route/quickswap.ts @@ -1,10 +1,13 @@ import { ethers } from "ethers"; -import { QUICKSWAP_FACTORY_ADDRESS, WMATIC_TOKEN_INFO } from "../utils/consts"; + +import { WMATIC_TOKEN_INFO } from "../utils/consts"; import { UstLocation } from "./generic"; import { UniswapV2Router } from "./uniswap-v2"; export { PROTOCOL } from "./uniswap-v2"; +const QUICKSWAP_FACTORY_ADDRESS = "0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32"; + export class QuickswapRouter extends UniswapV2Router { constructor(provider: ethers.providers.Provider) { super(provider); diff --git a/react/src/route/uniswap-v3.ts b/react/src/route/uniswap-v3.ts index d5e118a..cc59c44 100644 --- a/react/src/route/uniswap-v3.ts +++ b/react/src/route/uniswap-v3.ts @@ -13,12 +13,14 @@ import { Trade, } from "@uniswap/v3-sdk"; -import { UniEvmToken, UniswapRouterCore } from "./uniswap-core"; -import { WETH_TOKEN_INFO, UNISWAP_V3_FACTORY_ADDRESS } from "../utils/consts"; +import { UniswapRouterCore } from "./uniswap-core"; +import { WETH_TOKEN_INFO } from "../utils/consts"; import { UstLocation } from "./generic"; export const PROTOCOL = "UniswapV3"; +const UNISWAP_V3_FACTORY_ADDRESS = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; + export class UniswapV3Router extends UniswapRouterCore { poolContract: ethers.Contract; pool: Pool; diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts new file mode 100644 index 0000000..a75c4a1 --- /dev/null +++ b/react/src/swapper/helpers.ts @@ -0,0 +1,108 @@ +import { ethers } from "ethers"; +import { TransactionReceipt } from "@ethersproject/abstract-provider"; + +import { + EVM_ETH_NETWORK_CHAIN_ID, + EVM_POLYGON_NETWORK_CHAIN_ID, + EVM_AVAX_NETWORK_CHAIN_ID, + //EVM_BSC_NETWORK_CHAIN_ID, +} from "../utils/consts"; + +export const CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559 = { + gasLimit: "550000", + maxFeePerGas: "250000000000", + maxPriorityFeePerGas: "1690000000", +}; + +export const CROSSCHAINSWAP_GAS_PARAMETERS_EVM = { + gasLimit: "550000", + gasPrice: "250000000000", +}; + +export const EVM_EIP1559_CHAIN_IDS = [ + EVM_ETH_NETWORK_CHAIN_ID, + EVM_POLYGON_NETWORK_CHAIN_ID, + EVM_AVAX_NETWORK_CHAIN_ID, +]; + +export async function getEvmGasParametersForContract( + contract: ethers.Contract +): Promise { + const chainId = await getChainIdFromContract(contract); + + if (EVM_EIP1559_CHAIN_IDS.indexOf(chainId)) { + return CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559; + } + return CROSSCHAINSWAP_GAS_PARAMETERS_EVM; +} + +async function getChainIdFromContract( + contract: ethers.Contract +): Promise { + const network = await contract.provider.getNetwork(); + return network.chainId; +} + +// exact in +// +export async function evmSwapExactInFromVaaNative( + swapContractWithSigner: ethers.Contract, + signedVaa: Uint8Array +): Promise { + const gasParams = await getEvmGasParametersForContract( + swapContractWithSigner + ); + + const tx = await swapContractWithSigner.recvAndSwapExactNativeIn( + signedVaa, + gasParams + ); + return tx.wait(); +} + +export async function evmSwapExactInFromVaaToken( + swapContractWithSigner: ethers.Contract, + signedVaa: Uint8Array +): Promise { + const gasParams = await getEvmGasParametersForContract( + swapContractWithSigner + ); + + const tx = await swapContractWithSigner.recvAndSwapExactIn( + signedVaa, + gasParams + ); + return tx.wait(); +} + +// exact out +// +export async function evmSwapExactOutFromVaaNative( + swapContractWithSigner: ethers.Contract, + signedVaa: Uint8Array +): Promise { + const gasParams = await getEvmGasParametersForContract( + swapContractWithSigner + ); + + const tx = await swapContractWithSigner.recvAndSwapExactNativeOut( + signedVaa, + gasParams + ); + return tx.wait(); +} + +export async function evmSwapExactOutFromVaaToken( + swapContractWithSigner: ethers.Contract, + signedVaa: Uint8Array +): Promise { + const gasParams = await getEvmGasParametersForContract( + swapContractWithSigner + ); + + const tx = await swapContractWithSigner.recvAndSwapExactOut( + signedVaa, + gasParams + ); + return tx.wait(); +} diff --git a/react/src/swapper/swapper.ts b/react/src/swapper/swapper.ts index 6749c97..ff47a1e 100644 --- a/react/src/swapper/swapper.ts +++ b/react/src/swapper/swapper.ts @@ -1,10 +1,11 @@ -//@ts-nocheck import { ethers } from "ethers"; import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { ChainId, CHAIN_ID_ETH, CHAIN_ID_POLYGON, + CHAIN_ID_AVAX, + CHAIN_ID_BSC, CHAIN_ID_TERRA, getEmitterAddressEth, hexToUint8Array, @@ -25,9 +26,13 @@ import { TOKEN_BRIDGE_ADDRESS_ETHEREUM, TOKEN_BRIDGE_ADDRESS_POLYGON, TOKEN_BRIDGE_ADDRESS_TERRA, + TOKEN_BRIDGE_ADDRESS_AVALANCHE, + TOKEN_BRIDGE_ADDRESS_BSC, CORE_BRIDGE_ADDRESS_ETHEREUM, CORE_BRIDGE_ADDRESS_POLYGON, CORE_BRIDGE_ADDRESS_TERRA, + CORE_BRIDGE_ADDRESS_AVALANCHE, + CORE_BRIDGE_ADDRESS_BSC, WORMHOLE_RPC_HOSTS, //ETH_NETWORK_CHAIN_ID, //POLYGON_NETWORK_CHAIN_ID, @@ -37,16 +42,19 @@ import { UST_TOKEN_INFO, } from "../utils/consts"; import { - CROSSCHAINSWAP_GAS_PARAMETERS, - swapExactInFromVaaNative, - swapExactInFromVaaToken, - swapExactOutFromVaaNative, - swapExactOutFromVaaToken, -} from "./util"; + evmSwapExactInFromVaaNative, + evmSwapExactInFromVaaToken, + evmSwapExactOutFromVaaNative, + evmSwapExactOutFromVaaToken, + getEvmGasParametersForContract, +} from "./helpers"; 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 { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../addresses/goerli"; import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../addresses/mumbai"; +import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_AVALANCHE } from "../addresses/fuji"; +import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_BSC } from "../addresses/bsc"; +import { makeErc20Contract } from "../route/evm"; // placeholders const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA = ""; @@ -88,6 +96,28 @@ const EXECUTION_PARAMETERS_POLYGON: ExecutionParameters = { }, }; +const EXECUTION_PARAMETERS_AVALANCHE: ExecutionParameters = { + crossChainSwap: { + address: CROSSCHAINSWAP_CONTRACT_ADDRESS_AVALANCHE, + }, + wormhole: { + chainId: CHAIN_ID_AVAX, + coreBridgeAddress: CORE_BRIDGE_ADDRESS_AVALANCHE, + tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_AVALANCHE, + }, +}; + +const EXECUTION_PARAMETERS_BSC: ExecutionParameters = { + crossChainSwap: { + address: CROSSCHAINSWAP_CONTRACT_ADDRESS_BSC, + }, + wormhole: { + chainId: CHAIN_ID_BSC, + coreBridgeAddress: CORE_BRIDGE_ADDRESS_BSC, + tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_BSC, + }, +}; + const EXECUTION_PARAMETERS_TERRA: ExecutionParameters = { crossChainSwap: { address: CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA, @@ -107,6 +137,12 @@ function makeExecutionParameters(chainId: ChainId): ExecutionParameters { case CHAIN_ID_POLYGON: { return EXECUTION_PARAMETERS_POLYGON; } + case CHAIN_ID_AVAX: { + return EXECUTION_PARAMETERS_AVALANCHE; + } + case CHAIN_ID_BSC: { + return EXECUTION_PARAMETERS_BSC; + } case CHAIN_ID_TERRA: { return EXECUTION_PARAMETERS_TERRA; } @@ -124,7 +160,7 @@ async function evmApproveContractTokenSpend( amount: ethers.BigNumber ): Promise { // build transaction for token spending - const tokenContract = makeEvmToken(provider, tokenAddress).getContract(); + const tokenContract = await makeErc20Contract(provider, tokenAddress); const unsignedTx = await tokenContract.populateTransaction.approve( swapContractAddress, amount @@ -218,7 +254,7 @@ async function evmApproveAndSwapExactIn( quoteParams.dst.minAmountOut, addressToBytes32(address, dstWormholeChainId), quoteParams.src.deadline, - quoteParams.dst.poolFee || quoteParams.src.poolFee, + quoteParams.dst.poolFee || quoteParams.src.poolFee || 0, ]; const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); @@ -229,14 +265,10 @@ async function evmApproveAndSwapExactIn( ); const bridgeNonce = 69; + const gasParams = getEvmGasParametersForContract(swapContract); // do the swap if (isNative) { - const gasPlusValue = { - value: amountIn, - gasLimit: CROSSCHAINSWAP_GAS_PARAMETERS.gasLimit, - maxFeePerGas: CROSSCHAINSWAP_GAS_PARAMETERS.maxFeePerGas, - maxPriorityFeePerGas: CROSSCHAINSWAP_GAS_PARAMETERS.maxPriorityFeePerGas, - }; + const transactionParams = { value: amountIn, ...gasParams }; console.info("swapExactNativeInAndTransfer"); const tx = await contractWithSigner.swapExactNativeInAndTransfer( @@ -246,7 +278,7 @@ async function evmApproveAndSwapExactIn( dstWormholeChainId, dstContractAddress, bridgeNonce, - gasPlusValue + transactionParams ); return tx.wait(); } else { @@ -267,7 +299,7 @@ async function evmApproveAndSwapExactIn( dstWormholeChainId, dstContractAddress, bridgeNonce, - CROSSCHAINSWAP_GAS_PARAMETERS + gasParams ); return tx.wait(); } @@ -306,7 +338,7 @@ async function evmApproveAndSwapExactOut( quoteParams.dst.amountOut, addressToBytes32(address, dstWormholeChainId), quoteParams.src.deadline, - quoteParams.dst.poolFee || quoteParams.src.poolFee, + quoteParams.dst.poolFee || quoteParams.src.poolFee || 0, ]; const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); @@ -316,14 +348,10 @@ async function evmApproveAndSwapExactOut( ); const bridgeNonce = 69; + const gasParams = getEvmGasParametersForContract(swapContract); // do the swap if (isNative) { - const gasPlusValue = { - value: maxAmountIn, - gasLimit: CROSSCHAINSWAP_GAS_PARAMETERS.gasLimit, - maxFeePerGas: CROSSCHAINSWAP_GAS_PARAMETERS.maxFeePerGas, - maxPriorityFeePerGas: CROSSCHAINSWAP_GAS_PARAMETERS.maxPriorityFeePerGas, - }; + const gasPlusValue = { value: maxAmountIn, ...gasParams }; console.info("swapExactNativeOutAndTransfer"); const tx = await contractWithSigner.swapExactNativeOutAndTransfer( @@ -354,7 +382,7 @@ async function evmApproveAndSwapExactOut( dstWormholeChainId, dstContractAddress, bridgeNonce, - CROSSCHAINSWAP_GAS_PARAMETERS + gasParams ); return tx.wait(); } @@ -378,11 +406,11 @@ async function swapExactInFromVaa( const contractWithSigner = swapContract.connect(dstWallet); if (isNative) { - console.info("swapExactInFromVaaNative"); - return swapExactInFromVaaNative(contractWithSigner, signedVaa); + console.info("evmSwapExactInFromVaaNative"); + return evmSwapExactInFromVaaNative(contractWithSigner, signedVaa); } else { - console.info("swapExactInFromVaaToken"); - return swapExactInFromVaaToken(contractWithSigner, signedVaa); + console.info("evmSwapExactInFromVaaToken"); + return evmSwapExactInFromVaaToken(contractWithSigner, signedVaa); } } @@ -404,11 +432,11 @@ async function swapExactOutFromVaa( const contractWithSigner = swapContract.connect(dstWallet); if (isNative) { - console.info("swapExactOutFromVaaNative"); - return swapExactOutFromVaaNative(contractWithSigner, signedVaa); + console.info("evmSwapExactOutFromVaaNative"); + return evmSwapExactOutFromVaaNative(contractWithSigner, signedVaa); } else { - console.info("swapExactOutFromVaaToken"); - return swapExactOutFromVaaToken(contractWithSigner, signedVaa); + console.info("evmSwapExactOutFromVaaToken"); + return evmSwapExactOutFromVaaToken(contractWithSigner, signedVaa); } } @@ -446,7 +474,6 @@ export class UniswapToUniswapExecutor { cachedExactInParams: ExactInCrossParameters; cachedExactOutParams: ExactOutCrossParameters; quoteType: QuoteType; - tokens: CrossChainSwapTokens; // swapping isNative: boolean; diff --git a/react/src/swapper/util.ts b/react/src/swapper/util.ts deleted file mode 100644 index 09b9edd..0000000 --- a/react/src/swapper/util.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { ethers } from "ethers"; -import { TransactionReceipt } from "@ethersproject/abstract-provider"; - -export const CROSSCHAINSWAP_GAS_PARAMETERS = { - gasLimit: "550000", - maxFeePerGas: "250000000000", - maxPriorityFeePerGas: "1690000000", -}; - -// exact in -// -export async function swapExactInFromVaaNative( - swapContractWithSigner: ethers.Contract, - signedVaa: Uint8Array -): Promise { - const tx = await swapContractWithSigner.recvAndSwapExactNativeIn( - signedVaa, - CROSSCHAINSWAP_GAS_PARAMETERS - ); - return tx.wait(); -} - -export async function swapExactInFromVaaToken( - swapContractWithSigner: ethers.Contract, - signedVaa: Uint8Array -): Promise { - const tx = await swapContractWithSigner.recvAndSwapExactIn( - signedVaa, - CROSSCHAINSWAP_GAS_PARAMETERS - ); - return tx.wait(); -} - -// exact out (TODO: add to util) -// -export async function swapExactOutFromVaaNative( - swapContractWithSigner: ethers.Contract, - signedVaa: Uint8Array -): Promise { - const tx = await swapContractWithSigner.recvAndSwapExactNativeOut( - signedVaa, - CROSSCHAINSWAP_GAS_PARAMETERS - ); - return tx.wait(); -} - -export async function swapExactOutFromVaaToken( - swapContractWithSigner: ethers.Contract, - signedVaa: Uint8Array -): Promise { - const tx = await swapContractWithSigner.recvAndSwapExactOut( - signedVaa, - CROSSCHAINSWAP_GAS_PARAMETERS - ); - return tx.wait(); -} diff --git a/react/src/utils/consts.ts b/react/src/utils/consts.ts index dd5e3e6..dcc376d 100644 --- a/react/src/utils/consts.ts +++ b/react/src/utils/consts.ts @@ -3,10 +3,19 @@ import { CHAIN_ID_ETH, CHAIN_ID_POLYGON, CHAIN_ID_TERRA, + CHAIN_ID_AVAX, + CHAIN_ID_BSC, } from "@certusone/wormhole-sdk"; -import ethIcon from "../icons/eth.svg"; -import polygonIcon from "../icons/polygon.svg"; -import terraIcon from "../icons/terra.svg"; + +//import ethIcon from "../icons/eth.svg"; +//import polygonIcon from "../icons/polygon.svg"; +//import terraIcon from "../icons/terra.svg"; + +const ethIcon = ""; +const polygonIcon = ""; +const bnbIcon = ""; +const avaxIcon = ""; +const terraIcon = ""; export interface TokenInfo { name: string; @@ -18,6 +27,7 @@ export interface TokenInfo { ustPairedAddress: string | undefined; } +// matic export const MATIC_TOKEN_INFO: TokenInfo = { name: "MATIC", address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", // used to compute quote @@ -38,6 +48,7 @@ export const WMATIC_TOKEN_INFO: TokenInfo = { ustPairedAddress: "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c", }; +// eth export const ETH_TOKEN_INFO: TokenInfo = { name: "ETH", address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // used to compute quote @@ -58,6 +69,49 @@ export const WETH_TOKEN_INFO: TokenInfo = { ustPairedAddress: "0x36Ed51Afc79619b299b238898E72ce482600568a", }; +// avax +export const AVAX_TOKEN_INFO: TokenInfo = { + name: "AVAX", + address: "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3", + chainId: CHAIN_ID_AVAX, + logo: avaxIcon, + isNative: true, + maxAmount: 0.01, + ustPairedAddress: "0xe09ed38e5cd1014444846f62376ac88c5232cde9", +}; + +export const WAVAX_TOKEN_INFO: TokenInfo = { + name: "WAVAX", + address: "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3", + chainId: CHAIN_ID_AVAX, + logo: avaxIcon, + isNative: false, + maxAmount: 0.01, + ustPairedAddress: "0xe09ed38e5cd1014444846f62376ac88c5232cde9", +}; + +// bnb +export const BNB_TOKEN_INFO: TokenInfo = { + name: "BNB", + address: "0xae13d989dac2f0debff460ac112a837c89baa7cd", + chainId: CHAIN_ID_BSC, + logo: bnbIcon, + isNative: true, + maxAmount: 0.01, + ustPairedAddress: "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1", +}; + +export const WBNB_TOKEN_INFO: TokenInfo = { + name: "WBNB", + address: "0xae13d989dac2f0debff460ac112a837c89baa7cd", + chainId: CHAIN_ID_BSC, + logo: bnbIcon, + isNative: false, + maxAmount: 0.01, + ustPairedAddress: "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1", +}; + +// ust export const UST_TOKEN_INFO: TokenInfo = { name: "UST", address: "uusd", @@ -92,16 +146,30 @@ export const getSupportedSwaps = (tokenInfo: TokenInfo) => { return []; }; -export const ETH_NETWORK_CHAIN_ID = 5; +export const EVM_ETH_NETWORK_CHAIN_ID = 5; +export const EVM_POLYGON_NETWORK_CHAIN_ID = 80001; +export const EVM_AVAX_NETWORK_CHAIN_ID = 43113; +export const EVM_BSC_NETWORK_CHAIN_ID = 97; -export const POLYGON_NETWORK_CHAIN_ID = 80001; - -export const getEvmChainId = (chainId: ChainId) => - chainId === CHAIN_ID_ETH - ? ETH_NETWORK_CHAIN_ID - : chainId === CHAIN_ID_POLYGON - ? POLYGON_NETWORK_CHAIN_ID - : undefined; +export function getEvmChainId(chainId: ChainId): number { + switch (chainId) { + case CHAIN_ID_ETH: { + return EVM_ETH_NETWORK_CHAIN_ID; + } + case CHAIN_ID_POLYGON: { + return EVM_POLYGON_NETWORK_CHAIN_ID; + } + case CHAIN_ID_AVAX: { + return EVM_AVAX_NETWORK_CHAIN_ID; + } + case CHAIN_ID_BSC: { + return EVM_BSC_NETWORK_CHAIN_ID; + } + default: { + return undefined; + } + } +} export const RELAYER_FEE_UST = "0.25"; @@ -109,26 +177,35 @@ export const WORMHOLE_RPC_HOSTS = [ "https://wormhole-v2-testnet-api.certus.one", ]; +// core bridge export const CORE_BRIDGE_ADDRESS_ETHEREUM = "0x706abc4E45D419950511e474C7B9Ed348A4a716c"; export const CORE_BRIDGE_ADDRESS_POLYGON = "0x0CBE91CF822c73C2315FB05100C2F714765d5c20"; +export const CORE_BRIDGE_ADDRESS_AVALANCHE = + "0x7bbcE28e64B3F8b84d876Ab298393c38ad7aac4C"; + +export const CORE_BRIDGE_ADDRESS_BSC = + "0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D"; + export const CORE_BRIDGE_ADDRESS_TERRA = undefined; +// token bridge export const TOKEN_BRIDGE_ADDRESS_ETHEREUM = "0xF890982f9310df57d00f659cf4fd87e65adEd8d7"; export const TOKEN_BRIDGE_ADDRESS_POLYGON = "0x377D55a7928c046E18eEbb61977e714d2a76472a"; +export const TOKEN_BRIDGE_ADDRESS_BSC = + "0x9dcF9D205C9De35334D646BeE44b2D2859712A09"; + +export const TOKEN_BRIDGE_ADDRESS_AVALANCHE = + "0x61E44E506Ca5659E6c0bba9b678586fA2d729756"; + export const TOKEN_BRIDGE_ADDRESS_TERRA = undefined; -export const QUICKSWAP_FACTORY_ADDRESS = - "0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32"; - -export const UNISWAP_V3_FACTORY_ADDRESS = - "0x1F98431c8aD98523631AE4a59f267346ea31F984"; - +// gas export const APPROVAL_GAS_LIMIT = "100000"; From 3e310eeccc179cf6e7e6f57b705557eaae1918b7 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Fri, 28 Jan 2022 16:32:14 +0000 Subject: [PATCH 16/38] Add ts-nocheck --- react/src/swapper/swapper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/react/src/swapper/swapper.ts b/react/src/swapper/swapper.ts index ff47a1e..a4b1a5f 100644 --- a/react/src/swapper/swapper.ts +++ b/react/src/swapper/swapper.ts @@ -1,3 +1,4 @@ +//@ts-nocheck import { ethers } from "ethers"; import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { From 172230546bc757e03eda0d187a21bf08522bc49a Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Fri, 28 Jan 2022 17:17:36 +0000 Subject: [PATCH 17/38] Add logging --- react/src/swapper/helpers.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts index a75c4a1..1b3439c 100644 --- a/react/src/swapper/helpers.ts +++ b/react/src/swapper/helpers.ts @@ -53,6 +53,10 @@ export async function evmSwapExactInFromVaaNative( swapContractWithSigner ); + console.info( + `evmSwapExactInFromVaaNative... contract: ${swapContractWithSigner.address}, gasParams: ${gasParams}` + ); + const tx = await swapContractWithSigner.recvAndSwapExactNativeIn( signedVaa, gasParams From 57bdc2c5b15473e93c5779eea81deacfd57e541d Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Fri, 28 Jan 2022 17:19:47 +0000 Subject: [PATCH 18/38] Add logging --- react/src/swapper/helpers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts index 1b3439c..3caf71f 100644 --- a/react/src/swapper/helpers.ts +++ b/react/src/swapper/helpers.ts @@ -29,6 +29,7 @@ export async function getEvmGasParametersForContract( contract: ethers.Contract ): Promise { const chainId = await getChainIdFromContract(contract); + console.info(`getEvmGasParametersForContract... chainId: ${chainId}`); if (EVM_EIP1559_CHAIN_IDS.indexOf(chainId)) { return CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559; From c7a4e87b03f55b4ae5885b5dc8eb7eb1977689aa Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Fri, 28 Jan 2022 17:25:20 +0000 Subject: [PATCH 19/38] Add logging --- react/src/swapper/helpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts index 3caf71f..ee444e9 100644 --- a/react/src/swapper/helpers.ts +++ b/react/src/swapper/helpers.ts @@ -55,7 +55,9 @@ export async function evmSwapExactInFromVaaNative( ); console.info( - `evmSwapExactInFromVaaNative... contract: ${swapContractWithSigner.address}, gasParams: ${gasParams}` + `evmSwapExactInFromVaaNative... contract: ${ + swapContractWithSigner.address + }, gasParams: ${JSON.stringify(gasParams)}` ); const tx = await swapContractWithSigner.recvAndSwapExactNativeIn( From 416a5ac951c7460a93d25eb847209349e5bf9402 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Fri, 28 Jan 2022 17:31:15 +0000 Subject: [PATCH 20/38] Add logging --- react/src/swapper/helpers.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts index ee444e9..f83a461 100644 --- a/react/src/swapper/helpers.ts +++ b/react/src/swapper/helpers.ts @@ -32,8 +32,18 @@ export async function getEvmGasParametersForContract( console.info(`getEvmGasParametersForContract... chainId: ${chainId}`); if (EVM_EIP1559_CHAIN_IDS.indexOf(chainId)) { + console.info( + `eip1559? chainId: ${chainId}, eip1559 chains... ${JSON.stringify( + EVM_EIP1559_CHAIN_IDS + )}` + ); return CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559; } + console.info( + `not eip1559 chainId: ${chainId}, eip1559 chains... ${JSON.stringify( + EVM_EIP1559_CHAIN_IDS + )}` + ); return CROSSCHAINSWAP_GAS_PARAMETERS_EVM; } From 59c3a8965c6d51a62f74e60a6770755e578d4716 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Fri, 28 Jan 2022 17:37:05 +0000 Subject: [PATCH 21/38] Fix indexOf check --- react/src/swapper/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts index f83a461..8fc0355 100644 --- a/react/src/swapper/helpers.ts +++ b/react/src/swapper/helpers.ts @@ -31,7 +31,7 @@ export async function getEvmGasParametersForContract( const chainId = await getChainIdFromContract(contract); console.info(`getEvmGasParametersForContract... chainId: ${chainId}`); - if (EVM_EIP1559_CHAIN_IDS.indexOf(chainId)) { + if (EVM_EIP1559_CHAIN_IDS.indexOf(chainId) >= 0) { console.info( `eip1559? chainId: ${chainId}, eip1559 chains... ${JSON.stringify( EVM_EIP1559_CHAIN_IDS From f9870b03deec3597ccf74a7a9b2907618b8278d0 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Fri, 28 Jan 2022 17:43:04 +0000 Subject: [PATCH 22/38] Remove logging --- react/src/swapper/helpers.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts index 8fc0355..c5ed016 100644 --- a/react/src/swapper/helpers.ts +++ b/react/src/swapper/helpers.ts @@ -29,21 +29,11 @@ export async function getEvmGasParametersForContract( contract: ethers.Contract ): Promise { const chainId = await getChainIdFromContract(contract); - console.info(`getEvmGasParametersForContract... chainId: ${chainId}`); if (EVM_EIP1559_CHAIN_IDS.indexOf(chainId) >= 0) { - console.info( - `eip1559? chainId: ${chainId}, eip1559 chains... ${JSON.stringify( - EVM_EIP1559_CHAIN_IDS - )}` - ); return CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559; } - console.info( - `not eip1559 chainId: ${chainId}, eip1559 chains... ${JSON.stringify( - EVM_EIP1559_CHAIN_IDS - )}` - ); + return CROSSCHAINSWAP_GAS_PARAMETERS_EVM; } @@ -64,12 +54,6 @@ export async function evmSwapExactInFromVaaNative( swapContractWithSigner ); - console.info( - `evmSwapExactInFromVaaNative... contract: ${ - swapContractWithSigner.address - }, gasParams: ${JSON.stringify(gasParams)}` - ); - const tx = await swapContractWithSigner.recvAndSwapExactNativeIn( signedVaa, gasParams From ea822b5fb2fee8c6a923df1b911e387c4ead513f Mon Sep 17 00:00:00 2001 From: Bruce Riley Date: Fri, 28 Jan 2022 17:59:48 +0000 Subject: [PATCH 23/38] Relayer support for more EVMs --- swap_relayer/.env.sample | 27 +++- swap_relayer/package.json | 2 +- swap_relayer/src/evm.ts | 277 +++++++++++++++++++++---------------- swap_relayer/src/index.ts | 200 +++++++-------------------- swap_relayer/src/terra.ts | 280 +++++++++++++++++++++++++++++--------- 5 files changed, 452 insertions(+), 334 deletions(-) diff --git a/swap_relayer/.env.sample b/swap_relayer/.env.sample index aa91f7a..a07ed7b 100644 --- a/swap_relayer/.env.sample +++ b/swap_relayer/.env.sample @@ -1,12 +1,29 @@ # TestNet SPY_SERVICE_HOST=localhost:7073 -SPY_SERVICE_FILTERS=[{"chain_id":2,"emitter_address":"0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7"},{"chain_id":3,"emitter_address":"terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a"},{"chain_id":5,"emitter_address":"0x000000000000000000000000377d55a7928c046e18eebb61977e714d2a76472a"}] +# You can omit the following to get signed VAAs from every emitter. +#SPY_SERVICE_FILTERS=[{"chain_id":2,"emitter_address":"0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7"},{"chain_id":3,"emitter_address":"terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a"},{"chain_id":4,"emitter_address":"0x0000000000000000000000009dcF9D205C9De35334D646BeE44b2D2859712A09"},{"chain_id":5,"emitter_address":"0x000000000000000000000000377d55a7928c046e18eebb61977e714d2a76472a"},{"chain_id":6,"emitter_address":"0x0000000000000000000000007bbcE28e64B3F8b84d876Ab298393c38ad7aac4C"}] + +EVM_CHAINS=ETH,BSC,POLYGON,AVAX ETH_PROVIDER=https://goerli.infura.io/v3/your_project_id ETH_TOKEN_BRIDGE_ADDRESS=0xF890982f9310df57d00f659cf4fd87e65adEd8d7 +ETH_CHAIN_ID=2 +ETH_ABI=V3 + +BSC_PROVIDER= +BSC_TOKEN_BRIDGE_ADDRESS=0x9dcF9D205C9De35334D646BeE44b2D2859712A09 +BSC_CHAIN_ID=4 +BSC_ABI=V2 POLYGON_PROVIDER=https://polygon-mumbai.infura.io/v3/your_project_id POLYGON_TOKEN_BRIDGE_ADDRESS=0x377D55a7928c046E18eEbb61977e714d2a76472a +POLYGON_CHAIN_ID=5 +POLYGON_ABI=V2 + +AVAX_PROVIDER= +AVAX_TOKEN_BRIDGE_ADDRESS=0x61E44E506Ca5659E6c0bba9b678586fA2d729756 +AVAX_CHAIN_ID=6 +AVAX_ABI=V2 WALLET_PRIVATE_KEY=your_key_here @@ -20,6 +37,8 @@ TERRA_TOKEN_BRIDGE_ADDRESS=terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a #LOG_DIR=/var/logs LOG_LEVEL=debug -ETH_CONTRACT_ADDRESS=0x61D26732B190bdc5771e2a2b3ADB295e1b5A88BF -POLYGON_CONTRACT_ADDRESS=0xc5Ba16A974a0c0E7935285d99F496Ee65eDFB8BA -TERRA_CONTRACT_ADDRESS=terra_contract_address \ No newline at end of file +ETH_CONTRACT_ADDRESS=0x9e7Cae3a46ED297b0a05FCEeb41160fC5218E14f +BSC_CONTRACT_ADDRESS=0x0DC183c2eFAA5e1749B85f13621F5cC6aCcDa786 +POLYGON_CONTRACT_ADDRESS=0x72F2F646dC979a9fA8aA685B8a47b7afe2fE0516 +TERRA_CONTRACT_ADDRESS=terra163shc8unyqrndgcldaj2q9kgnqs82v0kgkhynf +AVAX_CONTRACT_ADDRESS=0x52D8A50AF35b0760335F29a4D6aaF0604B7D7484 \ No newline at end of file diff --git a/swap_relayer/package.json b/swap_relayer/package.json index 0720959..3c152ce 100644 --- a/swap_relayer/package.json +++ b/swap_relayer/package.json @@ -32,7 +32,7 @@ "cors": "^2.8.5", "dotenv": "^10.0.0", "ethers": "^5.5.3", - "@terra-money/terra.js": "^2.0.14", + "@terra-money/terra.js": "^3.0.4", "winston": "^3.3.3" } } diff --git a/swap_relayer/src/evm.ts b/swap_relayer/src/evm.ts index 470bdc5..cb1cb08 100644 --- a/swap_relayer/src/evm.ts +++ b/swap_relayer/src/evm.ts @@ -1,23 +1,29 @@ -import { Mutex } from "async-mutex"; -let CondVar = require("condition-variable"); - -import { getIsTransferCompletedEth } from "@certusone/wormhole-sdk"; - import { - importCoreWasm, - setDefaultWasm, -} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; + 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/util"; +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; @@ -27,48 +33,136 @@ type EvmContractData = { contractWithSigner: ethers.Contract; }; -let ethContractData: EvmContractData = null; -let polygonContractData: EvmContractData = null; +let evmContractData = new Map(); -export function makeEvmContractData(env: OurEnvironment) { - ethContractData = makeEthContractData(env); - polygonContractData = makePolygonContractData(env); +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; } -// Ethereum (Goerli) set up -function makeEthContractData(env: OurEnvironment): EvmContractData { - let contractAddress: string = env.eth_contract_address.toLowerCase(); - if (contractAddress.search("0x") == 0) { +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 Ethereum: contract address: [" + + "Connecting to " + + env.name + + ": chain_id: " + + env.chain_id + + ", contract address: [" + contractAddress + "], node: [" + - env.eth_provider_url + + env.provider_url + "], token bridge address: [" + - env.eth_token_bridge_address + + env.token_bridge_address + + "], abi version: [" + + env.abi_version + "]" ); - const provider = new ethers.providers.StaticJsonRpcProvider( - env.eth_provider_url - ); + const provider = new ethers.providers.StaticJsonRpcProvider(env.provider_url); const contract = new ethers.Contract( contractAddress, - SWAP_CONTRACT_V3_ABI, + env.abi_version == "V2" ? SWAP_CONTRACT_V2_ABI : SWAP_CONTRACT_V3_ABI, provider ); - const wallet = new ethers.Wallet(env.evm_wallet_private_key, provider); + const wallet = new ethers.Wallet(env.wallet_private_key, provider); const contractWithSigner = contract.connect(wallet); return { - name: "Ethereum", + chain_id: env.chain_id, + name: env.name, contractAddress: contractAddress, - tokenBridgeAddress: env.eth_token_bridge_address, + tokenBridgeAddress: env.token_bridge_address, contract: contract, provider: provider, wallet: wallet, @@ -76,53 +170,12 @@ function makeEthContractData(env: OurEnvironment): EvmContractData { }; } -// Polygon (Mumbai) set up -function makePolygonContractData(env: OurEnvironment): EvmContractData { - let contractAddress: string = env.polygon_contract_address.toLowerCase(); - if (contractAddress.search("0x") == 0) { - contractAddress = contractAddress.substring(2); - } - - logger.info( - "Connecting to Polygon: contract address: [" + - contractAddress + - "], node: [" + - env.polygon_provider_url + - "], token bridge address: [" + - env.polygon_token_bridge_address + - "]" - ); - - const provider = new ethers.providers.StaticJsonRpcProvider( - env.polygon_provider_url - ); - - const contract = new ethers.Contract( - contractAddress, - SWAP_CONTRACT_V2_ABI, - provider - ); - - const wallet = new ethers.Wallet(env.evm_wallet_private_key, provider); - const contractWithSigner = contract.connect(wallet); - - return { - name: "Polygon", - contractAddress: contractAddress, - tokenBridgeAddress: env.polygon_token_bridge_address, - contract: contract, - provider: provider, - wallet: wallet, - contractWithSigner: contractWithSigner, - }; -} - -export function isEvmContract(contractAddress: string): boolean { - return ( - (ethContractData && contractAddress === ethContractData.contractAddress) || - (polygonContractData && - contractAddress === polygonContractData.contractAddress) - ); +export function isEvmContract( + contractAddress: string, + chain_id: number +): boolean { + let ecd = evmContractData.get(chain_id); + return ecd && ecd.contractAddress === contractAddress; } /* @@ -177,16 +230,24 @@ export function isEvmContract(contractAddress: string): boolean { } */ -export async function relayVaaToEvm( - signedVaaArray: Uint8Array, - t3Payload: Type3Payload -) { +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( - "relayVaa: unsupported swapFunctionType: [" + + "relayVaaTo" + + ecd.name + + ": unsupported swapFunctionType: [" + t3Payload.swapFunctionType + "]" ); @@ -196,48 +257,30 @@ export async function relayVaaToEvm( if (t3Payload.swapCurrencyType === 1) { native = true; } else if (t3Payload.swapCurrencyType !== 2) { + error = true; logger.error( - "relayVaa: unsupported swapCurrencyType: [" + + "relayVaaTo" + + ecd.name + + ": unsupported swapCurrencyType: [" + t3Payload.swapCurrencyType + "]" ); } + if (error) return; + logger.debug( - "relayVaa: contractAddress: [" + + "relayVaaTo" + + ecd.name + + ": chain_id: " + + ecd.chain_id + + ", contractAddress: [" + t3Payload.contractAddress + - "], ethContract: [" + - ethContractData.contractAddress + - "], polygonContract[" + - polygonContractData.contractAddress + "]" ); - if (t3Payload.contractAddress === ethContractData.contractAddress) { - await relayVaaToEvmChain( - t3Payload, - ethContractData, - signedVaaArray, - exactIn, - native - ); - } else if ( - t3Payload.contractAddress === polygonContractData.contractAddress - ) { - await relayVaaToEvmChain( - t3Payload, - polygonContractData, - signedVaaArray, - exactIn, - native - ); - } else { - logger.error( - "relayVaa: unexpected contract: [" + - t3Payload.contractAddress + - "], this should not happen!" - ); - } + const signedVaaArray = hexToUint8Array(vaaBytes); + await relayVaaToEvmChain(t3Payload, ecd, signedVaaArray, exactIn, native); } async function relayVaaToEvmChain( @@ -289,24 +332,28 @@ async function relayVaaToEvmChain( let receipt: any = null; if (exactIn) { if (native) { - receipt = await swap.swapExactInFromVaaNative( + logger.debug("relayVaaTo: calling evmSwapExactInFromVaaNative()"); + receipt = await swap.evmSwapExactInFromVaaNative( tcd.contractWithSigner, signedVaaArray ); } else { - receipt = await swap.swapExactInFromVaaToken( + logger.debug("relayVaaTo: calling evmSwapExactInFromVaaToken()"); + receipt = await swap.evmSwapExactInFromVaaToken( tcd.contractWithSigner, signedVaaArray ); } } else { if (native) { - receipt = await swap.swapExactOutFromVaaNative( + logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaNative()"); + receipt = await swap.evmSwapExactOutFromVaaNative( tcd.contractWithSigner, signedVaaArray ); } else { - receipt = await swap.swapExactOutFromVaaToken( + logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaToken()"); + receipt = await swap.evmSwapExactOutFromVaaToken( tcd.contractWithSigner, signedVaaArray ); diff --git a/swap_relayer/src/index.ts b/swap_relayer/src/index.ts index 2a4b675..17dfdac 100644 --- a/swap_relayer/src/index.ts +++ b/swap_relayer/src/index.ts @@ -10,7 +10,6 @@ import { getEmitterAddressEth, getEmitterAddressSolana, getEmitterAddressTerra, - getIsTransferCompletedEth, } from "@certusone/wormhole-sdk"; import { @@ -25,17 +24,20 @@ import { 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/util"; - -import { isEvmContract, makeEvmContractData, relayVaaToEvm } from "./evm"; +import { + EvmEnvironment, + isEvmContract, + loadEvmConfig, + makeEvmContractData, + relayVaaToEvm, +} from "./evm"; import { isTerraContract, + loadTerraConfig, makeTerraContractData, relayVaaToTerra, + TerraEnvironment, } from "./terra"; export let logger: any; @@ -54,34 +56,8 @@ export type OurEnvironment = { spy_host: string; spy_filters: string; - eth_provider_url: string; - eth_contract_address: string; - eth_token_bridge_address: string; - - polygon_provider_url: string; - polygon_contract_address: string; - polygon_token_bridge_address: string; - - evm_wallet_private_key: string; - - terraEnabled: boolean; - terra_provider_url: string; - terra_chain_id: string; - terra_name: string; - terra_contract_address: string; - terra_token_bridge_address: string; - terra_wallet_private_key: string; - terra_gas_price_url: string; -}; - -type EvmContractData = { - name: string; - contractAddress: string; - tokenBridgeAddress: string; - contract: ethers.Contract; - provider: ethers.providers.StaticJsonRpcProvider; - wallet: ethers.Wallet; - contractWithSigner: ethers.Contract; + evm_configs: EvmEnvironment[]; + terra_config: TerraEnvironment; }; export type Type3Payload = { @@ -112,16 +88,14 @@ let pendingQueue = new Array(); if (success) { logger.info( - "swap_relay starting up, will listen for signed VAAs from [" + + "swap_relayer starting up, will listen for signed VAAs from [" + env.spy_host + "]" ); try { - makeEvmContractData(env); - if (env.terraEnabled) { - makeTerraContractData(env); - } + makeEvmContractData(env.evm_configs); + makeTerraContractData(env.terra_config); } catch (e: any) { logger.error("failed to connect to target contracts: %o", e); success = false; @@ -139,98 +113,22 @@ function loadConfig(): [boolean, OurEnvironment] { return [false, undefined]; } - if (!process.env.ETH_PROVIDER) { - logger.error("Missing environment variable ETH_PROVIDER"); - return [false, undefined]; - } - if (!process.env.ETH_CONTRACT_ADDRESS) { - logger.error("Missing environment variable ETH_CONTRACT_ADDRESS"); - return [false, undefined]; - } - if (!process.env.ETH_TOKEN_BRIDGE_ADDRESS) { - logger.error("Missing environment variable ETH_TOKEN_BRIDGE_ADDRESS"); - return [false, undefined]; + let evm_configs: EvmEnvironment[] = null; + if (process.env.EVM_CHAINS) { + evm_configs = loadEvmConfig(); + if (!evm_configs) return [false, undefined]; } - if (!process.env.POLYGON_PROVIDER) { - logger.error("Missing environment variable POLYGON_PROVIDER"); - return [false, undefined]; - } - if (!process.env.POLYGON_CONTRACT_ADDRESS) { - logger.error("Missing environment variable POLYGON_CONTRACT_ADDRESS"); - return [false, undefined]; - } - if (!process.env.POLYGON_TOKEN_BRIDGE_ADDRESS) { - logger.error("Missing environment variable POLYGON_TOKEN_BRIDGE_ADDRESS"); - return [false, undefined]; - } - - if (!process.env.WALLET_PRIVATE_KEY) { - logger.error("Missing environment variable WALLET_PRIVATE_KEY"); - return [false, undefined]; - } - - let terraEnabled: boolean = false; - if (process.env.TERRA_PROVIDER) { - terraEnabled = true; - - if (!process.env.TERRA_CHAIN_ID) { - logger.error("Missing environment variable WALLET_PRIVATE_KEY"); - return [false, undefined]; - throw "Missing environment variable TERRA_CHAIN_ID"; - } - - if (!process.env.TERRA_NAME) { - logger.error("Missing environment variable WALLET_PRIVATE_KEY"); - return [false, undefined]; - throw "Missing environment variable TERRA_NAME"; - } - - if (!process.env.TERRA_WALLET_PRIVATE_KEY) { - logger.error("Missing environment variable TERRA_WALLET_PRIVATE_KEY"); - return [false, undefined]; - } - - if (!process.env.TERRA_GAS_PRICE_URL) { - logger.error("Missing environment variable TERRA_GAS_PRICE_URL"); - return [false, undefined]; - } - - if (!process.env.TERRA_CONTRACT_ADDRESS) { - logger.error("Missing environment variable TERRA_CONTRACT_ADDRESS"); - return [false, undefined]; - } - - if (!process.env.TERRA_TOKEN_BRIDGE_ADDRESS) { - logger.error("Missing environment variable TERRA_TOKEN_BRIDGE_ADDRESS"); - return [false, undefined]; - } - } + let terra_config = loadTerraConfig(); + if (!terra_config) return [false, undefined]; return [ true, { spy_host: process.env.SPY_SERVICE_HOST, spy_filters: process.env.SPY_SERVICE_FILTERS, - - eth_provider_url: process.env.ETH_PROVIDER, - eth_contract_address: process.env.ETH_CONTRACT_ADDRESS, - eth_token_bridge_address: process.env.ETH_TOKEN_BRIDGE_ADDRESS, - - polygon_provider_url: process.env.POLYGON_PROVIDER, - polygon_contract_address: process.env.POLYGON_CONTRACT_ADDRESS, - polygon_token_bridge_address: process.env.POLYGON_TOKEN_BRIDGE_ADDRESS, - - evm_wallet_private_key: process.env.WALLET_PRIVATE_KEY, - - terraEnabled: terraEnabled, - terra_provider_url: process.env.TERRA_PROVIDER, - terra_chain_id: process.env.TERRA_CHAIN_ID, - terra_name: process.env.TERRA_NAME, - terra_contract_address: process.env.TERRA_CONTRACT_ADDRESS, - terra_token_bridge_address: process.env.TERRA_TOKEN_BRIDGE_ADDRESS, - terra_wallet_private_key: process.env.TERRA_WALLET_PRIVATE_KEY, - terra_gas_price_url: process.env.TERRA_GAS_PRICE_URL, + evm_configs: evm_configs, + terra_config: terra_config, }, ]; } @@ -279,7 +177,7 @@ async function spy_listen() { processVaa(vaaBytes); }); - logger.info("swap_relay waiting for transfer signed VAAs"); + logger.info("swap_relayer waiting for transfer signed VAAs"); })(); } @@ -300,10 +198,10 @@ async function encodeEmitterAddress( async function processVaa(vaaBytes: string) { let receiveTime = new Date(); - logger.debug("processVaa"); + // logger.debug("processVaa"); const { parse_vaa } = await importCoreWasm(); const parsedVAA = parse_vaa(hexToUint8Array(vaaBytes)); - logger.debug("processVaa: parsedVAA: %o", parsedVAA); + // logger.debug("processVaa: parsedVAA: %o", parsedVAA); let emitter_address: string = uint8ArrayToHex(parsedVAA.emitter_address); @@ -333,7 +231,7 @@ async function processVaa(vaaBytes: string) { } if (t3Payload) { - if (isOurContract(t3Payload.contractAddress)) { + if (isOurContract(t3Payload.contractAddress, t3Payload.targetChainId)) { logger.info( "enqueuing type 3 vaa: emitter: [" + parsedVAA.emitter_chain + @@ -372,17 +270,17 @@ async function processVaa(vaaBytes: string) { "]" ); } - } else { - logger.debug( - "dropping vaa: emitter: [" + - parsedVAA.emitter_chain + - ":" + - emitter_address + - "], seqNum: " + - parsedVAA.sequence + - " payloadType: " + - parsedVAA.payload[0] - ); + // } else { + // logger.debug( + // "dropping vaa: emitter: [" + + // parsedVAA.emitter_chain + + // ":" + + // emitter_address + + // "], seqNum: " + + // parsedVAA.sequence + + // " payloadType: " + + // parsedVAA.payload[0] + // ); } } @@ -412,11 +310,6 @@ function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload { payload.slice(67, 67 + 32).toString("hex") + "]" ); - logger.info( - "decodeSignedVAAPayloadType3: terraContractAddr: [" + - payload.slice(67, 67 + 32).toString() + - "]" - ); contractAddress = payload.slice(67, 67 + 32).toString("hex"); } else { @@ -431,21 +324,24 @@ function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload { } contractAddress = payload.slice(79, 79 + 20).toString("hex"); - swapFunctionType = payload.readUInt8(260); - swapCurrencyType = payload.readUInt8(261); + swapFunctionType = payload.readUInt8(272); + swapCurrencyType = payload.readUInt8(273); } return { targetChainId: targetChainId, - contractAddress: payload.slice(79, 79 + 20).toString("hex"), + contractAddress: contractAddress, relayerFee: ethers.BigNumber.from(payload.slice(101, 101 + 32)), swapFunctionType: swapFunctionType, swapCurrencyType: swapCurrencyType, }; } -function isOurContract(contractAddress: string): boolean { - return isEvmContract(contractAddress) || isTerraContract(contractAddress); +function isOurContract(contractAddress: string, chainId: number): boolean { + return ( + isEvmContract(contractAddress, chainId) || + isTerraContract(contractAddress, chainId) + ); } async function postVaa( @@ -533,14 +429,12 @@ async function callBack(err: any, result: any) { } async function relayVaa(vaaBytes: string, t3Payload: Type3Payload) { - const signedVaaArray = hexToUint8Array(vaaBytes); - if (t3Payload.targetChainId === 3) { - relayVaaToTerra(t3Payload, signedVaaArray); + await relayVaaToTerra(t3Payload, vaaBytes); return; } - relayVaaToEvm(signedVaaArray, t3Payload); + await relayVaaToEvm(vaaBytes, t3Payload); } ///////////////////////////////// Start of logger stuff /////////////////////////////////////////// diff --git a/swap_relayer/src/terra.ts b/swap_relayer/src/terra.ts index 692c96b..dbf6a20 100644 --- a/swap_relayer/src/terra.ts +++ b/swap_relayer/src/terra.ts @@ -1,20 +1,34 @@ import { + CHAIN_ID_TERRA, getIsTransferCompletedTerra, redeemOnTerra, + uint8ArrayToHex, } from "@certusone/wormhole-sdk"; -import { - importCoreWasm, - setDefaultWasm, -} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm"; +import axios from "axios"; +import { bech32 } from "bech32"; +import { zeroPad } from "ethers/lib/utils"; +import { fromUint8Array } from "js-base64"; import * as Terra from "@terra-money/terra.js"; import { logger, OurEnvironment, Type3Payload } from "./index"; +export type TerraEnvironment = { + terra_enabled: boolean; + terra_provider_url: string; + terra_chain_id: string; + terra_name: string; + terra_contract_address: string; + terra_token_bridge_address: string; + terra_wallet_private_key: string; + terra_gas_price_url: string; +}; + type TerraContractData = { name: string; contractAddress: string; + encodedContractAddress: string; tokenBridgeAddress: string; lcdConfig: Terra.LCDClientConfig; lcdClient: Terra.LCDClient; @@ -24,15 +38,70 @@ type TerraContractData = { let terraContractData: TerraContractData = null; -export function makeTerraContractData(env: OurEnvironment) { +export function loadTerraConfig(): TerraEnvironment { + let terra_enabled: boolean = false; + if (process.env.TERRA_PROVIDER) { + terra_enabled = true; + + if (!process.env.TERRA_CHAIN_ID) { + logger.error("Missing environment variable WALLET_PRIVATE_KEY"); + return undefined; + } + + if (!process.env.TERRA_NAME) { + logger.error("Missing environment variable WALLET_PRIVATE_KEY"); + return undefined; + } + + if (!process.env.TERRA_WALLET_PRIVATE_KEY) { + logger.error("Missing environment variable TERRA_WALLET_PRIVATE_KEY"); + return undefined; + } + + if (!process.env.TERRA_GAS_PRICE_URL) { + logger.error("Missing environment variable TERRA_GAS_PRICE_URL"); + return undefined; + } + + if (!process.env.TERRA_CONTRACT_ADDRESS) { + logger.error("Missing environment variable TERRA_CONTRACT_ADDRESS"); + return undefined; + } + + if (!process.env.TERRA_TOKEN_BRIDGE_ADDRESS) { + logger.error("Missing environment variable TERRA_TOKEN_BRIDGE_ADDRESS"); + return undefined; + } + } + + return { + terra_enabled: terra_enabled, + terra_provider_url: process.env.TERRA_PROVIDER, + terra_chain_id: process.env.TERRA_CHAIN_ID, + terra_name: process.env.TERRA_NAME, + terra_contract_address: process.env.TERRA_CONTRACT_ADDRESS, + terra_token_bridge_address: process.env.TERRA_TOKEN_BRIDGE_ADDRESS, + terra_wallet_private_key: process.env.TERRA_WALLET_PRIVATE_KEY, + terra_gas_price_url: process.env.TERRA_GAS_PRICE_URL, + }; +} + +export function makeTerraContractData(env: TerraEnvironment) { + if (!env.terra_enabled) return; let contractAddress: string = env.terra_contract_address.toLowerCase(); if (contractAddress.search("0x") == 0) { contractAddress = contractAddress.substring(2); } + let encodedContractAddress: string = Buffer.from( + zeroPad(bech32.fromWords(bech32.decode(contractAddress).words), 32) + ).toString("hex"); + logger.info( "Connecting to Terra: contract address: [" + contractAddress + + "], encoded contract address: [" + + encodedContractAddress + "], node: [" + env.terra_provider_url + "], token bridge address: [" + @@ -61,6 +130,7 @@ export function makeTerraContractData(env: OurEnvironment) { terraContractData = { name: "Terra", contractAddress: contractAddress, + encodedContractAddress: encodedContractAddress, tokenBridgeAddress: env.terra_token_bridge_address, lcdConfig: lcdConfig, lcdClient: lcdClient, @@ -69,33 +139,49 @@ export function makeTerraContractData(env: OurEnvironment) { }; } -export function isTerraContract(contractAddress: string): boolean { - return ( - terraContractData && contractAddress === terraContractData.contractAddress +export function isTerraContract( + contractAddress: string, + chain_id: number +): boolean { + if (chain_id !== CHAIN_ID_TERRA) return false; + if (!terraContractData) return false; + + let retVal: boolean = + terraContractData && + contractAddress === terraContractData.encodedContractAddress; + logger.debug( + "isTerraContract: comparing [" + + contractAddress + + "] to [" + + terraContractData.encodedContractAddress + + "]: " + + retVal ); + + return retVal; } export async function relayVaaToTerra( t3Payload: Type3Payload, - signedVaaArray: Uint8Array + vaaBytes: string ) { if (!terraContractData) return; - logger.debug( - "relayVaaToTerra: checking if already redeemed using tokenBridgeAddress [" + - terraContractData.tokenBridgeAddress + - "]" - ); + // logger.debug( + // "relayVaaToTerra: checking if already redeemed using tokenBridgeAddress [" + + // terraContractData.tokenBridgeAddress + + // "]" + // ); - if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { - logger.info( - "relayVaaToTerra: contract: [" + - t3Payload.contractAddress + - "]: already transferred" - ); + // if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { + // logger.info( + // "relayVaaToTerra: contract: [" + + // t3Payload.contractAddress + + // "]: already transferred" + // ); - return; - } + // return; + // } logger.info( "relayVaaToTerra: contract: [" + @@ -104,30 +190,79 @@ export async function relayVaaToTerra( ); try { - const msg = await redeemOnTerra( - terraContractData.contractAddress, - terraContractData.wallet.key.accAddress, - signedVaaArray + logger.debug( + "relayVaaToTerra: creating message using contract address [" + + terraContractData.contractAddress + + "]" ); - let receipt: any = null; + logger.debug("relayVaaToTerra: creating a message"); + + logger.debug( + "relayVaaToTerra: vaa as hex: [" + + Buffer.from(vaaBytes, "hex").toString("hex") + + "]" + ); + + logger.debug( + "relayVaaToTerra: vaa as base64: [" + + Buffer.from(vaaBytes, "hex").toString("base64") + + "]" + ); + const msg = new Terra.MsgExecuteContract( + terraContractData.wallet.key.accAddress, + terraContractData.contractAddress, + { + redeem_payload: { + data: Buffer.from(vaaBytes, "hex").toString("base64"), + }, + } + ); + + // logger.debug("relayVaaToTerra: getting gas prices"); + // const gasPrices = terraContractData.lcdClient.config.gasPrices; + + // logger.debug("relayVaaToTerra: estimating fees"); + // const feeEstimate = await terraContractData.lcdClient.tx.estimateFee(terraContractData.wallet.key.accAddress, [msg], { + // feeDenoms: ["uluna"], + // gasPrices, + // }); + + logger.debug("relayVaaToTerra: creating transaction"); + const tx = await terraContractData.wallet.createAndSignTx({ + msgs: [msg], + memo: "swap relayer", + feeDenoms: ["uluna"], + // gasPrices, + // fee: feeEstimate, + }); + + logger.debug("relayVaaToTerra: submitting transaction"); + const receipt = await terraContractData.lcdClient.tx.broadcast(tx); logger.info( "relayVaaToTerra: contract: [" + t3Payload.contractAddress + - "]: success, txHash: " + - receipt.transactionHash + "]: success: %o", + receipt ); - } catch (e: any) { - if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { - logger.info( - "relayVaaToTerra: contract: [" + - t3Payload.contractAddress + - "]: relay failed because the vaa has already been redeemed" - ); - return; - } + // logger.info( + // "relayVaaToTerra: contract: [" + + // t3Payload.contractAddress + + // "]: success, txHash: " + + // receipt.transactionHash + // ); + } catch (e: any) { + // if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { + // logger.info( + // "relayVaaToTerra: contract: [" + + // t3Payload.contractAddress + + // "]: relay failed because the vaa has already been redeemed" + // ); + + // return; + // } logger.error( "relayVaaToTerra: contract: [" + @@ -137,19 +272,19 @@ export async function relayVaaToTerra( ); } - if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { - logger.info( - "relayVaaToTerra: contract: [" + - t3Payload.contractAddress + - "]: redeem succeeded" - ); - } else { - logger.error( - "relayVaaToTerra: contract: [" + - t3Payload.contractAddress + - "]: redeem failed!" - ); - } + // if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { + // logger.info( + // "relayVaaToTerra: contract: [" + + // t3Payload.contractAddress + + // "]: redeem succeeded" + // ); + // } else { + // logger.error( + // "relayVaaToTerra: contract: [" + + // t3Payload.contractAddress + + // "]: redeem failed!" + // ); + // } } async function isRedeemedOnTerra( @@ -157,23 +292,46 @@ async function isRedeemedOnTerra( terraContractData: TerraContractData, signedVaaArray: Uint8Array ): Promise { - let redeemed: boolean = false; + let msg: Terra.MsgExecuteContract = null; + let sequenceNumber: number = 0; try { - redeemed = await await getIsTransferCompletedTerra( - terraContractData.tokenBridgeAddress, - signedVaaArray, + msg = new Terra.MsgExecuteContract( terraContractData.wallet.key.accAddress, - terraContractData.lcdClient, - terraContractData.gasPriceUrl + terraContractData.tokenBridgeAddress, + { + submit_vaa: { + data: fromUint8Array(signedVaaArray), + }, + } ); + + sequenceNumber = await terraContractData.wallet.sequence(); } catch (e) { logger.error( - "relayVaaTo" + - terraContractData.name + - ": failed to check if transfer is already complete, will attempt the transfer, e: %o", + "isRedeemedOnTerra: failed to create message or look up sequence number, e: %o", + e + ); + + return false; + } + + try { + await terraContractData.lcdClient.tx.estimateFee( + [{ sequenceNumber: sequenceNumber }], + { + msgs: [msg], + } + ); + } catch (e) { + if (e.response.data.error.includes("VaaAlreadyExecuted")) { + return true; + } + + logger.error( + "isRedeemedOnTerra: failed to check if transfer is already complete, e: %o", e ); } - return redeemed; + return false; } From a295568e58e3c7eeff9da58dff6d26c9240254bb Mon Sep 17 00:00:00 2001 From: Bruce Riley Date: Fri, 28 Jan 2022 20:00:56 +0000 Subject: [PATCH 24/38] Enhance logging in the relayer --- swap_relayer/src/evm.ts | 53 ++++++++++++++++++++-------- swap_relayer/src/index.ts | 17 ++++++--- swap_relayer/src/terra.ts | 74 +++++++++++++++++++++++++-------------- 3 files changed, 99 insertions(+), 45 deletions(-) diff --git a/swap_relayer/src/evm.ts b/swap_relayer/src/evm.ts index cb1cb08..fe495fe 100644 --- a/swap_relayer/src/evm.ts +++ b/swap_relayer/src/evm.ts @@ -300,17 +300,21 @@ async function relayVaaToEvmChain( "]" ); - if (await isRedeemedOnEvm(t3Payload, tcd, signedVaaArray)) { + if (await isRedeemedOnEvm(tcd, signedVaaArray)) { logger.info( "relayVaaTo" + tcd.name + - ": contract: [" + + ": srcChain: " + + t3Payload.sourceChainId + + ", targetChain: " + + t3Payload.targetChainId + + ", contract: [" + t3Payload.contractAddress + "], exactIn: " + exactIn + ", native: " + native + - ": already transferred" + ": completed: already transferred" ); return; @@ -319,7 +323,11 @@ async function relayVaaToEvmChain( logger.info( "relayVaaTo" + tcd.name + - ": contract: [" + + ": srcChain: " + + t3Payload.sourceChainId + + ", targetChain: " + + t3Payload.targetChainId + + ", contract: [" + t3Payload.contractAddress + "], exactIn: " + exactIn + @@ -363,27 +371,35 @@ async function relayVaaToEvmChain( logger.info( "relayVaaTo" + tcd.name + - ": contract: [" + + ": srcChain: " + + t3Payload.sourceChainId + + ", targetChain: " + + t3Payload.targetChainId + + ", contract: [" + t3Payload.contractAddress + "], exactIn: " + exactIn + ", native: " + native + - ": success, txHash: " + + ": completed: success, txHash: " + receipt.transactionHash ); } catch (e: any) { - if (await isRedeemedOnEvm(t3Payload, tcd, signedVaaArray)) { + if (await isRedeemedOnEvm(tcd, signedVaaArray)) { logger.info( "relayVaaTo" + tcd.name + - ": contract: [" + + ": srcChain: " + + t3Payload.sourceChainId + + ", targetChain: " + + t3Payload.targetChainId + + ", contract: [" + t3Payload.contractAddress + "], exactIn: " + exactIn + ", native: " + native + - ": relay failed because the vaa has already been redeemed" + ": completed: relay failed because the vaa has already been redeemed" ); return; @@ -403,35 +419,42 @@ async function relayVaaToEvmChain( ); } - if (await isRedeemedOnEvm(t3Payload, tcd, signedVaaArray)) { + if (await isRedeemedOnEvm(tcd, signedVaaArray)) { logger.info( "relayVaaTo" + tcd.name + - ": contract: [" + + ": srcChain: " + + t3Payload.sourceChainId + + ", targetChain: " + + t3Payload.targetChainId + + ", contract: [" + t3Payload.contractAddress + "], exactIn: " + exactIn + ", native: " + native + - ": redeem succeeded" + ": redeem confirmed" ); } else { logger.error( "relayVaaTo" + tcd.name + - ": contract: [" + + ": srcChain: " + + t3Payload.sourceChainId + + ", targetChain: " + + t3Payload.targetChainId + + ", contract: [" + t3Payload.contractAddress + "], exactIn: " + exactIn + ", native: " + native + - ": redeem failed!" + ": completed: failed to confirm redeem!" ); } } async function isRedeemedOnEvm( - t3Payload: Type3Payload, tcd: EvmContractData, signedVaaArray: Uint8Array ): Promise { diff --git a/swap_relayer/src/index.ts b/swap_relayer/src/index.ts index 17dfdac..7d33294 100644 --- a/swap_relayer/src/index.ts +++ b/swap_relayer/src/index.ts @@ -61,6 +61,7 @@ export type OurEnvironment = { }; export type Type3Payload = { + sourceChainId: number; targetChainId: number; contractAddress: string; relayerFee: ethers.BigNumber; @@ -224,7 +225,7 @@ async function processVaa(vaaBytes: string) { let t3Payload: Type3Payload = null; try { - t3Payload = decodeSignedVAAPayloadType3(parsedVAA); + t3Payload = decodeSignedVAAPayloadType3(parsedVAA, parsedVAA.emitter_chain); } catch (e) { logger.error("failed to parse type 3 vaa: %o", e); return; @@ -239,7 +240,9 @@ async function processVaa(vaaBytes: string) { emitter_address + "], seqNum: " + parsedVAA.sequence + - ", contractAddress: [" + + ", target: [" + + t3Payload.targetChainId + + ":" + t3Payload.contractAddress + "], relayerFee: [" + t3Payload.relayerFee + @@ -259,7 +262,9 @@ async function processVaa(vaaBytes: string) { emitter_address + "], seqNum: " + parsedVAA.sequence + - ", contractAddress: [" + + ", target: [" + + t3Payload.targetChainId + + ":" + t3Payload.contractAddress + "], relayerFee: [" + t3Payload.relayerFee + @@ -284,7 +289,10 @@ async function processVaa(vaaBytes: string) { } } -function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload { +function decodeSignedVAAPayloadType3( + parsedVAA: any, + sourceChainId: number +): Type3Payload { const payload = Buffer.from(new Uint8Array(parsedVAA.payload)); if (payload[0] !== 3) return undefined; @@ -329,6 +337,7 @@ function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload { } return { + sourceChainId: sourceChainId, targetChainId: targetChainId, contractAddress: contractAddress, relayerFee: ethers.BigNumber.from(payload.slice(101, 101 + 32)), diff --git a/swap_relayer/src/terra.ts b/swap_relayer/src/terra.ts index dbf6a20..ee7dd07 100644 --- a/swap_relayer/src/terra.ts +++ b/swap_relayer/src/terra.ts @@ -173,22 +173,20 @@ export async function relayVaaToTerra( // "]" // ); - // if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { + // if (await isRedeemedOnTerra(terraContractData, vaaBytes)) { // logger.info( - // "relayVaaToTerra: contract: [" + + // "relayVaaToTerra: srcChain: " + + // t3Payload.sourceChainId + + // ", targetChain: " + + // t3Payload.targetChainId + + // ", contract: [" + // t3Payload.contractAddress + - // "]: already transferred" + // "]: completed: already redeemed" // ); // return; // } - logger.info( - "relayVaaToTerra: contract: [" + - t3Payload.contractAddress + - "]: submitting redeem request" - ); - try { logger.debug( "relayVaaToTerra: creating message using contract address [" + @@ -237,13 +235,22 @@ export async function relayVaaToTerra( // fee: feeEstimate, }); - logger.debug("relayVaaToTerra: submitting transaction"); - const receipt = await terraContractData.lcdClient.tx.broadcast(tx); - logger.info( "relayVaaToTerra: contract: [" + t3Payload.contractAddress + - "]: success: %o", + "]: submitting redeem request" + ); + + const receipt = await terraContractData.lcdClient.tx.broadcast(tx); + + logger.info( + "relayVaaToTerra: srcChain: " + + t3Payload.sourceChainId + + ", targetChain: " + + t3Payload.targetChainId + + ", contract: [" + + t3Payload.contractAddress + + "]: completed: success: %o", receipt ); @@ -254,43 +261,58 @@ export async function relayVaaToTerra( // receipt.transactionHash // ); } catch (e: any) { - // if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { + // if (await isRedeemedOnTerra(terraContractData, vaaBytes)) { // logger.info( - // "relayVaaToTerra: contract: [" + + // "relayVaaToTerra: srcChain: " + + // t3Payload.sourceChainId + + // ", targetChain: " + + // t3Payload.targetChainId + + // ", contract: [" + // t3Payload.contractAddress + - // "]: relay failed because the vaa has already been redeemed" + // "]: completed: relay failed because the vaa has already been redeemed" // ); // return; // } logger.error( - "relayVaaToTerra: contract: [" + + "relayVaaToTerra: srcChain: " + + t3Payload.sourceChainId + + ", targetChain: " + + t3Payload.targetChainId + + ", contract: [" + t3Payload.contractAddress + - "]: transaction failed: %o", + "]: completed: transaction failed: %o", e ); } - // if (await isRedeemedOnTerra(t3Payload, terraContractData, signedVaaArray)) { + // if (await isRedeemedOnTerra(terraContractData, vaaBytes)) { // logger.info( - // "relayVaaToTerra: contract: [" + + // "relayVaaToTerra: srcChain: " + + // t3Payload.sourceChainId + + // ", targetChain: " + + // t3Payload.targetChainId + + // ", contract: [" + // t3Payload.contractAddress + - // "]: redeem succeeded" + // "]: redeem confirmed" // ); // } else { // logger.error( - // "relayVaaToTerra: contract: [" + + // "relayVaaToTerra: srcChain: " + + // t3Payload.sourceChainId + + // ", targetChain: " + + // t3Payload.targetChainId + + // ", contract: [" + // t3Payload.contractAddress + - // "]: redeem failed!" + // "]: completed: failed to confirm redeem!" // ); // } } async function isRedeemedOnTerra( - t3Payload: Type3Payload, terraContractData: TerraContractData, - signedVaaArray: Uint8Array + vaaBytes: string ): Promise { let msg: Terra.MsgExecuteContract = null; let sequenceNumber: number = 0; @@ -300,7 +322,7 @@ async function isRedeemedOnTerra( terraContractData.tokenBridgeAddress, { submit_vaa: { - data: fromUint8Array(signedVaaArray), + data: Buffer.from(vaaBytes, "hex").toString("base64"), }, } ); From fafd2ace847c1f99b2710e5e0c7969b7bed2f1bb Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Fri, 28 Jan 2022 22:31:03 +0000 Subject: [PATCH 25/38] Add terra bridge addresses; add ust dst handling --- misc/scripts/swap-with-vaa.ts | 69 +++++++++++++------ react/src/swapper/swapper.ts | 125 +++++++++++++++++++++++++--------- react/src/utils/consts.ts | 6 +- 3 files changed, 146 insertions(+), 54 deletions(-) diff --git a/misc/scripts/swap-with-vaa.ts b/misc/scripts/swap-with-vaa.ts index 990283f..bb89512 100644 --- a/misc/scripts/swap-with-vaa.ts +++ b/misc/scripts/swap-with-vaa.ts @@ -144,11 +144,18 @@ async function swapEverythingExactIn( tokenInAddress: string, tokenOutAddress: string, isNative: boolean, - amountIn: string + amountIn: string, + recipientAddress: string ): Promise { + const isTerraSrc = tokenInAddress === UST_TOKEN_INFO.address; + + if (isTerraSrc) { + throw Error("cannot use terra source yet"); + } // connect src wallet const srcWallet = determineWalletFromToken(tokenInAddress); - console.info(`wallet pubkey: ${await srcWallet.getAddress()}`); + console.info(`sender: ${await srcWallet.getAddress()}`); + console.info(`recipient: ${recipientAddress}`); // tokens selected, let's initialize await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); @@ -193,9 +200,17 @@ async function swapEverythingExactIn( logExactInParameters(swapper.quoter, exactInParameters); // do the src swap - console.info("approveAndSwap"); - const srcSwapReceipt = await swapper.evmApproveAndSwap(srcWallet); - console.info(`src transaction: ${srcSwapReceipt.transactionHash}`); + if (isTerraSrc) { + // do terra method + throw Error("terra src not implemented yet"); + } else { + console.info("approveAndSwap"); + const srcSwapReceipt = await swapper.evmApproveAndSwap( + srcWallet, + recipientAddress + ); + console.info(`src transaction: ${srcSwapReceipt.transactionHash}`); + } // do the dst swap after fetching vaa // connect dst wallet @@ -263,7 +278,8 @@ async function swapEverythingExactOut( tokenInAddress: string, tokenOutAddress: string, isNative: boolean, - amountOut: string + amountOut: string, + recipientAddress: string ): Promise { // connect src wallet const srcWallet = determineWalletFromToken(tokenInAddress); @@ -313,7 +329,10 @@ async function swapEverythingExactOut( // do the src swap console.info("approveAndSwap"); - const srcSwapReceipt = await swapper.evmApproveAndSwap(srcWallet); + const srcSwapReceipt = await swapper.evmApproveAndSwap( + srcWallet, + recipientAddress + ); console.info(`src transaction: ${srcSwapReceipt.transactionHash}`); // do the dst swap after fetching vaa @@ -336,7 +355,11 @@ async function main() { swapper.setTransport(NodeHttpTransport()); const tokenIn = ETH_TOKEN_INFO; - const tokenOut = AVAX_TOKEN_INFO; + //const tokenOut = MATIC_TOKEN_INFO; + const tokenOut = UST_TOKEN_INFO; + + //const recipientAddress = "0x4e2dfAD7D7d0076b5A0A41223E4Bee390C33251C"; + const recipientAddress = "terra1vewnsxcy5fqjslyyy409cw8js550esen38n8ey"; if (testExactIn) { console.info(`testing exact in. native=${isNative}`); @@ -347,17 +370,23 @@ async function main() { tokenIn.address, tokenOut.address, isNative, - determineAmountFromToken(tokenIn.address) + determineAmountFromToken(tokenIn.address), + recipientAddress ); - console.info(`${tokenOut.name} -> ${tokenIn.name}`); - await swapEverythingExactIn( - swapper, - tokenOut.address, - tokenIn.address, - isNative, - determineAmountFromToken(tokenOut.address) - ); + if (tokenOut.address === UST_TOKEN_INFO.address) { + console.warn("not pinging back"); + } else { + console.info(`${tokenOut.name} -> ${tokenIn.name}`); + await swapEverythingExactIn( + swapper, + tokenOut.address, + tokenIn.address, + isNative, + determineAmountFromToken(tokenOut.address), + recipientAddress + ); + } } else { console.info(`testing exact out. native=${isNative}`); @@ -367,7 +396,8 @@ async function main() { tokenIn.address, tokenOut.address, isNative, - determineAmountFromToken(tokenOut.address) + determineAmountFromToken(tokenOut.address), + recipientAddress ); console.info(`${tokenOut.name} -> ${tokenIn.name}`); @@ -376,7 +406,8 @@ async function main() { tokenOut.address, tokenIn.address, isNative, - determineAmountFromToken(tokenIn.address) + determineAmountFromToken(tokenIn.address), + recipientAddress ); } diff --git a/react/src/swapper/swapper.ts b/react/src/swapper/swapper.ts index a4b1a5f..cfbb119 100644 --- a/react/src/swapper/swapper.ts +++ b/react/src/swapper/swapper.ts @@ -1,4 +1,4 @@ -//@ts-nocheck +//@ts-nocheckk import { ethers } from "ethers"; import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { @@ -58,7 +58,16 @@ import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_BSC } from ".. import { makeErc20Contract } from "../route/evm"; // placeholders -const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA = ""; +const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA = + "terra163shc8unyqrndgcldaj2q9kgnqs82v0kgkhynf"; + +function makeNullSwapPath(): any[] { + const zeroBuffer = Buffer.alloc(20); + const nullAddress = "0x" + zeroBuffer.toString("hex"); + return [nullAddress, nullAddress]; +} + +const NULL_SWAP_PATH = makeNullSwapPath(); interface SwapContractParameters { address: string; @@ -223,6 +232,48 @@ function addressToBytes32( return hexToUint8Array(hexString); } +function evmMakeExactInSwapParameters( + amountIn: ethers.BigNumber, + recipientAddress: string, + dstWormholeChainId: ChainId, + quoteParams: ExactInCrossParameters +): any[] { + const src = quoteParams.src; + const dst = quoteParams.dst; + + if (dst === undefined) { + return [ + amountIn, + src.minAmountOut, + 0, + addressToBytes32(recipientAddress, dstWormholeChainId), + src.deadline, + src.poolFee || 0, + ]; + } + + return [ + amountIn, + src.minAmountOut, + dst.minAmountOut, + addressToBytes32(recipientAddress, dstWormholeChainId), + src.deadline, + dst.poolFee || src.poolFee || 0, + ]; +} + +function makePathArray( + quoteParams: ExactInCrossParameters | ExactOutCrossParameters +): any[] { + if (quoteParams.src === undefined) { + return NULL_SWAP_PATH.concat(quoteParams.dst.path); + } else if (quoteParams.dst === undefined) { + return quoteParams.src.path.concat(NULL_SWAP_PATH); + } else { + return quoteParams.src.path.concat(quoteParams.dst.path); + } +} + async function evmApproveAndSwapExactIn( srcProvider: ethers.providers.Provider, srcWallet: ethers.Signer, @@ -230,7 +281,8 @@ async function evmApproveAndSwapExactIn( quoteParams: ExactInCrossParameters, srcExecutionParams: ExecutionParameters, dstExecutionParams: ExecutionParameters, - isNative: boolean + isNative: boolean, + recipientAddress: string ): Promise { const swapContractParams = srcExecutionParams.crossChainSwap; @@ -244,21 +296,16 @@ async function evmApproveAndSwapExactIn( // approve and swap this amount const amountIn = quoteParams.src.amountIn; - - const address = await srcWallet.getAddress(); - const dstWormholeChainId = dstExecutionParams.wormhole.chainId; - const swapParams = [ + const swapParams = evmMakeExactInSwapParameters( amountIn, - quoteParams.src.minAmountOut, - quoteParams.dst.minAmountOut, - addressToBytes32(address, dstWormholeChainId), - quoteParams.src.deadline, - quoteParams.dst.poolFee || quoteParams.src.poolFee || 0, - ]; + recipientAddress, + dstWormholeChainId, + quoteParams + ); - const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); + const pathArray = makePathArray(quoteParams); const dstContractAddress = addressToBytes32( dstExecutionParams.crossChainSwap.address, @@ -306,6 +353,7 @@ async function evmApproveAndSwapExactIn( } } +// TODO: fix to resemble ExactIn async function evmApproveAndSwapExactOut( srcProvider: ethers.providers.Provider, srcWallet: ethers.Signer, @@ -313,11 +361,12 @@ async function evmApproveAndSwapExactOut( quoteParams: ExactOutCrossParameters, srcExecutionParams: ExecutionParameters, dstExecutionParams: ExecutionParameters, - isNative: boolean + isNative: boolean, + recipientAddress: string ): Promise { const swapContractParams = srcExecutionParams.crossChainSwap; - const protocol = quoteParams.src.protocol; + const protocol = quoteParams.src?.protocol; const swapContract = makeCrossChainSwapEvmContract( srcProvider, protocol, @@ -326,22 +375,19 @@ async function evmApproveAndSwapExactOut( const contractWithSigner = swapContract.connect(srcWallet); // approve and swap this amount - const amountOut = quoteParams.src.amountOut; - const maxAmountIn = quoteParams.src.maxAmountIn; - - const address = await srcWallet.getAddress(); - + const amountOut = quoteParams.src?.amountOut; + const maxAmountIn = quoteParams.src?.maxAmountIn; const dstWormholeChainId = dstExecutionParams.wormhole.chainId; const swapParams = [ amountOut, maxAmountIn, quoteParams.dst.amountOut, - addressToBytes32(address, dstWormholeChainId), + addressToBytes32(recipientAddress, dstWormholeChainId), quoteParams.src.deadline, quoteParams.dst.poolFee || quoteParams.src.poolFee || 0, ]; - const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); + const pathArray = makePathArray(quoteParams); const dstContractAddress = addressToBytes32( dstExecutionParams.crossChainSwap.address, @@ -613,30 +659,34 @@ export class UniswapToUniswapExecutor { } async evmApproveAndSwapExactIn( - wallet: ethers.Signer + srcWallet: ethers.Signer, + recipientAddress: string ): Promise { return evmApproveAndSwapExactIn( this.getSrcEvmProvider(), - wallet, + srcWallet, this.getTokenInAddress(), this.cachedExactInParams, this.srcExecutionParams, this.dstExecutionParams, - this.isNative + this.isNative, + recipientAddress ); } async evmApproveAndSwapExactOut( - wallet: ethers.Signer + srcWallet: ethers.Signer, + recipientAddress: string ): Promise { return evmApproveAndSwapExactOut( this.getSrcEvmProvider(), - wallet, + srcWallet, this.getTokenInAddress(), this.cachedExactOutParams, this.srcExecutionParams, this.dstExecutionParams, - this.isNative + this.isNative, + recipientAddress ); } @@ -647,18 +697,27 @@ export class UniswapToUniswapExecutor { ); } - async evmApproveAndSwap(wallet: ethers.Signer): Promise { + async evmApproveAndSwap( + wallet: ethers.Signer, + recipientAddress: string + ): Promise { const quoteType = this.quoteType; if (quoteType === QuoteType.ExactIn) { - this.srcEvmReceipt = await this.evmApproveAndSwapExactIn(wallet); + this.srcEvmReceipt = await this.evmApproveAndSwapExactIn( + wallet, + recipientAddress + ); } else if (quoteType === QuoteType.ExactOut) { - this.srcEvmReceipt = await this.evmApproveAndSwapExactOut(wallet); + this.srcEvmReceipt = await this.evmApproveAndSwapExactOut( + wallet, + recipientAddress + ); } else { throw Error("no quote found"); } - this.fetchAndSetEmitterAndSequence(); + this.fetchAndSetEvmEmitterAndSequence(); return this.srcEvmReceipt; } diff --git a/react/src/utils/consts.ts b/react/src/utils/consts.ts index dcc376d..e0aacda 100644 --- a/react/src/utils/consts.ts +++ b/react/src/utils/consts.ts @@ -190,7 +190,8 @@ export const CORE_BRIDGE_ADDRESS_AVALANCHE = export const CORE_BRIDGE_ADDRESS_BSC = "0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D"; -export const CORE_BRIDGE_ADDRESS_TERRA = undefined; +export const CORE_BRIDGE_ADDRESS_TERRA = + "terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v"; // token bridge export const TOKEN_BRIDGE_ADDRESS_ETHEREUM = @@ -205,7 +206,8 @@ export const TOKEN_BRIDGE_ADDRESS_BSC = export const TOKEN_BRIDGE_ADDRESS_AVALANCHE = "0x61E44E506Ca5659E6c0bba9b678586fA2d729756"; -export const TOKEN_BRIDGE_ADDRESS_TERRA = undefined; +export const TOKEN_BRIDGE_ADDRESS_TERRA = + "terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a"; // gas export const APPROVAL_GAS_LIMIT = "100000"; From 7b08b99ea615795cb3299fcc103387cd1f2de2c0 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Sat, 29 Jan 2022 20:46:10 +0000 Subject: [PATCH 26/38] Remove check-ust-quotes --- misc/scripts/check-ust-quotes.ts | 394 ------------------------------- 1 file changed, 394 deletions(-) delete mode 100644 misc/scripts/check-ust-quotes.ts diff --git a/misc/scripts/check-ust-quotes.ts b/misc/scripts/check-ust-quotes.ts deleted file mode 100644 index 0a9b2fc..0000000 --- a/misc/scripts/check-ust-quotes.ts +++ /dev/null @@ -1,394 +0,0 @@ -import { ethers } from "ethers"; - -import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport"; - -import { - ExactInCrossParameters, - ExactOutCrossParameters, - UniswapToUniswapQuoter, -} from "../src/route/cross-quote"; - -import { UniswapToUniswapExecutor } from "../src/swapper/swapper"; - -import { makeProvider } from "./src/provider"; -import { - ETH_TOKEN_INFO, - MATIC_TOKEN_INFO, - UST_TOKEN_INFO, -} from "../src/utils/consts"; - -require("dotenv").config({ path: ".env" }); - -// swap related parameters (configurable in UI) -const SWAP_AMOUNT_IN_WMATIC = "0.0069"; -const SWAP_AMOUNT_IN_WETH = "0.000907"; -const SWAP_AMOUNT_IN_UST = "3.40"; - -const SWAP_DEADLINE = "1800"; -const SWAP_SLIPPAGE = "0.01"; - -// token bridge things -const BRIDGE_RELAYER_FEE_UST = "0.25"; - -export function makeEvmWallet( - provider: ethers.providers.Provider -): ethers.Wallet { - return new ethers.Wallet(process.env.ETH_PRIVATE_KEY, provider); -} - -/* -async function fetchTokenBalance(signer, contract) { - const decimals = await contract.decimals(); - const balanceBeforeDecimals = (await contract.balanceOf(signer.address)).toString(); - const balance = ethers.utils.formatUnits(balanceBeforeDecimals, decimals); - return balance; -} -*/ - -// only exist as placeholder for actual wallet connection -function determineWalletFromToken(tokenAddress: string): ethers.Wallet { - if (tokenAddress === UST_TOKEN_INFO.address) { - return undefined; - } - return makeEvmWallet(makeProvider(tokenAddress)); -} - -function determineAmountFromToken(tokenAddress: string): string { - switch (tokenAddress) { - case ETH_TOKEN_INFO.address: { - return SWAP_AMOUNT_IN_WETH; - } - case MATIC_TOKEN_INFO.address: { - return SWAP_AMOUNT_IN_WMATIC; - } - case UST_TOKEN_INFO.address: { - return SWAP_AMOUNT_IN_UST; - } - default: { - throw Error("you suck"); - } - } -} - -function logExactInParameters( - quoter: UniswapToUniswapQuoter, - params: ExactInCrossParameters -): void { - console.info(`amountIn: ${params.amountIn}`); - console.info(`minAmountOut: ${params.minAmountOut}`); - - const src = params.src; - if (src === undefined) { - console.warn(` src is undefined (ust?)`); - } else { - console.info(`src`); - console.info(` protocol: ${src.protocol}`); - //console.info(` amountIn: ${quoter.srcTokenIn.formatAmount(src.amountIn)}`); - console.info( - ` amountIn: ${quoter.srcRouter.formatAmountIn( - src.amountIn.toString() - )}` - ); - console.info( - // ` minAmountOut: ${quoter.srcTokenOut.formatAmount(src.minAmountOut)}` - ` minAmountOut: ${quoter.srcRouter.formatAmountOut( - src.minAmountOut.toString() - )}` - ); - console.info(` poolFee: ${src.poolFee}`); - console.info(` deadline: ${src.deadline.toString()}`); - console.info(` path: ${src.path}`); - } - - const dst = params.dst; - if (dst === undefined) { - console.warn(` dst is undefined (ust?)`); - } else { - console.info(`dst`); - console.info(` protocol: ${dst.protocol}`); - //console.info(` amountIn: ${quoter.dstTokenIn.formatAmount(dst.amountIn)}`); - console.info( - ` amountIn: ${quoter.dstRouter.formatAmountIn( - dst.amountIn.toString() - )}` - ); - console.info( - // ` minAmountOut: ${quoter.dstTokenOut.formatAmount(dst.minAmountOut)}` - ` minAmountOut: ${quoter.dstRouter.formatAmountOut( - dst.minAmountOut.toString() - )}` - ); - console.info(` poolFee: ${dst.poolFee}`); - console.info(` deadline: ${dst.deadline.toString()}`); - console.info(` path: ${dst.path}`); - - const relayerFee = params.relayerFee; - console.info(`relayerFee`); - console.info(` tokenAddress: ${relayerFee.tokenAddress}`); - console.info( - ` amount: ${quoter.dstRouter.formatAmountIn(relayerFee.amount)}` - ); - } - - return; -} - -async function swapEverythingExactIn( - swapper: UniswapToUniswapExecutor, - tokenInAddress: string, - tokenOutAddress: string, - isNative: boolean, - amountIn: string -): Promise { - // connect src wallet - //const srcWallet = determineWalletFromToken(tokenInAddress); - //console.info(`wallet pubkey: ${await srcWallet.getAddress()}`); - - // tokens selected, let's initialize - await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); - console.info(`quoter initialized`); - - /* - const tokens = swapper.getTokens(); - - // display tokens on front-end? - console.info( - `srcTokenIn: ${tokens.srcIn.getAddress()} (${tokens.srcIn.getDecimals()})` - ); - console.info( - `srcTokenOut: ${tokens.srcOut.getAddress()} (${tokens.srcOut.getDecimals()})` - ); - console.info( - `dstTokenIn: ${tokens.dstIn.getAddress()} (${tokens.dstIn.getDecimals()})` - ); - console.info( - `dstTokenOut: ${tokens.dstOut.getAddress()} (${tokens.dstOut.getDecimals()})` - ); - */ - - // verify pool address on src and dst - await swapper - .computeAndVerifySrcPoolAddress() - .then((address) => { - console.info(`srcPool: ${address}`); - return address; - }) - .catch((response) => { - console.error( - `failed to find a pool address for src. how to handle in the front-end?` - ); - process.exit(1); - }); - - await swapper - .computeAndVerifyDstPoolAddress() - .then((address) => { - console.info(`dstPool: ${address}`); - return address; - }) - .catch((response) => { - console.error( - `failed to find a pool address for dst. how to handle in the front-end?` - ); - process.exit(1); - }); - - // set deadline - swapper.setDeadlines(SWAP_DEADLINE); - swapper.setSlippage(SWAP_SLIPPAGE); - swapper.setRelayerFee(BRIDGE_RELAYER_FEE_UST); - - const exactInParameters: ExactInCrossParameters = - await swapper.computeQuoteExactIn(amountIn); - - console.info("exactInParameters"); - logExactInParameters(swapper.quoter, exactInParameters); - - return; -} - -function logExactOutParameters( - quoter: UniswapToUniswapQuoter, - params: ExactOutCrossParameters -): void { - console.info(`amountIn: ${params.amountOut}`); - console.info(`minAmountOut: ${params.maxAmountIn}`); - - const src = params.src; - console.info(`src`); - if (src === undefined) { - } else { - } - console.info(` protocol: ${src.protocol}`); - console.info( - ` amountOut: ${quoter.srcRouter.formatAmountOut( - src.amountOut.toString() - )}` - ); - console.info( - ` maxAmountIn: ${quoter.srcRouter.formatAmountIn( - src.maxAmountIn.toString() - )}` - ); - console.info(` poolFee: ${src.poolFee}`); - console.info(` deadline: ${src.deadline.toString()}`); - console.info(` path: ${src.path}`); - - const dst = params.dst; - console.info(`dst`); - console.info(` protocol: ${dst.protocol}`); - console.info( - ` amountOut: ${quoter.dstRouter.formatAmountOut( - dst.amountOut.toString() - )}` - ); - console.info( - ` maxAmountIn: ${quoter.dstRouter.formatAmountIn( - dst.maxAmountIn.toString() - )}` - ); - console.info(` poolFee: ${dst.poolFee}`); - console.info(` deadline: ${dst.deadline.toString()}`); - console.info(` path: ${dst.path}`); - - const relayerFee = params.relayerFee; - console.info(`relayerFee`); - console.info(` tokenAddress: ${relayerFee.tokenAddress}`); - console.info( - ` amount: ${quoter.dstRouter.formatAmountIn( - relayerFee.amount.toString() - )}` - ); - return; -} - -async function swapEverythingExactOut( - swapper: UniswapToUniswapExecutor, - tokenInAddress: string, - tokenOutAddress: string, - isNative: boolean, - amountOut: string -): Promise { - // connect src wallet - //const srcWallet = determineWalletFromToken(tokenInAddress); - //console.info(`wallet pubkey: ${await srcWallet.getAddress()}`); - - // tokens selected, let's initialize - await swapper.initialize(tokenInAddress, tokenOutAddress, isNative); - console.info(`quoter initialized`); - - /* - const tokens = swapper.getTokens(); - - // display tokens on front-end? - console.info( - `srcTokenIn: ${tokens.srcIn.getAddress()} (${tokens.srcIn.getDecimals()})` - ); - console.info( - `srcTokenOut: ${tokens.srcOut.getAddress()} (${tokens.srcOut.getDecimals()})` - ); - console.info( - `dstTokenIn: ${tokens.dstIn.getAddress()} (${tokens.dstIn.getDecimals()})` - ); - console.info( - `dstTokenOut: ${tokens.dstOut.getAddress()} (${tokens.dstOut.getDecimals()})` - ); - */ - - // verify pool address on src and dst - await swapper - .computeAndVerifySrcPoolAddress() - .then((address) => { - console.info(`srcPool: ${address}`); - return address; - }) - .catch((response) => { - console.error( - `failed to find a pool address for src. how to handle in the front-end?` - ); - process.exit(1); - }); - - await swapper - .computeAndVerifyDstPoolAddress() - .then((address) => { - console.info(`dstPool: ${address}`); - return address; - }) - .catch((response) => { - console.error( - `failed to find a pool address for dst. how to handle in the front-end?` - ); - process.exit(1); - }); - - // set deadline - swapper.setDeadlines(SWAP_DEADLINE); - swapper.setSlippage(SWAP_SLIPPAGE); - swapper.setRelayerFee(BRIDGE_RELAYER_FEE_UST); - - const exactOutParameters: ExactOutCrossParameters = - await swapper.computeQuoteExactOut(amountOut); - - console.info("exactOutParameters"); - logExactOutParameters(swapper.quoter, exactOutParameters); - - return; -} - -async function main() { - const testExactIn = true; - const isNative = true; - - const swapper = new UniswapToUniswapExecutor(); - swapper.setTransport(NodeHttpTransport()); - - //const tokenInAddress = MATIC_TOKEN_INFO.address; - const tokenInAddress = UST_TOKEN_INFO.address; - const tokenOutAddress = ETH_TOKEN_INFO.address; - - if (testExactIn) { - console.info(`testing exact in. native=${isNative}`); - - console.info("wmatic -> weth"); - await swapEverythingExactIn( - swapper, - tokenInAddress, - tokenOutAddress, - isNative, - determineAmountFromToken(tokenInAddress) - ); - - console.info("weth -> wmatic"); - await swapEverythingExactIn( - swapper, - tokenOutAddress, - tokenInAddress, - isNative, - determineAmountFromToken(tokenOutAddress) - ); - } else { - console.info(`testing exact out. native=${isNative}`); - - console.info("wmatic -> weth"); - await swapEverythingExactOut( - swapper, - tokenInAddress, - tokenOutAddress, - isNative, - determineAmountFromToken(tokenOutAddress) - ); - - console.info("weth -> wmatic"); - await swapEverythingExactOut( - swapper, - tokenOutAddress, - tokenInAddress, - isNative, - determineAmountFromToken(tokenInAddress) - ); - } - - return; -} -main(); From 444be0d3a88c8b303fafe8c1718815f20b7a186c Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Sun, 30 Jan 2022 20:31:20 +0000 Subject: [PATCH 27/38] Lower gas amounts (need to test relayer) --- misc/tsconfig.json | 3 +-- react/src/swapper/helpers.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/misc/tsconfig.json b/misc/tsconfig.json index c15b631..3773eb2 100644 --- a/misc/tsconfig.json +++ b/misc/tsconfig.json @@ -4,7 +4,6 @@ "esModuleInterop": true }, "files": [ - "scripts/swap-with-vaa.ts", - "scripts/check-ust-quotes.ts" + "scripts/swap-with-vaa.ts" ] } diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts index c5ed016..bdf3613 100644 --- a/react/src/swapper/helpers.ts +++ b/react/src/swapper/helpers.ts @@ -9,14 +9,16 @@ import { } from "../utils/consts"; export const CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559 = { - gasLimit: "550000", - maxFeePerGas: "250000000000", + gasLimit: "694200", + //maxFeePerGas: "250000000000", + maxFeePerGas: "20420690000", maxPriorityFeePerGas: "1690000000", }; export const CROSSCHAINSWAP_GAS_PARAMETERS_EVM = { - gasLimit: "550000", - gasPrice: "250000000000", + gasLimit: "694200", + //gasPrice: "250000000000", + gasPrice: "20420690000", }; export const EVM_EIP1559_CHAIN_IDS = [ From 8f363e8cc50906a58460783a0770195841c96c41 Mon Sep 17 00:00:00 2001 From: Bruce Riley Date: Mon, 31 Jan 2022 15:21:07 +0000 Subject: [PATCH 28/38] Terra contract now uses submit_vaa not redeem_payload --- swap_relayer/src/terra.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/swap_relayer/src/terra.ts b/swap_relayer/src/terra.ts index ee7dd07..c0438bc 100644 --- a/swap_relayer/src/terra.ts +++ b/swap_relayer/src/terra.ts @@ -194,8 +194,6 @@ export async function relayVaaToTerra( "]" ); - logger.debug("relayVaaToTerra: creating a message"); - logger.debug( "relayVaaToTerra: vaa as hex: [" + Buffer.from(vaaBytes, "hex").toString("hex") + @@ -211,7 +209,7 @@ export async function relayVaaToTerra( terraContractData.wallet.key.accAddress, terraContractData.contractAddress, { - redeem_payload: { + submit_vaa: { data: Buffer.from(vaaBytes, "hex").toString("base64"), }, } From 0cf7d9e1c554423b58495e21772aced3c5ae5681 Mon Sep 17 00:00:00 2001 From: Kevin Peters Date: Mon, 31 Jan 2022 18:36:15 +0000 Subject: [PATCH 29/38] UI - added bsc and avax support --- react/.env.sample | 4 +- react/src/addresses/.gitignore | 5 +- react/src/components/Footer.tsx | 33 +++---- react/src/components/SwapProgress.tsx | 14 +-- react/src/hooks/useIsWalletReady.ts | 23 +---- react/src/icons/avax.svg | 15 +++ react/src/icons/bsc.svg | 12 +++ react/src/icons/wormhole-network.svg | 8 -- react/src/icons/wormhole_logo.svg | 23 +++++ react/src/route/generic.ts | 2 - react/src/route/hurricaneswap.ts | 6 +- react/src/route/pancakeswap.ts | 6 +- react/src/route/quickswap.ts | 4 +- react/src/route/uniswap-core.ts | 2 +- react/src/route/uniswap-v3.ts | 4 +- react/src/swapper/swapper.ts | 40 ++++---- react/src/utils/consts.ts | 136 ++++++++------------------ react/src/views/Home.tsx | 88 ++++++++--------- 18 files changed, 189 insertions(+), 236 deletions(-) create mode 100644 react/src/icons/avax.svg create mode 100644 react/src/icons/bsc.svg delete mode 100644 react/src/icons/wormhole-network.svg create mode 100644 react/src/icons/wormhole_logo.svg diff --git a/react/.env.sample b/react/.env.sample index 1324637..458c04b 100644 --- a/react/.env.sample +++ b/react/.env.sample @@ -1,2 +1,4 @@ REACT_APP_GOERLI_PROVIDER=https://goerli.infura.io/v3/YOUR-PROJECT-ID -REACT_APP_MUMBAI_PROVIDER=https://polygon-mumbai.infura.io/v3/YOUR-PROJECT-ID \ No newline at end of file +REACT_APP_MUMBAI_PROVIDER=https://polygon-mumbai.infura.io/v3/YOUR-PROJECT-ID +REACT_APP_FUJI_PROVIDER= +REACT_APP_BSC_PROVIDER= \ No newline at end of file diff --git a/react/src/addresses/.gitignore b/react/src/addresses/.gitignore index 680028e..4fa566d 100644 --- a/react/src/addresses/.gitignore +++ b/react/src/addresses/.gitignore @@ -1,5 +1,2 @@ -bsc.ts -fuji.ts -goerli.ts -mumbai.ts +*.ts *.js diff --git a/react/src/components/Footer.tsx b/react/src/components/Footer.tsx index ba8afef..f42fc68 100644 --- a/react/src/components/Footer.tsx +++ b/react/src/components/Footer.tsx @@ -5,7 +5,7 @@ import Github from "../icons/Github.svg"; import Medium from "../icons/Medium.svg"; import Telegram from "../icons/Telegram.svg"; import Twitter from "../icons/Twitter.svg"; -import Wormhole from "../icons/wormhole-network.svg"; +import Wormhole from "../icons/wormhole_logo.svg"; const useStyles = makeStyles((theme) => ({ footer: { @@ -19,7 +19,6 @@ const useStyles = makeStyles((theme) => ({ }, }, builtWithContainer: { - display: "flex", alignItems: "center", justifyContent: "center", opacity: 0.5, @@ -27,7 +26,7 @@ const useStyles = makeStyles((theme) => ({ }, wormholeIcon: { height: 48, - width: 48, + width: 192, filter: "contrast(0)", transition: "filter 0.5s", "&:hover": { @@ -92,24 +91,18 @@ export default function Footer() { Twitter
+
+ + Wormhole + +
-
- - Wormhole - -
-
- Open Source - Built with ❤ -
+ Open Source + Built with ❤
); diff --git a/react/src/components/SwapProgress.tsx b/react/src/components/SwapProgress.tsx index 2b42ff6..0b50bf9 100644 --- a/react/src/components/SwapProgress.tsx +++ b/react/src/components/SwapProgress.tsx @@ -1,7 +1,8 @@ -import { ChainId, CHAIN_ID_POLYGON, isEVMChain } from "@certusone/wormhole-sdk"; +import { ChainId, isEVMChain } from "@certusone/wormhole-sdk"; import { LinearProgress, makeStyles, Typography } from "@material-ui/core"; import { useEffect, useState } from "react"; import { useEthereumProvider } from "../contexts/EthereumProviderContext"; +import { getChainName } from "../utils/consts"; const useStyles = makeStyles((theme) => ({ root: { @@ -16,13 +17,11 @@ const useStyles = makeStyles((theme) => ({ export default function TransactionProgress({ chainId, txBlockNumber, - isSourceSwapComplete, hasSignedVAA, isTargetSwapComplete, }: { chainId: ChainId; txBlockNumber: number | undefined; - isSourceSwapComplete: boolean; hasSignedVAA: boolean; isTargetSwapComplete: boolean; }) { @@ -51,20 +50,21 @@ export default function TransactionProgress({ }; } }, [hasSignedVAA, chainId, provider, txBlockNumber]); - const blockDiff = + let blockDiff = txBlockNumber !== undefined && txBlockNumber && currentBlock ? currentBlock - txBlockNumber : 0; const expectedBlocks = 15; + blockDiff = Math.min(Math.max(blockDiff, 0), expectedBlocks); let value; let valueBuffer; let message; if (!hasSignedVAA) { value = (blockDiff / expectedBlocks) * 50; valueBuffer = 50; - message = `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${ - chainId === CHAIN_ID_POLYGON ? "Polygon" : "Ethereum" - }...`; + message = `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${getChainName( + chainId + )}...`; } else if (!isTargetSwapComplete) { value = 50; valueBuffer = 100; diff --git a/react/src/hooks/useIsWalletReady.ts b/react/src/hooks/useIsWalletReady.ts index 06e4fb8..085bf3f 100644 --- a/react/src/hooks/useIsWalletReady.ts +++ b/react/src/hooks/useIsWalletReady.ts @@ -1,18 +1,10 @@ -import { - ChainId, - CHAIN_ID_SOLANA, - CHAIN_ID_TERRA, - isEVMChain, -} from "@certusone/wormhole-sdk"; +import { ChainId, CHAIN_ID_TERRA, isEVMChain } from "@certusone/wormhole-sdk"; import { hexlify, hexStripZeros } from "@ethersproject/bytes"; import { useConnectedWallet } from "@terra-money/wallet-provider"; import { useCallback, useMemo } from "react"; import { useEthereumProvider } from "../contexts/EthereumProviderContext"; -// import { useSolanaWallet } from "../contexts/SolanaWalletContext"; import { getEvmChainId } from "../utils/consts"; -const CLUSTER = "testnet"; // TODO: change this - const createWalletStatus = ( isReady: boolean, statusMessage: string = "", @@ -35,8 +27,6 @@ function useIsWalletReady( forceNetworkSwitch: () => void; } { const autoSwitch = enableNetworkAutoswitch; - // const solanaWallet = useSolanaWallet(); - // const solPK = solanaWallet?.publicKey; const terraWallet = useConnectedWallet(); const hasTerraWallet = !!terraWallet; const { @@ -76,14 +66,6 @@ function useIsWalletReady( ); } if (isEVMChain(chainId) && hasEthInfo && signerAddress) { - //if (chainId === CHAIN_ID_SOLANA && solPK) { - // return createWalletStatus( - // true, - // undefined, - // forceNetworkSwitch, - // solPK.toString() - // ); - //} if (hasCorrectEvmNetwork) { return createWalletStatus( true, @@ -97,7 +79,7 @@ function useIsWalletReady( } return createWalletStatus( false, - `Wallet is not connected to ${CLUSTER}. Expected Chain ID: ${correctEvmNetwork}`, + `Wallet is not connected to testnet. Expected Chain ID: ${correctEvmNetwork}`, forceNetworkSwitch, undefined ); @@ -115,7 +97,6 @@ function useIsWalletReady( autoSwitch, forceNetworkSwitch, hasTerraWallet, - // solPK, hasEthInfo, correctEvmNetwork, hasCorrectEvmNetwork, diff --git a/react/src/icons/avax.svg b/react/src/icons/avax.svg new file mode 100644 index 0000000..a5787ea --- /dev/null +++ b/react/src/icons/avax.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/react/src/icons/bsc.svg b/react/src/icons/bsc.svg new file mode 100644 index 0000000..61c684d --- /dev/null +++ b/react/src/icons/bsc.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/react/src/icons/wormhole-network.svg b/react/src/icons/wormhole-network.svg deleted file mode 100644 index cd0e70d..0000000 --- a/react/src/icons/wormhole-network.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/react/src/icons/wormhole_logo.svg b/react/src/icons/wormhole_logo.svg new file mode 100644 index 0000000..ec6231e --- /dev/null +++ b/react/src/icons/wormhole_logo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/react/src/route/generic.ts b/react/src/route/generic.ts index 7d2392d..0a33a2c 100644 --- a/react/src/route/generic.ts +++ b/react/src/route/generic.ts @@ -1,5 +1,3 @@ -import { FixedNumber } from "ethers"; - export enum UstLocation { In = 1, Out, diff --git a/react/src/route/hurricaneswap.ts b/react/src/route/hurricaneswap.ts index f696c81..f1b946b 100644 --- a/react/src/route/hurricaneswap.ts +++ b/react/src/route/hurricaneswap.ts @@ -1,12 +1,12 @@ import { ethers } from "ethers"; -import { WAVAX_TOKEN_INFO } from "../utils/consts"; +import { AVAX_TOKEN_INFO } from "../utils/consts"; import { UstLocation } from "./generic"; import { UniswapV2Router } from "./uniswap-v2"; export { PROTOCOL } from "./uniswap-v2"; -const HURRICANESWAP_FACTORY_ADDRESS = null; +const HURRICANESWAP_FACTORY_ADDRESS = ""; export class HurricaneswapRouter extends UniswapV2Router { constructor(provider: ethers.providers.Provider) { @@ -15,7 +15,7 @@ export class HurricaneswapRouter extends UniswapV2Router { } async initialize(ustLocation: UstLocation): Promise { - await super.initializeTokens(WAVAX_TOKEN_INFO, ustLocation); + await super.initializeTokens(AVAX_TOKEN_INFO, ustLocation); return; } diff --git a/react/src/route/pancakeswap.ts b/react/src/route/pancakeswap.ts index 37ce319..36e1f52 100644 --- a/react/src/route/pancakeswap.ts +++ b/react/src/route/pancakeswap.ts @@ -1,12 +1,12 @@ import { ethers } from "ethers"; -import { WBNB_TOKEN_INFO } from "../utils/consts"; +import { BNB_TOKEN_INFO } from "../utils/consts"; import { UstLocation } from "./generic"; import { UniswapV2Router } from "./uniswap-v2"; export { PROTOCOL } from "./uniswap-v2"; -const PANCAKESWAP_FACTORY_ADDRESS = null; +const PANCAKESWAP_FACTORY_ADDRESS = ""; export class PancakeswapRouter extends UniswapV2Router { constructor(provider: ethers.providers.Provider) { @@ -15,7 +15,7 @@ export class PancakeswapRouter extends UniswapV2Router { } async initialize(ustLocation: UstLocation): Promise { - await super.initializeTokens(WBNB_TOKEN_INFO, ustLocation); + await super.initializeTokens(BNB_TOKEN_INFO, ustLocation); return; } diff --git a/react/src/route/quickswap.ts b/react/src/route/quickswap.ts index 7c4d421..b31d5cc 100644 --- a/react/src/route/quickswap.ts +++ b/react/src/route/quickswap.ts @@ -1,6 +1,6 @@ import { ethers } from "ethers"; -import { WMATIC_TOKEN_INFO } from "../utils/consts"; +import { MATIC_TOKEN_INFO } from "../utils/consts"; import { UstLocation } from "./generic"; import { UniswapV2Router } from "./uniswap-v2"; @@ -15,7 +15,7 @@ export class QuickswapRouter extends UniswapV2Router { } async initialize(ustLocation: UstLocation): Promise { - await super.initializeTokens(WMATIC_TOKEN_INFO, ustLocation); + await super.initializeTokens(MATIC_TOKEN_INFO, ustLocation); return; } } diff --git a/react/src/route/uniswap-core.ts b/react/src/route/uniswap-core.ts index 0479eff..e85e435 100644 --- a/react/src/route/uniswap-core.ts +++ b/react/src/route/uniswap-core.ts @@ -166,7 +166,7 @@ export abstract class UniswapRouterCore extends RouterCore { const network = this.network; - if (ustLocation == UstLocation.Out) { + if (ustLocation === UstLocation.Out) { [this.tokenIn, this.tokenOut] = await Promise.all([ makeUniEvmToken(this.provider, network.chainId, tokenInfo.address), makeUniEvmToken( diff --git a/react/src/route/uniswap-v3.ts b/react/src/route/uniswap-v3.ts index cc59c44..46f5f44 100644 --- a/react/src/route/uniswap-v3.ts +++ b/react/src/route/uniswap-v3.ts @@ -14,7 +14,7 @@ import { } from "@uniswap/v3-sdk"; import { UniswapRouterCore } from "./uniswap-core"; -import { WETH_TOKEN_INFO } from "../utils/consts"; +import { ETH_TOKEN_INFO } from "../utils/consts"; import { UstLocation } from "./generic"; export const PROTOCOL = "UniswapV3"; @@ -34,7 +34,7 @@ export class UniswapV3Router extends UniswapRouterCore { } async initialize(ustLocation: UstLocation): Promise { - await this.initializeTokens(WETH_TOKEN_INFO, ustLocation); + await this.initializeTokens(ETH_TOKEN_INFO, ustLocation); return; } diff --git a/react/src/swapper/swapper.ts b/react/src/swapper/swapper.ts index cfbb119..d4102ca 100644 --- a/react/src/swapper/swapper.ts +++ b/react/src/swapper/swapper.ts @@ -1,4 +1,4 @@ -//@ts-nocheckk +//@ts-nocheck import { ethers } from "ethers"; import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { @@ -38,8 +38,6 @@ import { //ETH_NETWORK_CHAIN_ID, //POLYGON_NETWORK_CHAIN_ID, //TERRA_NETWORK_CHAIN_ID, - WETH_TOKEN_INFO, - WMATIC_TOKEN_INFO, UST_TOKEN_INFO, } from "../utils/consts"; import { @@ -493,26 +491,28 @@ interface VaaSearchParams { } export function makeEvmProvider(tokenAddress: string) { + let url; switch (tokenAddress) { - case WETH_TOKEN_INFO.address: { - const url = process.env.REACT_APP_GOERLI_PROVIDER; - if (!url) { - throw new Error("Could not find REACT_APP_GOERLI_PROVIDER"); - } - return new ethers.providers.StaticJsonRpcProvider(url); - } - case WMATIC_TOKEN_INFO.address: { - const url = process.env.REACT_APP_MUMBAI_PROVIDER; - if (!url) { - throw new Error("Could not find REACT_APP_MUMBAI_PROVIDER"); - } - return new ethers.providers.StaticJsonRpcProvider(url); - } - default: { - console.log("huh?", tokenAddress); + case ETH_TOKEN_INFO.address: + url = process.env.REACT_APP_GOERLI_PROVIDER; + if (!url) throw new Error("REACT_APP_GOERLI_PROVIDER not set"); + break; + case MATIC_TOKEN_INFO.address: + url = process.env.REACT_APP_MUMBAI_PROVIDER; + if (!url) throw new Error("REACT_APP_MUMBAI_PROVIDER not set"); + break; + case AVAX_TOKEN_INFO.address: + url = process.env.REACT_APP_FUJI_PROVIDER; + if (!url) throw new Error("REACT_APP_FUJI_PROVIDER not set"); + break; + case BSC_TOKEN_INFO.address: + url = process.env.REACT_APP_BSC_PROVIDER; + if (!url) throw new Error("REACT_APP_BSC_PROVIDER not set"); + break; + default: throw Error("unrecognized token address"); - } } + return new ethers.providers.StaticJsonRpcProvider(url); } export class UniswapToUniswapExecutor { diff --git a/react/src/utils/consts.ts b/react/src/utils/consts.ts index e0aacda..cf7a2ba 100644 --- a/react/src/utils/consts.ts +++ b/react/src/utils/consts.ts @@ -7,169 +7,119 @@ import { CHAIN_ID_BSC, } from "@certusone/wormhole-sdk"; -//import ethIcon from "../icons/eth.svg"; -//import polygonIcon from "../icons/polygon.svg"; -//import terraIcon from "../icons/terra.svg"; +import ethIcon from "../icons/eth.svg"; +import polygonIcon from "../icons/polygon.svg"; +import terraIcon from "../icons/terra.svg"; +import bscIcon from "../icons/bsc.svg"; +import avaxIcon from "../icons/avax.svg"; -const ethIcon = ""; -const polygonIcon = ""; -const bnbIcon = ""; -const avaxIcon = ""; -const terraIcon = ""; +export const EVM_POLYGON_NETWORK_CHAIN_ID = 80001; +export const EVM_ETH_NETWORK_CHAIN_ID = 5; +export const EVM_AVAX_NETWORK_CHAIN_ID = 43113; +export const EVM_BSC_NETWORK_CHAIN_ID = 97; export interface TokenInfo { name: string; address: string; chainId: ChainId; + evmChainId: number | undefined; logo: string; - isNative: boolean; maxAmount: number; ustPairedAddress: string | undefined; } -// matic export const MATIC_TOKEN_INFO: TokenInfo = { name: "MATIC", - address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", // used to compute quote - chainId: CHAIN_ID_POLYGON, - logo: polygonIcon, - isNative: true, - maxAmount: 0.1, - ustPairedAddress: "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c", -}; - -export const WMATIC_TOKEN_INFO: TokenInfo = { - name: "WMATIC", address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", chainId: CHAIN_ID_POLYGON, + evmChainId: EVM_POLYGON_NETWORK_CHAIN_ID, logo: polygonIcon, - isNative: false, maxAmount: 0.1, ustPairedAddress: "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c", }; -// eth export const ETH_TOKEN_INFO: TokenInfo = { name: "ETH", - address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // used to compute quote - chainId: CHAIN_ID_ETH, - logo: ethIcon, - isNative: true, - maxAmount: 0.01, - ustPairedAddress: "0x36Ed51Afc79619b299b238898E72ce482600568a", -}; - -export const WETH_TOKEN_INFO: TokenInfo = { - name: "WETH", address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", chainId: CHAIN_ID_ETH, + evmChainId: EVM_ETH_NETWORK_CHAIN_ID, logo: ethIcon, - isNative: false, maxAmount: 0.01, ustPairedAddress: "0x36Ed51Afc79619b299b238898E72ce482600568a", }; -// avax export const AVAX_TOKEN_INFO: TokenInfo = { name: "AVAX", address: "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3", chainId: CHAIN_ID_AVAX, + evmChainId: EVM_AVAX_NETWORK_CHAIN_ID, logo: avaxIcon, - isNative: true, maxAmount: 0.01, ustPairedAddress: "0xe09ed38e5cd1014444846f62376ac88c5232cde9", }; -export const WAVAX_TOKEN_INFO: TokenInfo = { - name: "WAVAX", - address: "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3", - chainId: CHAIN_ID_AVAX, - logo: avaxIcon, - isNative: false, - maxAmount: 0.01, - ustPairedAddress: "0xe09ed38e5cd1014444846f62376ac88c5232cde9", -}; - -// bnb export const BNB_TOKEN_INFO: TokenInfo = { name: "BNB", address: "0xae13d989dac2f0debff460ac112a837c89baa7cd", chainId: CHAIN_ID_BSC, - logo: bnbIcon, - isNative: true, + evmChainId: EVM_BSC_NETWORK_CHAIN_ID, + logo: bscIcon, maxAmount: 0.01, ustPairedAddress: "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1", }; -export const WBNB_TOKEN_INFO: TokenInfo = { - name: "WBNB", - address: "0xae13d989dac2f0debff460ac112a837c89baa7cd", - chainId: CHAIN_ID_BSC, - logo: bnbIcon, - isNative: false, - maxAmount: 0.01, - ustPairedAddress: "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1", -}; - -// ust export const UST_TOKEN_INFO: TokenInfo = { name: "UST", address: "uusd", chainId: CHAIN_ID_TERRA, + evmChainId: undefined, logo: terraIcon, - isNative: true, // TODO: change? maxAmount: 10.0, ustPairedAddress: undefined, }; export const TOKEN_INFOS = [ MATIC_TOKEN_INFO, - WMATIC_TOKEN_INFO, ETH_TOKEN_INFO, - WETH_TOKEN_INFO, - UST_TOKEN_INFO, + AVAX_TOKEN_INFO, + BNB_TOKEN_INFO, + // TODO: support swaps from/to terra + // UST_TOKEN_INFO, ]; export const getSupportedSwaps = (tokenInfo: TokenInfo) => { - switch (tokenInfo) { - case MATIC_TOKEN_INFO: - return [ETH_TOKEN_INFO, UST_TOKEN_INFO]; - case WMATIC_TOKEN_INFO: - return [WETH_TOKEN_INFO]; - case ETH_TOKEN_INFO: - return [MATIC_TOKEN_INFO, UST_TOKEN_INFO]; - case WETH_TOKEN_INFO: - return [WMATIC_TOKEN_INFO]; - case UST_TOKEN_INFO: - return [ETH_TOKEN_INFO, MATIC_TOKEN_INFO]; - } - return []; + return TOKEN_INFOS.filter((x) => x !== tokenInfo); }; -export const EVM_ETH_NETWORK_CHAIN_ID = 5; -export const EVM_POLYGON_NETWORK_CHAIN_ID = 80001; -export const EVM_AVAX_NETWORK_CHAIN_ID = 43113; -export const EVM_BSC_NETWORK_CHAIN_ID = 97; - -export function getEvmChainId(chainId: ChainId): number { +export const getEvmChainId = (chainId: ChainId): number | undefined => { switch (chainId) { - case CHAIN_ID_ETH: { + case CHAIN_ID_ETH: return EVM_ETH_NETWORK_CHAIN_ID; - } - case CHAIN_ID_POLYGON: { + case CHAIN_ID_POLYGON: return EVM_POLYGON_NETWORK_CHAIN_ID; - } - case CHAIN_ID_AVAX: { + case CHAIN_ID_AVAX: return EVM_AVAX_NETWORK_CHAIN_ID; - } - case CHAIN_ID_BSC: { + case CHAIN_ID_BSC: return EVM_BSC_NETWORK_CHAIN_ID; - } - default: { + default: return undefined; - } } -} +}; + +export const getChainName = (chainId: ChainId) => { + switch (chainId) { + case CHAIN_ID_ETH: + return "Ethereum"; + case CHAIN_ID_POLYGON: + return "Polygon"; + case CHAIN_ID_AVAX: + return "Avalanche"; + case CHAIN_ID_BSC: + return "BSC"; + default: + return ""; + } +}; export const RELAYER_FEE_UST = "0.25"; diff --git a/react/src/views/Home.tsx b/react/src/views/Home.tsx index 042ab5a..429ae6b 100644 --- a/react/src/views/Home.tsx +++ b/react/src/views/Home.tsx @@ -39,11 +39,7 @@ import parseError from "../utils/parseError"; import Settings from "../components/Settings"; import getIsTransferCompletedEvmWithRetry from "../utils/getIsTransferCompletedWithRetry"; import CircleLoader from "../components/CircleLoader"; -import { - ArrowForward, - CheckCircleOutlineRounded, - QueueTwoTone, -} from "@material-ui/icons"; +import { ArrowForward, CheckCircleOutlineRounded } from "@material-ui/icons"; import SwapProgress from "../components/SwapProgress"; import Footer from "../components/Footer"; import TerraWalletKey from "../components/TerraWalletKey"; @@ -163,13 +159,19 @@ const switchEvmProviderNetwork = async ( } }; -const ConnectedWalletAddress = ({ chainId }: { chainId: ChainId }) => { +const ConnectedWalletAddress = ({ + chainId, + prefix, +}: { + chainId: ChainId; + prefix: string; +}) => { const { walletAddress } = useIsWalletReady(chainId, false); if (walletAddress) { const is0x = walletAddress.startsWith("0x"); return ( - {walletAddress?.substring(0, is0x ? 6 : 3)}... + {prefix} {walletAddress?.substring(0, is0x ? 6 : 3)}... {walletAddress?.substring(walletAddress.length - (is0x ? 4 : 3))} ); @@ -190,16 +192,13 @@ const SwapButton = ({ showLoader: boolean; onClick: () => void; }) => { - const { isReady: isSourceWalletReady, walletAddress: sourceWalletAddress } = - useIsWalletReady(source.chainId, false); - const { isReady: isTargetWalletReady, walletAddress: targetWalletAddress } = - useIsWalletReady(target.chainId, false); - - console.log( - "sourceWalletAddress", - sourceWalletAddress, - "targetWalletAddress", - targetWalletAddress + const { isReady: isSourceWalletReady } = useIsWalletReady( + source.chainId, + !disabled + ); + const { isReady: isTargetWalletReady } = useIsWalletReady( + target.chainId, + !isEVMChain(source.chainId) ); if (!isSourceWalletReady) { @@ -220,6 +219,7 @@ const SwapButton = ({ ) : null; } + return ( { (async () => { setHasQuote(false); @@ -274,7 +272,7 @@ export default function Home() { await executor.initialize( sourceTokenInfo.address, targetTokenInfo.address, - sourceTokenInfo.isNative + true ); await executor.computeAndVerifySrcPoolAddress().catch((e) => { throw new Error("failed to verify source pool address"); @@ -286,23 +284,9 @@ export default function Home() { executor.setSlippage((parseFloat(slippage) / 100).toString()); executor.setRelayerFee(RELAYER_FEE_UST); const quote = await executor.computeQuoteExactIn(amountIn); - // TODO: FIX - if (!quote || !quote.dst) { - throw new Error("failed to compute quote"); - } setExecutor(executor); - setAmountOut( - parseFloat( - // executor.tokens.dstOut.formatAmount(quote.dst.minAmountOut) - quote.minAmountOut - ).toFixed(8) - ); - setAmountInUST( - parseFloat( - //executor.tokens.dstIn.formatAmount(quote.dst.amountIn) - quote.ustAmountIn - ).toFixed(2) - ); + setAmountOut(parseFloat(quote.minAmountOut).toFixed(8)); + setAmountInUST(parseFloat(quote.ustAmountIn).toFixed(2)); setHasQuote(true); } } catch (e) { @@ -352,7 +336,6 @@ export default function Home() { const tokenInfo = TOKEN_INFOS.find((x) => x.name === event.target.value); if (tokenInfo) { const supportedSwaps = getSupportedSwaps(tokenInfo); - console.log(supportedSwaps); if (supportedSwaps) { setSourceTokenInfo(tokenInfo); if (!supportedSwaps.find((x) => x.name === targetTokenInfo.name)) { @@ -378,15 +361,17 @@ export default function Home() { setIsSwapping(false); setHasQuote(false); setIsSourceSwapComplete(false); + setHasSignedVAA(false); setIsTargetSwapComplete(false); setAmountIn(""); setAmountOut(""); setSourceTxBlockNumber(undefined); setRelayerTimeoutString(""); - }, []); + disconnect(); + }, [disconnect]); const handleSwapClick = useCallback(async () => { - if (provider && signer && executor) { + if (provider && signer && signerAddress && executor) { try { setIsSwapping(true); setIsSourceSwapComplete(false); @@ -394,9 +379,12 @@ export default function Home() { setIsTargetSwapComplete(false); setRelayerTimeoutString(""); await switchEvmProviderNetwork(provider, sourceTokenInfo.chainId); + console.log(signerAddress); - // TODO: fix - const sourceReceipt = await executor.evmApproveAndSwap(signer); + const sourceReceipt = await executor.evmApproveAndSwap( + signer, + signerAddress + ); console.info( "firstSwapTransactionHash:", sourceReceipt.transactionHash @@ -415,8 +403,8 @@ export default function Home() { // Check if the signedVAA has redeemed by the relayer const isCompleted = await getIsTransferCompletedEvmWithRetry( executor.dstExecutionParams.wormhole.tokenBridgeAddress, - // TODO: fix - //@ts-ignore + // TODO: fix typescript error + // @ts-ignore executor.quoter.getDstEvmProvider(), vaaBytes, // retry for two minutes @@ -447,6 +435,7 @@ export default function Home() { }, [ provider, signer, + signerAddress, executor, enqueueSnackbar, sourceTokenInfo, @@ -461,9 +450,7 @@ export default function Home() {
- - Wormhole NativeSwap Demo - + Wormhole NativeSwap Demo
@@ -496,7 +483,10 @@ export default function Home() { color="error" >{`The max input amount is ${sourceTokenInfo.maxAmount} ${sourceTokenInfo.name}`} ) : null} - +
{`Slippage tolerance: ${slippage}%`} @@ -575,7 +566,6 @@ export default function Home() { From 49ae9bf0a1b54df46a065292a9bf639175dca223 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Mon, 31 Jan 2022 18:47:09 +0000 Subject: [PATCH 30/38] Fix gas --- react/src/swapper/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts index bdf3613..d397f6e 100644 --- a/react/src/swapper/helpers.ts +++ b/react/src/swapper/helpers.ts @@ -11,14 +11,14 @@ import { export const CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559 = { gasLimit: "694200", //maxFeePerGas: "250000000000", - maxFeePerGas: "20420690000", + maxFeePerGas: "25420690000", maxPriorityFeePerGas: "1690000000", }; export const CROSSCHAINSWAP_GAS_PARAMETERS_EVM = { gasLimit: "694200", //gasPrice: "250000000000", - gasPrice: "20420690000", + gasPrice: "25420690000", }; export const EVM_EIP1559_CHAIN_IDS = [ From d059b46e854df21f70fe1dc9555f31caceb39b91 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Mon, 31 Jan 2022 19:58:45 +0000 Subject: [PATCH 31/38] Fix icon handling --- react/src/components/TokenSelect.tsx | 31 +++++++++++++++++++++++++++- react/src/utils/consts.ts | 20 ++++++++++++------ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/react/src/components/TokenSelect.tsx b/react/src/components/TokenSelect.tsx index f13c15f..c1eeb55 100644 --- a/react/src/components/TokenSelect.tsx +++ b/react/src/components/TokenSelect.tsx @@ -7,6 +7,12 @@ import { } from "@material-ui/core"; import { TokenInfo } from "../utils/consts"; +import ethIcon from "../icons/eth.svg"; +import polygonIcon from "../icons/polygon.svg"; +import terraIcon from "../icons/terra.svg"; +import bscIcon from "../icons/bsc.svg"; +import avaxIcon from "../icons/avax.svg"; + const useStyles = makeStyles((theme) => ({ select: { "& .MuiSelect-root": { @@ -23,10 +29,33 @@ const useStyles = makeStyles((theme) => ({ }, })); +function getLogo(name: string): string { + switch (name) { + case "ETH": { + return ethIcon; + } + case "MATIC": { + return polygonIcon; + } + case "UST": { + return terraIcon; + } + case "AVAX": { + return avaxIcon; + } + case "BNB": { + return bscIcon; + } + default: { + return ""; + } + } +} + const createTokenMenuItem = ({ name, logo }: TokenInfo, classes: any) => ( - {name} + {name} {name} diff --git a/react/src/utils/consts.ts b/react/src/utils/consts.ts index cf7a2ba..9d2dffd 100644 --- a/react/src/utils/consts.ts +++ b/react/src/utils/consts.ts @@ -7,11 +7,19 @@ import { CHAIN_ID_BSC, } from "@certusone/wormhole-sdk"; +/* import ethIcon from "../icons/eth.svg"; import polygonIcon from "../icons/polygon.svg"; import terraIcon from "../icons/terra.svg"; import bscIcon from "../icons/bsc.svg"; import avaxIcon from "../icons/avax.svg"; +*/ + +const ethIcon = ""; +const polygonIcon = ""; +const terraIcon = ""; +const bscIcon = ""; +const avaxIcon = ""; export const EVM_POLYGON_NETWORK_CHAIN_ID = 80001; export const EVM_ETH_NETWORK_CHAIN_ID = 5; @@ -23,7 +31,7 @@ export interface TokenInfo { address: string; chainId: ChainId; evmChainId: number | undefined; - logo: string; + //logo: string; maxAmount: number; ustPairedAddress: string | undefined; } @@ -33,7 +41,7 @@ export const MATIC_TOKEN_INFO: TokenInfo = { address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", chainId: CHAIN_ID_POLYGON, evmChainId: EVM_POLYGON_NETWORK_CHAIN_ID, - logo: polygonIcon, + //logo: polygonIcon, maxAmount: 0.1, ustPairedAddress: "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c", }; @@ -43,7 +51,7 @@ export const ETH_TOKEN_INFO: TokenInfo = { address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", chainId: CHAIN_ID_ETH, evmChainId: EVM_ETH_NETWORK_CHAIN_ID, - logo: ethIcon, + //logo: ethIcon, maxAmount: 0.01, ustPairedAddress: "0x36Ed51Afc79619b299b238898E72ce482600568a", }; @@ -53,7 +61,7 @@ export const AVAX_TOKEN_INFO: TokenInfo = { address: "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3", chainId: CHAIN_ID_AVAX, evmChainId: EVM_AVAX_NETWORK_CHAIN_ID, - logo: avaxIcon, + //logo: avaxIcon, maxAmount: 0.01, ustPairedAddress: "0xe09ed38e5cd1014444846f62376ac88c5232cde9", }; @@ -63,7 +71,7 @@ export const BNB_TOKEN_INFO: TokenInfo = { address: "0xae13d989dac2f0debff460ac112a837c89baa7cd", chainId: CHAIN_ID_BSC, evmChainId: EVM_BSC_NETWORK_CHAIN_ID, - logo: bscIcon, + //logo: bscIcon, maxAmount: 0.01, ustPairedAddress: "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1", }; @@ -73,7 +81,7 @@ export const UST_TOKEN_INFO: TokenInfo = { address: "uusd", chainId: CHAIN_ID_TERRA, evmChainId: undefined, - logo: terraIcon, + //logo: terraIcon, maxAmount: 10.0, ustPairedAddress: undefined, }; From 3155cb88b942f52a7b0b85527c1f641570ea6b31 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Mon, 31 Jan 2022 19:59:39 +0000 Subject: [PATCH 32/38] Fix swap-with-vaa; add swap-everything --- misc/scripts/swap-everything.sh | 17 +++++++++ misc/scripts/swap-with-vaa.ts | 64 ++++++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 misc/scripts/swap-everything.sh diff --git a/misc/scripts/swap-everything.sh b/misc/scripts/swap-everything.sh new file mode 100644 index 0000000..36607d9 --- /dev/null +++ b/misc/scripts/swap-everything.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -euo pipefail + +root=$(dirname $0) +script="${root}/swap-with-vaa.js" + +echo `which node` + +node $script --in ETH --out MATIC +node $script --in ETH --out BNB +node $script --in ETH --out AVAX +node $script --in MATIC --out BNB +node $script --in MATIC --out AVAX +node $script --in BNB --out MATIC + +echo "done" \ No newline at end of file diff --git a/misc/scripts/swap-with-vaa.ts b/misc/scripts/swap-with-vaa.ts index bb89512..c0b298c 100644 --- a/misc/scripts/swap-with-vaa.ts +++ b/misc/scripts/swap-with-vaa.ts @@ -1,3 +1,4 @@ +import yargs from "yargs"; import { ethers } from "ethers"; import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport"; @@ -33,6 +34,34 @@ const SWAP_SLIPPAGE = "0.01"; // token bridge things const BRIDGE_RELAYER_FEE_UST = "0.25"; +interface Arguments { + in: string; + out: string; +} + +function parseArgs(): Arguments { + const parsed = yargs(process.argv.slice(2)) + .option("in", { + string: true, + description: "Name of inbound token", + required: true, + }) + .option("out", { + string: true, + description: "Name of outbound token", + required: true, + }) + .help("h") + .alias("h", "help").argv; + + const args: Arguments = { + in: parsed.in, + out: parsed.out, + }; + + return args; +} + export function makeEvmWallet( provider: ethers.providers.Provider ): ethers.Wallet { @@ -347,19 +376,44 @@ async function swapEverythingExactOut( return; } +function getTokenInfo(name: string) { + switch (name) { + case "ETH": { + return ETH_TOKEN_INFO; + } + case "MATIC": { + return MATIC_TOKEN_INFO; + } + case "UST": { + return UST_TOKEN_INFO; + } + case "AVAX": { + return AVAX_TOKEN_INFO; + } + case "BNB": { + return BNB_TOKEN_INFO; + } + default: { + throw Error("invalid token name"); + } + } +} + async function main() { + const args = parseArgs(); + const testExactIn = true; const isNative = true; const swapper = new UniswapToUniswapExecutor(); swapper.setTransport(NodeHttpTransport()); - const tokenIn = ETH_TOKEN_INFO; - //const tokenOut = MATIC_TOKEN_INFO; - const tokenOut = UST_TOKEN_INFO; + const tokenIn = getTokenInfo(args.in); + const tokenOut = getTokenInfo(args.out); + //const tokenOut = UST_TOKEN_INFO; - //const recipientAddress = "0x4e2dfAD7D7d0076b5A0A41223E4Bee390C33251C"; - const recipientAddress = "terra1vewnsxcy5fqjslyyy409cw8js550esen38n8ey"; + const recipientAddress = "0x4e2dfAD7D7d0076b5A0A41223E4Bee390C33251C"; + //const recipientAddress = "terra1vewnsxcy5fqjslyyy409cw8js550esen38n8ey"; if (testExactIn) { console.info(`testing exact in. native=${isNative}`); From 73e9c8cf42b528b8ef626fe4cf6eae1cbb7de556 Mon Sep 17 00:00:00 2001 From: Kevin Peters Date: Mon, 31 Jan 2022 20:14:57 +0000 Subject: [PATCH 33/38] UI - fixed some typescript errors --- react/src/components/TokenSelect.tsx | 49 ++++++++++++++-------------- react/src/utils/consts.ts | 15 --------- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/react/src/components/TokenSelect.tsx b/react/src/components/TokenSelect.tsx index c1eeb55..300a1f4 100644 --- a/react/src/components/TokenSelect.tsx +++ b/react/src/components/TokenSelect.tsx @@ -5,7 +5,14 @@ import { MenuItem, TextField, } from "@material-ui/core"; -import { TokenInfo } from "../utils/consts"; +import { + AVAX_TOKEN_INFO, + BNB_TOKEN_INFO, + ETH_TOKEN_INFO, + MATIC_TOKEN_INFO, + TokenInfo, + UST_TOKEN_INFO, +} from "../utils/consts"; import ethIcon from "../icons/eth.svg"; import polygonIcon from "../icons/polygon.svg"; @@ -29,30 +36,24 @@ const useStyles = makeStyles((theme) => ({ }, })); -function getLogo(name: string): string { - switch (name) { - case "ETH": { - return ethIcon; - } - case "MATIC": { - return polygonIcon; - } - case "UST": { - return terraIcon; - } - case "AVAX": { - return avaxIcon; - } - case "BNB": { - return bscIcon; - } - default: { - return ""; - } - } -} +const getLogo = (name: string) => { + switch (name) { + case ETH_TOKEN_INFO.name: + return ethIcon; + case MATIC_TOKEN_INFO.name: + return polygonIcon; + case UST_TOKEN_INFO.name: + return terraIcon; + case AVAX_TOKEN_INFO.name: + return avaxIcon; + case BNB_TOKEN_INFO.name: + return bscIcon; + default: + return ""; + } +}; -const createTokenMenuItem = ({ name, logo }: TokenInfo, classes: any) => ( +const createTokenMenuItem = ({ name }: TokenInfo, classes: any) => ( {name} diff --git a/react/src/utils/consts.ts b/react/src/utils/consts.ts index 9d2dffd..c0ced83 100644 --- a/react/src/utils/consts.ts +++ b/react/src/utils/consts.ts @@ -7,20 +7,6 @@ import { CHAIN_ID_BSC, } from "@certusone/wormhole-sdk"; -/* -import ethIcon from "../icons/eth.svg"; -import polygonIcon from "../icons/polygon.svg"; -import terraIcon from "../icons/terra.svg"; -import bscIcon from "../icons/bsc.svg"; -import avaxIcon from "../icons/avax.svg"; -*/ - -const ethIcon = ""; -const polygonIcon = ""; -const terraIcon = ""; -const bscIcon = ""; -const avaxIcon = ""; - export const EVM_POLYGON_NETWORK_CHAIN_ID = 80001; export const EVM_ETH_NETWORK_CHAIN_ID = 5; export const EVM_AVAX_NETWORK_CHAIN_ID = 43113; @@ -31,7 +17,6 @@ export interface TokenInfo { address: string; chainId: ChainId; evmChainId: number | undefined; - //logo: string; maxAmount: number; ustPairedAddress: string | undefined; } From 3b6e8963e75fe428ead6da091d0149d0337cf86d Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Mon, 31 Jan 2022 22:08:11 +0000 Subject: [PATCH 34/38] Update gas parameters --- react/src/swapper/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react/src/swapper/helpers.ts b/react/src/swapper/helpers.ts index d397f6e..65cf840 100644 --- a/react/src/swapper/helpers.ts +++ b/react/src/swapper/helpers.ts @@ -11,14 +11,14 @@ import { export const CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559 = { gasLimit: "694200", //maxFeePerGas: "250000000000", - maxFeePerGas: "25420690000", + maxFeePerGas: "100420690000", maxPriorityFeePerGas: "1690000000", }; export const CROSSCHAINSWAP_GAS_PARAMETERS_EVM = { gasLimit: "694200", //gasPrice: "250000000000", - gasPrice: "25420690000", + gasPrice: "20420690000", }; export const EVM_EIP1559_CHAIN_IDS = [ From 3b41ec9ae2c66085c09834424f18050cac3de231 Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Mon, 31 Jan 2022 22:42:18 +0000 Subject: [PATCH 35/38] Update README; add avax and bsc rpc --- README.md | 6 +++--- contracts/.env.sample | 4 +++- react/.env.sample | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f4d0938..804d81b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Multi-chain native-to-native token swap using existing DEXes. ### Details -Using liquidity of native vs UST (i.e. the UST highway), one can swap from native A on chain A to native B on chain B. For this specific example, we demonstrate a swap between Polygon (Mumbai testnet) and Ethereum (Goerli testnet) between MATIC and ETH. We wrote example smart contracts to interact with Uniswap V3 and Uniswap V2 forks (QuickSwap in this specific example for Polygon). Any DEX can be used to replace our example as long as the swap for a particular DEX has all of its parameters to perform the swap(s). +Using liquidity of native vs UST (i.e. the UST highway), one can swap from native A on chain A to native B on chain B. For this specific example, we demonstrate a swap between any combination of ETH (Goerli testnet), AVAX (Fuji testnet), MATIC (Mumbai testnet) and BNB (BSC testnet). We wrote example smart contracts to interact with Uniswap V3 and Uniswap V2 forks. Any DEX can be used to replace our example as long as the swap for a particular DEX has all of its parameters to perform the swap(s). A protocol that hosts NativeSwap is expected to run its own relayer to enhance its user experience by only requiring a one-click transaction to perform the complete swap. Otherwise the user will have to perform an extra transaction to manually allow the final swap. @@ -49,8 +49,8 @@ cp .env.sample .env Then deploy the example contracts: ``` -./deploy_to_goerli.sh -./deploy_to_mumbai.sh +./deploy_v2.sh +./deploy_v3.sh ``` Then change into the react directory, copy sample.env to .env and replace YOUR-PROJECT-ID with your Infura Goerli and Mumbai Project IDs diff --git a/contracts/.env.sample b/contracts/.env.sample index 2e2f50b..b906cf0 100644 --- a/contracts/.env.sample +++ b/contracts/.env.sample @@ -1,3 +1,5 @@ GOERLI_PROVIDER=https://goerli.infura.io/v3/YOUR-PROJECT-ID MUMBAI_PROVIDER=https://polygon-mumbai.infura.io/v3/YOUR-PROJECT-ID -ETH_PRIVATE_KEY= \ No newline at end of file +FUJI_PROVIDER="https://api.avax-test.network/ext/bc/C/rpc" +BSC_PROVIDER="https://data-seed-prebsc-1-s1.binance.org:8545" +ETH_PRIVATE_KEY= diff --git a/react/.env.sample b/react/.env.sample index 458c04b..5d6c4ec 100644 --- a/react/.env.sample +++ b/react/.env.sample @@ -1,4 +1,4 @@ -REACT_APP_GOERLI_PROVIDER=https://goerli.infura.io/v3/YOUR-PROJECT-ID -REACT_APP_MUMBAI_PROVIDER=https://polygon-mumbai.infura.io/v3/YOUR-PROJECT-ID -REACT_APP_FUJI_PROVIDER= -REACT_APP_BSC_PROVIDER= \ No newline at end of file +REACT_APP_GOERLI_PROVIDER="https://goerli.infura.io/v3/YOUR-PROJECT-ID" +REACT_APP_MUMBAI_PROVIDER="https://polygon-mumbai.infura.io/v3/YOUR-PROJECT-ID" +REACT_APP_FUJI_PROVIDER="https://api.avax-test.network/ext/bc/C/rpc" +REACT_APP_BSC_PROVIDER="https://data-seed-prebsc-1-s1.binance.org:8545" From fe5fa892014f617199a4a6b378908bfb20fdb67d Mon Sep 17 00:00:00 2001 From: Karl Kempe Date: Mon, 31 Jan 2022 22:47:12 +0000 Subject: [PATCH 36/38] Add more faucet links --- react/src/views/Home.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/react/src/views/Home.tsx b/react/src/views/Home.tsx index 429ae6b..7d1a188 100644 --- a/react/src/views/Home.tsx +++ b/react/src/views/Home.tsx @@ -576,19 +576,31 @@ export default function Home() { {relayerTimeoutString} )} - WARNING: this is a Testnet release only + WARNING: this is a testnet release only