Add terra ust for quoting

This commit is contained in:
Karl Kempe 2022-01-26 17:06:26 +00:00
parent 29704b701d
commit 0c50e93dc0
11 changed files with 750 additions and 372 deletions

View File

@ -1,41 +1,92 @@
import { ethers } from "ethers"; import { ethers } from "ethers";
import { UniEvmToken } from "./uniswap-core";
import { QuickswapRouter } from "./quickswap"; import { QuickswapRouter as MaticRouter } from "./quickswap";
import { SingleAmmSwapRouter as UniswapV3Router } from "./uniswap-v3"; import { UniswapV3Router as EthRouter } from "./uniswap-v3";
import { TerraUstTransfer as UstRouter } from "./terra-ust-transfer";
import { import {
ETH_NETWORK_CHAIN_ID, WETH_TOKEN_INFO,
POLYGON_NETWORK_CHAIN_ID, WMATIC_TOKEN_INFO,
UST_TOKEN_INFO,
WORMHOLE_CHAIN_ID_ETHEREUM,
WORMHOLE_CHAIN_ID_POLYGON,
WORMHOLE_CHAIN_ID_TERRA,
} from "../utils/consts"; } 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_V2 } from "./uniswap-v2";
export { PROTOCOL as PROTOCOL_UNISWAP_V3 } from "./uniswap-v3"; 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 { export enum QuoteType {
ExactIn = 1, ExactIn = 1,
ExactOut, ExactOut,
} }
function makeRouter(provider: ethers.providers.Provider, id: number) { export function makeEvmProviderFromAddress(tokenAddress: string) {
switch (id) { switch (tokenAddress) {
case ETH_NETWORK_CHAIN_ID: { case WETH_TOKEN_INFO.address: {
return new UniswapV3Router(provider); 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: { case WMATIC_TOKEN_INFO.address: {
return new QuickswapRouter(provider); 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: { default: {
throw Error("unrecognized chain id"); throw Error("unrecognized evm token address");
} }
} }
} }
export function getUstAddress(id: number): string { export function getChainIdFromAddress(tokenAddress: string) {
switch (id) { switch (tokenAddress) {
case ETH_NETWORK_CHAIN_ID: { case WETH_TOKEN_INFO.address: {
return "0x36Ed51Afc79619b299b238898E72ce482600568a"; return WORMHOLE_CHAIN_ID_ETHEREUM;
} }
case POLYGON_NETWORK_CHAIN_ID: { case WMATIC_TOKEN_INFO.address: {
return "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c"; 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: { default: {
throw Error("unrecognized chain id"); throw Error("unrecognized chain id");
@ -51,123 +102,105 @@ function splitSlippageInHalf(totalSlippage: string): string {
.toString(); .toString();
} }
interface RelayerFee { export interface RelayerFee {
amount: ethers.BigNumber; amount: string;
tokenAddress: 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 { export interface ExactInCrossParameters {
amountIn: string;
minAmountOut: string;
src: ExactInParameters; src: ExactInParameters;
dst: ExactInParameters; dst: ExactInParameters;
relayerFee: RelayerFee; relayerFee: RelayerFee;
} }
export interface ExactOutParameters {
protocol: string;
amountOut: ethers.BigNumber;
maxAmountIn: ethers.BigNumber;
deadline: ethers.BigNumber;
poolFee: string;
path: [string, string];
}
export interface ExactOutCrossParameters { export interface ExactOutCrossParameters {
amountOut: string;
maxAmountIn: string;
src: ExactOutParameters; src: ExactOutParameters;
dst: ExactOutParameters; dst: ExactOutParameters;
relayerFee: RelayerFee; relayerFee: RelayerFee;
} }
export class UniswapToUniswapQuoter { export class UniswapToUniswapQuoter {
// providers // tokens
srcProvider: ethers.providers.Provider; tokenInAddress: string;
dstProvider: ethers.providers.Provider; tokenOutAddress: string;
// networks
srcNetwork: ethers.providers.Network;
dstNetwork: ethers.providers.Network;
// routers // routers
srcRouter: UniswapV3Router | QuickswapRouter; srcRouter: UstRouter | EthRouter | MaticRouter;
dstRouter: UniswapV3Router | QuickswapRouter; dstRouter: UstRouter | EthRouter | MaticRouter;
// tokens constructor() {}
srcTokenIn: UniEvmToken;
srcTokenOut: UniEvmToken;
dstTokenIn: UniEvmToken;
dstTokenOut: UniEvmToken;
constructor( async initialize(
srcProvider: ethers.providers.Provider, tokenInAddress: string,
dstProvider: ethers.providers.Provider tokenOutAddress: string
) { ): Promise<void> {
this.srcProvider = srcProvider; if (tokenInAddress !== this.tokenInAddress) {
this.dstProvider = dstProvider; this.tokenInAddress = tokenInAddress;
} this.srcRouter = await makeRouter(tokenInAddress, UstLocation.Out);
}
async initialize(): Promise<void> { if (tokenOutAddress != this.tokenOutAddress) {
[this.srcNetwork, this.dstNetwork] = await Promise.all([ this.tokenOutAddress = tokenOutAddress;
this.srcProvider.getNetwork(), this.dstRouter = await makeRouter(tokenOutAddress, UstLocation.In);
this.dstProvider.getNetwork(), }
]);
this.srcRouter = makeRouter(this.srcProvider, this.srcNetwork.chainId);
this.dstRouter = makeRouter(this.dstProvider, this.dstNetwork.chainId);
return; 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<string> { async computeAndVerifySrcPoolAddress(): Promise<string> {
return this.srcRouter.computeAndVerifyPoolAddress( return this.srcRouter.computeAndVerifyPoolAddress();
this.srcTokenIn,
this.srcTokenOut
);
} }
async computeAndVerifyDstPoolAddress(): Promise<string> { async computeAndVerifyDstPoolAddress(): Promise<string> {
return this.dstRouter.computeAndVerifyPoolAddress( return this.dstRouter.computeAndVerifyPoolAddress();
this.dstTokenIn, }
this.dstTokenOut
); 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( async computeExactInParameters(
@ -175,71 +208,68 @@ export class UniswapToUniswapQuoter {
slippage: string, slippage: string,
relayerFeeUst: string relayerFeeUst: string
): Promise<ExactInCrossParameters> { ): Promise<ExactInCrossParameters> {
const singleSlippage = splitSlippageInHalf(slippage); const singleSlippage = this.computeSwapSlippage(slippage);
// src quote // src quote
const srcRouter = this.srcRouter; const srcRouter = this.srcRouter;
const srcTokenIn = this.srcTokenIn; const srcMinAmountOut = await srcRouter.fetchExactInQuote(
const srcTokenOut = this.srcTokenOut;
const srcMinAmountOut = await srcRouter.fetchQuoteAmountOut(
srcTokenIn,
srcTokenOut,
amountIn, amountIn,
singleSlippage singleSlippage
); );
// dst quote // dst quote
const dstRouter = this.dstRouter; const dstRouter = this.dstRouter;
const dstAmountIn = this.srcTokenOut.formatAmount(srcMinAmountOut); const dstAmountIn = srcMinAmountOut; //srcRouter.formatAmountOut(srcMinAmountOut);
if (Number(dstAmountIn) < Number(relayerFeeUst)) { if (Number(dstAmountIn) < Number(relayerFeeUst)) {
throw Error( throw Error(
`srcAmountOut <= relayerFeeUst. ${dstAmountIn} vs ${relayerFeeUst}` `srcAmountOut <= relayerFeeUst. ${dstAmountIn} vs ${relayerFeeUst}`
); );
} }
const dstTokenIn = this.dstTokenIn; const dstAmountInAfterFee = subtractFixedAmounts(
const dstTokenOut = this.dstTokenOut;
const dstAmountInAfterFee = dstTokenIn.subtractAmounts(
dstAmountIn, dstAmountIn,
relayerFeeUst relayerFeeUst,
dstRouter.getTokenInDecimals()
); );
const dstMinAmountOut = await dstRouter.fetchQuoteAmountOut( const dstMinAmountOut = await dstRouter.fetchExactInQuote(
dstTokenIn,
dstTokenOut,
dstAmountInAfterFee, dstAmountInAfterFee,
singleSlippage singleSlippage
); );
const srcParameters: ExactInParameters = { // organize parameters
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()],
};
const params: ExactInCrossParameters = { const params: ExactInCrossParameters = {
src: srcParameters, amountIn: amountIn,
dst: dstParameters, minAmountOut: dstMinAmountOut,
relayerFee: { src: this.makeSrcExactInParameters(amountIn, srcMinAmountOut),
amount: dstTokenIn.computeUnitAmount(relayerFeeUst), dst: this.makeDstExactInParameters(dstAmountInAfterFee, dstMinAmountOut),
tokenAddress: this.dstTokenIn.getAddress(), relayerFee: this.getRelayerFee(relayerFeeUst),
},
}; };
return params; 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( async computeExactOutParameters(
amountOut: string, amountOut: string,
slippage: string, slippage: string,
@ -249,69 +279,85 @@ export class UniswapToUniswapQuoter {
// dst quote first // dst quote first
const dstRouter = this.dstRouter; const dstRouter = this.dstRouter;
const dstTokenIn = this.dstTokenIn; const dstMaxAmountIn = await dstRouter.fetchExactOutQuote(
const dstTokenOut = this.dstTokenOut;
const dstMaxAmountIn = await dstRouter.fetchQuoteAmountIn(
dstTokenIn,
dstTokenOut,
amountOut, amountOut,
singleSlippage singleSlippage
); );
// src quote // src quote
const srcRouter = this.srcRouter; const srcRouter = this.srcRouter;
const srcAmountOut = this.dstTokenIn.formatAmount(dstMaxAmountIn); const srcAmountOut = dstMaxAmountIn;
if (Number(srcAmountOut) < Number(relayerFeeUst)) { if (Number(srcAmountOut) < Number(relayerFeeUst)) {
throw Error( throw Error(
`dstAmountIn <= relayerFeeUst. ${srcAmountOut} vs ${relayerFeeUst}` `dstAmountIn <= relayerFeeUst. ${srcAmountOut} vs ${relayerFeeUst}`
); );
} }
const srcTokenIn = this.srcTokenIn; const srcAmountOutBeforeFee = addFixedAmounts(
const srcTokenOut = this.srcTokenOut;
const srcAmountOutBeforeFee = srcTokenOut.addAmounts(
srcAmountOut, srcAmountOut,
relayerFeeUst relayerFeeUst,
srcRouter.getTokenOutDecimals()
); );
const srcMaxAmountIn = await srcRouter.fetchQuoteAmountIn( const srcMaxAmountIn = await srcRouter.fetchExactOutQuote(
srcTokenIn,
srcTokenOut,
srcAmountOutBeforeFee, srcAmountOutBeforeFee,
singleSlippage singleSlippage
); );
const srcParameters: ExactOutParameters = { // organize parameters
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()],
};
const params: ExactOutCrossParameters = { const params: ExactOutCrossParameters = {
src: srcParameters, amountOut: amountOut,
dst: dstParameters, maxAmountIn: srcMaxAmountIn,
relayerFee: { src: this.makeSrcExactOutParameters(
amount: dstTokenIn.computeUnitAmount(relayerFeeUst), srcAmountOutBeforeFee,
tokenAddress: this.dstTokenIn.getAddress(), srcMaxAmountIn
}, ),
dst: this.makeDstExactOutParameters(amountOut, dstMaxAmountIn),
relayerFee: this.getRelayerFee(relayerFeeUst),
}; };
return params; return params;
} }
setDeadlines(deadline: string): void { setDeadlines(deadline: string): void {
this.srcRouter.setDeadline(deadline); if (!this.isSrcUst()) {
this.dstRouter.setDeadline(deadline); // @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);
} }
} }

View File

@ -3,7 +3,7 @@ import { ethers } from "ethers";
import { GenericToken } from "./generic"; import { GenericToken } from "./generic";
// erc20 spec // erc20 spec
import { abi as Erc20Abi } from "../abi/erc20.json"; import { abi as Erc20Abi } from "../../abi/erc20.json";
import { import {
TransactionReceipt, TransactionReceipt,
TransactionRequest, TransactionRequest,

View File

@ -1,7 +1,40 @@
export abstract class DexRouter { import { FixedNumber } from "ethers";
abstract makeToken(tokenAddress: string): any;
abstract quoteLot(tokenA: any, tokenB: any, amount: string): Promise<any>; export enum UstLocation {
abstract setSlippage(slippage: string): void; In = 1,
Out,
}
export abstract class RouterCore {
abstract computeAndVerifyPoolAddress(): Promise<string>;
abstract computePoolAddress(): string;
//abstract computeUnitAmountIn(amount: string): string;
abstract computeUnitAmountOut(amount: string): string;
abstract fetchExactInQuote(
amountOut: string,
slippage: string
): Promise<string>;
abstract fetchExactOutQuote(
amountOut: string,
slippage: string
): Promise<string>;
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 { export abstract class GenericToken {
@ -9,16 +42,3 @@ export abstract class GenericToken {
abstract getDecimals(): number; 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;
}
}

View File

@ -1,12 +1,18 @@
import { ethers } from "ethers"; import { ethers } from "ethers";
import { QUICKSWAP_FACTORY_ADDRESS } from "../utils/consts"; import { QUICKSWAP_FACTORY_ADDRESS, WMATIC_TOKEN_INFO } from "../utils/consts";
import { SingleAmmSwapRouter } from "./uniswap-v2"; import { UstLocation } from "./generic";
import { UniswapV2Router } from "./uniswap-v2";
export { PROTOCOL } from "./uniswap-v2"; export { PROTOCOL } from "./uniswap-v2";
export class QuickswapRouter extends SingleAmmSwapRouter { export class QuickswapRouter extends UniswapV2Router {
constructor(provider: ethers.providers.Provider) { constructor(provider: ethers.providers.Provider) {
super(provider); super(provider);
super.setFactoryAddress(QUICKSWAP_FACTORY_ADDRESS); super.setFactoryAddress(QUICKSWAP_FACTORY_ADDRESS);
} }
async initialize(ustLocation: UstLocation): Promise<void> {
await super.initializeTokens(WMATIC_TOKEN_INFO, ustLocation);
return;
}
} }

View File

@ -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<string> {
return new Promise<string>((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<string> {
return amountIn;
}
async fetchExactOutQuote(
amountOut: string,
slippage: string
): Promise<string> {
return amountOut;
}
getTokenInDecimals(): number {
return UST_DECIMALS;
}
getTokenOutDecimals(): number {
return UST_DECIMALS;
}
getTokenOutAddress(): string {
return this.computePoolAddress();
}
}

View File

@ -2,6 +2,8 @@ import { ethers } from "ethers";
import { CurrencyAmount, Token } from "@uniswap/sdk-core"; import { CurrencyAmount, Token } from "@uniswap/sdk-core";
import { EvmToken } from "./evm"; import { EvmToken } from "./evm";
import { RouterCore, UstLocation } from "./generic";
import { TokenInfo } from "../utils/consts";
export function computeTradeDeadline(deadline: string): ethers.BigNumber { export function computeTradeDeadline(deadline: string): ethers.BigNumber {
return ethers.BigNumber.from(Math.floor(Date.now() / 1000)).add(deadline); return ethers.BigNumber.from(Math.floor(Date.now() / 1000)).add(deadline);
@ -78,46 +80,112 @@ export async function makeUniEvmToken(
return new UniEvmToken(chainId, erc20); 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; provider: ethers.providers.Provider;
network: ethers.providers.Network;
// wormhole
chainId: number;
// tokens
tokenIn: UniEvmToken;
tokenOut: UniEvmToken;
// params // params
deadline: string = ""; deadline: string = "";
constructor(provider: ethers.providers.Provider) { constructor(provider: ethers.providers.Provider) {
super();
this.provider = provider; this.provider = provider;
} }
public async makeToken(tokenAddress: string): Promise<UniEvmToken> { public getProvider(): ethers.providers.Provider {
const network = await this.provider.getNetwork(); return this.provider;
return makeUniEvmToken(this.provider, network.chainId, tokenAddress);
} }
abstract computePoolAddress( public async initializeTokens(
tokenIn: UniEvmToken, tokenInfo: TokenInfo,
tokenOut: UniEvmToken ustLocation: UstLocation
): string; ): Promise<void> {
this.network = await this.provider.getNetwork();
abstract computeAndVerifyPoolAddress( const network = this.network;
tokenIn: UniEvmToken,
tokenOut: UniEvmToken
): Promise<string>;
abstract fetchQuoteAmountOut( if (ustLocation == UstLocation.Out) {
tokenIn: UniEvmToken, [this.tokenIn, this.tokenOut] = await Promise.all([
tokenOut: UniEvmToken, makeUniEvmToken(this.provider, network.chainId, tokenInfo.address),
amountOut: string, makeUniEvmToken(
slippage: string this.provider,
): Promise<ethers.BigNumber>; network.chainId,
tokenInfo.ustPairedAddress
abstract fetchQuoteAmountIn( ),
tokenIn: UniEvmToken, ]);
tokenOut: UniEvmToken, } else {
amountOut: string, [this.tokenIn, this.tokenOut] = await Promise.all([
slippage: string makeUniEvmToken(
): Promise<ethers.BigNumber>; this.provider,
network.chainId,
abstract getProtocol(): string; tokenInfo.ustPairedAddress
),
makeUniEvmToken(this.provider, network.chainId, tokenInfo.address),
]);
}
return;
}
public getPoolFee(): string { public getPoolFee(): string {
return ""; return "";
@ -130,4 +198,36 @@ export abstract class UniswapRouterCore {
public getTradeDeadline(): ethers.BigNumber { public getTradeDeadline(): ethers.BigNumber {
return computeTradeDeadline(this.deadline); 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;
} }

View File

@ -3,11 +3,19 @@ import { CurrencyAmount, TradeType } from "@uniswap/sdk-core";
import { abi as IUniswapV2PairABI } from "@uniswap/v2-core/build/UniswapV2Pair.json"; import { abi as IUniswapV2PairABI } from "@uniswap/v2-core/build/UniswapV2Pair.json";
import { computePairAddress, Pair, Route, Trade } from "@uniswap/v2-sdk"; 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 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; factoryAddress: string;
pairContract: ethers.Contract; pairContract: ethers.Contract;
pair: Pair; pair: Pair;
@ -17,23 +25,20 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
return; return;
} }
computePoolAddress(tokenIn: UniEvmToken, tokenOut: UniEvmToken): string { computePoolAddress(): string {
if (this.factoryAddress === undefined) { if (this.factoryAddress === undefined) {
throw Error("factoryAddress is undefined. use setFactoryAddress"); throw Error("factoryAddress is undefined. use setFactoryAddress");
} }
return computePairAddress({ return computePairAddress({
factoryAddress: this.factoryAddress, factoryAddress: this.factoryAddress,
tokenA: tokenIn.getUniToken(), tokenA: this.tokenIn.getUniToken(),
tokenB: tokenOut.getUniToken(), tokenB: this.tokenOut.getUniToken(),
}); });
} }
async computeAndVerifyPoolAddress( async computeAndVerifyPoolAddress(): Promise<string> {
tokenIn: UniEvmToken, const pairAddress = this.computePoolAddress();
tokenOut: UniEvmToken
): Promise<string> {
const pairAddress = this.computePoolAddress(tokenIn, tokenOut);
// verify by attempting to call factory() // verify by attempting to call factory()
const poolContract = new ethers.Contract( const poolContract = new ethers.Contract(
@ -46,8 +51,8 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
return pairAddress; return pairAddress;
} }
async createPool(tokenIn: UniEvmToken, tokenOut: UniEvmToken): Promise<Pair> { async createPool(): Promise<Pair> {
const pairAddress = this.computePoolAddress(tokenIn, tokenOut); const pairAddress = this.computePoolAddress();
const pairContract = new ethers.Contract( const pairContract = new ethers.Contract(
pairAddress, pairAddress,
@ -63,6 +68,9 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
const reserve0 = reserves._reserve0.toString(); const reserve0 = reserves._reserve0.toString();
const reserve1 = reserves._reserve1.toString(); const reserve1 = reserves._reserve1.toString();
const tokenIn = this.tokenIn;
const tokenOut = this.tokenOut;
if (token0.toLowerCase() === tokenIn.getAddress().toLowerCase()) { if (token0.toLowerCase() === tokenIn.getAddress().toLowerCase()) {
return new Pair( return new Pair(
CurrencyAmount.fromRawAmount(tokenIn.getUniToken(), reserve0), CurrencyAmount.fromRawAmount(tokenIn.getUniToken(), reserve0),
@ -76,15 +84,13 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
); );
} }
async fetchQuoteAmountOut( async fetchExactInQuote(amountIn: string, slippage: string): Promise<string> {
tokenIn: UniEvmToken,
tokenOut: UniEvmToken,
amountIn: string,
slippage: string
): Promise<ethers.BigNumber> {
// create pool // create pool
const pair = await this.createPool(tokenIn, tokenOut); const pair = await this.createPool();
// let's get that quote // let's get that quote
const tokenIn = this.tokenIn;
const tokenOut = this.tokenOut;
const route = new Route( const route = new Route(
[pair], [pair],
@ -108,18 +114,24 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
.mulUnsafe(slippageMultiplier) .mulUnsafe(slippageMultiplier)
.round(decimals); .round(decimals);
return tokenOut.computeUnitAmount(minAmountOutWithSlippage.toString()); /*
return tokenOut
.computeUnitAmount(minAmountOutWithSlippage.toString())
.toString();
*/
return minAmountOutWithSlippage.toString();
} }
async fetchQuoteAmountIn( async fetchExactOutQuote(
tokenIn: UniEvmToken,
tokenOut: UniEvmToken,
amountOut: string, amountOut: string,
slippage: string slippage: string
): Promise<ethers.BigNumber> { ): Promise<string> {
// create pool // create pool
const pair = await this.createPool(tokenIn, tokenOut); const pair = await this.createPool();
// let's get that quote // let's get that quote
const tokenIn = this.tokenIn;
const tokenOut = this.tokenOut;
const route = new Route( const route = new Route(
[pair], [pair],
@ -142,7 +154,12 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
.divUnsafe(slippageDivisor) .divUnsafe(slippageDivisor)
.round(decimals); .round(decimals);
return tokenIn.computeUnitAmount(maxAmountInWithSlippage.toString()); /*
return tokenIn
.computeUnitAmount(maxAmountInWithSlippage.toString())
.toString();
*/
return maxAmountInWithSlippage.toString();
} }
getProtocol(): string { getProtocol(): string {

View File

@ -14,11 +14,12 @@ import {
} from "@uniswap/v3-sdk"; } from "@uniswap/v3-sdk";
import { UniEvmToken, UniswapRouterCore } from "./uniswap-core"; 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 const PROTOCOL = "UniswapV3";
export class SingleAmmSwapRouter extends UniswapRouterCore { export class UniswapV3Router extends UniswapRouterCore {
poolContract: ethers.Contract; poolContract: ethers.Contract;
pool: Pool; pool: Pool;
poolFee: FeeAmount; poolFee: FeeAmount;
@ -30,24 +31,26 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
this.poolFee = FeeAmount.MEDIUM; this.poolFee = FeeAmount.MEDIUM;
} }
async initialize(ustLocation: UstLocation): Promise<void> {
await this.initializeTokens(WETH_TOKEN_INFO, ustLocation);
return;
}
getPoolFee(): string { getPoolFee(): string {
return this.poolFee.toString(); return this.poolFee.toString();
} }
computePoolAddress(tokenIn: UniEvmToken, tokenOut: UniEvmToken): string { computePoolAddress(): string {
return computePoolAddress({ return computePoolAddress({
factoryAddress: UNISWAP_V3_FACTORY_ADDRESS, factoryAddress: UNISWAP_V3_FACTORY_ADDRESS,
fee: this.poolFee, fee: this.poolFee,
tokenA: tokenIn.getUniToken(), tokenA: this.tokenIn.getUniToken(),
tokenB: tokenOut.getUniToken(), tokenB: this.tokenOut.getUniToken(),
}); });
} }
async computeAndVerifyPoolAddress( async computeAndVerifyPoolAddress(): Promise<string> {
tokenIn: UniEvmToken, const pairAddress = this.computePoolAddress();
tokenOut: UniEvmToken
): Promise<string> {
const pairAddress = this.computePoolAddress(tokenIn, tokenOut);
// verify by attempting to call factory() // verify by attempting to call factory()
const poolContract = new ethers.Contract( const poolContract = new ethers.Contract(
@ -60,8 +63,8 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
return pairAddress; return pairAddress;
} }
async createPool(tokenIn: UniEvmToken, tokenOut: UniEvmToken): Promise<Pool> { async createPool(): Promise<Pool> {
const poolAddress = this.computePoolAddress(tokenIn, tokenOut); const poolAddress = this.computePoolAddress();
const poolContract = new ethers.Contract( const poolContract = new ethers.Contract(
poolAddress, poolAddress,
@ -103,8 +106,8 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
]; ];
return new Pool( return new Pool(
tokenIn.getUniToken(), this.tokenIn.getUniToken(),
tokenOut.getUniToken(), this.tokenOut.getUniToken(),
this.poolFee, this.poolFee,
sqrtPriceX96.toString(), //note the description discrepancy - sqrtPriceX96 and sqrtRatioX96 are interchangable values sqrtPriceX96.toString(), //note the description discrepancy - sqrtPriceX96 and sqrtRatioX96 are interchangable values
liquidity, liquidity,
@ -114,13 +117,15 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
} }
async computeTradeExactIn( async computeTradeExactIn(
tokenIn: UniEvmToken,
tokenOut: UniEvmToken,
amount: string amount: string
): Promise<Trade<Token, Token, TradeType.EXACT_INPUT>> { ): Promise<Trade<Token, Token, TradeType.EXACT_INPUT>> {
// create pool // create pool
const pool = await this.createPool(tokenIn, tokenOut); const pool = await this.createPool();
// let's get that quote // let's get that quote
const tokenIn = this.tokenIn;
const tokenOut = this.tokenOut;
const amountIn = tokenIn.computeUnitAmount(amount); const amountIn = tokenIn.computeUnitAmount(amount);
const route = new Route( const route = new Route(
@ -136,13 +141,15 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
} }
async computeTradeExactOut( async computeTradeExactOut(
tokenIn: UniEvmToken,
tokenOut: UniEvmToken,
amount: string amount: string
): Promise<Trade<Token, Token, TradeType.EXACT_OUTPUT>> { ): Promise<Trade<Token, Token, TradeType.EXACT_OUTPUT>> {
// create pool // create pool
const pool = await this.createPool(tokenIn, tokenOut); const pool = await this.createPool();
// let's get that quote // let's get that quote
const tokenIn = this.tokenIn;
const tokenOut = this.tokenOut;
const amountOut = tokenOut.computeUnitAmount(amount); const amountOut = tokenOut.computeUnitAmount(amount);
const route = new Route( const route = new Route(
@ -160,15 +167,11 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
); );
} }
async fetchQuoteAmountOut( async fetchExactInQuote(amountIn: string, slippage: string): Promise<string> {
tokenIn: UniEvmToken,
tokenOut: UniEvmToken,
amountIn: string,
slippage: string
): Promise<ethers.BigNumber> {
// get the quote // 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(); const decimals = tokenOut.getDecimals();
// calculate output amount with slippage // calculate output amount with slippage
@ -183,18 +186,22 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
.mulUnsafe(slippageMultiplier) .mulUnsafe(slippageMultiplier)
.round(decimals); .round(decimals);
return tokenOut.computeUnitAmount(minAmountOutWithSlippage.toString()); /*
return tokenOut
.computeUnitAmount(minAmountOutWithSlippage.toString())
.toString();
*/
return minAmountOutWithSlippage.toString();
} }
async fetchQuoteAmountIn( async fetchExactOutQuote(
tokenIn: UniEvmToken,
tokenOut: UniEvmToken,
amountOut: string, amountOut: string,
slippage: string slippage: string
): Promise<ethers.BigNumber> { ): Promise<string> {
// get the quote // 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(); const decimals = tokenIn.getDecimals();
// calculate output amount with slippage // calculate output amount with slippage
@ -209,7 +216,12 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
.divUnsafe(slippageDivisor) .divUnsafe(slippageDivisor)
.round(decimals); .round(decimals);
return tokenIn.computeUnitAmount(maxAmountInWithSlippage.toString()); /*
return tokenIn
.computeUnitAmount(maxAmountInWithSlippage.toString())
.toString();
*/
return maxAmountInWithSlippage.toString();
} }
getProtocol(): string { getProtocol(): string {

View File

@ -1,15 +1,14 @@
import { ethers } from "ethers"; import { ethers } from "ethers";
import { TransactionReceipt } from "@ethersproject/abstract-provider"; import { TransactionReceipt } from "@ethersproject/abstract-provider";
import { import {
CHAIN_ID_POLYGON as WORMHOLE_CHAIN_ID_POLYGON,
CHAIN_ID_ETH as WORMHOLE_CHAIN_ID_ETHEREUM,
ChainId, ChainId,
getEmitterAddressEth, getEmitterAddressEth,
hexToUint8Array, hexToUint8Array,
nativeToHexString, nativeToHexString,
parseSequenceFromLogEth, parseSequenceFromLogEth,
getSignedVAAWithRetry, //getSignedVAAWithRetry,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/cjs/rpc/getSignedVAAWithRetry";
import { grpc } from "@improbable-eng/grpc-web"; import { grpc } from "@improbable-eng/grpc-web";
import { UniEvmToken } from "../route/uniswap-core"; import { UniEvmToken } from "../route/uniswap-core";
import { import {
@ -21,15 +20,22 @@ import {
UniswapToUniswapQuoter, UniswapToUniswapQuoter,
} from "../route/cross-quote"; } from "../route/cross-quote";
import { import {
TOKEN_BRIDGE_ADDRESS_ETHEREUM,
TOKEN_BRIDGE_ADDRESS_POLYGON, TOKEN_BRIDGE_ADDRESS_POLYGON,
TOKEN_BRIDGE_ADDRESS_TERRA,
CORE_BRIDGE_ADDRESS_ETHEREUM, CORE_BRIDGE_ADDRESS_ETHEREUM,
CORE_BRIDGE_ADDRESS_POLYGON, 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, 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, WETH_TOKEN_INFO,
WMATIC_TOKEN_INFO, WMATIC_TOKEN_INFO,
UST_TOKEN_INFO,
} from "../utils/consts"; } from "../utils/consts";
import { import {
CROSSCHAINSWAP_GAS_PARAMETERS, CROSSCHAINSWAP_GAS_PARAMETERS,
@ -38,10 +44,13 @@ import {
swapExactOutFromVaaNative, swapExactOutFromVaaNative,
swapExactOutFromVaaToken, swapExactOutFromVaaToken,
} from "./util"; } from "./util";
import { abi as SWAP_CONTRACT_V2_ABI } from "../abi/contracts/CrossChainSwapV2.json"; 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 { 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_ETHEREUM } from "../../scripts/contract-addresses/goerli";
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../addresses/mumbai"; import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../../scripts/contract-addresses/mumbai";
// placeholders
const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA = "";
interface SwapContractParameters { interface SwapContractParameters {
address: string; address: string;
@ -80,21 +89,35 @@ const EXECUTION_PARAMETERS_POLYGON: ExecutionParameters = {
}, },
}; };
function makeExecutionParameters(id: number): ExecutionParameters { const EXECUTION_PARAMETERS_TERRA: ExecutionParameters = {
switch (id) { crossChainSwap: {
case ETH_NETWORK_CHAIN_ID: { 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; return EXECUTION_PARAMETERS_ETHEREUM;
} }
case POLYGON_NETWORK_CHAIN_ID: { case WORMHOLE_CHAIN_ID_POLYGON: {
return EXECUTION_PARAMETERS_POLYGON; return EXECUTION_PARAMETERS_POLYGON;
} }
case WORMHOLE_CHAIN_ID_TERRA: {
return EXECUTION_PARAMETERS_TERRA;
}
default: { default: {
throw Error("unrecognized chain id"); throw Error("unrecognized chain id");
} }
} }
} }
async function approveContractTokenSpend( async function evmApproveContractTokenSpend(
provider: ethers.providers.Provider, provider: ethers.providers.Provider,
signer: ethers.Signer, signer: ethers.Signer,
tokenContract: ethers.Contract, tokenContract: ethers.Contract,
@ -140,7 +163,7 @@ function makeCrossChainSwapV2Contract(
return new ethers.Contract(contractAddress, SWAP_CONTRACT_V2_ABI, provider); return new ethers.Contract(contractAddress, SWAP_CONTRACT_V2_ABI, provider);
} }
function makeCrossChainSwapContract( function makeCrossChainSwapEvmContract(
provider: ethers.providers.Provider, provider: ethers.providers.Provider,
protocol: string, protocol: string,
contractAddress: string contractAddress: string
@ -163,7 +186,7 @@ function addressToBytes32(
return hexToUint8Array(hexString); return hexToUint8Array(hexString);
} }
async function approveAndSwapExactIn( async function evmApproveAndSwapExactIn(
srcProvider: ethers.providers.Provider, srcProvider: ethers.providers.Provider,
srcWallet: ethers.Signer, srcWallet: ethers.Signer,
srcTokenIn: UniEvmToken, srcTokenIn: UniEvmToken,
@ -175,7 +198,7 @@ async function approveAndSwapExactIn(
const swapContractParams = srcExecutionParams.crossChainSwap; const swapContractParams = srcExecutionParams.crossChainSwap;
const protocol = quoteParams.src.protocol; const protocol = quoteParams.src.protocol;
const swapContract = makeCrossChainSwapContract( const swapContract = makeCrossChainSwapEvmContract(
srcProvider, srcProvider,
protocol, protocol,
swapContractParams.address swapContractParams.address
@ -187,18 +210,19 @@ async function approveAndSwapExactIn(
const address = await srcWallet.getAddress(); const address = await srcWallet.getAddress();
const dstWormholeChainId = dstExecutionParams.wormhole.chainId;
const swapParams = [ const swapParams = [
amountIn, amountIn,
quoteParams.src.minAmountOut, quoteParams.src.minAmountOut,
quoteParams.dst.minAmountOut, quoteParams.dst.minAmountOut,
address, addressToBytes32(address, dstWormholeChainId),
quoteParams.src.deadline, quoteParams.src.deadline,
quoteParams.dst.poolFee || quoteParams.src.poolFee, quoteParams.dst.poolFee || quoteParams.src.poolFee,
]; ];
const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); const pathArray = quoteParams.src.path.concat(quoteParams.dst.path);
const dstWormholeChainId = dstExecutionParams.wormhole.chainId;
const dstContractAddress = addressToBytes32( const dstContractAddress = addressToBytes32(
dstExecutionParams.crossChainSwap.address, dstExecutionParams.crossChainSwap.address,
dstWormholeChainId dstWormholeChainId
@ -227,7 +251,7 @@ async function approveAndSwapExactIn(
return tx.wait(); return tx.wait();
} else { } else {
console.info("approving contract to spend token in"); console.info("approving contract to spend token in");
await approveContractTokenSpend( await evmApproveContractTokenSpend(
srcProvider, srcProvider,
srcWallet, srcWallet,
srcTokenIn.getContract(), srcTokenIn.getContract(),
@ -249,7 +273,7 @@ async function approveAndSwapExactIn(
} }
} }
async function approveAndSwapExactOut( async function evmApproveAndSwapExactOut(
srcProvider: ethers.providers.Provider, srcProvider: ethers.providers.Provider,
srcWallet: ethers.Signer, srcWallet: ethers.Signer,
srcTokenIn: UniEvmToken, srcTokenIn: UniEvmToken,
@ -261,7 +285,7 @@ async function approveAndSwapExactOut(
const swapContractParams = srcExecutionParams.crossChainSwap; const swapContractParams = srcExecutionParams.crossChainSwap;
const protocol = quoteParams.src.protocol; const protocol = quoteParams.src.protocol;
const swapContract = makeCrossChainSwapContract( const swapContract = makeCrossChainSwapEvmContract(
srcProvider, srcProvider,
protocol, protocol,
swapContractParams.address swapContractParams.address
@ -274,17 +298,18 @@ async function approveAndSwapExactOut(
const address = await srcWallet.getAddress(); const address = await srcWallet.getAddress();
const dstWormholeChainId = dstExecutionParams.wormhole.chainId;
const swapParams = [ const swapParams = [
amountOut, amountOut,
maxAmountIn, maxAmountIn,
quoteParams.dst.amountOut, quoteParams.dst.amountOut,
address, addressToBytes32(address, dstWormholeChainId),
quoteParams.src.deadline, quoteParams.src.deadline,
quoteParams.dst.poolFee || quoteParams.src.poolFee, quoteParams.dst.poolFee || quoteParams.src.poolFee,
]; ];
const pathArray = quoteParams.src.path.concat(quoteParams.dst.path); const pathArray = quoteParams.src.path.concat(quoteParams.dst.path);
const dstWormholeChainId = dstExecutionParams.wormhole.chainId;
const dstContractAddress = addressToBytes32( const dstContractAddress = addressToBytes32(
dstExecutionParams.crossChainSwap.address, dstExecutionParams.crossChainSwap.address,
dstWormholeChainId dstWormholeChainId
@ -313,7 +338,7 @@ async function approveAndSwapExactOut(
return tx.wait(); return tx.wait();
} else { } else {
console.info("approving contract to spend token in"); console.info("approving contract to spend token in");
await approveContractTokenSpend( await evmApproveContractTokenSpend(
srcProvider, srcProvider,
srcWallet, srcWallet,
srcTokenIn.getContract(), srcTokenIn.getContract(),
@ -345,7 +370,7 @@ async function swapExactInFromVaa(
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
const swapContractParams = dstExecutionParams.crossChainSwap; const swapContractParams = dstExecutionParams.crossChainSwap;
const swapContract = makeCrossChainSwapContract( const swapContract = makeCrossChainSwapEvmContract(
dstProvider, dstProvider,
dstProtocol, dstProtocol,
swapContractParams.address swapContractParams.address
@ -371,7 +396,7 @@ async function swapExactOutFromVaa(
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
const swapContractParams = dstExecutionParams.crossChainSwap; const swapContractParams = dstExecutionParams.crossChainSwap;
const swapContract = makeCrossChainSwapContract( const swapContract = makeCrossChainSwapEvmContract(
dstProvider, dstProvider,
dstProtocol, dstProtocol,
swapContractParams.address swapContractParams.address
@ -399,7 +424,7 @@ interface VaaSearchParams {
emitterAddress: string; emitterAddress: string;
} }
export function makeProvider(tokenAddress: string) { export function makeEvmProvider(tokenAddress: string) {
switch (tokenAddress) { switch (tokenAddress) {
case WETH_TOKEN_INFO.address: { case WETH_TOKEN_INFO.address: {
const url = process.env.REACT_APP_GOERLI_PROVIDER; const url = process.env.REACT_APP_GOERLI_PROVIDER;
@ -416,6 +441,7 @@ export function makeProvider(tokenAddress: string) {
return new ethers.providers.StaticJsonRpcProvider(url); return new ethers.providers.StaticJsonRpcProvider(url);
} }
default: { default: {
console.log("huh?", tokenAddress);
throw Error("unrecognized token address"); throw Error("unrecognized token address");
} }
} }
@ -440,8 +466,16 @@ export class UniswapToUniswapExecutor {
transportFactory: grpc.TransportFactory; transportFactory: grpc.TransportFactory;
vaaSearchParams: VaaSearchParams; vaaSearchParams: VaaSearchParams;
vaaBytes: Uint8Array; vaaBytes: Uint8Array;
srcReceipt: TransactionReceipt;
dstReceipt: TransactionReceipt; // receipts
srcEvmReceipt: TransactionReceipt;
dstEvmReceipt: TransactionReceipt;
srcTerraReceipt: any;
dstTerraReceipt: any;
constructor() {
this.quoter = new UniswapToUniswapQuoter();
}
async initialize( async initialize(
tokenInAddress: string, tokenInAddress: string,
@ -450,20 +484,14 @@ export class UniswapToUniswapExecutor {
): Promise<void> { ): Promise<void> {
this.isNative = isNative; this.isNative = isNative;
const srcProvider = makeProvider(tokenInAddress); await this.quoter.initialize(tokenInAddress, tokenOutAddress);
const dstProvider = makeProvider(tokenOutAddress);
this.quoter = new UniswapToUniswapQuoter(srcProvider, dstProvider);
await this.quoter.initialize();
await this.makeTokens(tokenInAddress, tokenOutAddress);
// now that we have a chain id for each network, get contract info for each chain // now that we have a chain id for each network, get contract info for each chain
this.srcExecutionParams = makeExecutionParameters( this.srcExecutionParams = makeExecutionParameters(
this.quoter.srcNetwork.chainId this.quoter.getSrcChainId()
); );
this.dstExecutionParams = makeExecutionParameters( this.dstExecutionParams = makeExecutionParameters(
this.quoter.dstNetwork.chainId this.quoter.getDstChainId()
); );
} }
@ -483,6 +511,7 @@ export class UniswapToUniswapExecutor {
this.quoter.setDeadlines(deadline); this.quoter.setDeadlines(deadline);
} }
/*
async makeTokens( async makeTokens(
tokenInAddress: string, tokenInAddress: string,
tokenOutAddress: string tokenOutAddress: string
@ -507,7 +536,7 @@ export class UniswapToUniswapExecutor {
getTokens(): CrossChainSwapTokens { getTokens(): CrossChainSwapTokens {
return this.tokens; return this.tokens;
} }
*/
async computeAndVerifySrcPoolAddress(): Promise<string> { async computeAndVerifySrcPoolAddress(): Promise<string> {
return this.quoter.computeAndVerifySrcPoolAddress(); return this.quoter.computeAndVerifySrcPoolAddress();
} }
@ -546,19 +575,19 @@ export class UniswapToUniswapExecutor {
return this.cachedExactOutParams; return this.cachedExactOutParams;
} }
getSrcProvider(): ethers.providers.Provider { getSrcEvmProvider(): ethers.providers.Provider {
return this.quoter.srcProvider; return this.quoter.getSrcEvmProvider();
} }
getDstProvider(): ethers.providers.Provider { getDstEvmProvider(): ethers.providers.Provider {
return this.quoter.dstProvider; return this.quoter.getDstEvmProvider();
} }
async approveAndSwapExactIn( async evmApproveAndSwapExactIn(
wallet: ethers.Signer wallet: ethers.Signer
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
return approveAndSwapExactIn( return evmApproveAndSwapExactIn(
this.getSrcProvider(), this.getSrcEvmProvider(),
wallet, wallet,
this.tokens.srcIn, this.tokens.srcIn,
this.cachedExactInParams, this.cachedExactInParams,
@ -568,11 +597,11 @@ export class UniswapToUniswapExecutor {
); );
} }
async approveAndSwapExactOut( async evmApproveAndSwapExactOut(
wallet: ethers.Signer wallet: ethers.Signer
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
return approveAndSwapExactOut( return evmApproveAndSwapExactOut(
this.getSrcProvider(), this.getSrcEvmProvider(),
wallet, wallet,
this.tokens.srcIn, this.tokens.srcIn,
this.cachedExactOutParams, this.cachedExactOutParams,
@ -582,23 +611,40 @@ export class UniswapToUniswapExecutor {
); );
} }
async approveAndSwap(wallet: ethers.Signer): Promise<TransactionReceipt> { srcIsUst(): boolean {
return (
this.quoter.tokenInAddress === UST_TOKEN_INFO.address &&
this.cachedExactInParams.src === undefined
);
}
async evmApproveAndSwap(wallet: ethers.Signer): Promise<TransactionReceipt> {
const quoteType = this.quoteType; const quoteType = this.quoteType;
if (quoteType === QuoteType.ExactIn) { if (quoteType === QuoteType.ExactIn) {
this.srcReceipt = await this.approveAndSwapExactIn(wallet); this.srcEvmReceipt = await this.evmApproveAndSwapExactIn(wallet);
} else if (quoteType === QuoteType.ExactOut) { } else if (quoteType === QuoteType.ExactOut) {
this.srcReceipt = await this.approveAndSwapExactOut(wallet); this.srcEvmReceipt = await this.evmApproveAndSwapExactOut(wallet);
} else { } else {
throw Error("no quote found"); throw Error("no quote found");
} }
this.fetchAndSetEmitterAndSequence(); this.fetchAndSetEmitterAndSequence();
return this.srcReceipt; return this.srcEvmReceipt;
} }
fetchAndSetEmitterAndSequence(): void { fetchAndSetEmitterAndSequence(): void {
const receipt = this.srcReceipt; // TODO
return;
}
fetchAndSetTerraEmitterAndSequence(): void {
// TODO
return;
}
fetchAndSetEvmEmitterAndSequence(): void {
const receipt = this.srcEvmReceipt;
if (receipt === undefined) { if (receipt === undefined) {
throw Error("no swap receipt found"); throw Error("no swap receipt found");
} }
@ -623,11 +669,15 @@ export class UniswapToUniswapExecutor {
const emitterAddress = vaaSearchParams.emitterAddress; const emitterAddress = vaaSearchParams.emitterAddress;
console.info(`sequence: ${sequence}, emitterAddress: ${emitterAddress}`); console.info(`sequence: ${sequence}, emitterAddress: ${emitterAddress}`);
// wait for VAA to be signed // wait for VAA to be signed
const vaaResponse = await getSignedVAAWithRetry( const vaaResponse = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS, WORMHOLE_RPC_HOSTS,
this.srcExecutionParams.wormhole.chainId, this.srcExecutionParams.wormhole.chainId,
vaaSearchParams.emitterAddress, vaaSearchParams.emitterAddress,
vaaSearchParams.sequence vaaSearchParams.sequence,
{
transport: this.transportFactory,
}
); );
// grab vaaBytes // grab vaaBytes
this.vaaBytes = vaaResponse.vaaBytes; this.vaaBytes = vaaResponse.vaaBytes;
@ -636,22 +686,27 @@ export class UniswapToUniswapExecutor {
async fetchVaaAndSwap(wallet: ethers.Signer): Promise<TransactionReceipt> { async fetchVaaAndSwap(wallet: ethers.Signer): Promise<TransactionReceipt> {
await this.fetchSignedVaaFromSwap(); await this.fetchSignedVaaFromSwap();
// check if Terra transaction
// TODO: change return as something else (not evm TransactionReceipt)
const quoteType = this.quoteType; const quoteType = this.quoteType;
if (quoteType === QuoteType.ExactIn) { if (quoteType === QuoteType.ExactIn) {
this.dstReceipt = await this.swapExactInFromVaa(wallet); this.dstEvmReceipt = await this.evmSwapExactInFromVaa(wallet);
} else if (quoteType === QuoteType.ExactOut) { } else if (quoteType === QuoteType.ExactOut) {
this.dstReceipt = await this.swapExactOutFromVaa(wallet); this.dstEvmReceipt = await this.evmSwapExactOutFromVaa(wallet);
} else { } else {
throw Error("no quote found"); throw Error("no quote found");
} }
return this.dstReceipt; return this.dstEvmReceipt;
} }
async swapExactInFromVaa(wallet: ethers.Signer): Promise<TransactionReceipt> { async evmSwapExactInFromVaa(
wallet: ethers.Signer
): Promise<TransactionReceipt> {
return swapExactInFromVaa( return swapExactInFromVaa(
this.getDstProvider(), this.getDstEvmProvider(),
wallet, wallet,
this.dstExecutionParams, this.dstExecutionParams,
this.cachedExactInParams.dst.protocol, this.cachedExactInParams.dst.protocol,
@ -660,11 +715,11 @@ export class UniswapToUniswapExecutor {
); );
} }
async swapExactOutFromVaa( async evmSwapExactOutFromVaa(
wallet: ethers.Signer wallet: ethers.Signer
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
return swapExactOutFromVaa( return swapExactOutFromVaa(
this.getDstProvider(), this.getDstEvmProvider(),
wallet, wallet,
this.dstExecutionParams, this.dstExecutionParams,
this.cachedExactOutParams.dst.protocol, this.cachedExactOutParams.dst.protocol,
@ -673,6 +728,10 @@ export class UniswapToUniswapExecutor {
); );
} }
setTransport(transportFactory: grpc.TransportFactory) {
this.transportFactory = transportFactory;
}
//getSwapResult( //getSwapResult(
// walletAddress: string, // walletAddress: string,
// onSwapResult: (result: boolean) => void // onSwapResult: (result: boolean) => void
@ -680,7 +739,7 @@ export class UniswapToUniswapExecutor {
// console.log(this.cachedExactInParams.dst.protocol); // console.log(this.cachedExactInParams.dst.protocol);
// console.log(this.dstExecutionParams.crossChainSwap.address); // console.log(this.dstExecutionParams.crossChainSwap.address);
// const contract = makeCrossChainSwapContract( // const contract = makeCrossChainSwapContract(
// this.getDstProvider(), // this.getDstEvmProvider(),
// this.quoteType === QuoteType.ExactIn // this.quoteType === QuoteType.ExactIn
// ? this.cachedExactInParams.dst.protocol // ? this.cachedExactInParams.dst.protocol
// : this.cachedExactOutParams.dst.protocol, // : this.cachedExactOutParams.dst.protocol,

View File

@ -1,10 +1,21 @@
import { import {
ChainId, ChainId,
CHAIN_ID_ETH, CHAIN_ID_ETH as WORMHOLE_CHAIN_ID_ETHEREUM,
CHAIN_ID_POLYGON, CHAIN_ID_POLYGON as WORMHOLE_CHAIN_ID_POLYGON,
CHAIN_ID_TERRA as WORMHOLE_CHAIN_ID_TERRA,
} from "@certusone/wormhole-sdk"; } from "@certusone/wormhole-sdk";
import ethIcon from "../icons/eth.svg"; //import ethIcon from "../icons/eth.svg";
import polygonIcon from "../icons/polygon.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 { export interface TokenInfo {
name: string; name: string;
@ -13,42 +24,57 @@ export interface TokenInfo {
logo: string; logo: string;
isNative: boolean; isNative: boolean;
maxAmount: number; maxAmount: number;
ustPairedAddress: string;
} }
export const MATIC_TOKEN_INFO: TokenInfo = { export const MATIC_TOKEN_INFO: TokenInfo = {
name: "MATIC", name: "MATIC",
address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", // used to compute quote address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", // used to compute quote
chainId: CHAIN_ID_POLYGON, chainId: WORMHOLE_CHAIN_ID_POLYGON,
logo: polygonIcon, logo: polygonIcon,
isNative: true, isNative: true,
maxAmount: 0.1, maxAmount: 0.1,
ustPairedAddress: "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c",
}; };
export const WMATIC_TOKEN_INFO: TokenInfo = { export const WMATIC_TOKEN_INFO: TokenInfo = {
name: "WMATIC", name: "WMATIC",
address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889",
chainId: CHAIN_ID_POLYGON, chainId: WORMHOLE_CHAIN_ID_POLYGON,
logo: polygonIcon, logo: polygonIcon,
isNative: false, isNative: false,
maxAmount: 0.1, maxAmount: 0.1,
ustPairedAddress: "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c",
}; };
export const ETH_TOKEN_INFO: TokenInfo = { export const ETH_TOKEN_INFO: TokenInfo = {
name: "ETH", name: "ETH",
address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // used to compute quote address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // used to compute quote
chainId: CHAIN_ID_ETH, chainId: WORMHOLE_CHAIN_ID_ETHEREUM,
logo: ethIcon, logo: ethIcon,
isNative: true, isNative: true,
maxAmount: 0.01, maxAmount: 0.01,
ustPairedAddress: "0x36Ed51Afc79619b299b238898E72ce482600568a",
}; };
export const WETH_TOKEN_INFO: TokenInfo = { export const WETH_TOKEN_INFO: TokenInfo = {
name: "WETH", name: "WETH",
address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
chainId: CHAIN_ID_ETH, chainId: WORMHOLE_CHAIN_ID_ETHEREUM,
logo: ethIcon, logo: ethIcon,
isNative: false, isNative: false,
maxAmount: 0.01, 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 = [ export const TOKEN_INFOS = [
@ -56,19 +82,21 @@ export const TOKEN_INFOS = [
WMATIC_TOKEN_INFO, WMATIC_TOKEN_INFO,
ETH_TOKEN_INFO, ETH_TOKEN_INFO,
WETH_TOKEN_INFO, WETH_TOKEN_INFO,
UST_TOKEN_INFO,
]; ];
export const ETH_NETWORK_CHAIN_ID = 5; // evm handling
export const EVM_ETH_NETWORK_CHAIN_ID = 5;
export const POLYGON_NETWORK_CHAIN_ID = 80001; export const EVM_POLYGON_NETWORK_CHAIN_ID = 80001;
export const getEvmChainId = (chainId: ChainId) => export const getEvmChainId = (chainId: ChainId) =>
chainId === CHAIN_ID_ETH chainId === WORMHOLE_CHAIN_ID_ETHEREUM
? ETH_NETWORK_CHAIN_ID ? EVM_ETH_NETWORK_CHAIN_ID
: chainId === CHAIN_ID_POLYGON : chainId === WORMHOLE_CHAIN_ID_POLYGON
? POLYGON_NETWORK_CHAIN_ID ? EVM_POLYGON_NETWORK_CHAIN_ID
: undefined; : undefined;
// misc
export const RELAYER_FEE_UST = "0.25"; export const RELAYER_FEE_UST = "0.25";
export const WORMHOLE_RPC_HOSTS = [ export const WORMHOLE_RPC_HOSTS = [
@ -81,12 +109,16 @@ export const CORE_BRIDGE_ADDRESS_ETHEREUM =
export const CORE_BRIDGE_ADDRESS_POLYGON = export const CORE_BRIDGE_ADDRESS_POLYGON =
"0x0CBE91CF822c73C2315FB05100C2F714765d5c20"; "0x0CBE91CF822c73C2315FB05100C2F714765d5c20";
export const CORE_BRIDGE_ADDRESS_TERRA = undefined;
export const TOKEN_BRIDGE_ADDRESS_ETHEREUM = export const TOKEN_BRIDGE_ADDRESS_ETHEREUM =
"0xF890982f9310df57d00f659cf4fd87e65adEd8d7"; "0xF890982f9310df57d00f659cf4fd87e65adEd8d7";
export const TOKEN_BRIDGE_ADDRESS_POLYGON = export const TOKEN_BRIDGE_ADDRESS_POLYGON =
"0x377D55a7928c046E18eEbb61977e714d2a76472a"; "0x377D55a7928c046E18eEbb61977e714d2a76472a";
export const TOKEN_BRIDGE_ADDRESS_TERRA = undefined;
export const QUICKSWAP_FACTORY_ADDRESS = export const QUICKSWAP_FACTORY_ADDRESS =
"0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32"; "0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32";

19
react/src/utils/math.ts Normal file
View File

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