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(); +}