From 7829f365208c1a232a0bbc85c2a40ea89d87f586 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Tue, 7 Nov 2023 17:31:46 +0000 Subject: [PATCH 01/26] update graphql-request --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e7cb1437..44caed02 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "dayjs": "^1.11.0", "debug": "^4.3.4", "decimal.js-light": "^2.5.1", - "graphql-request": "^4.3.0", + "graphql-request": "^6.1.0", "jsbi": "^3.1.1", "lodash.flatmap": "^4.5.0", "memoizee": "^0.4.15", @@ -62,7 +62,7 @@ "devDependencies": { "@graphql-codegen/cli": "2.6.2", "@graphql-codegen/typescript": "2.5.1", - "@graphql-codegen/typescript-graphql-request": "^4.4.10", + "@graphql-codegen/typescript-graphql-request": "^6.0.1", "@graphql-codegen/typescript-operations": "^2.4.2", "@testing-library/jest-dom": "^5.16.4", "@types/big.js": "^4.0.5", From 0a9c79a06a7bcd86bfed6ed8b81ac5fb7f291c25 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Mon, 4 Dec 2023 12:09:46 +0000 Subject: [PATCH 02/26] fix: remove empty utils, remove extra // --- src/entities/trades/curve/contracts/utils.ts | 0 src/entities/trades/gnosis-protocol/CoWTrade.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/entities/trades/curve/contracts/utils.ts diff --git a/src/entities/trades/curve/contracts/utils.ts b/src/entities/trades/curve/contracts/utils.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/entities/trades/gnosis-protocol/CoWTrade.ts b/src/entities/trades/gnosis-protocol/CoWTrade.ts index f7fc7ee1..c564bb3b 100644 --- a/src/entities/trades/gnosis-protocol/CoWTrade.ts +++ b/src/entities/trades/gnosis-protocol/CoWTrade.ts @@ -172,7 +172,7 @@ export class CoWTrade extends Trade { invariant(!tokenIn.equals(tokenOut), 'CURRENCY') // const etherOut = this.outputAmount.currency === nativeCurrency - // // the router does not support both ether in and out + // the router does not support both ether in and out // invariant(!(etherIn && etherOut), 'ETHER_IN_OUT') try { const quoteResponse = await CoWTrade.getCowSdk(chainId).cowApi.getQuote({ From e611f20a8f0b0d5e95cea05104ce49c868202556 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Mon, 4 Dec 2023 12:16:46 +0000 Subject: [PATCH 03/26] Add first draft swapr-v3 --- src/entities/trades/index.ts | 1 + .../routable-platform/RoutablePlatform.ts | 7 +- src/entities/trades/swapr-v3/SwaprV3.ts | 190 ++++++ src/entities/trades/swapr-v3/abi/index.ts | 2 + .../swapr-v3/abi/swapr-algebra-quoter.ts | 201 ++++++ .../swapr-v3/abi/swapr-algebra-router.ts | 634 ++++++++++++++++++ src/entities/trades/swapr-v3/index.ts | 1 + src/entities/trades/swapr-v3/swapr-v3.spec.ts | 75 +++ src/entities/trades/utils.ts | 1 + 9 files changed, 1109 insertions(+), 3 deletions(-) create mode 100644 src/entities/trades/swapr-v3/SwaprV3.ts create mode 100644 src/entities/trades/swapr-v3/abi/index.ts create mode 100644 src/entities/trades/swapr-v3/abi/swapr-algebra-quoter.ts create mode 100644 src/entities/trades/swapr-v3/abi/swapr-algebra-router.ts create mode 100644 src/entities/trades/swapr-v3/index.ts create mode 100644 src/entities/trades/swapr-v3/swapr-v3.spec.ts diff --git a/src/entities/trades/index.ts b/src/entities/trades/index.ts index 765a9c10..e6e73c7e 100644 --- a/src/entities/trades/index.ts +++ b/src/entities/trades/index.ts @@ -8,3 +8,4 @@ export { BaseRoutablePlatform, RoutablePlatform, UniswapV2RoutablePlatform } fro export * from './uniswap' export * from './uniswap-v2' export * from './velodrome' +export * from './swapr-v3' diff --git a/src/entities/trades/routable-platform/RoutablePlatform.ts b/src/entities/trades/routable-platform/RoutablePlatform.ts index 1d77a29c..09c2f5ed 100644 --- a/src/entities/trades/routable-platform/RoutablePlatform.ts +++ b/src/entities/trades/routable-platform/RoutablePlatform.ts @@ -21,16 +21,17 @@ export class RoutablePlatform extends BaseRoutablePlatform { ], '1Inch' ) - public static readonly COW = new RoutablePlatform([ChainId.MAINNET, ChainId.XDAI], 'CoW') - public static readonly CURVE = new RoutablePlatform([ChainId.MAINNET, ChainId.ARBITRUM_ONE, ChainId.XDAI], 'Curve') + public static readonly COW = new RoutablePlatform([ChainId.MAINNET, ChainId.GNOSIS], 'CoW') + public static readonly CURVE = new RoutablePlatform([ChainId.MAINNET, ChainId.ARBITRUM_ONE, ChainId.GNOSIS], 'Curve') /** * @deprecated Use {@link RoutablePlatform.COW} instead. */ - public static readonly GNOSIS_PROTOCOL = new RoutablePlatform([ChainId.MAINNET, ChainId.XDAI], 'CoW') + public static readonly GNOSIS_PROTOCOL = new RoutablePlatform([ChainId.MAINNET, ChainId.GNOSIS], 'CoW') public static readonly UNISWAP = new RoutablePlatform( [ChainId.MAINNET, ChainId.ARBITRUM_ONE, ChainId.POLYGON, ChainId.OPTIMISM_MAINNET], 'Uniswap' ) public static readonly VELODROME = new RoutablePlatform([ChainId.OPTIMISM_MAINNET], 'Velodrome') + public static readonly SWAPR_V3 = new RoutablePlatform([ChainId.GNOSIS], 'Swapr V3') } diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts new file mode 100644 index 00000000..7e3a9aa2 --- /dev/null +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -0,0 +1,190 @@ +// @ts-nocheck +import type { BaseProvider } from '@ethersproject/providers' + +import { Fraction, validateAndParseAddress } from '@uniswap/sdk-core' +// import debug from 'debug' +import invariant from 'tiny-invariant' + +import { CurrencyAmount, Percent, Price, TokenAmount } from '../../fractions' + +import { TradeWithSwapTransaction } from '../interfaces/trade' +import { RoutablePlatform } from '../routable-platform' +import { getProvider, tryGetChainId } from '../utils' +import { ONE, TradeType } from '../../../constants' + +import { SWAPR_ALGEBRA_ROUTER_ABI, SWAPR_ALGEBRA_QUOTER_ABI } from './abi' +import { Contract, UnsignedTransaction } from 'ethers' +import { AddressZero } from '@ethersproject/constants' +import { TradeOptions } from '../interfaces/trade-options' + +// Constants +export const GNOSIS_CONTRACTS = { + quoter: '0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7', + router: '0xfFB643E73f280B97809A8b41f7232AB401a04ee1', +} + +export function getRouterContract() { + return new Contract(GNOSIS_CONTRACTS.router, SWAPR_ALGEBRA_ROUTER_ABI, getProvider(100)) +} + +export function getQuoterContract() { + return new Contract(GNOSIS_CONTRACTS.quoter, SWAPR_ALGEBRA_QUOTER_ABI, getProvider(100)) +} + +interface SwaprV3ConstructorParams { + maximumSlippage: Percent + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + tradeType: TradeType + chainId: number + priceImpact: Percent +} + +export class SwaprV3Trade extends TradeWithSwapTransaction { + public constructor({ + inputAmount, + outputAmount, + maximumSlippage, + priceImpact, + tradeType, + chainId, + }: SwaprV3ConstructorParams) { + super({ + details: undefined, + type: tradeType, + inputAmount, + outputAmount, + maximumSlippage, + platform: RoutablePlatform.SWAPR_V3, + chainId, + executionPrice: new Price({ + baseCurrency: inputAmount.currency, + quoteCurrency: outputAmount.currency, + denominator: inputAmount.raw, + numerator: outputAmount.raw, + }), + priceImpact, + fee: new Percent('2', '10000'), // todo: change fee + approveAddress: GNOSIS_CONTRACTS['router'], + }) + } + + static async getQuote( + { amount, quoteCurrency, tradeType, recipient, maximumSlippage }: any, + provider?: BaseProvider + ): Promise { + const chainId = tryGetChainId(amount, quoteCurrency) + invariant(chainId, 'SwaprV3Trade.getQuote: chainId is required') + // Defaults + recipient = recipient || AddressZero + maximumSlippage = maximumSlippage || 0 + provider = provider || getProvider(chainId) + + invariant( + (await provider.getNetwork()).chainId == chainId, + `SwaprV3Trade.getQuote: currencies chainId does not match provider's chainId` + ) + + // const formatedAmount = formatUnits(amount.numerator[0], amount.currency.decimals) + // console.log('formatedAmount:', formatedAmount) + + let quotedAmountOut + + if (tradeType === TradeType.EXACT_INPUT) { + quotedAmountOut = await getQuoterContract() + .callStatic.quoteExactInputSingle(amount.currency.address, quoteCurrency.address, amount.numerator[0], 0) + .catch((error) => { + console.error(`Error sending quoteExactInputSingle transaction: ${error}`) + }) + } else { + quotedAmountOut = await getQuoterContract() + .callStatic.quoteExactOutputSingle(amount.currency.address, quoteCurrency.address, amount.numerator[0], 0) + .catch((error) => { + console.error(`Error sending quoteExactOutputSingle transaction: ${error}`) + }) + } + + if (quotedAmountOut) { + return new SwaprV3Trade({ + maximumSlippage, + inputAmount: amount, + outputAmount: new TokenAmount(quoteCurrency, quotedAmountOut), + tradeType: tradeType, + chainId: chainId, + priceImpact: new Percent('0', '1000'), + }) + } + + return null + } + + public minimumAmountOut(): CurrencyAmount { + if (this.tradeType === TradeType.EXACT_OUTPUT) { + return this.outputAmount + } else { + const slippageAdjustedAmountOut = new Fraction(ONE) + .add(this.maximumSlippage as Fraction) + .invert() + .multiply(this.outputAmount.raw).quotient + return this.outputAmount instanceof TokenAmount + ? new TokenAmount(this.outputAmount.token, slippageAdjustedAmountOut) + : CurrencyAmount.nativeCurrency(slippageAdjustedAmountOut, this.chainId) + } + } + + public maximumAmountIn(): CurrencyAmount { + if (this.tradeType === TradeType.EXACT_INPUT) { + return this.inputAmount + } else { + const slippageAdjustedAmountIn = new Fraction(ONE) + .add(this.maximumSlippage as Fraction) + .multiply(this.inputAmount.raw).quotient + return this.inputAmount instanceof TokenAmount + ? new TokenAmount(this.inputAmount.token, slippageAdjustedAmountIn) + : CurrencyAmount.nativeCurrency(slippageAdjustedAmountIn, this.chainId) + } + } + + // struct ExactInputSingleParams { + // uint256 amountIn; // Amount of the input token to be swapped + // address recipient; // Address that will receive the output tokens + // uint160 limitSqrtPrice; // Limit on the square root price of the swap + // uint256 amountOutMinimum; // Minimum amount of output tokens expected + // uint256 deadline; // Timestamp by which the transaction must be mined + // address tokenIn; // Address of the input token + // address tokenOut; // Address of the output token + // } + + public async swapTransaction(options: TradeOptions): Promise { + const to: string = validateAndParseAddress(options.recipient) + const amountIn: string = toHex(this.maximumAmountIn()) + const amountOut: string = toHex(this.minimumAmountOut()) + const methodName = this.tradeType === TradeType.EXACT_INPUT ? 'exactInputSingle' : 'exactOutputSingle' + + const exactInputSingleParams = { + amountIn: amountIn, + recipient: to, + amountOutMinimum: amountOut, + tokenIn: this.inputAmount.currency.address, + tokenOut: this.outputAmount.currency.address, + deadline: 1, + sqrtPriceLimitX96: 0, + } + + const routerContract = getRouterContract() + + // const encodedData = routerContract.interface.encodeFunctionData(methodName, [exactInputSingleParams]) + // console.log('=====================================') + // console.log('encodedData:', encodedData) + // console.log('=====================================') + + // Populate the transaction + const populatedTransaction = await routerContract.populateTransaction[methodName](exactInputSingleParams) + + return populatedTransaction + } +} + +function toHex(currencyAmount: CurrencyAmount) { + return `0x${currencyAmount.raw.toString(16)}` +} diff --git a/src/entities/trades/swapr-v3/abi/index.ts b/src/entities/trades/swapr-v3/abi/index.ts new file mode 100644 index 00000000..62194bf7 --- /dev/null +++ b/src/entities/trades/swapr-v3/abi/index.ts @@ -0,0 +1,2 @@ +export * from './swapr-algebra-quoter' +export * from './swapr-algebra-router' diff --git a/src/entities/trades/swapr-v3/abi/swapr-algebra-quoter.ts b/src/entities/trades/swapr-v3/abi/swapr-algebra-quoter.ts new file mode 100644 index 00000000..ff58c697 --- /dev/null +++ b/src/entities/trades/swapr-v3/abi/swapr-algebra-quoter.ts @@ -0,0 +1,201 @@ +export const SWAPR_ALGEBRA_QUOTER_ABI = [ + { + inputs: [ + { + internalType: 'address', + name: '_factory', + type: 'address', + }, + { + internalType: 'address', + name: '_WNativeToken', + type: 'address', + }, + { + internalType: 'address', + name: '_poolDeployer', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'WNativeToken', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'int256', + name: 'amount0Delta', + type: 'int256', + }, + { + internalType: 'int256', + name: 'amount1Delta', + type: 'int256', + }, + { + internalType: 'bytes', + name: 'path', + type: 'bytes', + }, + ], + name: 'AlgebraSwapCallback', + outputs: [], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'poolDeployer', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'path', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + ], + name: 'quoteExactInput', + outputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'tokenIn', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenOut', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint160', + name: 'sqrtPriceLimitX96', + type: 'uint160', + }, + ], + name: 'quoteExactInputSingle', + outputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'path', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + ], + name: 'quoteExactOutput', + outputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'tokenIn', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenOut', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'uint160', + name: 'sqrtPriceLimitX96', + type: 'uint160', + }, + ], + name: 'quoteExactOutputSingle', + outputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] diff --git a/src/entities/trades/swapr-v3/abi/swapr-algebra-router.ts b/src/entities/trades/swapr-v3/abi/swapr-algebra-router.ts new file mode 100644 index 00000000..7a9bc5f5 --- /dev/null +++ b/src/entities/trades/swapr-v3/abi/swapr-algebra-router.ts @@ -0,0 +1,634 @@ +export const SWAPR_ALGEBRA_ROUTER_ABI = [ + { + inputs: [ + { + internalType: 'address', + name: '_factory', + type: 'address', + }, + { + internalType: 'address', + name: '_WNativeToken', + type: 'address', + }, + { + internalType: 'address', + name: '_poolDeployer', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'WNativeToken', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'path', + type: 'bytes', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMinimum', + type: 'uint256', + }, + ], + internalType: 'struct ISwapRouter.ExactInputParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactInput', + outputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'tokenIn', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenOut', + type: 'address', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMinimum', + type: 'uint256', + }, + { + internalType: 'uint160', + name: 'sqrtPriceLimitX96', + type: 'uint160', + }, + ], + internalType: 'struct ISwapRouter.ExactInputSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactInputSingle', + outputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'tokenIn', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenOut', + type: 'address', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOutMinimum', + type: 'uint256', + }, + { + internalType: 'uint160', + name: 'sqrtPriceLimitX96', + type: 'uint160', + }, + ], + internalType: 'struct ISwapRouter.ExactInputSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactInputSingleSupportingFeeOnTransferTokens', + outputs: [ + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'path', + type: 'bytes', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountInMaximum', + type: 'uint256', + }, + ], + internalType: 'struct ISwapRouter.ExactOutputParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactOutput', + outputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'tokenIn', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenOut', + type: 'address', + }, + { + internalType: 'uint24', + name: 'fee', + type: 'uint24', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountOut', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountInMaximum', + type: 'uint256', + }, + { + internalType: 'uint160', + name: 'sqrtPriceLimitX96', + type: 'uint160', + }, + ], + internalType: 'struct ISwapRouter.ExactOutputSingleParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'exactOutputSingle', + outputs: [ + { + internalType: 'uint256', + name: 'amountIn', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'factory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'int256', + name: 'amount0Delta', + type: 'int256', + }, + { + internalType: 'int256', + name: 'amount1Delta', + type: 'int256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'AlgebraSwapCallback', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes[]', + name: 'data', + type: 'bytes[]', + }, + ], + name: 'multicall', + outputs: [ + { + internalType: 'bytes[]', + name: 'results', + type: 'bytes[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'poolDeployer', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'refundNativeToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermit', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermitAllowed', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermitAllowedIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'selfPermitIfNecessary', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountMinimum', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + ], + name: 'sweepToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amountMinimum', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'feeBips', + type: 'uint256', + }, + { + internalType: 'address', + name: 'feeRecipient', + type: 'address', + }, + ], + name: 'sweepTokenWithFee', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountMinimum', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + ], + name: 'unwrapWNativeToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amountMinimum', + type: 'uint256', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'feeBips', + type: 'uint256', + }, + { + internalType: 'address', + name: 'feeRecipient', + type: 'address', + }, + ], + name: 'unwrapWNativeTokenWithFee', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] diff --git a/src/entities/trades/swapr-v3/index.ts b/src/entities/trades/swapr-v3/index.ts new file mode 100644 index 00000000..a639c073 --- /dev/null +++ b/src/entities/trades/swapr-v3/index.ts @@ -0,0 +1 @@ +export * from './SwaprV3' diff --git a/src/entities/trades/swapr-v3/swapr-v3.spec.ts b/src/entities/trades/swapr-v3/swapr-v3.spec.ts new file mode 100644 index 00000000..f04e5e4e --- /dev/null +++ b/src/entities/trades/swapr-v3/swapr-v3.spec.ts @@ -0,0 +1,75 @@ +import { parseUnits } from '@ethersproject/units' +import { SwaprV3Trade } from './SwaprV3' + +import { ChainId, TradeType } from '../../../constants' +import { Percent, TokenAmount } from '../../fractions' +import { Token } from '../../token' + +const maximumSlippage = new Percent('3', '100') +const tokenUSDC = new Token(ChainId.GNOSIS, '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', 6, 'USDC', 'USDC') +const tokenWXDAI = new Token(ChainId.GNOSIS, '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', 18, 'WXDAI', 'WXDAI') +//const tokenWETH = new Token(ChainId.GNOSIS, '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1', 18, 'WETH', 'WETH') + +const VITALIK_ADDRESS = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045' +// const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' +const recipient = VITALIK_ADDRESS + +describe('SwaprV3', () => { + describe('getQuote', () => { + test('should return a exact input quote on Gnosis for USDC - WXDAI', async () => { + const recipient = VITALIK_ADDRESS + const currencyAmount = new TokenAmount(tokenUSDC, parseUnits('2', 6).toString()) + const trade = await SwaprV3Trade.getQuote({ + quoteCurrency: tokenWXDAI, + amount: currencyAmount, + maximumSlippage, + recipient, + tradeType: TradeType.EXACT_INPUT, + }) + console.log('trade', trade) + expect(trade).toBeDefined() + expect(trade?.chainId).toEqual(ChainId.GNOSIS) + expect(trade?.tradeType).toEqual(TradeType.EXACT_INPUT) + expect(trade?.outputAmount.currency.address).toBe(tokenWXDAI.address) + }) + test('should return a exact output quote on Gnosis for USDC - WXDAI', async () => { + const recipient = VITALIK_ADDRESS + const currencyAmount = new TokenAmount(tokenUSDC, parseUnits('2', 6).toString()) + const trade = await SwaprV3Trade.getQuote({ + quoteCurrency: tokenWXDAI, + amount: currencyAmount, + maximumSlippage, + recipient, + tradeType: TradeType.EXACT_OUTPUT, + }) + console.log('trade', trade) + expect(trade).toBeDefined() + expect(trade?.chainId).toEqual(ChainId.GNOSIS) + expect(trade?.tradeType).toEqual(TradeType.EXACT_OUTPUT) + expect(trade?.outputAmount.currency.address).toBe(tokenWXDAI.address) + }) + }) + describe('Swap', () => { + test('should return a swap for Gnosis for USDC - WXDAI', async () => { + const currencyAmount = new TokenAmount(tokenUSDC, parseUnits('2', 6).toString()) + + const trade = await SwaprV3Trade.getQuote({ + quoteCurrency: tokenWXDAI, + amount: currencyAmount, + maximumSlippage, + recipient, + tradeType: TradeType.EXACT_INPUT, + }) + + const swapOptions = { + recipient: recipient, + account: recipient, + } + + const swapr = await trade?.swapTransaction(swapOptions) + + console.log('swapr', swapr) + expect(swapr !== undefined) + }) + }) +}) diff --git a/src/entities/trades/utils.ts b/src/entities/trades/utils.ts index d4a09cd0..efe207f9 100644 --- a/src/entities/trades/utils.ts +++ b/src/entities/trades/utils.ts @@ -59,6 +59,7 @@ export function tryGetChainId(currencyAmount: CurrencyAmount, currency: Currency export const RPC_PROVIDER_LIST: Record = { [ChainId.MAINNET]: 'https://mainnet.infura.io/v3/e1a3bfc40093494ca4f36b286ab36f2d', [ChainId.XDAI]: 'https://poa-xdai.gateway.pokt.network/v1/lb/627cd67433e8770039fe3dba', + // [ChainId.GNOSIS]: 'https://gnosis.public-rpc.com', [ChainId.RINKEBY]: 'https://rinkeby.infura.io/v3/e1a3bfc40093494ca4f36b286ab36f2d', [ChainId.ARBITRUM_ONE]: 'https://arb1.arbitrum.io/rpc', [ChainId.ARBITRUM_RINKEBY]: 'https://rinkeby.arbitrum.io/rpc', From 2b27529abcb036681ab7a7982a31aad47903bfa7 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 20 Dec 2023 13:30:35 +0000 Subject: [PATCH 04/26] 2nd draft --- src/entities/trades/swapr-v3/SwaprV3.ts | 143 ++- src/entities/trades/swapr-v3/abi/index.ts | 1 + .../trades/swapr-v3/abi/swapr-algebra-pool.ts | 1041 +++++++++++++++++ .../trades/swapr-v3/computePoolAddress.ts | 55 + src/entities/trades/swapr-v3/constants.ts | 61 + src/entities/trades/swapr-v3/encode-route.ts | 39 + src/entities/trades/swapr-v3/pool.ts | 326 ++++++ src/entities/trades/swapr-v3/pools.ts | 166 +++ src/entities/trades/swapr-v3/route.ts | 89 ++ src/entities/trades/swapr-v3/routes.ts | 47 + src/entities/trades/swapr-v3/swapr-v3.spec.ts | 89 +- 11 files changed, 1998 insertions(+), 59 deletions(-) create mode 100644 src/entities/trades/swapr-v3/abi/swapr-algebra-pool.ts create mode 100644 src/entities/trades/swapr-v3/computePoolAddress.ts create mode 100644 src/entities/trades/swapr-v3/constants.ts create mode 100644 src/entities/trades/swapr-v3/encode-route.ts create mode 100644 src/entities/trades/swapr-v3/pool.ts create mode 100644 src/entities/trades/swapr-v3/pools.ts create mode 100644 src/entities/trades/swapr-v3/route.ts create mode 100644 src/entities/trades/swapr-v3/routes.ts diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index 7e3a9aa2..af81f988 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -1,8 +1,7 @@ -// @ts-nocheck import type { BaseProvider } from '@ethersproject/providers' +import dayjs from 'dayjs' -import { Fraction, validateAndParseAddress } from '@uniswap/sdk-core' -// import debug from 'debug' +import { Currency, Fraction, validateAndParseAddress } from '@uniswap/sdk-core' import invariant from 'tiny-invariant' import { CurrencyAmount, Percent, Price, TokenAmount } from '../../fractions' @@ -12,10 +11,17 @@ import { RoutablePlatform } from '../routable-platform' import { getProvider, tryGetChainId } from '../utils' import { ONE, TradeType } from '../../../constants' -import { SWAPR_ALGEBRA_ROUTER_ABI, SWAPR_ALGEBRA_QUOTER_ABI } from './abi' +import { SWAPR_ALGEBRA_ROUTER_ABI, SWAPR_ALGEBRA_QUOTER_ABI, SWAPR_ALGEBRA_POOL_ABI } from './abi' import { Contract, UnsignedTransaction } from 'ethers' import { AddressZero } from '@ethersproject/constants' import { TradeOptions } from '../interfaces/trade-options' +import { formatUnits, parseUnits } from '@ethersproject/units' +import { getRoutes } from './routes' +import { Route } from './route' + +// const quoterInterface = new Interface(SWAPR_ALGEBRA_QUOTER_ABI) + +// import { getBestV3TradeExactIn } from './algebra/getBestV3Trades' // Constants export const GNOSIS_CONTRACTS = { @@ -23,6 +29,10 @@ export const GNOSIS_CONTRACTS = { router: '0xfFB643E73f280B97809A8b41f7232AB401a04ee1', } +export function getPoolsContract(pool_address: string) { + return new Contract(pool_address, SWAPR_ALGEBRA_POOL_ABI, getProvider(100)) +} + export function getRouterContract() { return new Contract(GNOSIS_CONTRACTS.router, SWAPR_ALGEBRA_ROUTER_ABI, getProvider(100)) } @@ -64,55 +74,104 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { numerator: outputAmount.raw, }), priceImpact, - fee: new Percent('2', '10000'), // todo: change fee + fee: new Percent('0', '100'), approveAddress: GNOSIS_CONTRACTS['router'], }) } + // SwaprV3Trade.getQuote({ + // quoteCurrency: currencyOut, + // amount: currencyAmountIn, + // maximumSlippage, + // recipient: receiver, + // tradeType: TradeType.EXACT_INPUT, + // }) + static async getQuote( { amount, quoteCurrency, tradeType, recipient, maximumSlippage }: any, - provider?: BaseProvider + provider?: BaseProvider, ): Promise { const chainId = tryGetChainId(amount, quoteCurrency) invariant(chainId, 'SwaprV3Trade.getQuote: chainId is required') // Defaults + recipient = recipient || AddressZero maximumSlippage = maximumSlippage || 0 provider = provider || getProvider(chainId) + const tokenIn = amount.currency + const tokenOut = quoteCurrency + invariant( (await provider.getNetwork()).chainId == chainId, - `SwaprV3Trade.getQuote: currencies chainId does not match provider's chainId` + `SwaprV3Trade.getQuote: currencies chainId does not match provider's chainId`, ) - // const formatedAmount = formatUnits(amount.numerator[0], amount.currency.decimals) - // console.log('formatedAmount:', formatedAmount) - - let quotedAmountOut - if (tradeType === TradeType.EXACT_INPUT) { - quotedAmountOut = await getQuoterContract() - .callStatic.quoteExactInputSingle(amount.currency.address, quoteCurrency.address, amount.numerator[0], 0) + const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) + console.log('routes:', routes) + + console.log('tokenIn:', tokenIn) + console.log('tokenOut:', tokenOut) + + const quotedAmountOut = await getQuoterContract() + .callStatic.quoteExactInputSingle( + tokenIn.address, + tokenOut.address, + parseUnits(amount.toSignificant(), amount.currency.decimals), + 0, + ) .catch((error) => { console.error(`Error sending quoteExactInputSingle transaction: ${error}`) }) + + const amountInReadable = amount.toSignificant() + console.log('amountIn :', amountInReadable) + const amountOutReadable = formatUnits(quotedAmountOut, tokenOut.decimals) + console.log('amountOut:', amountOutReadable) + + if (quotedAmountOut) { + return new SwaprV3Trade({ + maximumSlippage, + inputAmount: amount, + outputAmount: new TokenAmount(quoteCurrency, quotedAmountOut), + tradeType: tradeType, + chainId: chainId, + priceImpact: new Percent('0', '1000'), + }) + } } else { - quotedAmountOut = await getQuoterContract() - .callStatic.quoteExactOutputSingle(amount.currency.address, quoteCurrency.address, amount.numerator[0], 0) + const quotedAmountIn = await getQuoterContract() + .callStatic.quoteExactOutputSingle( + amount.currency.address, + quoteCurrency.address, + parseUnits(amount.toSignificant(), amount.currency.decimals), + 0, + ) .catch((error) => { console.error(`Error sending quoteExactOutputSingle transaction: ${error}`) }) - } - if (quotedAmountOut) { - return new SwaprV3Trade({ - maximumSlippage, - inputAmount: amount, - outputAmount: new TokenAmount(quoteCurrency, quotedAmountOut), - tradeType: tradeType, - chainId: chainId, - priceImpact: new Percent('0', '1000'), - }) + console.log('====== SDK -> EXACT_OUTPUT ========') + console.log('quoteCurrency', quoteCurrency) + console.log('amount', amount) + console.log('amount.toSignificant()', amount.toSignificant()) + console.log('amount.currency.decimals', amount.currency.decimals) + console.log('quotedAmountIn:', quotedAmountIn.toString()) + + // const amountIn: string = toHex(trade.maximumAmountIn(options.slippageTolerance, inputAmount).quotient) + // const amountOut: string = toHex(trade.minimumAmountOut(options.slippageTolerance, outputAmount).quotient) + + if (quotedAmountIn) { + return new SwaprV3Trade({ + maximumSlippage, + inputAmount: new TokenAmount(quoteCurrency, quotedAmountIn), + outputAmount: amount, + tradeType: tradeType, + chainId: chainId, + priceImpact: new Percent('0', '1000'), + }) + } } return null @@ -159,27 +218,33 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { const to: string = validateAndParseAddress(options.recipient) const amountIn: string = toHex(this.maximumAmountIn()) const amountOut: string = toHex(this.minimumAmountOut()) - const methodName = this.tradeType === TradeType.EXACT_INPUT ? 'exactInputSingle' : 'exactOutputSingle' + const isTradeExactInput = this.tradeType === TradeType.EXACT_INPUT + const routerContract = getRouterContract() - const exactInputSingleParams = { - amountIn: amountIn, - recipient: to, - amountOutMinimum: amountOut, + const baseParams = { tokenIn: this.inputAmount.currency.address, tokenOut: this.outputAmount.currency.address, - deadline: 1, + recipient: to, + deadline: dayjs().add(30, 'm').unix(), sqrtPriceLimitX96: 0, + // fee: this.fee --> route.pools[0].fee, } - const routerContract = getRouterContract() + const exactInputSingleParams = { + ...baseParams, + amountIn: amountIn, + amountOutMinimum: amountOut, + } - // const encodedData = routerContract.interface.encodeFunctionData(methodName, [exactInputSingleParams]) - // console.log('=====================================') - // console.log('encodedData:', encodedData) - // console.log('=====================================') + const exactOutputSingleParams = { + ...baseParams, + amountOut: amountOut, + amountInMaximum: amountIn, + } - // Populate the transaction - const populatedTransaction = await routerContract.populateTransaction[methodName](exactInputSingleParams) + const methodName = isTradeExactInput ? 'exactInputSingle' : 'exactOutputSingle' + const params = isTradeExactInput ? exactInputSingleParams : exactOutputSingleParams + const populatedTransaction = await routerContract.populateTransaction[methodName](params) return populatedTransaction } diff --git a/src/entities/trades/swapr-v3/abi/index.ts b/src/entities/trades/swapr-v3/abi/index.ts index 62194bf7..646fd13f 100644 --- a/src/entities/trades/swapr-v3/abi/index.ts +++ b/src/entities/trades/swapr-v3/abi/index.ts @@ -1,2 +1,3 @@ export * from './swapr-algebra-quoter' export * from './swapr-algebra-router' +export * from './swapr-algebra-pool' diff --git a/src/entities/trades/swapr-v3/abi/swapr-algebra-pool.ts b/src/entities/trades/swapr-v3/abi/swapr-algebra-pool.ts new file mode 100644 index 00000000..22fec310 --- /dev/null +++ b/src/entities/trades/swapr-v3/abi/swapr-algebra-pool.ts @@ -0,0 +1,1041 @@ +export const SWAPR_ALGEBRA_POOL_ABI = [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "bottomTick", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "topTick", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidityAmount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "bottomTick", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "topTick", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "communityFee0New", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "communityFee1New", + "type": "uint8" + } + ], + "name": "CommunityFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "fee", + "type": "uint16" + } + ], + "name": "Fee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid1", + "type": "uint256" + } + ], + "name": "Flash", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "virtualPoolAddress", + "type": "address" + } + ], + "name": "Incentive", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint160", + "name": "price", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "liquidityCooldown", + "type": "uint32" + } + ], + "name": "LiquidityCooldown", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "bottomTick", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "topTick", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidityAmount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "price", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [], + "name": "activeIncentive", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "bottomTick", + "type": "int24" + }, + { + "internalType": "int24", + "name": "topTick", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "bottomTick", + "type": "int24" + }, + { + "internalType": "int24", + "name": "topTick", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dataStorageOperator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "flash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "bottomTick", + "type": "int24" + }, + { + "internalType": "int24", + "name": "topTick", + "type": "int24" + } + ], + "name": "getInnerCumulatives", + "outputs": [ + { + "internalType": "int56", + "name": "innerTickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "innerSecondsSpentPerLiquidity", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "innerSecondsSpent", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32[]", + "name": "secondsAgos", + "type": "uint32[]" + } + ], + "name": "getTimepoints", + "outputs": [ + { + "internalType": "int56[]", + "name": "tickCumulatives", + "type": "int56[]" + }, + { + "internalType": "uint160[]", + "name": "secondsPerLiquidityCumulatives", + "type": "uint160[]" + }, + { + "internalType": "uint112[]", + "name": "volatilityCumulatives", + "type": "uint112[]" + }, + { + "internalType": "uint256[]", + "name": "volumePerAvgLiquiditys", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "globalState", + "outputs": [ + { + "internalType": "uint160", + "name": "price", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "fee", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "timepointIndex", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "communityFeeToken0", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "communityFeeToken1", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint160", + "name": "initialPrice", + "type": "uint160" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liquidityCooldown", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidityPerTick", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "bottomTick", + "type": "int24" + }, + { + "internalType": "int24", + "name": "topTick", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "liquidityDesired", + "type": "uint128" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "liquidityActual", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint32", + "name": "lastLiquidityAddTimestamp", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "innerFeeGrowth0Token", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "innerFeeGrowth1Token", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "fees0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "fees1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "communityFee0", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "communityFee1", + "type": "uint8" + } + ], + "name": "setCommunityFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "virtualPoolAddress", + "type": "address" + } + ], + "name": "setIncentive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "newLiquidityCooldown", + "type": "uint32" + } + ], + "name": "setLiquidityCooldown", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "zeroToOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountRequired", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "limitSqrtPrice", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "zeroToOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountRequired", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "limitSqrtPrice", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swapSupportingFeeOnInputTokens", + "outputs": [ + { + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "tickSpacing", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int16", + "name": "", + "type": "int16" + } + ], + "name": "tickTable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "name": "ticks", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidityTotal", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityDelta", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "outerFeeGrowth0Token", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "outerFeeGrowth1Token", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "outerTickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "outerSecondsPerLiquidity", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "outerSecondsSpent", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "timepoints", + "outputs": [ + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + }, + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulative", + "type": "uint160" + }, + { + "internalType": "uint88", + "name": "volatilityCumulative", + "type": "uint88" + }, + { + "internalType": "int24", + "name": "averageTick", + "type": "int24" + }, + { + "internalType": "uint144", + "name": "volumePerLiquidityCumulative", + "type": "uint144" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalFeeGrowth0Token", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalFeeGrowth1Token", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/entities/trades/swapr-v3/computePoolAddress.ts b/src/entities/trades/swapr-v3/computePoolAddress.ts new file mode 100644 index 00000000..19f3a4cb --- /dev/null +++ b/src/entities/trades/swapr-v3/computePoolAddress.ts @@ -0,0 +1,55 @@ +import { defaultAbiCoder } from '@ethersproject/abi' +import { getCreate2Address } from '@ethersproject/address' +import { keccak256 } from '@ethersproject/solidity' +import { Token } from '@uniswap/sdk-core' +import { POOL_INIT_CODE_HASH } from './constants' + +export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000' + +/** + * The default factory enabled fee amounts, denominated in hundredths of bips. + */ +export enum FeeAmount { + LOW = 'LOW', + MEDIUM = 'MEDIUM', + HIGH = 'HIGH', +} + +/** + * The default factory tick spacings by fee amount. + */ +export const TICK_SPACINGS: { [amount in FeeAmount]: number } = { + [FeeAmount.LOW]: 60, + [FeeAmount.MEDIUM]: 60, + [FeeAmount.HIGH]: 60, +} + +/** + * This function computes the pool address for a given pair of tokens and a fee tier. + * It uses the Swapr factory address, the addresses of the two tokens, and the pool initialization code hash. + * The tokens are sorted before the computation, and the initialization code hash can be manually overridden. + * @param poolDeployer This is the address of the factory contract that deploys new pools. + * @param tokenA The first token of the pair + * @param tokenB The second token of the pair + * @param initCodeHashManualOverride Optional manual override for the initialization code hash + * @returns The computed pool address. This address can then be used to interact with the pool on the blockchain. eg. https://gnosisscan.io/address/0x6a1507579b50abfc7ccc8f9e2b428095b5063538#tokentxns + */ + +export function computePoolAddress({ + poolDeployer, + tokenA, + tokenB, + initCodeHashManualOverride, +}: { + poolDeployer: string + tokenA: Token + tokenB: Token + initCodeHashManualOverride?: string +}): string { + const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] // does safety checks + return getCreate2Address( + poolDeployer, + keccak256(['bytes'], [defaultAbiCoder.encode(['address', 'address'], [token0.address, token1.address])]), + initCodeHashManualOverride ?? POOL_INIT_CODE_HASH, + ) +} diff --git a/src/entities/trades/swapr-v3/constants.ts b/src/entities/trades/swapr-v3/constants.ts new file mode 100644 index 00000000..b862753d --- /dev/null +++ b/src/entities/trades/swapr-v3/constants.ts @@ -0,0 +1,61 @@ +export const ROUTER_ADDRESS = '0xfFB643E73f280B97809A8b41f7232AB401a04ee1' +export const POOL_DEPLOYER_ADDRESS = '0xC1b576AC6Ec749d5Ace1787bF9Ec6340908ddB47' +export const POOL_INIT_CODE_HASH = '0xbce37a54eab2fcd71913a0d40723e04238970e7fc1159bfd58ad5b79531697e7' + +export type BaseToken = { + chainId: number + decimals: number + symbol: string + name: string + isNative: boolean + isToken: boolean + address: string +} + +export const baseTokens: BaseToken[] = [ + { + chainId: 100, + decimals: 18, + symbol: 'WXDAI', + name: 'Wrapped XDAI', + isNative: false, + isToken: true, + address: '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', + }, + { + chainId: 100, + decimals: 6, + symbol: 'USDC', + name: 'USD//C on Gnosis', + isNative: false, + isToken: true, + address: '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', + }, + { + chainId: 100, + decimals: 18, + symbol: 'WETH', + name: 'Wrapped Ether on Gnosis chain', + isNative: false, + isToken: true, + address: '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1', + }, + { + chainId: 100, + decimals: 18, + symbol: 'GNO', + name: 'Gnosis Token on Gnosis chain', + isNative: false, + isToken: true, + address: '0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb', + }, + // { + // chainId: 100, + // decimals: 18, + // symbol: 'SWPR', + // name: 'SWAPR Token on Gnosis chain', + // isNative: false, + // isToken: true, + // address: '0x532801ED6f82FFfD2DAB70A19fC2d7B2772C4f4b', + // }, +] diff --git a/src/entities/trades/swapr-v3/encode-route.ts b/src/entities/trades/swapr-v3/encode-route.ts new file mode 100644 index 00000000..3b5a4fda --- /dev/null +++ b/src/entities/trades/swapr-v3/encode-route.ts @@ -0,0 +1,39 @@ +import { pack } from '@ethersproject/solidity' +import { Currency, Token } from '@uniswap/sdk-core' +import { Pool } from './pool' +import { Route } from './route' + +/** + * Converts a route to a hex encoded path + * @param route the v3 path to convert to an encoded path + * @param exactOutput whether the route should be encoded in reverse, for making exact output swaps + */ +export function encodeRouteToPath(route: Route, exactOutput: boolean): string { + const firstInputToken: Token = route.input.wrapped + + const { path, types } = route.pools.reduce( + ( + { inputToken, path, types }: { inputToken: Token; path: (string | number)[]; types: string[] }, + pool: Pool, + index, + ): { inputToken: Token; path: (string | number)[]; types: string[] } => { + const outputToken: Token = pool.token0.equals(inputToken) ? pool.token1 : pool.token0 + if (index === 0) { + return { + inputToken: outputToken, + types: ['address', 'address'], + path: [inputToken.address, outputToken.address], + } + } else { + return { + inputToken: outputToken, + types: [...types, 'address'], + path: [...path, outputToken.address], + } + } + }, + { inputToken: firstInputToken, path: [], types: [] }, + ) + + return exactOutput ? pack(types.reverse(), path.reverse()) : pack(types, path) +} diff --git a/src/entities/trades/swapr-v3/pool.ts b/src/entities/trades/swapr-v3/pool.ts new file mode 100644 index 00000000..130e718a --- /dev/null +++ b/src/entities/trades/swapr-v3/pool.ts @@ -0,0 +1,326 @@ +import { BigintIsh, CurrencyAmount, Price, Token } from '@uniswap/sdk-core' +import JSBI from 'jsbi' +import invariant from 'tiny-invariant' +import { POOL_DEPLOYER_ADDRESS } from './constants' +import { + FeeAmount, + LiquidityMath, + NoTickDataProvider, + SwapMath, + Tick, + TickConstructorArgs, + TickDataProvider, + TickListDataProvider, + TickMath, + computePoolAddress, +} from '@uniswap/v3-sdk' + +// used in liquidity amount math +const Q96 = JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(96)) +const Q192 = JSBI.exponentiate(Q96, JSBI.BigInt(2)) + +interface StepComputations { + sqrtPriceStartX96: JSBI + tickNext: number + initialized: boolean + sqrtPriceNextX96: JSBI + amountIn: JSBI + amountOut: JSBI + feeAmount: JSBI +} + +/** + * By default, pools will not allow operations that require ticks. + */ +const NO_TICK_DATA_PROVIDER_DEFAULT = new NoTickDataProvider() + +/** + * Represents a V3 pool + */ +export class Pool { + public readonly token0: Token + public readonly token1: Token + public readonly fee: FeeAmount + public readonly sqrtRatioX96: JSBI + public readonly liquidity: JSBI + public readonly tickCurrent: number + public readonly tickDataProvider: TickDataProvider + + /** + * Construct a pool + * @param tokenA One of the tokens in the pool + * @param tokenB The other token in the pool + * @param fee The fee in hundredths of a bips of the input amount of every swap that is collected by the pool + * @param sqrtRatioX96 The sqrt of the current ratio of amounts of token1 to token0 + * @param liquidity The current value of in range liquidity + * @param tickCurrent The current tick of the pool + * @param ticks The current state of the pool ticks or a data provider that can return tick data + */ + public constructor( + tokenA: Token, + tokenB: Token, + fee: FeeAmount, + sqrtRatioX96: BigintIsh, + liquidity: BigintIsh, + tickCurrent: number, + ticks: TickDataProvider | (Tick | TickConstructorArgs)[] = NO_TICK_DATA_PROVIDER_DEFAULT, + ) { + invariant(Number.isInteger(fee) && fee < 1_000_000, 'FEE') + + const tickCurrentSqrtRatioX96 = TickMath.getSqrtRatioAtTick(tickCurrent) + const nextTickSqrtRatioX96 = TickMath.getSqrtRatioAtTick(tickCurrent + 1) + invariant( + JSBI.greaterThanOrEqual(JSBI.BigInt(sqrtRatioX96), tickCurrentSqrtRatioX96) && + JSBI.lessThanOrEqual(JSBI.BigInt(sqrtRatioX96), nextTickSqrtRatioX96), + 'PRICE_BOUNDS', + ) + // always create a copy of the list since we want the pool's tick list to be immutable + ;[this.token0, this.token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] + this.fee = fee + this.sqrtRatioX96 = JSBI.BigInt(sqrtRatioX96) + this.liquidity = JSBI.BigInt(liquidity) + this.tickCurrent = tickCurrent + this.tickDataProvider = Array.isArray(ticks) ? new TickListDataProvider(ticks, 60) : ticks + } + + private _token0Price?: Price + + /** + * Returns the current mid price of the pool in terms of token0, i.e. the ratio of token1 over token0 + */ + public get token0Price(): Price { + return ( + this._token0Price ?? + (this._token0Price = new Price( + this.token0, + this.token1, + Q192, + JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96), + )) + ) + } + + private _token1Price?: Price + + /** + * Returns the current mid price of the pool in terms of token1, i.e. the ratio of token0 over token1 + */ + public get token1Price(): Price { + return ( + this._token1Price ?? + (this._token1Price = new Price( + this.token1, + this.token0, + JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96), + Q192, + )) + ) + } + + /** + * Returns the chain ID of the tokens in the pool. + */ + public get chainId(): number { + return this.token0.chainId + } + + public get tickSpacing(): number { + return 60 + } + + public static getAddress(tokenA: Token, tokenB: Token, fee: FeeAmount, initCodeHashManualOverride?: string): string { + return computePoolAddress({ + factoryAddress: POOL_DEPLOYER_ADDRESS, + fee, + tokenA, + tokenB, + initCodeHashManualOverride, + }) + } + + /** + * Returns true if the token is either token0 or token1 + * @param token The token to check + * @returns True if token is either token0 or token + */ + public involvesToken(token: Token): boolean { + return token.equals(this.token0) || token.equals(this.token1) + } + + /** + * Return the price of the given token in terms of the other token in the pool. + * @param token The token to return price of + * @returns The price of the given token, in terms of the other. + */ + public priceOf(token: Token): Price { + invariant(this.involvesToken(token), 'TOKEN') + return token.equals(this.token0) ? this.token0Price : this.token1Price + } + + /** + * Given an input amount of a token, return the computed output amount, and a pool with state updated after the trade + * @param inputAmount The input amount for which to quote the output amount + * @param sqrtPriceLimitX96 The Q64.96 sqrt price limit + * @returns The output amount and the pool with updated state + */ + public async getOutputAmount( + inputAmount: CurrencyAmount, + sqrtPriceLimitX96?: JSBI, + ): Promise<[CurrencyAmount, Pool]> { + invariant(this.involvesToken(inputAmount.currency), 'TOKEN') + + const zeroForOne = inputAmount.currency.equals(this.token0) + + const { + amountCalculated: outputAmount, + sqrtRatioX96, + liquidity, + tickCurrent, + } = await this.swap(zeroForOne, inputAmount.quotient, sqrtPriceLimitX96) + const outputToken = zeroForOne ? this.token1 : this.token0 + return [ + CurrencyAmount.fromRawAmount(outputToken, JSBI.multiply(outputAmount, JSBI.BigInt(-1))), + new Pool(this.token0, this.token1, this.fee, sqrtRatioX96, liquidity, tickCurrent, this.tickDataProvider), + ] + } + + /** + * Given a desired output amount of a token, return the computed input amount and a pool with state updated after the trade + * @param outputAmount the output amount for which to quote the input amount + * @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap + * @returns The input amount and the pool with updated state + */ + public async getInputAmount( + outputAmount: CurrencyAmount, + sqrtPriceLimitX96?: JSBI, + ): Promise<[CurrencyAmount, Pool]> { + invariant(outputAmount.currency.isToken && this.involvesToken(outputAmount.currency), 'TOKEN') + + const zeroForOne = outputAmount.currency.equals(this.token1) + + const { + amountCalculated: inputAmount, + sqrtRatioX96, + liquidity, + tickCurrent, + } = await this.swap(zeroForOne, JSBI.multiply(outputAmount.quotient, JSBI.BigInt(-1)), sqrtPriceLimitX96) + const inputToken = zeroForOne ? this.token0 : this.token1 + return [ + CurrencyAmount.fromRawAmount(inputToken, inputAmount), + new Pool(this.token0, this.token1, this.fee, sqrtRatioX96, liquidity, tickCurrent, this.tickDataProvider), + ] + } + + /** + * Executes a swap + * @param zeroForOne Whether the amount in is token0 or token1 + * @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + * @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap + * @returns amountCalculated + * @returns sqrtRatioX96 + * @returns liquidity + * @returns tickCurrent + */ + private async swap( + zeroForOne: boolean, + amountSpecified: JSBI, + sqrtPriceLimitX96?: JSBI, + ): Promise<{ amountCalculated: JSBI; sqrtRatioX96: JSBI; liquidity: JSBI; tickCurrent: number }> { + if (!sqrtPriceLimitX96) + sqrtPriceLimitX96 = zeroForOne + ? JSBI.add(TickMath.MIN_SQRT_RATIO, JSBI.BigInt(1)) + : JSBI.subtract(TickMath.MAX_SQRT_RATIO, JSBI.BigInt(1)) + + if (zeroForOne) { + invariant(JSBI.greaterThan(sqrtPriceLimitX96, TickMath.MIN_SQRT_RATIO), 'RATIO_MIN') + invariant(JSBI.lessThan(sqrtPriceLimitX96, this.sqrtRatioX96), 'RATIO_CURRENT') + } else { + invariant(JSBI.lessThan(sqrtPriceLimitX96, TickMath.MAX_SQRT_RATIO), 'RATIO_MAX') + invariant(JSBI.greaterThan(sqrtPriceLimitX96, this.sqrtRatioX96), 'RATIO_CURRENT') + } + + const exactInput = JSBI.greaterThanOrEqual(amountSpecified, JSBI.BigInt(0)) + + // keep track of swap state + + const state = { + amountSpecifiedRemaining: amountSpecified, + amountCalculated: JSBI.BigInt(0), + sqrtPriceX96: this.sqrtRatioX96, + tick: this.tickCurrent, + liquidity: this.liquidity, + } + + // start swap while loop + while (JSBI.notEqual(state.amountSpecifiedRemaining, JSBI.BigInt(0)) && state.sqrtPriceX96 != sqrtPriceLimitX96) { + const step: Partial = {} + step.sqrtPriceStartX96 = state.sqrtPriceX96 + + // because each iteration of the while loop rounds, we can't optimize this code (relative to the smart contract) + // by simply traversing to the next available tick, we instead need to exactly replicate + // tickBitmap.nextInitializedTickWithinOneWord + ;[step.tickNext, step.initialized] = await this.tickDataProvider.nextInitializedTickWithinOneWord( + state.tick, + zeroForOne, + this.tickSpacing, + ) + + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK + } + + step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext) + ;[state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount] = SwapMath.computeSwapStep( + state.sqrtPriceX96, + ( + zeroForOne + ? JSBI.lessThan(step.sqrtPriceNextX96, sqrtPriceLimitX96) + : JSBI.greaterThan(step.sqrtPriceNextX96, sqrtPriceLimitX96) + ) + ? sqrtPriceLimitX96 + : step.sqrtPriceNextX96, + state.liquidity, + state.amountSpecifiedRemaining, + this.fee, + ) + + if (exactInput) { + state.amountSpecifiedRemaining = JSBI.subtract( + state.amountSpecifiedRemaining, + JSBI.add(step.amountIn, step.feeAmount), + ) + state.amountCalculated = JSBI.subtract(state.amountCalculated, step.amountOut) + } else { + state.amountSpecifiedRemaining = JSBI.add(state.amountSpecifiedRemaining, step.amountOut) + state.amountCalculated = JSBI.add(state.amountCalculated, JSBI.add(step.amountIn, step.feeAmount)) + } + + // TODO + if (JSBI.equal(state.sqrtPriceX96, step.sqrtPriceNextX96)) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + let liquidityNet = JSBI.BigInt((await this.tickDataProvider.getTick(step.tickNext)).liquidityNet) + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + if (zeroForOne) liquidityNet = JSBI.multiply(liquidityNet, JSBI.BigInt(-1)) + + state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet) + } + + state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext + } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { + // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved + state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96) + } + } + + return { + amountCalculated: state.amountCalculated, + sqrtRatioX96: state.sqrtPriceX96, + liquidity: state.liquidity, + tickCurrent: state.tick, + } + } +} diff --git a/src/entities/trades/swapr-v3/pools.ts b/src/entities/trades/swapr-v3/pools.ts new file mode 100644 index 00000000..07c56643 --- /dev/null +++ b/src/entities/trades/swapr-v3/pools.ts @@ -0,0 +1,166 @@ +// @ts-nocheck +import { Token } from '@uniswap/sdk-core' +import { POOL_DEPLOYER_ADDRESS, baseTokens } from './constants' +import { computePoolAddress } from './computePoolAddress' +import { getPoolsContract } from './SwaprV3' +import { Pool } from './pool' + +const GNOSIS_CHAIN_ID = 100 + +const getBaseTokens: Token[] = baseTokens.map( + ({ address, decimals, symbol, name }) => new Token(GNOSIS_CHAIN_ID, address, decimals, symbol, name), +) + +export const setupTokens = (currencyIn: Token, currencyOut: Token) => { + const tokenIn = new Token( + GNOSIS_CHAIN_ID, + currencyIn.address, + currencyIn.decimals, + currencyIn.symbol, + currencyIn.name, + ) + + const tokenOut = new Token( + GNOSIS_CHAIN_ID, + currencyOut.address, + currencyOut.decimals, + currencyOut.symbol, + currencyOut.name, + ) + + const [tokenA, tokenB] = [tokenIn?.wrapped, tokenOut?.wrapped] + + return { tokenA, tokenB } +} + +const pairsDiffCombinations = (tokenA: Token, tokenB: Token) => { + const basePairs: [Token, Token][] = getBaseTokens + .flatMap((base): [Token, Token][] => getBaseTokens.map((otherBase) => [base, otherBase])) + .filter(([t0, t1]) => !t0.equals(t1)) + + return ( + [ + // the direct pair + [tokenA, tokenB] as [Token, Token], + // token A against all bases + ...getBaseTokens.map((base): [Token, Token] => [tokenA, base]), + // token B against all bases + ...getBaseTokens.map((base): [Token, Token] => [tokenB, base]), + // each base against all bases + ...basePairs, + ] // filter out invalid pairs comprised of the same asset (e.g. WETH<>WETH) + .filter(([t0, t1]) => !t0.equals(t1)) + // filter out duplicate pairs + .filter(([t0, t1], i, otherPairs) => { + // find the first index in the array at which there are the same 2 tokens as the current + const firstIndexInOtherPairs = otherPairs.findIndex(([t0Other, t1Other]) => { + return (t0.equals(t0Other) && t1.equals(t1Other)) || (t0.equals(t1Other) && t1.equals(t0Other)) + }) + // only accept the first occurrence of the same 2 tokens + return firstIndexInOtherPairs === i + }) + ) +} + +export const getPools = async (currencyIn: Token, currencyOut: Token) => { + const { tokenA, tokenB } = setupTokens(currencyIn, currencyOut) + const pairsCombinations = pairsDiffCombinations(tokenA, tokenB) + + const sortedPairs = pairsCombinations.map(([currencyA, currencyB]) => { + const [token0, token1] = currencyA.sortsBefore(currencyB) ? [currencyA, currencyB] : [currencyB, currencyA] + return [token0, token1] + }) + + const poolAddresses = sortedPairs.map((value) => { + return computePoolAddress({ + poolDeployer: POOL_DEPLOYER_ADDRESS, + tokenA: value[0], + tokenB: value[1], + }) + }) + // console.log('poolAddresses:', poolAddresses) + console.log('poolAddresses lenght:', poolAddresses.length) + + const poolsGlobalSpace = () => + Promise.allSettled(poolAddresses.map((poolAddress) => fetchPoolGlobalState(poolAddress))).then((results) => + results + .map((result, index) => { + if (result.status === 'fulfilled') { + return { value: result.value, poolAddress: poolAddresses[index] } + } + }) + .filter((result) => result), + ) + + const poolsLiquidity = () => + Promise.allSettled(poolAddresses.map((poolAddress) => fetchPoolLiquidity(poolAddress))).then((results) => + results + .map((result, index) => { + if (result.status === 'fulfilled') { + return { value: result.value, poolAddress: poolAddresses[index] } + } + }) + .filter((result) => result), + ) + + const getPoolsGlobalSpaceResults = async () => { + try { + const results = await poolsGlobalSpace() + return results + } catch (error) { + console.error('Error fetching pool globalSpace results:', error) + } + } + + const getPoolsLiquiditiesResults = async () => { + try { + const results = await poolsLiquidity() + return results + } catch (error) { + console.error('Error fetching pool liquidity results:', error) + } + } + + const [liquidityResults, globalSpaceResults] = await Promise.all([ + await getPoolsLiquiditiesResults(), + await getPoolsGlobalSpaceResults(), + ]) + console.log('globalSpaceResults:', globalSpaceResults) + console.log('liquidityResults:', liquidityResults) + + console.log('globalSpaceResults length:', globalSpaceResults.length) + console.log('liquidityResults length:', liquidityResults.length) + + const combinedResults = poolAddresses.flatMap((poolAddress) => { + const liquidityResult = liquidityResults.find( + ({ poolAddress: liquidityPoolAddress }) => liquidityPoolAddress === poolAddress, + ) + + const globalSpaceResult = globalSpaceResults.find( + ({ poolAddress: globalSpacePoolAddress }) => globalSpacePoolAddress === poolAddress, + ) + + if (globalSpaceResult && liquidityResult) { + return new Pool( + tokenA, + tokenB, + globalSpaceResult.value.fee, + globalSpaceResult.value.price, + liquidityResult ? liquidityResult.value : null, + globalSpaceResult.value.tick, + ) + } + + return [] + }) + + return combinedResults +} + +const fetchPoolGlobalState = async (poolAddress: string) => { + return getPoolsContract(poolAddress).globalState() +} + +const fetchPoolLiquidity = async (poolAddress: string) => { + return getPoolsContract(poolAddress).liquidity() +} diff --git a/src/entities/trades/swapr-v3/route.ts b/src/entities/trades/swapr-v3/route.ts new file mode 100644 index 00000000..6345100c --- /dev/null +++ b/src/entities/trades/swapr-v3/route.ts @@ -0,0 +1,89 @@ +import invariant from 'tiny-invariant' + +import { Currency, Price, Token } from '@uniswap/sdk-core' +import { Pool } from './pool' + +/** + * Represents a list of pools through which a swap can occur + * @template TInput The input token + * @template TOutput The output token + */ +export class Route { + public readonly pools: Pool[] + public readonly tokenPath: Token[] + public readonly input: TInput + public readonly output: TOutput + + /** + * Creates an instance of route. + * @param pools An array of `Pool` objects, ordered by the route the swap will take + * @param input The input token + * @param output The output token + */ + public constructor(pools: Pool[], input: TInput, output: TOutput) { + invariant(pools.length > 0, 'POOLS') + + const chainId = pools[0].chainId + const allOnSameChain = pools.every((pool) => pool.chainId === chainId) + invariant(allOnSameChain, 'CHAIN_IDS') + + const wrappedInput = input.wrapped + invariant(pools[0].involvesToken(wrappedInput), 'INPUT') + + invariant(pools[pools.length - 1].involvesToken(output.wrapped), 'OUTPUT') + + /** + * Normalizes token0-token1 order and selects the next token/fee step to add to the path + * */ + const tokenPath: Token[] = [wrappedInput] + for (const [i, pool] of pools.entries()) { + const currentInputToken = tokenPath[i] + invariant(currentInputToken.equals(pool.token0) || currentInputToken.equals(pool.token1), 'PATH') + const nextToken = currentInputToken.equals(pool.token0) ? pool.token1 : pool.token0 + tokenPath.push(nextToken) + } + + this.pools = pools + this.tokenPath = tokenPath + this.input = input + this.output = output ?? tokenPath[tokenPath.length - 1] + } + + private _midPrice: Price | null = null + + /** + * Returns the mid price of the route + */ + public get midPrice(): Price { + if (this._midPrice !== null) return this._midPrice + + const price = this.pools.slice(1).reduce( + ({ nextInput, price }, pool) => { + return nextInput.equals(pool.token0) + ? { + nextInput: pool.token1, + price: price.multiply(pool.token0Price), + } + : { + nextInput: pool.token0, + price: price.multiply(pool.token1Price), + } + }, + this.pools[0].token0.equals(this.input.wrapped) + ? { + nextInput: this.pools[0].token1, + price: this.pools[0].token0Price, + } + : { + nextInput: this.pools[0].token0, + price: this.pools[0].token1Price, + }, + ).price + + return (this._midPrice = new Price(this.input, this.output, price.denominator, price.numerator)) + } + + public get chainId(): number { + return this.pools[0].chainId + } +} diff --git a/src/entities/trades/swapr-v3/routes.ts b/src/entities/trades/swapr-v3/routes.ts new file mode 100644 index 00000000..5442fe44 --- /dev/null +++ b/src/entities/trades/swapr-v3/routes.ts @@ -0,0 +1,47 @@ +import { Currency, Token } from '@uniswap/sdk-core' +import { Pool } from './pool' +import { Route } from './route' +import { getPools } from './pools' + +/** + * Returns true if poolA is equivalent to poolB + * @param poolA one of the two pools + * @param poolB the other pool + */ +function poolEquals(poolA: Pool, poolB: Pool): boolean { + return ( + poolA === poolB || + (poolA.token0.equals(poolB.token0) && poolA.token1.equals(poolB.token1) && poolA.fee === poolB.fee) + ) +} + +export function computeAllRoutes( + pools: Pool[], + chainId: number, + currentPath: Pool[] = [], + allPaths: Route[] = [], + maxHops = 2, +): Route[] { + for (const pool of pools) { + if (!pool.involvesToken(pool.token0) || currentPath.some((pathPool) => poolEquals(pool, pathPool))) continue + + const outputToken = pool.token0.equals(pool.token0) ? pool.token1 : pool.token0 + if (outputToken.equals(pool.token1)) { + allPaths.push(new Route([...currentPath, pool], pool.token0, pool.token1)) + } else if (maxHops > 1) { + computeAllRoutes(pools, chainId, [...currentPath, pool], allPaths, maxHops - 1) + } + } + + return allPaths +} + +export async function getRoutes( + currencyIn: Token, + currencyOut: Token, + chainId: number, +): Promise[]> { + const pools = await getPools(currencyIn, currencyOut) + console.log('pools size', pools.length) + return computeAllRoutes(pools, chainId, [], [], 3) +} diff --git a/src/entities/trades/swapr-v3/swapr-v3.spec.ts b/src/entities/trades/swapr-v3/swapr-v3.spec.ts index f04e5e4e..e0439247 100644 --- a/src/entities/trades/swapr-v3/swapr-v3.spec.ts +++ b/src/entities/trades/swapr-v3/swapr-v3.spec.ts @@ -6,49 +6,98 @@ import { Percent, TokenAmount } from '../../fractions' import { Token } from '../../token' const maximumSlippage = new Percent('3', '100') -const tokenUSDC = new Token(ChainId.GNOSIS, '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', 6, 'USDC', 'USDC') const tokenWXDAI = new Token(ChainId.GNOSIS, '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', 18, 'WXDAI', 'WXDAI') -//const tokenWETH = new Token(ChainId.GNOSIS, '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1', 18, 'WETH', 'WETH') +const tokenUSDC = new Token(ChainId.GNOSIS, '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', 6, 'USDC', 'USDC') +const tokenWETH = new Token(ChainId.GNOSIS, '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1', 18, 'WETH', 'Wrapped WETH') +const tokenSWPR = new Token(ChainId.GNOSIS, '0x532801ED6f82FFfD2DAB70A19fC2d7B2772C4f4b', 18, 'SWPR', 'Swapr token') +// const tokenGNO = new Token(ChainId.GNOSIS, '0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb', 18, 'GNO', 'Gnosis token') -const VITALIK_ADDRESS = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045' -// const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' -const recipient = VITALIK_ADDRESS +const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' +const recipient = NULL_ADDRESS describe('SwaprV3', () => { describe('getQuote', () => { - test('should return a exact input quote on Gnosis for USDC - WXDAI', async () => { - const recipient = VITALIK_ADDRESS - const currencyAmount = new TokenAmount(tokenUSDC, parseUnits('2', 6).toString()) + test('should return a EXACT INPUT quote on Gnosis for USDC - WXDAI', async () => { + const currencyAmount = new TokenAmount(tokenUSDC, parseUnits('2.59', 6).toString()) const trade = await SwaprV3Trade.getQuote({ - quoteCurrency: tokenWXDAI, amount: currencyAmount, + quoteCurrency: tokenWXDAI, maximumSlippage, recipient, tradeType: TradeType.EXACT_INPUT, }) - console.log('trade', trade) + // console.log('trade', trade) expect(trade).toBeDefined() expect(trade?.chainId).toEqual(ChainId.GNOSIS) expect(trade?.tradeType).toEqual(TradeType.EXACT_INPUT) expect(trade?.outputAmount.currency.address).toBe(tokenWXDAI.address) }) - test('should return a exact output quote on Gnosis for USDC - WXDAI', async () => { - const recipient = VITALIK_ADDRESS - const currencyAmount = new TokenAmount(tokenUSDC, parseUnits('2', 6).toString()) + + test('should return a EXACT INPUT quote on Gnosis for WETH - WXDAI', async () => { + const currencyAmount = new TokenAmount(tokenWETH, parseUnits('1', 18).toString()) const trade = await SwaprV3Trade.getQuote({ - quoteCurrency: tokenWXDAI, amount: currencyAmount, + quoteCurrency: tokenWXDAI, maximumSlippage, recipient, - tradeType: TradeType.EXACT_OUTPUT, + tradeType: TradeType.EXACT_INPUT, }) - console.log('trade', trade) + expect(trade).toBeDefined() expect(trade?.chainId).toEqual(ChainId.GNOSIS) - expect(trade?.tradeType).toEqual(TradeType.EXACT_OUTPUT) + expect(trade?.tradeType).toEqual(TradeType.EXACT_INPUT) expect(trade?.outputAmount.currency.address).toBe(tokenWXDAI.address) }) + + test('should return a EXACT INPUT quote on Gnosis for SWPR - WXDAI', async () => { + const currencyAmount = new TokenAmount(tokenWXDAI, parseUnits('1', 18).toString()) + const trade = await SwaprV3Trade.getQuote({ + amount: currencyAmount, + quoteCurrency: tokenSWPR, + maximumSlippage, + recipient, + tradeType: TradeType.EXACT_INPUT, + }) + + expect(trade).toBeDefined() + expect(trade?.chainId).toEqual(ChainId.GNOSIS) + expect(trade?.tradeType).toEqual(TradeType.EXACT_INPUT) + expect(trade?.outputAmount.currency.address).toBe(tokenSWPR.address) + }) + + // test('should return a EXACT INPUT quote on Gnosis for GNO - WETH', async () => { + // const currencyAmount = new TokenAmount(tokenGNO, parseUnits('1', 18).toString()) + // const trade = await SwaprV3Trade.getQuote({ + // amount: currencyAmount, + // quoteCurrency: tokenWETH, + // maximumSlippage, + // recipient, + // tradeType: TradeType.EXACT_INPUT, + // }) + + // expect(trade).toBeDefined() + // expect(trade?.chainId).toEqual(ChainId.GNOSIS) + // expect(trade?.tradeType).toEqual(TradeType.EXACT_INPUT) + // expect(trade?.outputAmount.currency.address).toBe(tokenWETH.address) + // }) + + // test('should return a exact output quote on Gnosis for WXDAI - USDC', async () => { + // const currencyAmount = new TokenAmount(tokenWXDAI, parseUnits('2', 18).toString()) + // const trade = await SwaprV3Trade.getQuote({ + // quoteCurrency: tokenUSDC, + // amount: currencyAmount, + // maximumSlippage, + // recipient, + // tradeType: TradeType.EXACT_OUTPUT, + // }) + // console.log('trade', trade) + // expect(trade).toBeDefined() + // expect(trade?.chainId).toEqual(ChainId.GNOSIS) + // expect(trade?.tradeType).toEqual(TradeType.EXACT_OUTPUT) + // expect(trade?.outputAmount.currency.address).toBe(tokenUSDC.address) + // }) }) + describe('Swap', () => { test('should return a swap for Gnosis for USDC - WXDAI', async () => { const currencyAmount = new TokenAmount(tokenUSDC, parseUnits('2', 6).toString()) @@ -66,10 +115,10 @@ describe('SwaprV3', () => { account: recipient, } - const swapr = await trade?.swapTransaction(swapOptions) + const swap = await trade?.swapTransaction(swapOptions) - console.log('swapr', swapr) - expect(swapr !== undefined) + console.log('swap', swap) + expect(swap !== undefined) }) }) }) From 730286418916ea258265b459be0f4da3ceaf8007 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 20 Dec 2023 13:51:03 +0000 Subject: [PATCH 05/26] add fee support --- src/entities/trades/swapr-v3/SwaprV3.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index af81f988..9efa7ee2 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -48,6 +48,7 @@ interface SwaprV3ConstructorParams { tradeType: TradeType chainId: number priceImpact: Percent + fee: Percent } export class SwaprV3Trade extends TradeWithSwapTransaction { @@ -58,6 +59,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { priceImpact, tradeType, chainId, + fee, }: SwaprV3ConstructorParams) { super({ details: undefined, @@ -74,7 +76,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { numerator: outputAmount.raw, }), priceImpact, - fee: new Percent('0', '100'), + fee, approveAddress: GNOSIS_CONTRACTS['router'], }) } @@ -109,10 +111,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { if (tradeType === TradeType.EXACT_INPUT) { const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) - console.log('routes:', routes) - - console.log('tokenIn:', tokenIn) - console.log('tokenOut:', tokenOut) + console.log('routes[0]', routes[0]) const quotedAmountOut = await getQuoterContract() .callStatic.quoteExactInputSingle( @@ -126,9 +125,12 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { }) const amountInReadable = amount.toSignificant() - console.log('amountIn :', amountInReadable) const amountOutReadable = formatUnits(quotedAmountOut, tokenOut.decimals) + const fee = new Percent(routes[0].pools[0].fee.toString(), '1000000') + + console.log('amountIn :', amountInReadable) console.log('amountOut:', amountOutReadable) + console.log('fee', fee.toSignificant()) if (quotedAmountOut) { return new SwaprV3Trade({ @@ -138,6 +140,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { tradeType: tradeType, chainId: chainId, priceImpact: new Percent('0', '1000'), + fee, }) } } else { @@ -170,6 +173,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { tradeType: tradeType, chainId: chainId, priceImpact: new Percent('0', '1000'), + fee: new Percent('0', '100'), }) } } @@ -227,7 +231,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { recipient: to, deadline: dayjs().add(30, 'm').unix(), sqrtPriceLimitX96: 0, - // fee: this.fee --> route.pools[0].fee, + fee: this.fee, } const exactInputSingleParams = { From 2b082e404e03032899be8614febfd4b577f3f5e3 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 21 Dec 2023 18:54:13 +0000 Subject: [PATCH 06/26] Add support for Quote Exact Out --- src/entities/trades/swapr-v3/SwaprV3.ts | 33 +++++++++---------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index 9efa7ee2..0ba67e6c 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -15,7 +15,7 @@ import { SWAPR_ALGEBRA_ROUTER_ABI, SWAPR_ALGEBRA_QUOTER_ABI, SWAPR_ALGEBRA_POOL_ import { Contract, UnsignedTransaction } from 'ethers' import { AddressZero } from '@ethersproject/constants' import { TradeOptions } from '../interfaces/trade-options' -import { formatUnits, parseUnits } from '@ethersproject/units' +import { parseUnits } from '@ethersproject/units' import { getRoutes } from './routes' import { Route } from './route' @@ -95,7 +95,6 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { ): Promise { const chainId = tryGetChainId(amount, quoteCurrency) invariant(chainId, 'SwaprV3Trade.getQuote: chainId is required') - // Defaults recipient = recipient || AddressZero maximumSlippage = maximumSlippage || 0 @@ -122,16 +121,11 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { ) .catch((error) => { console.error(`Error sending quoteExactInputSingle transaction: ${error}`) + return null }) - const amountInReadable = amount.toSignificant() - const amountOutReadable = formatUnits(quotedAmountOut, tokenOut.decimals) const fee = new Percent(routes[0].pools[0].fee.toString(), '1000000') - console.log('amountIn :', amountInReadable) - console.log('amountOut:', amountOutReadable) - console.log('fee', fee.toSignificant()) - if (quotedAmountOut) { return new SwaprV3Trade({ maximumSlippage, @@ -139,32 +133,27 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { outputAmount: new TokenAmount(quoteCurrency, quotedAmountOut), tradeType: tradeType, chainId: chainId, - priceImpact: new Percent('0', '1000'), + priceImpact: new Percent('0', '1000'), // todo: fix this fee, }) } } else { + const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) + + const fee = new Percent(routes[0].pools[0].fee.toString(), '1000000') + const quotedAmountIn = await getQuoterContract() .callStatic.quoteExactOutputSingle( - amount.currency.address, quoteCurrency.address, + amount.currency.address, parseUnits(amount.toSignificant(), amount.currency.decimals), 0, ) .catch((error) => { console.error(`Error sending quoteExactOutputSingle transaction: ${error}`) + return null }) - console.log('====== SDK -> EXACT_OUTPUT ========') - console.log('quoteCurrency', quoteCurrency) - console.log('amount', amount) - console.log('amount.toSignificant()', amount.toSignificant()) - console.log('amount.currency.decimals', amount.currency.decimals) - console.log('quotedAmountIn:', quotedAmountIn.toString()) - - // const amountIn: string = toHex(trade.maximumAmountIn(options.slippageTolerance, inputAmount).quotient) - // const amountOut: string = toHex(trade.minimumAmountOut(options.slippageTolerance, outputAmount).quotient) - if (quotedAmountIn) { return new SwaprV3Trade({ maximumSlippage, @@ -172,8 +161,8 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { outputAmount: amount, tradeType: tradeType, chainId: chainId, - priceImpact: new Percent('0', '1000'), - fee: new Percent('0', '100'), + priceImpact: new Percent('0', '1000'), // todo: fix this + fee: fee, }) } } From 9805ab5d31a819b4d997179670bf732c200b7248 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 21 Dec 2023 18:57:49 +0000 Subject: [PATCH 07/26] remove comment code --- src/entities/trades/swapr-v3/SwaprV3.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index 0ba67e6c..70b47452 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -19,10 +19,6 @@ import { parseUnits } from '@ethersproject/units' import { getRoutes } from './routes' import { Route } from './route' -// const quoterInterface = new Interface(SWAPR_ALGEBRA_QUOTER_ABI) - -// import { getBestV3TradeExactIn } from './algebra/getBestV3Trades' - // Constants export const GNOSIS_CONTRACTS = { quoter: '0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7', From ff1c89a3e5ca34e1d96a15f729c63c91dcd0f2cd Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Fri, 22 Dec 2023 17:59:46 +0000 Subject: [PATCH 08/26] update graphql-request --- package-lock.json | 277 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 239 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ecd2f71..60a62a11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "dayjs": "^1.11.0", "debug": "^4.3.4", "decimal.js-light": "^2.5.1", - "graphql-request": "^4.3.0", + "graphql-request": "^6.1.0", "jsbi": "^3.1.1", "lodash.flatmap": "^4.5.0", "memoizee": "^0.4.15", @@ -40,7 +40,7 @@ "devDependencies": { "@graphql-codegen/cli": "2.6.2", "@graphql-codegen/typescript": "2.5.1", - "@graphql-codegen/typescript-graphql-request": "^4.4.10", + "@graphql-codegen/typescript-graphql-request": "^6.0.1", "@graphql-codegen/typescript-operations": "^2.4.2", "@testing-library/jest-dom": "^5.16.4", "@types/big.js": "^4.0.5", @@ -1331,6 +1331,30 @@ "paraswap-core": "^1.0.2" } }, + "node_modules/@cowprotocol/cow-sdk/node_modules/extract-files": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz", + "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==", + "engines": { + "node": "^10.17.0 || ^12.0.0 || >= 13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/jaydenseric" + } + }, + "node_modules/@cowprotocol/cow-sdk/node_modules/graphql-request": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-4.3.0.tgz", + "integrity": "sha512-2v6hQViJvSsifK606AliqiNiijb1uwWp6Re7o0RTyH+uRTv/u7Uqm2g4Fjq/LgZIzARB38RZEvVBFOQOVdlBow==", + "dependencies": { + "cross-fetch": "^3.1.5", + "extract-files": "^9.0.0", + "form-data": "^3.0.0" + }, + "peerDependencies": { + "graphql": "14 - 16" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2366,22 +2390,79 @@ } }, "node_modules/@graphql-codegen/typescript-graphql-request": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-4.5.8.tgz", - "integrity": "sha512-XsuAA35Ou03LsklNgnIWXZ5HOHsJ5w1dBuDKtvqM9rD0cAI8x0f4TY0n6O1EraSBSvyHLP3npb1lOTPZzG2TjA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-6.0.1.tgz", + "integrity": "sha512-aScw7ICyscW7bYLh2HyjQU3geCAjvFy6sRIlzgdkeFvcKBdjCil69upkyZAyntnSno2C4ZoUv7sHOpyQ9hQmFQ==", "dev": true, "dependencies": { - "@graphql-codegen/plugin-helpers": "^2.7.2", + "@graphql-codegen/plugin-helpers": "^3.0.0", "@graphql-codegen/visitor-plugin-common": "2.13.1", "auto-bind": "~4.0.0", - "tslib": "~2.4.0" + "tslib": "~2.6.0" + }, + "engines": { + "node": ">= 16.0.0" }, "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", - "graphql-request": "^3.4.0 || ^4.0.0 || ^5.0.0", + "graphql-request": "^6.0.0", "graphql-tag": "^2.0.0" } }, + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/plugin-helpers": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-3.1.2.tgz", + "integrity": "sha512-emOQiHyIliVOIjKVKdsI5MXj312zmRDwmHpyUTZMjfpvxq/UVAHUJIVdVf+lnjjrI+LXBTgMlTWTgHQfmICxjg==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^9.0.0", + "change-case-all": "1.0.15", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.4.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/plugin-helpers/node_modules/@graphql-tools/utils": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", + "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "dev": true, + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/plugin-helpers/node_modules/change-case-all": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.15.tgz", + "integrity": "sha512-3+GIFhk3sNuvFAJKU46o26OdzudQlPNBCu1ZQi3cMeMHhty1bhDxu2WrEilVNYaGvqUtR1VSigFcJOiS13dRhQ==", + "dev": true, + "dependencies": { + "change-case": "^4.1.2", + "is-lower-case": "^2.0.2", + "is-upper-case": "^2.0.2", + "lower-case": "^2.0.2", + "lower-case-first": "^2.0.2", + "sponge-case": "^1.0.1", + "swap-case": "^2.0.2", + "title-case": "^3.0.3", + "upper-case": "^2.0.2", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/plugin-helpers/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/visitor-plugin-common": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.13.1.tgz", @@ -2403,6 +2484,35 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/@graphql-codegen/plugin-helpers": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.7.2.tgz", + "integrity": "sha512-kln2AZ12uii6U59OQXdjLk5nOlh1pHis1R98cDZGFnfaiAbX9V3fxcZ1MMJkB7qFUymTALzyjZoXXdyVmPMfRg==", + "dev": true, + "dependencies": { + "@graphql-tools/utils": "^8.8.0", + "change-case-all": "1.0.14", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.4.0" + }, + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/@graphql-codegen/visitor-plugin-common/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + }, + "node_modules/@graphql-codegen/typescript-graphql-request/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/@graphql-codegen/typescript-operations": { "version": "2.5.10", "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-2.5.10.tgz", @@ -10039,27 +10149,23 @@ } }, "node_modules/graphql-request": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-4.3.0.tgz", - "integrity": "sha512-2v6hQViJvSsifK606AliqiNiijb1uwWp6Re7o0RTyH+uRTv/u7Uqm2g4Fjq/LgZIzARB38RZEvVBFOQOVdlBow==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", + "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", "dependencies": { - "cross-fetch": "^3.1.5", - "extract-files": "^9.0.0", - "form-data": "^3.0.0" + "@graphql-typed-document-node/core": "^3.2.0", + "cross-fetch": "^3.1.5" }, "peerDependencies": { "graphql": "14 - 16" } }, - "node_modules/graphql-request/node_modules/extract-files": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz", - "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==", - "engines": { - "node": "^10.17.0 || ^12.0.0 || >= 13.7.0" - }, - "funding": { - "url": "https://github.com/sponsors/jaydenseric" + "node_modules/graphql-request/node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "node_modules/graphql-tag": { @@ -19818,6 +19924,23 @@ "multiformats": "^9.6.4", "paraswap": "^5.2.0", "paraswap-core": "^1.0.2" + }, + "dependencies": { + "extract-files": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz", + "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==" + }, + "graphql-request": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-4.3.0.tgz", + "integrity": "sha512-2v6hQViJvSsifK606AliqiNiijb1uwWp6Re7o0RTyH+uRTv/u7Uqm2g4Fjq/LgZIzARB38RZEvVBFOQOVdlBow==", + "requires": { + "cross-fetch": "^3.1.5", + "extract-files": "^9.0.0", + "form-data": "^3.0.0" + } + } } }, "@cspotcode/source-map-support": { @@ -20512,17 +20635,67 @@ } }, "@graphql-codegen/typescript-graphql-request": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-4.5.8.tgz", - "integrity": "sha512-XsuAA35Ou03LsklNgnIWXZ5HOHsJ5w1dBuDKtvqM9rD0cAI8x0f4TY0n6O1EraSBSvyHLP3npb1lOTPZzG2TjA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-6.0.1.tgz", + "integrity": "sha512-aScw7ICyscW7bYLh2HyjQU3geCAjvFy6sRIlzgdkeFvcKBdjCil69upkyZAyntnSno2C4ZoUv7sHOpyQ9hQmFQ==", "dev": true, "requires": { - "@graphql-codegen/plugin-helpers": "^2.7.2", + "@graphql-codegen/plugin-helpers": "^3.0.0", "@graphql-codegen/visitor-plugin-common": "2.13.1", "auto-bind": "~4.0.0", - "tslib": "~2.4.0" + "tslib": "~2.6.0" }, "dependencies": { + "@graphql-codegen/plugin-helpers": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-3.1.2.tgz", + "integrity": "sha512-emOQiHyIliVOIjKVKdsI5MXj312zmRDwmHpyUTZMjfpvxq/UVAHUJIVdVf+lnjjrI+LXBTgMlTWTgHQfmICxjg==", + "dev": true, + "requires": { + "@graphql-tools/utils": "^9.0.0", + "change-case-all": "1.0.15", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.4.0" + }, + "dependencies": { + "@graphql-tools/utils": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", + "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "dev": true, + "requires": { + "@graphql-typed-document-node/core": "^3.1.1", + "tslib": "^2.4.0" + } + }, + "change-case-all": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.15.tgz", + "integrity": "sha512-3+GIFhk3sNuvFAJKU46o26OdzudQlPNBCu1ZQi3cMeMHhty1bhDxu2WrEilVNYaGvqUtR1VSigFcJOiS13dRhQ==", + "dev": true, + "requires": { + "change-case": "^4.1.2", + "is-lower-case": "^2.0.2", + "is-upper-case": "^2.0.2", + "lower-case": "^2.0.2", + "lower-case-first": "^2.0.2", + "sponge-case": "^1.0.1", + "swap-case": "^2.0.2", + "title-case": "^3.0.3", + "upper-case": "^2.0.2", + "upper-case-first": "^2.0.2" + } + }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } + } + }, "@graphql-codegen/visitor-plugin-common": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.13.1.tgz", @@ -20539,7 +20712,35 @@ "graphql-tag": "^2.11.0", "parse-filepath": "^1.0.2", "tslib": "~2.4.0" + }, + "dependencies": { + "@graphql-codegen/plugin-helpers": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.7.2.tgz", + "integrity": "sha512-kln2AZ12uii6U59OQXdjLk5nOlh1pHis1R98cDZGFnfaiAbX9V3fxcZ1MMJkB7qFUymTALzyjZoXXdyVmPMfRg==", + "dev": true, + "requires": { + "@graphql-tools/utils": "^8.8.0", + "change-case-all": "1.0.14", + "common-tags": "1.8.2", + "import-from": "4.0.0", + "lodash": "~4.17.0", + "tslib": "~2.4.0" + } + }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true + } } + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true } } }, @@ -26690,19 +26891,19 @@ } }, "graphql-request": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-4.3.0.tgz", - "integrity": "sha512-2v6hQViJvSsifK606AliqiNiijb1uwWp6Re7o0RTyH+uRTv/u7Uqm2g4Fjq/LgZIzARB38RZEvVBFOQOVdlBow==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", + "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", "requires": { - "cross-fetch": "^3.1.5", - "extract-files": "^9.0.0", - "form-data": "^3.0.0" + "@graphql-typed-document-node/core": "^3.2.0", + "cross-fetch": "^3.1.5" }, "dependencies": { - "extract-files": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz", - "integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==" + "@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "requires": {} } } }, From 8f57ed03d2ee20da35786127cd51336a15fe45eb Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Fri, 22 Dec 2023 18:03:27 +0000 Subject: [PATCH 09/26] clean up the code --- src/entities/trades/swapr-v3/SwaprV3.ts | 13 +++++---- .../trades/swapr-v3/{ => entities}/pool.ts | 22 +++++++-------- .../trades/swapr-v3/{ => entities}/route.ts | 2 +- .../trades/swapr-v3/{pools.ts => getPools.ts} | 27 +++++++------------ .../swapr-v3/{routes.ts => getRoutes.ts} | 11 ++++---- src/entities/trades/swapr-v3/swapr-v3.spec.ts | 2 +- .../{ => utils}/computePoolAddress.ts | 4 +-- .../swapr-v3/{ => utils}/encode-route.ts | 8 +++--- 8 files changed, 40 insertions(+), 49 deletions(-) rename src/entities/trades/swapr-v3/{ => entities}/pool.ts (96%) rename src/entities/trades/swapr-v3/{ => entities}/route.ts (99%) rename src/entities/trades/swapr-v3/{pools.ts => getPools.ts} (87%) rename src/entities/trades/swapr-v3/{routes.ts => getRoutes.ts} (88%) rename src/entities/trades/swapr-v3/{ => utils}/computePoolAddress.ts (94%) rename src/entities/trades/swapr-v3/{ => utils}/encode-route.ts (89%) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index 70b47452..5616cb15 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -16,8 +16,8 @@ import { Contract, UnsignedTransaction } from 'ethers' import { AddressZero } from '@ethersproject/constants' import { TradeOptions } from '../interfaces/trade-options' import { parseUnits } from '@ethersproject/units' -import { getRoutes } from './routes' -import { Route } from './route' +import { getRoutes } from './getRoutes' +import { Route } from './entities/route' // Constants export const GNOSIS_CONTRACTS = { @@ -87,7 +87,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { static async getQuote( { amount, quoteCurrency, tradeType, recipient, maximumSlippage }: any, - provider?: BaseProvider, + provider?: BaseProvider ): Promise { const chainId = tryGetChainId(amount, quoteCurrency) invariant(chainId, 'SwaprV3Trade.getQuote: chainId is required') @@ -101,19 +101,18 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { invariant( (await provider.getNetwork()).chainId == chainId, - `SwaprV3Trade.getQuote: currencies chainId does not match provider's chainId`, + `SwaprV3Trade.getQuote: currencies chainId does not match provider's chainId` ) if (tradeType === TradeType.EXACT_INPUT) { const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) - console.log('routes[0]', routes[0]) const quotedAmountOut = await getQuoterContract() .callStatic.quoteExactInputSingle( tokenIn.address, tokenOut.address, parseUnits(amount.toSignificant(), amount.currency.decimals), - 0, + 0 ) .catch((error) => { console.error(`Error sending quoteExactInputSingle transaction: ${error}`) @@ -143,7 +142,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { quoteCurrency.address, amount.currency.address, parseUnits(amount.toSignificant(), amount.currency.decimals), - 0, + 0 ) .catch((error) => { console.error(`Error sending quoteExactOutputSingle transaction: ${error}`) diff --git a/src/entities/trades/swapr-v3/pool.ts b/src/entities/trades/swapr-v3/entities/pool.ts similarity index 96% rename from src/entities/trades/swapr-v3/pool.ts rename to src/entities/trades/swapr-v3/entities/pool.ts index 130e718a..9a9c355f 100644 --- a/src/entities/trades/swapr-v3/pool.ts +++ b/src/entities/trades/swapr-v3/entities/pool.ts @@ -1,7 +1,7 @@ import { BigintIsh, CurrencyAmount, Price, Token } from '@uniswap/sdk-core' import JSBI from 'jsbi' import invariant from 'tiny-invariant' -import { POOL_DEPLOYER_ADDRESS } from './constants' +import { POOL_DEPLOYER_ADDRESS } from '../constants' import { FeeAmount, LiquidityMath, @@ -63,7 +63,7 @@ export class Pool { sqrtRatioX96: BigintIsh, liquidity: BigintIsh, tickCurrent: number, - ticks: TickDataProvider | (Tick | TickConstructorArgs)[] = NO_TICK_DATA_PROVIDER_DEFAULT, + ticks: TickDataProvider | (Tick | TickConstructorArgs)[] = NO_TICK_DATA_PROVIDER_DEFAULT ) { invariant(Number.isInteger(fee) && fee < 1_000_000, 'FEE') @@ -72,7 +72,7 @@ export class Pool { invariant( JSBI.greaterThanOrEqual(JSBI.BigInt(sqrtRatioX96), tickCurrentSqrtRatioX96) && JSBI.lessThanOrEqual(JSBI.BigInt(sqrtRatioX96), nextTickSqrtRatioX96), - 'PRICE_BOUNDS', + 'PRICE_BOUNDS' ) // always create a copy of the list since we want the pool's tick list to be immutable ;[this.token0, this.token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] @@ -95,7 +95,7 @@ export class Pool { this.token0, this.token1, Q192, - JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96), + JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96) )) ) } @@ -112,7 +112,7 @@ export class Pool { this.token1, this.token0, JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96), - Q192, + Q192 )) ) } @@ -165,7 +165,7 @@ export class Pool { */ public async getOutputAmount( inputAmount: CurrencyAmount, - sqrtPriceLimitX96?: JSBI, + sqrtPriceLimitX96?: JSBI ): Promise<[CurrencyAmount, Pool]> { invariant(this.involvesToken(inputAmount.currency), 'TOKEN') @@ -192,7 +192,7 @@ export class Pool { */ public async getInputAmount( outputAmount: CurrencyAmount, - sqrtPriceLimitX96?: JSBI, + sqrtPriceLimitX96?: JSBI ): Promise<[CurrencyAmount, Pool]> { invariant(outputAmount.currency.isToken && this.involvesToken(outputAmount.currency), 'TOKEN') @@ -224,7 +224,7 @@ export class Pool { private async swap( zeroForOne: boolean, amountSpecified: JSBI, - sqrtPriceLimitX96?: JSBI, + sqrtPriceLimitX96?: JSBI ): Promise<{ amountCalculated: JSBI; sqrtRatioX96: JSBI; liquidity: JSBI; tickCurrent: number }> { if (!sqrtPriceLimitX96) sqrtPriceLimitX96 = zeroForOne @@ -262,7 +262,7 @@ export class Pool { ;[step.tickNext, step.initialized] = await this.tickDataProvider.nextInitializedTickWithinOneWord( state.tick, zeroForOne, - this.tickSpacing, + this.tickSpacing ) if (step.tickNext < TickMath.MIN_TICK) { @@ -283,13 +283,13 @@ export class Pool { : step.sqrtPriceNextX96, state.liquidity, state.amountSpecifiedRemaining, - this.fee, + this.fee ) if (exactInput) { state.amountSpecifiedRemaining = JSBI.subtract( state.amountSpecifiedRemaining, - JSBI.add(step.amountIn, step.feeAmount), + JSBI.add(step.amountIn, step.feeAmount) ) state.amountCalculated = JSBI.subtract(state.amountCalculated, step.amountOut) } else { diff --git a/src/entities/trades/swapr-v3/route.ts b/src/entities/trades/swapr-v3/entities/route.ts similarity index 99% rename from src/entities/trades/swapr-v3/route.ts rename to src/entities/trades/swapr-v3/entities/route.ts index 6345100c..946f5b1b 100644 --- a/src/entities/trades/swapr-v3/route.ts +++ b/src/entities/trades/swapr-v3/entities/route.ts @@ -77,7 +77,7 @@ export class Route { : { nextInput: this.pools[0].token0, price: this.pools[0].token1Price, - }, + } ).price return (this._midPrice = new Price(this.input, this.output, price.denominator, price.numerator)) diff --git a/src/entities/trades/swapr-v3/pools.ts b/src/entities/trades/swapr-v3/getPools.ts similarity index 87% rename from src/entities/trades/swapr-v3/pools.ts rename to src/entities/trades/swapr-v3/getPools.ts index 07c56643..904e6dca 100644 --- a/src/entities/trades/swapr-v3/pools.ts +++ b/src/entities/trades/swapr-v3/getPools.ts @@ -1,14 +1,14 @@ // @ts-nocheck import { Token } from '@uniswap/sdk-core' import { POOL_DEPLOYER_ADDRESS, baseTokens } from './constants' -import { computePoolAddress } from './computePoolAddress' +import { computePoolAddress } from './utils/computePoolAddress' import { getPoolsContract } from './SwaprV3' -import { Pool } from './pool' +import { Pool } from './entities/pool' const GNOSIS_CHAIN_ID = 100 const getBaseTokens: Token[] = baseTokens.map( - ({ address, decimals, symbol, name }) => new Token(GNOSIS_CHAIN_ID, address, decimals, symbol, name), + ({ address, decimals, symbol, name }) => new Token(GNOSIS_CHAIN_ID, address, decimals, symbol, name) ) export const setupTokens = (currencyIn: Token, currencyOut: Token) => { @@ -17,7 +17,7 @@ export const setupTokens = (currencyIn: Token, currencyOut: Token) => { currencyIn.address, currencyIn.decimals, currencyIn.symbol, - currencyIn.name, + currencyIn.name ) const tokenOut = new Token( @@ -25,7 +25,7 @@ export const setupTokens = (currencyIn: Token, currencyOut: Token) => { currencyOut.address, currencyOut.decimals, currencyOut.symbol, - currencyOut.name, + currencyOut.name ) const [tokenA, tokenB] = [tokenIn?.wrapped, tokenOut?.wrapped] @@ -78,8 +78,6 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { tokenB: value[1], }) }) - // console.log('poolAddresses:', poolAddresses) - console.log('poolAddresses lenght:', poolAddresses.length) const poolsGlobalSpace = () => Promise.allSettled(poolAddresses.map((poolAddress) => fetchPoolGlobalState(poolAddress))).then((results) => @@ -89,7 +87,7 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { return { value: result.value, poolAddress: poolAddresses[index] } } }) - .filter((result) => result), + .filter((result) => result) ) const poolsLiquidity = () => @@ -100,7 +98,7 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { return { value: result.value, poolAddress: poolAddresses[index] } } }) - .filter((result) => result), + .filter((result) => result) ) const getPoolsGlobalSpaceResults = async () => { @@ -125,19 +123,14 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { await getPoolsLiquiditiesResults(), await getPoolsGlobalSpaceResults(), ]) - console.log('globalSpaceResults:', globalSpaceResults) - console.log('liquidityResults:', liquidityResults) - - console.log('globalSpaceResults length:', globalSpaceResults.length) - console.log('liquidityResults length:', liquidityResults.length) const combinedResults = poolAddresses.flatMap((poolAddress) => { const liquidityResult = liquidityResults.find( - ({ poolAddress: liquidityPoolAddress }) => liquidityPoolAddress === poolAddress, + ({ poolAddress: liquidityPoolAddress }) => liquidityPoolAddress === poolAddress ) const globalSpaceResult = globalSpaceResults.find( - ({ poolAddress: globalSpacePoolAddress }) => globalSpacePoolAddress === poolAddress, + ({ poolAddress: globalSpacePoolAddress }) => globalSpacePoolAddress === poolAddress ) if (globalSpaceResult && liquidityResult) { @@ -147,7 +140,7 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { globalSpaceResult.value.fee, globalSpaceResult.value.price, liquidityResult ? liquidityResult.value : null, - globalSpaceResult.value.tick, + globalSpaceResult.value.tick ) } diff --git a/src/entities/trades/swapr-v3/routes.ts b/src/entities/trades/swapr-v3/getRoutes.ts similarity index 88% rename from src/entities/trades/swapr-v3/routes.ts rename to src/entities/trades/swapr-v3/getRoutes.ts index 5442fe44..5e3e3c35 100644 --- a/src/entities/trades/swapr-v3/routes.ts +++ b/src/entities/trades/swapr-v3/getRoutes.ts @@ -1,7 +1,7 @@ import { Currency, Token } from '@uniswap/sdk-core' -import { Pool } from './pool' -import { Route } from './route' -import { getPools } from './pools' +import { Pool } from './entities/pool' +import { Route } from './entities/route' +import { getPools } from './getPools' /** * Returns true if poolA is equivalent to poolB @@ -20,7 +20,7 @@ export function computeAllRoutes( chainId: number, currentPath: Pool[] = [], allPaths: Route[] = [], - maxHops = 2, + maxHops = 2 ): Route[] { for (const pool of pools) { if (!pool.involvesToken(pool.token0) || currentPath.some((pathPool) => poolEquals(pool, pathPool))) continue @@ -39,9 +39,8 @@ export function computeAllRoutes( export async function getRoutes( currencyIn: Token, currencyOut: Token, - chainId: number, + chainId: number ): Promise[]> { const pools = await getPools(currencyIn, currencyOut) - console.log('pools size', pools.length) return computeAllRoutes(pools, chainId, [], [], 3) } diff --git a/src/entities/trades/swapr-v3/swapr-v3.spec.ts b/src/entities/trades/swapr-v3/swapr-v3.spec.ts index e0439247..67c59c87 100644 --- a/src/entities/trades/swapr-v3/swapr-v3.spec.ts +++ b/src/entities/trades/swapr-v3/swapr-v3.spec.ts @@ -17,7 +17,7 @@ const recipient = NULL_ADDRESS describe('SwaprV3', () => { describe('getQuote', () => { - test('should return a EXACT INPUT quote on Gnosis for USDC - WXDAI', async () => { + test('should return a EXACT INPUT quote on Gnosis for USDC - WXDAI', async () => { const currencyAmount = new TokenAmount(tokenUSDC, parseUnits('2.59', 6).toString()) const trade = await SwaprV3Trade.getQuote({ amount: currencyAmount, diff --git a/src/entities/trades/swapr-v3/computePoolAddress.ts b/src/entities/trades/swapr-v3/utils/computePoolAddress.ts similarity index 94% rename from src/entities/trades/swapr-v3/computePoolAddress.ts rename to src/entities/trades/swapr-v3/utils/computePoolAddress.ts index 19f3a4cb..bfc61c20 100644 --- a/src/entities/trades/swapr-v3/computePoolAddress.ts +++ b/src/entities/trades/swapr-v3/utils/computePoolAddress.ts @@ -2,7 +2,7 @@ import { defaultAbiCoder } from '@ethersproject/abi' import { getCreate2Address } from '@ethersproject/address' import { keccak256 } from '@ethersproject/solidity' import { Token } from '@uniswap/sdk-core' -import { POOL_INIT_CODE_HASH } from './constants' +import { POOL_INIT_CODE_HASH } from '../constants' export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000' @@ -50,6 +50,6 @@ export function computePoolAddress({ return getCreate2Address( poolDeployer, keccak256(['bytes'], [defaultAbiCoder.encode(['address', 'address'], [token0.address, token1.address])]), - initCodeHashManualOverride ?? POOL_INIT_CODE_HASH, + initCodeHashManualOverride ?? POOL_INIT_CODE_HASH ) } diff --git a/src/entities/trades/swapr-v3/encode-route.ts b/src/entities/trades/swapr-v3/utils/encode-route.ts similarity index 89% rename from src/entities/trades/swapr-v3/encode-route.ts rename to src/entities/trades/swapr-v3/utils/encode-route.ts index 3b5a4fda..5b1af7c0 100644 --- a/src/entities/trades/swapr-v3/encode-route.ts +++ b/src/entities/trades/swapr-v3/utils/encode-route.ts @@ -1,7 +1,7 @@ import { pack } from '@ethersproject/solidity' import { Currency, Token } from '@uniswap/sdk-core' -import { Pool } from './pool' -import { Route } from './route' +import { Pool } from '../entities/pool' +import { Route } from '../entities/route' /** * Converts a route to a hex encoded path @@ -15,7 +15,7 @@ export function encodeRouteToPath(route: Route, exactOutput: ( { inputToken, path, types }: { inputToken: Token; path: (string | number)[]; types: string[] }, pool: Pool, - index, + index ): { inputToken: Token; path: (string | number)[]; types: string[] } => { const outputToken: Token = pool.token0.equals(inputToken) ? pool.token1 : pool.token0 if (index === 0) { @@ -32,7 +32,7 @@ export function encodeRouteToPath(route: Route, exactOutput: } } }, - { inputToken: firstInputToken, path: [], types: [] }, + { inputToken: firstInputToken, path: [], types: [] } ) return exactOutput ? pack(types.reverse(), path.reverse()) : pack(types, path) From c9d5793def61a89ae0b62a7e98a33ac337faf2b7 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 27 Dec 2023 17:37:57 +0000 Subject: [PATCH 10/26] remove logs, de-comment, and fix tests --- src/entities/trades/swapr-v3/swapr-v3.spec.ts | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/entities/trades/swapr-v3/swapr-v3.spec.ts b/src/entities/trades/swapr-v3/swapr-v3.spec.ts index 67c59c87..1da306a6 100644 --- a/src/entities/trades/swapr-v3/swapr-v3.spec.ts +++ b/src/entities/trades/swapr-v3/swapr-v3.spec.ts @@ -26,7 +26,7 @@ describe('SwaprV3', () => { recipient, tradeType: TradeType.EXACT_INPUT, }) - // console.log('trade', trade) + expect(trade).toBeDefined() expect(trade?.chainId).toEqual(ChainId.GNOSIS) expect(trade?.tradeType).toEqual(TradeType.EXACT_INPUT) @@ -65,6 +65,21 @@ describe('SwaprV3', () => { expect(trade?.outputAmount.currency.address).toBe(tokenSWPR.address) }) + test('should return a exact output quote on Gnosis for WXDAI - USDC', async () => { + const currencyAmount = new TokenAmount(tokenWXDAI, parseUnits('2', 18).toString()) + const trade = await SwaprV3Trade.getQuote({ + quoteCurrency: tokenUSDC, + amount: currencyAmount, + maximumSlippage, + recipient, + tradeType: TradeType.EXACT_OUTPUT, + }) + expect(trade).toBeDefined() + expect(trade?.chainId).toEqual(ChainId.GNOSIS) + expect(trade?.tradeType).toEqual(TradeType.EXACT_OUTPUT) + expect(trade?.inputAmount.currency.address).toBe(tokenUSDC.address) + }) + // test('should return a EXACT INPUT quote on Gnosis for GNO - WETH', async () => { // const currencyAmount = new TokenAmount(tokenGNO, parseUnits('1', 18).toString()) // const trade = await SwaprV3Trade.getQuote({ @@ -80,22 +95,6 @@ describe('SwaprV3', () => { // expect(trade?.tradeType).toEqual(TradeType.EXACT_INPUT) // expect(trade?.outputAmount.currency.address).toBe(tokenWETH.address) // }) - - // test('should return a exact output quote on Gnosis for WXDAI - USDC', async () => { - // const currencyAmount = new TokenAmount(tokenWXDAI, parseUnits('2', 18).toString()) - // const trade = await SwaprV3Trade.getQuote({ - // quoteCurrency: tokenUSDC, - // amount: currencyAmount, - // maximumSlippage, - // recipient, - // tradeType: TradeType.EXACT_OUTPUT, - // }) - // console.log('trade', trade) - // expect(trade).toBeDefined() - // expect(trade?.chainId).toEqual(ChainId.GNOSIS) - // expect(trade?.tradeType).toEqual(TradeType.EXACT_OUTPUT) - // expect(trade?.outputAmount.currency.address).toBe(tokenUSDC.address) - // }) }) describe('Swap', () => { @@ -116,8 +115,6 @@ describe('SwaprV3', () => { } const swap = await trade?.swapTransaction(swapOptions) - - console.log('swap', swap) expect(swap !== undefined) }) }) From 359e674f6d15a8794b6ba7da8cefda3839232be8 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 27 Dec 2023 17:38:18 +0000 Subject: [PATCH 11/26] move addresses to constants --- src/entities/trades/swapr-v3/constants.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/entities/trades/swapr-v3/constants.ts b/src/entities/trades/swapr-v3/constants.ts index b862753d..d60b6b1e 100644 --- a/src/entities/trades/swapr-v3/constants.ts +++ b/src/entities/trades/swapr-v3/constants.ts @@ -1,7 +1,11 @@ -export const ROUTER_ADDRESS = '0xfFB643E73f280B97809A8b41f7232AB401a04ee1' export const POOL_DEPLOYER_ADDRESS = '0xC1b576AC6Ec749d5Ace1787bF9Ec6340908ddB47' export const POOL_INIT_CODE_HASH = '0xbce37a54eab2fcd71913a0d40723e04238970e7fc1159bfd58ad5b79531697e7' +export const SWAPR_ALGEBRA_CONTRACTS = { + quoter: '0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7', + router: '0xfFB643E73f280B97809A8b41f7232AB401a04ee1', +} + export type BaseToken = { chainId: number decimals: number @@ -49,13 +53,4 @@ export const baseTokens: BaseToken[] = [ isToken: true, address: '0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb', }, - // { - // chainId: 100, - // decimals: 18, - // symbol: 'SWPR', - // name: 'SWAPR Token on Gnosis chain', - // isNative: false, - // isToken: true, - // address: '0x532801ED6f82FFfD2DAB70A19fC2d7B2772C4f4b', - // }, ] From a969ba1a1db6a3f4bb30061b1c2c0034b9b39f12 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 27 Dec 2023 17:38:44 +0000 Subject: [PATCH 12/26] extract contracts to a contracts.ts file --- src/entities/trades/swapr-v3/SwaprV3.ts | 25 ++++------------------- src/entities/trades/swapr-v3/contracts.ts | 16 +++++++++++++++ src/entities/trades/swapr-v3/getPools.ts | 2 +- 3 files changed, 21 insertions(+), 22 deletions(-) create mode 100644 src/entities/trades/swapr-v3/contracts.ts diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index 5616cb15..b5ac5636 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -11,31 +11,14 @@ import { RoutablePlatform } from '../routable-platform' import { getProvider, tryGetChainId } from '../utils' import { ONE, TradeType } from '../../../constants' -import { SWAPR_ALGEBRA_ROUTER_ABI, SWAPR_ALGEBRA_QUOTER_ABI, SWAPR_ALGEBRA_POOL_ABI } from './abi' -import { Contract, UnsignedTransaction } from 'ethers' +import { UnsignedTransaction } from 'ethers' import { AddressZero } from '@ethersproject/constants' import { TradeOptions } from '../interfaces/trade-options' import { parseUnits } from '@ethersproject/units' import { getRoutes } from './getRoutes' import { Route } from './entities/route' - -// Constants -export const GNOSIS_CONTRACTS = { - quoter: '0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7', - router: '0xfFB643E73f280B97809A8b41f7232AB401a04ee1', -} - -export function getPoolsContract(pool_address: string) { - return new Contract(pool_address, SWAPR_ALGEBRA_POOL_ABI, getProvider(100)) -} - -export function getRouterContract() { - return new Contract(GNOSIS_CONTRACTS.router, SWAPR_ALGEBRA_ROUTER_ABI, getProvider(100)) -} - -export function getQuoterContract() { - return new Contract(GNOSIS_CONTRACTS.quoter, SWAPR_ALGEBRA_QUOTER_ABI, getProvider(100)) -} +import { SWAPR_ALGEBRA_CONTRACTS } from './constants' +import { getQuoterContract, getRouterContract } from './contracts' interface SwaprV3ConstructorParams { maximumSlippage: Percent @@ -73,7 +56,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { }), priceImpact, fee, - approveAddress: GNOSIS_CONTRACTS['router'], + approveAddress: SWAPR_ALGEBRA_CONTRACTS['router'], }) } diff --git a/src/entities/trades/swapr-v3/contracts.ts b/src/entities/trades/swapr-v3/contracts.ts new file mode 100644 index 00000000..2ad550b8 --- /dev/null +++ b/src/entities/trades/swapr-v3/contracts.ts @@ -0,0 +1,16 @@ +import { Contract } from 'ethers' +import { SWAPR_ALGEBRA_POOL_ABI, SWAPR_ALGEBRA_QUOTER_ABI, SWAPR_ALGEBRA_ROUTER_ABI } from './abi' +import { getProvider } from '../utils' +import { SWAPR_ALGEBRA_CONTRACTS } from './constants' + +export function getPoolsContract(pool_address: string) { + return new Contract(pool_address, SWAPR_ALGEBRA_POOL_ABI, getProvider(100)) +} + +export function getRouterContract() { + return new Contract(SWAPR_ALGEBRA_CONTRACTS.router, SWAPR_ALGEBRA_ROUTER_ABI, getProvider(100)) +} + +export function getQuoterContract() { + return new Contract(SWAPR_ALGEBRA_CONTRACTS.quoter, SWAPR_ALGEBRA_QUOTER_ABI, getProvider(100)) +} diff --git a/src/entities/trades/swapr-v3/getPools.ts b/src/entities/trades/swapr-v3/getPools.ts index 904e6dca..6b965200 100644 --- a/src/entities/trades/swapr-v3/getPools.ts +++ b/src/entities/trades/swapr-v3/getPools.ts @@ -2,8 +2,8 @@ import { Token } from '@uniswap/sdk-core' import { POOL_DEPLOYER_ADDRESS, baseTokens } from './constants' import { computePoolAddress } from './utils/computePoolAddress' -import { getPoolsContract } from './SwaprV3' import { Pool } from './entities/pool' +import { getPoolsContract } from './contracts' const GNOSIS_CHAIN_ID = 100 From caf58b7e2f83a79a2072a75478e6c4c3fcb5801f Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 27 Dec 2023 18:36:02 +0000 Subject: [PATCH 13/26] rename files --- src/entities/trades/swapr-v3/SwaprV3.ts | 6 ++---- src/entities/trades/swapr-v3/{getPools.ts => pools.ts} | 0 src/entities/trades/swapr-v3/{getRoutes.ts => routes.ts} | 2 +- src/entities/trades/swapr-v3/swapr-v3.spec.ts | 2 +- src/utils.ts | 6 ++---- 5 files changed, 6 insertions(+), 10 deletions(-) rename src/entities/trades/swapr-v3/{getPools.ts => pools.ts} (100%) rename src/entities/trades/swapr-v3/{getRoutes.ts => routes.ts} (97%) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index b5ac5636..a4f5aa5d 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -12,13 +12,12 @@ import { getProvider, tryGetChainId } from '../utils' import { ONE, TradeType } from '../../../constants' import { UnsignedTransaction } from 'ethers' -import { AddressZero } from '@ethersproject/constants' import { TradeOptions } from '../interfaces/trade-options' import { parseUnits } from '@ethersproject/units' -import { getRoutes } from './getRoutes' import { Route } from './entities/route' import { SWAPR_ALGEBRA_CONTRACTS } from './constants' import { getQuoterContract, getRouterContract } from './contracts' +import { getRoutes } from './routes' interface SwaprV3ConstructorParams { maximumSlippage: Percent @@ -69,13 +68,12 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { // }) static async getQuote( - { amount, quoteCurrency, tradeType, recipient, maximumSlippage }: any, + { amount, quoteCurrency, tradeType, maximumSlippage }: any, provider?: BaseProvider ): Promise { const chainId = tryGetChainId(amount, quoteCurrency) invariant(chainId, 'SwaprV3Trade.getQuote: chainId is required') - recipient = recipient || AddressZero maximumSlippage = maximumSlippage || 0 provider = provider || getProvider(chainId) diff --git a/src/entities/trades/swapr-v3/getPools.ts b/src/entities/trades/swapr-v3/pools.ts similarity index 100% rename from src/entities/trades/swapr-v3/getPools.ts rename to src/entities/trades/swapr-v3/pools.ts diff --git a/src/entities/trades/swapr-v3/getRoutes.ts b/src/entities/trades/swapr-v3/routes.ts similarity index 97% rename from src/entities/trades/swapr-v3/getRoutes.ts rename to src/entities/trades/swapr-v3/routes.ts index 5e3e3c35..a81919b9 100644 --- a/src/entities/trades/swapr-v3/getRoutes.ts +++ b/src/entities/trades/swapr-v3/routes.ts @@ -1,7 +1,7 @@ import { Currency, Token } from '@uniswap/sdk-core' import { Pool } from './entities/pool' import { Route } from './entities/route' -import { getPools } from './getPools' +import { getPools } from './pools' /** * Returns true if poolA is equivalent to poolB diff --git a/src/entities/trades/swapr-v3/swapr-v3.spec.ts b/src/entities/trades/swapr-v3/swapr-v3.spec.ts index 1da306a6..99aae0cf 100644 --- a/src/entities/trades/swapr-v3/swapr-v3.spec.ts +++ b/src/entities/trades/swapr-v3/swapr-v3.spec.ts @@ -16,7 +16,7 @@ const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' const recipient = NULL_ADDRESS describe('SwaprV3', () => { - describe('getQuote', () => { + describe('Quote', () => { test('should return a EXACT INPUT quote on Gnosis for USDC - WXDAI', async () => { const currencyAmount = new TokenAmount(tokenUSDC, parseUnits('2.59', 6).toString()) const trade = await SwaprV3Trade.getQuote({ diff --git a/src/utils.ts b/src/utils.ts index 32aecadc..c7158584 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,6 @@ import { getAddress } from '@ethersproject/address' import JSBI from 'jsbi' import invariant from 'tiny-invariant' -import warning from 'tiny-warning' import { BigintIsh, ONE, SOLIDITY_TYPE_MAXIMA, SolidityType, THREE, TWO, ZERO } from './constants' @@ -14,7 +13,6 @@ export function validateSolidityTypeInstance(value: JSBI, solidityType: Solidity export function validateAndParseAddress(address: string): string { try { const checksummedAddress = getAddress(address) - warning(address === checksummedAddress, `${address} is not checksummed.`) return checksummedAddress } catch (error) { invariant(false, `${address} is not a valid address.`) @@ -25,8 +23,8 @@ export function parseBigintIsh(bigintIsh: BigintIsh): JSBI { return bigintIsh instanceof JSBI ? bigintIsh : typeof bigintIsh === 'bigint' - ? JSBI.BigInt(bigintIsh.toString()) - : JSBI.BigInt(bigintIsh) + ? JSBI.BigInt(bigintIsh.toString()) + : JSBI.BigInt(bigintIsh) } // mock the on-chain sqrt function From 0f6f90888b613d93a96ac7166b6021e8084bf39c Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 28 Dec 2023 18:00:20 +0000 Subject: [PATCH 14/26] fix types on pools --- src/entities/trades/swapr-v3/pools.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/entities/trades/swapr-v3/pools.ts b/src/entities/trades/swapr-v3/pools.ts index 6b965200..c90d034e 100644 --- a/src/entities/trades/swapr-v3/pools.ts +++ b/src/entities/trades/swapr-v3/pools.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import { Token } from '@uniswap/sdk-core' import { POOL_DEPLOYER_ADDRESS, baseTokens } from './constants' import { computePoolAddress } from './utils/computePoolAddress' @@ -85,9 +84,12 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { .map((result, index) => { if (result.status === 'fulfilled') { return { value: result.value, poolAddress: poolAddresses[index] } + } else { + console.warn(`Error fetching pool global state for ${poolAddresses[index]}`) + return { value: null, poolAddress: poolAddresses[index] } } }) - .filter((result) => result) + .filter((result) => result.value) ) const poolsLiquidity = () => @@ -96,17 +98,20 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { .map((result, index) => { if (result.status === 'fulfilled') { return { value: result.value, poolAddress: poolAddresses[index] } + } else { + console.warn(`Error fetching pool liquidity for ${poolAddresses[index]}`) + return { value: null, poolAddress: poolAddresses[index] } } }) - .filter((result) => result) + .filter((result) => result.value) ) - const getPoolsGlobalSpaceResults = async () => { try { const results = await poolsGlobalSpace() return results } catch (error) { console.error('Error fetching pool globalSpace results:', error) + return null } } @@ -116,6 +121,7 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { return results } catch (error) { console.error('Error fetching pool liquidity results:', error) + return null } } @@ -125,11 +131,11 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { ]) const combinedResults = poolAddresses.flatMap((poolAddress) => { - const liquidityResult = liquidityResults.find( + const liquidityResult = liquidityResults?.find( ({ poolAddress: liquidityPoolAddress }) => liquidityPoolAddress === poolAddress ) - const globalSpaceResult = globalSpaceResults.find( + const globalSpaceResult = globalSpaceResults?.find( ({ poolAddress: globalSpacePoolAddress }) => globalSpacePoolAddress === poolAddress ) From 70760c18bbdab30d7fa17c3bb0c877bca2154092 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 28 Dec 2023 18:00:43 +0000 Subject: [PATCH 15/26] remove local GNOSIS_CHAIN_ID and use ChainId --- src/entities/trades/swapr-v3/pools.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/entities/trades/swapr-v3/pools.ts b/src/entities/trades/swapr-v3/pools.ts index c90d034e..76e986d5 100644 --- a/src/entities/trades/swapr-v3/pools.ts +++ b/src/entities/trades/swapr-v3/pools.ts @@ -3,24 +3,17 @@ import { POOL_DEPLOYER_ADDRESS, baseTokens } from './constants' import { computePoolAddress } from './utils/computePoolAddress' import { Pool } from './entities/pool' import { getPoolsContract } from './contracts' - -const GNOSIS_CHAIN_ID = 100 +import { ChainId } from '../../../constants' const getBaseTokens: Token[] = baseTokens.map( - ({ address, decimals, symbol, name }) => new Token(GNOSIS_CHAIN_ID, address, decimals, symbol, name) + ({ address, decimals, symbol, name }) => new Token(ChainId.GNOSIS, address, decimals, symbol, name) ) export const setupTokens = (currencyIn: Token, currencyOut: Token) => { - const tokenIn = new Token( - GNOSIS_CHAIN_ID, - currencyIn.address, - currencyIn.decimals, - currencyIn.symbol, - currencyIn.name - ) + const tokenIn = new Token(ChainId.GNOSIS, currencyIn.address, currencyIn.decimals, currencyIn.symbol, currencyIn.name) const tokenOut = new Token( - GNOSIS_CHAIN_ID, + ChainId.GNOSIS, currencyOut.address, currencyOut.decimals, currencyOut.symbol, From b2da77f35f9462e73c6de1206c65966932edf07a Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 28 Dec 2023 18:00:58 +0000 Subject: [PATCH 16/26] improve SwaprV3Trade --- src/entities/trades/swapr-v3/SwaprV3.ts | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index a4f5aa5d..e84ef04e 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -59,14 +59,6 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { }) } - // SwaprV3Trade.getQuote({ - // quoteCurrency: currencyOut, - // amount: currencyAmountIn, - // maximumSlippage, - // recipient: receiver, - // tradeType: TradeType.EXACT_INPUT, - // }) - static async getQuote( { amount, quoteCurrency, tradeType, maximumSlippage }: any, provider?: BaseProvider @@ -75,7 +67,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { invariant(chainId, 'SwaprV3Trade.getQuote: chainId is required') maximumSlippage = maximumSlippage || 0 - provider = provider || getProvider(chainId) + provider = provider ?? getProvider(chainId) const tokenIn = amount.currency const tokenOut = quoteCurrency @@ -100,7 +92,10 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { return null }) - const fee = new Percent(routes[0].pools[0].fee.toString(), '1000000') + const fee = + routes?.length > 0 && routes[0].pools.length > 0 + ? new Percent(routes[0].pools[0].fee.toString(), '1000000') + : new Percent('0', '0') if (quotedAmountOut) { return new SwaprV3Trade({ @@ -173,16 +168,6 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { } } - // struct ExactInputSingleParams { - // uint256 amountIn; // Amount of the input token to be swapped - // address recipient; // Address that will receive the output tokens - // uint160 limitSqrtPrice; // Limit on the square root price of the swap - // uint256 amountOutMinimum; // Minimum amount of output tokens expected - // uint256 deadline; // Timestamp by which the transaction must be mined - // address tokenIn; // Address of the input token - // address tokenOut; // Address of the output token - // } - public async swapTransaction(options: TradeOptions): Promise { const to: string = validateAndParseAddress(options.recipient) const amountIn: string = toHex(this.maximumAmountIn()) From 4d0bd4c96c504910a8ecf1b51303b1b2622ee5a9 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 28 Dec 2023 18:01:46 +0000 Subject: [PATCH 17/26] improve warning log on Pools --- src/entities/trades/swapr-v3/pools.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/entities/trades/swapr-v3/pools.ts b/src/entities/trades/swapr-v3/pools.ts index 76e986d5..2106e09b 100644 --- a/src/entities/trades/swapr-v3/pools.ts +++ b/src/entities/trades/swapr-v3/pools.ts @@ -78,7 +78,7 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { if (result.status === 'fulfilled') { return { value: result.value, poolAddress: poolAddresses[index] } } else { - console.warn(`Error fetching pool global state for ${poolAddresses[index]}`) + console.warn(`Failed fetching pool global state for ${poolAddresses[index]}`) return { value: null, poolAddress: poolAddresses[index] } } }) @@ -92,7 +92,7 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { if (result.status === 'fulfilled') { return { value: result.value, poolAddress: poolAddresses[index] } } else { - console.warn(`Error fetching pool liquidity for ${poolAddresses[index]}`) + console.warn(`Failed fetching pool liquidity for ${poolAddresses[index]}`) return { value: null, poolAddress: poolAddresses[index] } } }) @@ -103,7 +103,7 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { const results = await poolsGlobalSpace() return results } catch (error) { - console.error('Error fetching pool globalSpace results:', error) + console.error('Failed fetching pool globalSpace results:', error) return null } } @@ -113,7 +113,7 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { const results = await poolsLiquidity() return results } catch (error) { - console.error('Error fetching pool liquidity results:', error) + console.error('Failed fetching pool liquidity results:', error) return null } } From 06a2ef443e0d803ddf6f26fa57d665afd2d3642a Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 28 Dec 2023 18:05:09 +0000 Subject: [PATCH 18/26] remove comment --- src/entities/trades/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/entities/trades/utils.ts b/src/entities/trades/utils.ts index efe207f9..d4a09cd0 100644 --- a/src/entities/trades/utils.ts +++ b/src/entities/trades/utils.ts @@ -59,7 +59,6 @@ export function tryGetChainId(currencyAmount: CurrencyAmount, currency: Currency export const RPC_PROVIDER_LIST: Record = { [ChainId.MAINNET]: 'https://mainnet.infura.io/v3/e1a3bfc40093494ca4f36b286ab36f2d', [ChainId.XDAI]: 'https://poa-xdai.gateway.pokt.network/v1/lb/627cd67433e8770039fe3dba', - // [ChainId.GNOSIS]: 'https://gnosis.public-rpc.com', [ChainId.RINKEBY]: 'https://rinkeby.infura.io/v3/e1a3bfc40093494ca4f36b286ab36f2d', [ChainId.ARBITRUM_ONE]: 'https://arb1.arbitrum.io/rpc', [ChainId.ARBITRUM_RINKEBY]: 'https://rinkeby.arbitrum.io/rpc', From e6114cd6a40c4109e5b430ee17e76ed304086f9c Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 28 Dec 2023 18:07:47 +0000 Subject: [PATCH 19/26] remove warning log --- src/entities/trades/swapr-v3/pools.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/entities/trades/swapr-v3/pools.ts b/src/entities/trades/swapr-v3/pools.ts index 2106e09b..78401336 100644 --- a/src/entities/trades/swapr-v3/pools.ts +++ b/src/entities/trades/swapr-v3/pools.ts @@ -78,7 +78,6 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { if (result.status === 'fulfilled') { return { value: result.value, poolAddress: poolAddresses[index] } } else { - console.warn(`Failed fetching pool global state for ${poolAddresses[index]}`) return { value: null, poolAddress: poolAddresses[index] } } }) @@ -92,7 +91,6 @@ export const getPools = async (currencyIn: Token, currencyOut: Token) => { if (result.status === 'fulfilled') { return { value: result.value, poolAddress: poolAddresses[index] } } else { - console.warn(`Failed fetching pool liquidity for ${poolAddresses[index]}`) return { value: null, poolAddress: poolAddresses[index] } } }) From 5790d230029f44825df3b47aa882d62959dddb86 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Fri, 29 Dec 2023 15:11:44 +0000 Subject: [PATCH 20/26] improve SwaprV3 --- src/entities/trades/swapr-v3/SwaprV3.ts | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index e84ef04e..71160d73 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -18,6 +18,7 @@ import { Route } from './entities/route' import { SWAPR_ALGEBRA_CONTRACTS } from './constants' import { getQuoterContract, getRouterContract } from './contracts' import { getRoutes } from './routes' +import { maximumSlippage as defaultMaximumSlippage } from '../constants' interface SwaprV3ConstructorParams { maximumSlippage: Percent @@ -66,7 +67,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { const chainId = tryGetChainId(amount, quoteCurrency) invariant(chainId, 'SwaprV3Trade.getQuote: chainId is required') - maximumSlippage = maximumSlippage || 0 + maximumSlippage = maximumSlippage || defaultMaximumSlippage provider = provider ?? getProvider(chainId) const tokenIn = amount.currency @@ -77,9 +78,14 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { `SwaprV3Trade.getQuote: currencies chainId does not match provider's chainId` ) - if (tradeType === TradeType.EXACT_INPUT) { - const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) + const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) + + const fee = + routes?.length > 0 && routes[0].pools.length > 0 + ? new Percent(routes[0].pools[0].fee.toString(), '1000000') + : new Percent('0', '0') + if (tradeType === TradeType.EXACT_INPUT) { const quotedAmountOut = await getQuoterContract() .callStatic.quoteExactInputSingle( tokenIn.address, @@ -92,11 +98,6 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { return null }) - const fee = - routes?.length > 0 && routes[0].pools.length > 0 - ? new Percent(routes[0].pools[0].fee.toString(), '1000000') - : new Percent('0', '0') - if (quotedAmountOut) { return new SwaprV3Trade({ maximumSlippage, @@ -104,15 +105,11 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { outputAmount: new TokenAmount(quoteCurrency, quotedAmountOut), tradeType: tradeType, chainId: chainId, - priceImpact: new Percent('0', '1000'), // todo: fix this + priceImpact: new Percent('0', '1000'), fee, }) } } else { - const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) - - const fee = new Percent(routes[0].pools[0].fee.toString(), '1000000') - const quotedAmountIn = await getQuoterContract() .callStatic.quoteExactOutputSingle( quoteCurrency.address, @@ -132,7 +129,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { outputAmount: amount, tradeType: tradeType, chainId: chainId, - priceImpact: new Percent('0', '1000'), // todo: fix this + priceImpact: new Percent('0', '1000'), fee: fee, }) } From 69db2304b0417fbd56cf4e1a4474c71b20635ba5 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Tue, 2 Jan 2024 16:24:46 +0000 Subject: [PATCH 21/26] Small fixes based on comments --- src/entities/trades/swapr-v3/SwaprV3.ts | 34 +++++++++++++++++-- src/entities/trades/swapr-v3/constants.ts | 10 +++--- src/entities/trades/swapr-v3/contracts.ts | 7 ++-- src/entities/trades/swapr-v3/entities/pool.ts | 1 - 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index 71160d73..094b3116 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -1,7 +1,7 @@ import type { BaseProvider } from '@ethersproject/providers' import dayjs from 'dayjs' -import { Currency, Fraction, validateAndParseAddress } from '@uniswap/sdk-core' +import { Currency, Fraction, Token, validateAndParseAddress } from '@uniswap/sdk-core' import invariant from 'tiny-invariant' import { CurrencyAmount, Percent, Price, TokenAmount } from '../../fractions' @@ -20,6 +20,28 @@ import { getQuoterContract, getRouterContract } from './contracts' import { getRoutes } from './routes' import { maximumSlippage as defaultMaximumSlippage } from '../constants' +const WXDAI_ADDRESS = '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d' + +const currencyAddress = (currency: Currency) => { + return currency.isNative ? WXDAI_ADDRESS : currency.address +} + +export const setupTokens = (currencyIn: Currency, currencyOut: Currency) => { + const tokenIn = new Token(100, currencyAddress(currencyIn), currencyIn.decimals, currencyIn.symbol, currencyIn.name) + + const tokenOut = new Token( + 100, + currencyAddress(currencyOut), + currencyOut.decimals, + currencyOut.symbol, + currencyOut.name + ) + + const [tokenA, tokenB] = [tokenIn?.wrapped, tokenOut?.wrapped] + + return { tokenA, tokenB } +} + interface SwaprV3ConstructorParams { maximumSlippage: Percent inputAmount: CurrencyAmount @@ -30,6 +52,14 @@ interface SwaprV3ConstructorParams { fee: Percent } +export interface SwaprV3GetQuoteParams { + amount: CurrencyAmount + quoteCurrency: Currency + tradeType: TradeType + maximumSlippage?: Percent + recipient?: string +} + export class SwaprV3Trade extends TradeWithSwapTransaction { public constructor({ inputAmount, @@ -67,7 +97,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { const chainId = tryGetChainId(amount, quoteCurrency) invariant(chainId, 'SwaprV3Trade.getQuote: chainId is required') - maximumSlippage = maximumSlippage || defaultMaximumSlippage + maximumSlippage = maximumSlippage ?? defaultMaximumSlippage provider = provider ?? getProvider(chainId) const tokenIn = amount.currency diff --git a/src/entities/trades/swapr-v3/constants.ts b/src/entities/trades/swapr-v3/constants.ts index d60b6b1e..e9ff70ef 100644 --- a/src/entities/trades/swapr-v3/constants.ts +++ b/src/entities/trades/swapr-v3/constants.ts @@ -1,3 +1,5 @@ +import { ChainId } from '../../../constants' + export const POOL_DEPLOYER_ADDRESS = '0xC1b576AC6Ec749d5Ace1787bF9Ec6340908ddB47' export const POOL_INIT_CODE_HASH = '0xbce37a54eab2fcd71913a0d40723e04238970e7fc1159bfd58ad5b79531697e7' @@ -18,7 +20,7 @@ export type BaseToken = { export const baseTokens: BaseToken[] = [ { - chainId: 100, + chainId: ChainId.GNOSIS, decimals: 18, symbol: 'WXDAI', name: 'Wrapped XDAI', @@ -27,7 +29,7 @@ export const baseTokens: BaseToken[] = [ address: '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', }, { - chainId: 100, + chainId: ChainId.GNOSIS, decimals: 6, symbol: 'USDC', name: 'USD//C on Gnosis', @@ -36,7 +38,7 @@ export const baseTokens: BaseToken[] = [ address: '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', }, { - chainId: 100, + chainId: ChainId.GNOSIS, decimals: 18, symbol: 'WETH', name: 'Wrapped Ether on Gnosis chain', @@ -45,7 +47,7 @@ export const baseTokens: BaseToken[] = [ address: '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1', }, { - chainId: 100, + chainId: ChainId.GNOSIS, decimals: 18, symbol: 'GNO', name: 'Gnosis Token on Gnosis chain', diff --git a/src/entities/trades/swapr-v3/contracts.ts b/src/entities/trades/swapr-v3/contracts.ts index 2ad550b8..76c8906d 100644 --- a/src/entities/trades/swapr-v3/contracts.ts +++ b/src/entities/trades/swapr-v3/contracts.ts @@ -2,15 +2,16 @@ import { Contract } from 'ethers' import { SWAPR_ALGEBRA_POOL_ABI, SWAPR_ALGEBRA_QUOTER_ABI, SWAPR_ALGEBRA_ROUTER_ABI } from './abi' import { getProvider } from '../utils' import { SWAPR_ALGEBRA_CONTRACTS } from './constants' +import { ChainId } from '../../../constants' export function getPoolsContract(pool_address: string) { - return new Contract(pool_address, SWAPR_ALGEBRA_POOL_ABI, getProvider(100)) + return new Contract(pool_address, SWAPR_ALGEBRA_POOL_ABI, getProvider(ChainId.GNOSIS)) } export function getRouterContract() { - return new Contract(SWAPR_ALGEBRA_CONTRACTS.router, SWAPR_ALGEBRA_ROUTER_ABI, getProvider(100)) + return new Contract(SWAPR_ALGEBRA_CONTRACTS.router, SWAPR_ALGEBRA_ROUTER_ABI, getProvider(ChainId.GNOSIS)) } export function getQuoterContract() { - return new Contract(SWAPR_ALGEBRA_CONTRACTS.quoter, SWAPR_ALGEBRA_QUOTER_ABI, getProvider(100)) + return new Contract(SWAPR_ALGEBRA_CONTRACTS.quoter, SWAPR_ALGEBRA_QUOTER_ABI, getProvider(ChainId.GNOSIS)) } diff --git a/src/entities/trades/swapr-v3/entities/pool.ts b/src/entities/trades/swapr-v3/entities/pool.ts index 9a9c355f..bbe66a67 100644 --- a/src/entities/trades/swapr-v3/entities/pool.ts +++ b/src/entities/trades/swapr-v3/entities/pool.ts @@ -15,7 +15,6 @@ import { computePoolAddress, } from '@uniswap/v3-sdk' -// used in liquidity amount math const Q96 = JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(96)) const Q192 = JSBI.exponentiate(Q96, JSBI.BigInt(2)) From 5c387daf5373f3c28fdd59f2f5c6a3ba983bd521 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Tue, 2 Jan 2024 18:03:15 +0000 Subject: [PATCH 22/26] improve types --- src/entities/trades/swapr-v3/SwaprV3.ts | 62 ++++++++++++------------- src/entities/trades/swapr-v3/pools.ts | 22 +++++++-- src/entities/trades/swapr-v3/routes.ts | 6 +-- 3 files changed, 50 insertions(+), 40 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index 094b3116..d679470d 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -1,7 +1,7 @@ import type { BaseProvider } from '@ethersproject/providers' import dayjs from 'dayjs' -import { Currency, Fraction, Token, validateAndParseAddress } from '@uniswap/sdk-core' +import { Currency as CurrencyType, Fraction, validateAndParseAddress } from '@uniswap/sdk-core' import invariant from 'tiny-invariant' import { CurrencyAmount, Percent, Price, TokenAmount } from '../../fractions' @@ -9,7 +9,7 @@ import { CurrencyAmount, Percent, Price, TokenAmount } from '../../fractions' import { TradeWithSwapTransaction } from '../interfaces/trade' import { RoutablePlatform } from '../routable-platform' import { getProvider, tryGetChainId } from '../utils' -import { ONE, TradeType } from '../../../constants' +import { ChainId, ONE, TradeType } from '../../../constants' import { UnsignedTransaction } from 'ethers' import { TradeOptions } from '../interfaces/trade-options' @@ -19,28 +19,8 @@ import { SWAPR_ALGEBRA_CONTRACTS } from './constants' import { getQuoterContract, getRouterContract } from './contracts' import { getRoutes } from './routes' import { maximumSlippage as defaultMaximumSlippage } from '../constants' - -const WXDAI_ADDRESS = '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d' - -const currencyAddress = (currency: Currency) => { - return currency.isNative ? WXDAI_ADDRESS : currency.address -} - -export const setupTokens = (currencyIn: Currency, currencyOut: Currency) => { - const tokenIn = new Token(100, currencyAddress(currencyIn), currencyIn.decimals, currencyIn.symbol, currencyIn.name) - - const tokenOut = new Token( - 100, - currencyAddress(currencyOut), - currencyOut.decimals, - currencyOut.symbol, - currencyOut.name - ) - - const [tokenA, tokenB] = [tokenIn?.wrapped, tokenOut?.wrapped] - - return { tokenA, tokenB } -} +import { Currency } from '../../currency' +import { Token, WXDAI } from '../../token' interface SwaprV3ConstructorParams { maximumSlippage: Percent @@ -54,7 +34,7 @@ interface SwaprV3ConstructorParams { export interface SwaprV3GetQuoteParams { amount: CurrencyAmount - quoteCurrency: Currency + quoteCurrency: Token tradeType: TradeType maximumSlippage?: Percent recipient?: string @@ -91,7 +71,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { } static async getQuote( - { amount, quoteCurrency, tradeType, maximumSlippage }: any, + { amount, quoteCurrency, tradeType, maximumSlippage }: SwaprV3GetQuoteParams, provider?: BaseProvider ): Promise { const chainId = tryGetChainId(amount, quoteCurrency) @@ -100,15 +80,33 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { maximumSlippage = maximumSlippage ?? defaultMaximumSlippage provider = provider ?? getProvider(chainId) - const tokenIn = amount.currency - const tokenOut = quoteCurrency + const tokenIn = Currency.isNative(amount.currency) + ? WXDAI[ChainId.GNOSIS] + : new Token( + ChainId.GNOSIS, + // @ts-expect-error + amount.currency.address, + amount.currency.decimals, + amount.currency.symbol, + amount.currency.name + ) + + const tokenOut = Currency.isNative(quoteCurrency) + ? WXDAI[ChainId.GNOSIS] + : new Token( + ChainId.GNOSIS, + quoteCurrency.address, + quoteCurrency.decimals, + quoteCurrency.symbol, + quoteCurrency.name + ) invariant( (await provider.getNetwork()).chainId == chainId, `SwaprV3Trade.getQuote: currencies chainId does not match provider's chainId` ) - const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) + const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) const fee = routes?.length > 0 && routes[0].pools.length > 0 @@ -132,7 +130,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { return new SwaprV3Trade({ maximumSlippage, inputAmount: amount, - outputAmount: new TokenAmount(quoteCurrency, quotedAmountOut), + outputAmount: new TokenAmount(tokenOut, quotedAmountOut), tradeType: tradeType, chainId: chainId, priceImpact: new Percent('0', '1000'), @@ -142,7 +140,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { } else { const quotedAmountIn = await getQuoterContract() .callStatic.quoteExactOutputSingle( - quoteCurrency.address, + tokenOut.address, amount.currency.address, parseUnits(amount.toSignificant(), amount.currency.decimals), 0 @@ -155,7 +153,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { if (quotedAmountIn) { return new SwaprV3Trade({ maximumSlippage, - inputAmount: new TokenAmount(quoteCurrency, quotedAmountIn), + inputAmount: new TokenAmount(tokenOut, quotedAmountIn), outputAmount: amount, tradeType: tradeType, chainId: chainId, diff --git a/src/entities/trades/swapr-v3/pools.ts b/src/entities/trades/swapr-v3/pools.ts index 78401336..b8f2392b 100644 --- a/src/entities/trades/swapr-v3/pools.ts +++ b/src/entities/trades/swapr-v3/pools.ts @@ -1,4 +1,4 @@ -import { Token } from '@uniswap/sdk-core' +import { Currency, Token } from '@uniswap/sdk-core' import { POOL_DEPLOYER_ADDRESS, baseTokens } from './constants' import { computePoolAddress } from './utils/computePoolAddress' import { Pool } from './entities/pool' @@ -9,12 +9,24 @@ const getBaseTokens: Token[] = baseTokens.map( ({ address, decimals, symbol, name }) => new Token(ChainId.GNOSIS, address, decimals, symbol, name) ) -export const setupTokens = (currencyIn: Token, currencyOut: Token) => { - const tokenIn = new Token(ChainId.GNOSIS, currencyIn.address, currencyIn.decimals, currencyIn.symbol, currencyIn.name) +const WXDAI_ADDRESS = '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d' + +const currencyAddress = (currency: Currency) => { + return currency.isNative ? WXDAI_ADDRESS : currency.address +} + +export const setupTokens = (currencyIn: Currency, currencyOut: Currency) => { + const tokenIn = new Token( + ChainId.GNOSIS, + currencyAddress(currencyIn), + currencyIn.decimals, + currencyIn.symbol, + currencyIn.name + ) const tokenOut = new Token( ChainId.GNOSIS, - currencyOut.address, + currencyAddress(currencyOut), currencyOut.decimals, currencyOut.symbol, currencyOut.name @@ -54,7 +66,7 @@ const pairsDiffCombinations = (tokenA: Token, tokenB: Token) => { ) } -export const getPools = async (currencyIn: Token, currencyOut: Token) => { +export const getPools = async (currencyIn: Currency, currencyOut: Currency) => { const { tokenA, tokenB } = setupTokens(currencyIn, currencyOut) const pairsCombinations = pairsDiffCombinations(tokenA, tokenB) diff --git a/src/entities/trades/swapr-v3/routes.ts b/src/entities/trades/swapr-v3/routes.ts index a81919b9..e1fd484c 100644 --- a/src/entities/trades/swapr-v3/routes.ts +++ b/src/entities/trades/swapr-v3/routes.ts @@ -1,4 +1,4 @@ -import { Currency, Token } from '@uniswap/sdk-core' +import { Currency } from '@uniswap/sdk-core' import { Pool } from './entities/pool' import { Route } from './entities/route' import { getPools } from './pools' @@ -37,8 +37,8 @@ export function computeAllRoutes( } export async function getRoutes( - currencyIn: Token, - currencyOut: Token, + currencyIn: any, + currencyOut: any, chainId: number ): Promise[]> { const pools = await getPools(currencyIn, currencyOut) From 05505c259c526ce207a661fd3108c474602d0d0b Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 3 Jan 2024 16:56:21 +0000 Subject: [PATCH 23/26] quoteCurrency should be Currency --- src/entities/trades/swapr-v3/SwaprV3.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index d679470d..419a1fd1 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -34,7 +34,7 @@ interface SwaprV3ConstructorParams { export interface SwaprV3GetQuoteParams { amount: CurrencyAmount - quoteCurrency: Token + quoteCurrency: Currency tradeType: TradeType maximumSlippage?: Percent recipient?: string @@ -95,7 +95,8 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { ? WXDAI[ChainId.GNOSIS] : new Token( ChainId.GNOSIS, - quoteCurrency.address, + // @ts-expect-error + quoteCurrency?.address, quoteCurrency.decimals, quoteCurrency.symbol, quoteCurrency.name From 98b0dcf943049b6a0b3bfa2112c2e895f50e956b Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 4 Jan 2024 12:40:03 +0000 Subject: [PATCH 24/26] improve types --- src/entities/trades/swapr-v3/SwaprV3.ts | 10 +++++----- src/entities/trades/swapr-v3/pools.ts | 5 ++--- src/entities/trades/swapr-v3/routes.ts | 6 +----- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index 419a1fd1..eba67ca8 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -1,7 +1,7 @@ import type { BaseProvider } from '@ethersproject/providers' import dayjs from 'dayjs' -import { Currency as CurrencyType, Fraction, validateAndParseAddress } from '@uniswap/sdk-core' +import { Fraction, validateAndParseAddress } from '@uniswap/sdk-core' import invariant from 'tiny-invariant' import { CurrencyAmount, Percent, Price, TokenAmount } from '../../fractions' @@ -14,7 +14,6 @@ import { ChainId, ONE, TradeType } from '../../../constants' import { UnsignedTransaction } from 'ethers' import { TradeOptions } from '../interfaces/trade-options' import { parseUnits } from '@ethersproject/units' -import { Route } from './entities/route' import { SWAPR_ALGEBRA_CONTRACTS } from './constants' import { getQuoterContract, getRouterContract } from './contracts' import { getRoutes } from './routes' @@ -80,23 +79,23 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { maximumSlippage = maximumSlippage ?? defaultMaximumSlippage provider = provider ?? getProvider(chainId) + invariant(amount.currency.address, `SwaprV3Trade.getQuote: amount.currency.address is required`) const tokenIn = Currency.isNative(amount.currency) ? WXDAI[ChainId.GNOSIS] : new Token( ChainId.GNOSIS, - // @ts-expect-error amount.currency.address, amount.currency.decimals, amount.currency.symbol, amount.currency.name ) + invariant(quoteCurrency.address, `SwaprV3Trade.getQuote: quoteCurrency.address is required`) const tokenOut = Currency.isNative(quoteCurrency) ? WXDAI[ChainId.GNOSIS] : new Token( ChainId.GNOSIS, - // @ts-expect-error - quoteCurrency?.address, + quoteCurrency.address, quoteCurrency.decimals, quoteCurrency.symbol, quoteCurrency.name @@ -107,6 +106,7 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { `SwaprV3Trade.getQuote: currencies chainId does not match provider's chainId` ) + const routes = await getRoutes(tokenIn, tokenOut, chainId) const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) const fee = diff --git a/src/entities/trades/swapr-v3/pools.ts b/src/entities/trades/swapr-v3/pools.ts index b8f2392b..bfabb281 100644 --- a/src/entities/trades/swapr-v3/pools.ts +++ b/src/entities/trades/swapr-v3/pools.ts @@ -4,15 +4,14 @@ import { computePoolAddress } from './utils/computePoolAddress' import { Pool } from './entities/pool' import { getPoolsContract } from './contracts' import { ChainId } from '../../../constants' +import { WXDAI } from '../../token' const getBaseTokens: Token[] = baseTokens.map( ({ address, decimals, symbol, name }) => new Token(ChainId.GNOSIS, address, decimals, symbol, name) ) -const WXDAI_ADDRESS = '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d' - const currencyAddress = (currency: Currency) => { - return currency.isNative ? WXDAI_ADDRESS : currency.address + return currency.isNative ? WXDAI[ChainId.GNOSIS].address : currency.address } export const setupTokens = (currencyIn: Currency, currencyOut: Currency) => { diff --git a/src/entities/trades/swapr-v3/routes.ts b/src/entities/trades/swapr-v3/routes.ts index e1fd484c..91d8ee81 100644 --- a/src/entities/trades/swapr-v3/routes.ts +++ b/src/entities/trades/swapr-v3/routes.ts @@ -36,11 +36,7 @@ export function computeAllRoutes( return allPaths } -export async function getRoutes( - currencyIn: any, - currencyOut: any, - chainId: number -): Promise[]> { +export async function getRoutes(currencyIn: any, currencyOut: any, chainId: number) { const pools = await getPools(currencyIn, currencyOut) return computeAllRoutes(pools, chainId, [], [], 3) } From 4a923be165d03453a4ae45a1914f0a7adf611279 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 4 Jan 2024 13:23:17 +0000 Subject: [PATCH 25/26] HOT FIX - remove repeated routes --- src/entities/trades/swapr-v3/SwaprV3.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index eba67ca8..eb967e5e 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -107,7 +107,6 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { ) const routes = await getRoutes(tokenIn, tokenOut, chainId) - const routes: Route[] = await getRoutes(tokenIn, tokenOut, chainId) const fee = routes?.length > 0 && routes[0].pools.length > 0 From abbc194710087486af0c6ba18b6e6216dba3a2d2 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Thu, 4 Jan 2024 13:34:06 +0000 Subject: [PATCH 26/26] remove toHex func --- src/entities/trades/swapr-v3/SwaprV3.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/entities/trades/swapr-v3/SwaprV3.ts b/src/entities/trades/swapr-v3/SwaprV3.ts index eb967e5e..ea678dad 100644 --- a/src/entities/trades/swapr-v3/SwaprV3.ts +++ b/src/entities/trades/swapr-v3/SwaprV3.ts @@ -195,8 +195,8 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { public async swapTransaction(options: TradeOptions): Promise { const to: string = validateAndParseAddress(options.recipient) - const amountIn: string = toHex(this.maximumAmountIn()) - const amountOut: string = toHex(this.minimumAmountOut()) + const amountIn: string = `0x${this.maximumAmountIn().raw.toString(16)}` + const amountOut: string = `0x${this.minimumAmountOut().raw.toString(16)}` const isTradeExactInput = this.tradeType === TradeType.EXACT_INPUT const routerContract = getRouterContract() @@ -228,7 +228,3 @@ export class SwaprV3Trade extends TradeWithSwapTransaction { return populatedTransaction } } - -function toHex(currencyAmount: CurrencyAmount) { - return `0x${currencyAmount.raw.toString(16)}` -}