From f584df61185a8440a0ca3c46589cb39c67fecd18 Mon Sep 17 00:00:00 2001 From: Keith Broughton <101608096+keithbro-imx@users.noreply.github.com> Date: Thu, 19 Oct 2023 18:27:32 +1100 Subject: [PATCH] chore: Create separate public types for Amounts and Tokens in the DEX (#1030) --- .../sdk/src/gasEstimate/gasEstimator.test.ts | 2 - .../constructBridgeRequirements.test.ts | 14 +-- .../routing/routingCalculator.test.ts | 22 ++--- .../routing/swap/dexQuoteCache.test.ts | 53 +++++------ .../routing/swap/quoteFetcher.test.ts | 22 ++--- .../routing/swap/swapRoute.test.ts | 48 ++++------ .../smartCheckout/routing/swap/swapRoute.ts | 8 +- .../sdk/src/smartCheckout/routing/types.ts | 6 +- packages/checkout/widgets-lib/package.json | 3 +- .../dex/sdk/src/config/config.test.ts | 3 +- packages/internal/dex/sdk/src/config/index.ts | 7 +- ...ange.getUnsignedSwapTxFromAmountIn.test.ts | 47 ++++++++-- ...nge.getUnsignedSwapTxFromAmountOut.test.ts | 2 +- packages/internal/dex/sdk/src/exchange.ts | 49 +++++++--- packages/internal/dex/sdk/src/lib/fees.ts | 16 ++-- .../dex/sdk/src/lib/getQuotesForRoutes.ts | 8 +- packages/internal/dex/sdk/src/lib/index.ts | 1 - .../sdk/src/lib/nativeTokenService.test.ts | 16 +++- .../dex/sdk/src/lib/nativeTokenService.ts | 15 +-- packages/internal/dex/sdk/src/lib/router.ts | 8 +- .../src/lib/transactionUtils/approval.test.ts | 2 +- .../sdk/src/lib/transactionUtils/approval.ts | 28 +++--- .../dex/sdk/src/lib/transactionUtils/gas.ts | 7 +- .../sdk/src/lib/transactionUtils/getQuote.ts | 34 +------ .../sdk/src/lib/transactionUtils/swap.test.ts | 3 +- .../dex/sdk/src/lib/transactionUtils/swap.ts | 9 +- packages/internal/dex/sdk/src/lib/utils.ts | 79 ++++++++++------ packages/internal/dex/sdk/src/test/utils.ts | 17 ++-- packages/internal/dex/sdk/src/types/index.ts | 94 ++++++++++++++----- 29 files changed, 351 insertions(+), 272 deletions(-) diff --git a/packages/checkout/sdk/src/gasEstimate/gasEstimator.test.ts b/packages/checkout/sdk/src/gasEstimate/gasEstimator.test.ts index b637f388ad..abf72dfc4b 100644 --- a/packages/checkout/sdk/src/gasEstimate/gasEstimator.test.ts +++ b/packages/checkout/sdk/src/gasEstimate/gasEstimator.test.ts @@ -91,7 +91,6 @@ describe('gasServiceEstimator', () => { name: 'TEST', decimals: 18, chainId: 1, - type: 'erc20', }, }, }, @@ -151,7 +150,6 @@ describe('gasServiceEstimator', () => { name: 'TEST', decimals: 18, chainId: 1, - type: 'erc20', }, }, }, diff --git a/packages/checkout/sdk/src/smartCheckout/routing/bridgeAndSwap/constructBridgeRequirements.test.ts b/packages/checkout/sdk/src/smartCheckout/routing/bridgeAndSwap/constructBridgeRequirements.test.ts index fd377c0995..5581abe853 100644 --- a/packages/checkout/sdk/src/smartCheckout/routing/bridgeAndSwap/constructBridgeRequirements.test.ts +++ b/packages/checkout/sdk/src/smartCheckout/routing/bridgeAndSwap/constructBridgeRequirements.test.ts @@ -1,5 +1,5 @@ import { BigNumber } from 'ethers'; -import { ERC20 as DexTokenInfo } from '@imtbl/dex-sdk'; +import { Token as DexTokenInfo } from '@imtbl/dex-sdk'; import { DexQuote } from '../types'; import { ChainId, @@ -75,7 +75,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: 'ERC20', name: 'ERC20', - type: 'erc20', }; const swapTokenInfoB: DexTokenInfo = { @@ -84,7 +83,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: 'ERC20', name: 'ERC20', - type: 'erc20', }; const feesTokenInfo: DexTokenInfo = { @@ -93,7 +91,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: 'ERC20', name: 'ERC20', - type: 'erc20', }; const dexQuoteA = constructDexQuote( @@ -198,7 +195,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: 'ETH', name: 'ETH', - type: 'erc20', }; const feesTokenInfo: DexTokenInfo = { @@ -207,7 +203,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: 'IMX', name: 'IMX', - type: 'erc20', }; const ethDexQuote = constructDexQuote( @@ -283,7 +278,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: 'ERC20', name: 'ERC20', - type: 'erc20', }; const swapTokenInfoB: DexTokenInfo = { @@ -292,7 +286,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: 'ERC20', name: 'ERC20', - type: 'erc20', }; const feesTokenInfo: DexTokenInfo = { @@ -301,7 +294,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: 'ERC20', name: 'ERC20', - type: 'erc20', }; const dexQuoteA = constructDexQuote( @@ -402,7 +394,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: '0xIMX', name: '0xIMX', - type: 'erc20', }; const feesTokenInfo: DexTokenInfo = { @@ -411,7 +402,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: '0xIMX', name: '0xIMX', - type: 'erc20', }; const dexQuote = constructDexQuote( @@ -497,7 +487,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: '0xL2', name: '0xL2', - type: 'erc20', }; const feesTokenInfo: DexTokenInfo = { @@ -506,7 +495,6 @@ describe('constructBridgeRequirements', () => { decimals: 18, symbol: '0xIMX', name: '0xIMX', - type: 'erc20', }; const dexQuote = constructDexQuote( diff --git a/packages/checkout/sdk/src/smartCheckout/routing/routingCalculator.test.ts b/packages/checkout/sdk/src/smartCheckout/routing/routingCalculator.test.ts index 51fe931ca2..caf040ff8d 100644 --- a/packages/checkout/sdk/src/smartCheckout/routing/routingCalculator.test.ts +++ b/packages/checkout/sdk/src/smartCheckout/routing/routingCalculator.test.ts @@ -1,7 +1,7 @@ import { BigNumber, utils } from 'ethers'; import { Environment } from '@imtbl/config'; import { JsonRpcProvider } from '@ethersproject/providers'; -import { ERC20 } from '@imtbl/dex-sdk'; +import { Token } from '@imtbl/dex-sdk'; import { getBridgeAndSwapFundingSteps, getSwapFundingSteps, @@ -67,11 +67,11 @@ describe('routingCalculator', () => { symbol: 'ERC20_2', decimals: 18, address: '0xERC20_2', - } as ERC20, + } as Token, }, amountWithMaxSlippage: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ @@ -84,7 +84,7 @@ describe('routingCalculator', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, recipient: '', basisPoints: 0, @@ -99,11 +99,11 @@ describe('routingCalculator', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, swap: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, }, ], @@ -123,11 +123,11 @@ describe('routingCalculator', () => { symbol: 'ERC20_1', decimals: 18, address: '0xERC20_1', - } as ERC20, + } as Token, }, amountWithMaxSlippage: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ @@ -140,7 +140,7 @@ describe('routingCalculator', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, recipient: '', basisPoints: 0, @@ -155,11 +155,11 @@ describe('routingCalculator', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, swap: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, }, ], diff --git a/packages/checkout/sdk/src/smartCheckout/routing/swap/dexQuoteCache.test.ts b/packages/checkout/sdk/src/smartCheckout/routing/swap/dexQuoteCache.test.ts index d684d0dd13..4d16e16bd0 100644 --- a/packages/checkout/sdk/src/smartCheckout/routing/swap/dexQuoteCache.test.ts +++ b/packages/checkout/sdk/src/smartCheckout/routing/swap/dexQuoteCache.test.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { ERC20 } from '@imtbl/dex-sdk'; +import { Token } from '@imtbl/dex-sdk'; import { BigNumber } from 'ethers'; import { Environment } from '@imtbl/config'; import { getOrSetQuotesFromCache } from './dexQuoteCache'; @@ -27,18 +26,18 @@ describe('dexQuoteCache', () => { quote: { amount: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, amountWithMaxSlippage: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ { amount: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, recipient: '', basisPoints: 0, @@ -47,11 +46,11 @@ describe('dexQuoteCache', () => { }, approval: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, swap: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, }, ], @@ -65,18 +64,18 @@ describe('dexQuoteCache', () => { quote: { amount: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, amountWithMaxSlippage: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ { amount: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, recipient: '', basisPoints: 0, @@ -85,11 +84,11 @@ describe('dexQuoteCache', () => { }, approval: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, swap: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, }, ], @@ -120,18 +119,18 @@ describe('dexQuoteCache', () => { quote: { amount: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, amountWithMaxSlippage: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ { amount: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, recipient: '', basisPoints: 0, @@ -140,11 +139,11 @@ describe('dexQuoteCache', () => { }, approval: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, swap: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, }, ], @@ -166,18 +165,18 @@ describe('dexQuoteCache', () => { quote: { amount: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, amountWithMaxSlippage: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ { amount: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, recipient: '', basisPoints: 0, @@ -186,11 +185,11 @@ describe('dexQuoteCache', () => { }, approval: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, swap: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, }, ], @@ -222,18 +221,18 @@ describe('dexQuoteCache', () => { quote: { amount: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, amountWithMaxSlippage: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ { amount: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, recipient: '', basisPoints: 0, @@ -242,11 +241,11 @@ describe('dexQuoteCache', () => { }, approval: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, swap: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, }, ], diff --git a/packages/checkout/sdk/src/smartCheckout/routing/swap/quoteFetcher.test.ts b/packages/checkout/sdk/src/smartCheckout/routing/swap/quoteFetcher.test.ts index 66853a199f..a9c397afee 100644 --- a/packages/checkout/sdk/src/smartCheckout/routing/swap/quoteFetcher.test.ts +++ b/packages/checkout/sdk/src/smartCheckout/routing/swap/quoteFetcher.test.ts @@ -1,7 +1,7 @@ import { Environment } from '@imtbl/config'; import { BigNumber } from 'ethers'; import { - ERC20, + Token, TransactionDetails, TransactionResponse, } from '@imtbl/dex-sdk'; @@ -29,18 +29,18 @@ describe('quoteFetcher', () => { quote: { amount: { value: BigNumber.from(quoteAmount), - token: {} as ERC20, + token: {} as Token, }, amountWithMaxSlippage: { value: BigNumber.from(quoteAmount), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ { amount: { value: BigNumber.from(feeAmount), - token: {} as ERC20, + token: {} as Token, }, recipient: '', basisPoints: 0, @@ -53,7 +53,7 @@ describe('quoteFetcher', () => { if (swapGasFeeEstimate) { transactionResponse.swap.gasFeeEstimate = { value: BigNumber.from(swapGasFeeEstimate), - token: {} as ERC20, + token: {} as Token, }; } @@ -61,7 +61,7 @@ describe('quoteFetcher', () => { transactionResponse.approval = { gasFeeEstimate: { value: BigNumber.from(approvalGasFeeEstimate), - token: {} as ERC20, + token: {} as Token, }, } as TransactionDetails; } @@ -81,18 +81,18 @@ describe('quoteFetcher', () => { quote: { amount: { value: BigNumber.from(quoteAmount), - token: {} as ERC20, + token: {} as Token, }, amountWithMaxSlippage: { value: BigNumber.from(quoteAmount), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ { amount: { value: BigNumber.from(feeAmount), - token: {} as ERC20, + token: {} as Token, }, recipient: '', basisPoints: 0, @@ -104,14 +104,14 @@ describe('quoteFetcher', () => { if (swap) { dexQuote.swap = { value: BigNumber.from(swap), - token: {} as ERC20, + token: {} as Token, }; } if (approval) { dexQuote.approval = { value: BigNumber.from(approval), - token: {} as ERC20, + token: {} as Token, }; } diff --git a/packages/checkout/sdk/src/smartCheckout/routing/swap/swapRoute.test.ts b/packages/checkout/sdk/src/smartCheckout/routing/swap/swapRoute.test.ts index 1624280f66..9c372c30bf 100644 --- a/packages/checkout/sdk/src/smartCheckout/routing/swap/swapRoute.test.ts +++ b/packages/checkout/sdk/src/smartCheckout/routing/swap/swapRoute.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Environment } from '@imtbl/config'; -import { Fee, ERC20 } from '@imtbl/dex-sdk'; +import { Fee, Token } from '@imtbl/dex-sdk'; import { BigNumber, utils } from 'ethers'; import { CheckoutConfiguration } from '../../../config'; import { BalanceRequirement, BalanceCheckResult } from '../../balanceCheck/types'; @@ -48,11 +48,11 @@ describe('swapRoute', () => { symbol: 'ERC20_2', decimals: 18, address: '0xERC20_2', - } as ERC20, + } as Token, }, amountWithMaxSlippage: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ @@ -65,7 +65,7 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, recipient: '', basisPoints: 0, @@ -80,7 +80,7 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, swap: { value: BigNumber.from(2), @@ -90,7 +90,7 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, }, ], @@ -105,11 +105,11 @@ describe('swapRoute', () => { symbol: 'ERC20_3', decimals: 18, address: '0xERC20_3', - } as ERC20, + } as Token, }, amountWithMaxSlippage: { value: BigNumber.from(1), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ @@ -122,7 +122,7 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, recipient: '', basisPoints: 0, @@ -137,7 +137,7 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, swap: { value: BigNumber.from(2), @@ -147,7 +147,7 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, }, ], @@ -167,11 +167,11 @@ describe('swapRoute', () => { symbol: 'ERC20_1', decimals: 18, address: '0xERC20_1', - } as ERC20, + } as Token, }, amountWithMaxSlippage: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, slippage: 0, fees: [ @@ -184,7 +184,7 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, recipient: '', basisPoints: 0, @@ -199,11 +199,11 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - } as ERC20, + } as Token, }, swap: { value: BigNumber.from(2), - token: {} as ERC20, + token: {} as Token, }, }, ], @@ -1298,7 +1298,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - type: 'erc20', }, }, ); @@ -1334,7 +1333,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - type: 'erc20', }, }, ); @@ -1370,7 +1368,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - type: 'erc20', }, }, ); @@ -1406,7 +1403,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - type: 'erc20', }, }, ); @@ -1448,7 +1444,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - type: 'erc20', }, }, }, @@ -1501,7 +1496,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - type: 'erc20', }, }, }, @@ -1577,7 +1571,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: '0xERC20_1', - type: 'erc20', }, }, }, @@ -1592,7 +1585,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: '0xERC20_2', - type: 'erc20', }, }, }, @@ -1607,7 +1599,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: '0xERC20_3', - type: 'erc20', }, }, }, @@ -1661,7 +1652,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - type: 'erc20', }, }, }, @@ -1714,7 +1704,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: IMX_ADDRESS_ZKEVM, - type: 'erc20', }, }, }, @@ -1767,7 +1756,6 @@ describe('swapRoute', () => { symbol: 'IMX', decimals: 18, address: '0xERC20', - type: 'erc20', }, }, }, @@ -1921,7 +1909,6 @@ describe('swapRoute', () => { symbol: 'ERC20', decimals: 18, address: '0xERC20', - type: 'erc20', }, }, }, @@ -1936,7 +1923,6 @@ describe('swapRoute', () => { symbol: 'ERC20', decimals: 18, address: '0xERC20', - type: 'erc20', }, }, }, @@ -2043,7 +2029,6 @@ describe('swapRoute', () => { symbol: 'ERC20', decimals: 18, address: '0xERC20', - type: 'erc20', }, }, }, @@ -2058,7 +2043,6 @@ describe('swapRoute', () => { symbol: 'ERC20', decimals: 18, address: '0xERC20', - type: 'erc20', }, }, }, diff --git a/packages/checkout/sdk/src/smartCheckout/routing/swap/swapRoute.ts b/packages/checkout/sdk/src/smartCheckout/routing/swap/swapRoute.ts index 8f2bb0baf4..d1506534aa 100644 --- a/packages/checkout/sdk/src/smartCheckout/routing/swap/swapRoute.ts +++ b/packages/checkout/sdk/src/smartCheckout/routing/swap/swapRoute.ts @@ -1,5 +1,5 @@ import { BigNumber, utils } from 'ethers'; -import { Amount, ERC20, Fee } from '@imtbl/dex-sdk'; +import { Amount, Fee } from '@imtbl/dex-sdk'; import { CheckoutConfiguration, getL2ChainId } from '../../../config'; import { AvailableRoutingOptions, @@ -17,8 +17,8 @@ import { DexQuoteCache, TokenBalanceResult } from '../types'; import { getOrSetQuotesFromCache } from './dexQuoteCache'; const constructFees = ( - approvalGasFees: Amount | null | undefined, - swapGasFees: Amount | null, + approvalGasFees: Amount | null | undefined, + swapGasFees: Amount | null, swapFees: Fee[], ): SwapFees => { let approvalGasFeeAmount = BigNumber.from(0); @@ -137,7 +137,7 @@ export const getRequiredToken = ( type SufficientApprovalFees = { sufficient: boolean, approvalGasFee: BigNumber, approvalGasTokenAddress: string }; export const checkUserCanCoverApprovalFees = ( l2Balances: GetBalanceResult[], - approval: Amount | null, + approval: Amount | null, ): SufficientApprovalFees => { // Check if approval required if (!approval) return { sufficient: true, approvalGasFee: BigNumber.from(0), approvalGasTokenAddress: '' }; diff --git a/packages/checkout/sdk/src/smartCheckout/routing/types.ts b/packages/checkout/sdk/src/smartCheckout/routing/types.ts index e663d359e5..0e53e2b653 100644 --- a/packages/checkout/sdk/src/smartCheckout/routing/types.ts +++ b/packages/checkout/sdk/src/smartCheckout/routing/types.ts @@ -1,4 +1,4 @@ -import { Amount, ERC20, Quote } from '@imtbl/dex-sdk'; +import { Amount, Quote } from '@imtbl/dex-sdk'; import { ChainId, GetBalanceResult, @@ -19,6 +19,6 @@ export type DexQuoteCache = Map; export type DexQuotes = Map; export type DexQuote = { quote: Quote, - approval: Amount | null, - swap: Amount | null, + approval: Amount | null, + swap: Amount | null, }; diff --git a/packages/checkout/widgets-lib/package.json b/packages/checkout/widgets-lib/package.json index 01df852893..28b718d659 100644 --- a/packages/checkout/widgets-lib/package.json +++ b/packages/checkout/widgets-lib/package.json @@ -78,6 +78,7 @@ "test": "jest test --passWithNoTests", "test:cypress": "NODE_ENV=test cypress run --component --headless", "test:cypress:open": "NODE_ENV=test cypress open --component --browser electron", - "test:watch": "jest test --passWithNoTests --watch" + "test:watch": "jest test --passWithNoTests --watch", + "typecheck": "tsc --noEmit" } } diff --git a/packages/internal/dex/sdk/src/config/config.test.ts b/packages/internal/dex/sdk/src/config/config.test.ts index 7e21b0be53..bdab9c88cd 100644 --- a/packages/internal/dex/sdk/src/config/config.test.ts +++ b/packages/internal/dex/sdk/src/config/config.test.ts @@ -1,7 +1,8 @@ import { Environment, ImmutableConfiguration } from '@imtbl/config'; import { ChainNotSupportedError, InvalidConfigurationError } from 'errors'; import * as test from 'test/utils'; -import { ExchangeModuleConfiguration, ExchangeOverrides, ERC20 } from '../types'; +import { ERC20 } from 'types'; +import { ExchangeModuleConfiguration, ExchangeOverrides } from '../types'; import { ExchangeConfiguration, ExchangeContracts } from './index'; import { IMMUTABLE_TESTNET_CHAIN_ID } from '../constants/chains'; diff --git a/packages/internal/dex/sdk/src/config/index.ts b/packages/internal/dex/sdk/src/config/index.ts index c0f96b4774..31e9bd50e2 100644 --- a/packages/internal/dex/sdk/src/config/index.ts +++ b/packages/internal/dex/sdk/src/config/index.ts @@ -1,13 +1,12 @@ import { Environment, ImmutableConfiguration } from '@imtbl/config'; import { ChainNotSupportedError, InvalidConfigurationError } from 'errors'; -import { SecondaryFee, isValidNonZeroAddress } from 'lib'; -import { Chain, ExchangeModuleConfiguration, ExchangeOverrides } from '../types'; +import { isValidNonZeroAddress } from 'lib'; +import { ExchangeModuleConfiguration, ExchangeOverrides, SecondaryFee, Chain } from '../types'; import { IMMUTABLE_TESTNET_CHAIN_ID, IMMUTABLE_TESTNET_COMMON_ROUTING_TOKENS, IMMUTABLE_TESTNET_RPC_URL, MAX_SECONDARY_FEE_BASIS_POINTS, - NATIVE_IMX_IMMUTABLE_TESTNET, TIMX_IMMUTABLE_TESTNET, } from '../constants'; @@ -35,7 +34,7 @@ export const SUPPORTED_SANDBOX_CHAINS: Record = { rpcUrl: IMMUTABLE_TESTNET_RPC_URL, contracts: CONTRACTS_FOR_CHAIN_ID[IMMUTABLE_TESTNET_CHAIN_ID], commonRoutingTokens: IMMUTABLE_TESTNET_COMMON_ROUTING_TOKENS, - nativeToken: NATIVE_IMX_IMMUTABLE_TESTNET, + nativeToken: TIMX_IMMUTABLE_TESTNET, // TODO: TP-1649: Change to Native when ready. wrappedNativeToken: TIMX_IMMUTABLE_TESTNET, // TODO: TP-1649: Change to WIMX when ready. }, }; diff --git a/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountIn.test.ts b/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountIn.test.ts index eaa95617a8..68c9492d4b 100644 --- a/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountIn.test.ts +++ b/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountIn.test.ts @@ -6,6 +6,9 @@ import { } from 'errors'; import { ERC20__factory } from 'contracts/types/factories/ERC20__factory'; import { constants, utils } from 'ethers'; +import { SecondaryFee } from 'types'; +import { Environment } from '@imtbl/config'; +import { IMMUTABLE_TESTNET_CHAIN_ID, TIMX_IMMUTABLE_TESTNET } from './constants'; import { Exchange } from './exchange'; import { mockRouterImplementation, @@ -29,9 +32,13 @@ import { newAmountFromString, formatTokenAmount, WIMX_TEST_TOKEN, + NATIVE_TEST_TOKEN, + makeAddr, + createPool, + WETH_TEST_TOKEN, } from './test/utils'; import { - addAmount, Router, SecondaryFee, + addAmount, Router, } from './lib'; jest.mock('@ethersproject/providers'); @@ -74,6 +81,34 @@ describe('getUnsignedSwapTxFromAmountIn', () => { ) as unknown as JsonRpcProvider; }); + describe('with the out-of-the-box minimal configuration', () => { + it('uses the edge tIMX as the gas token', async () => { + const tokenIn = { ...USDC_TEST_TOKEN, chainId: IMMUTABLE_TESTNET_CHAIN_ID }; + const tokenOut = { ...WETH_TEST_TOKEN, chainId: IMMUTABLE_TESTNET_CHAIN_ID }; + const params = { pools: [createPool(tokenIn, tokenOut)] }; + + mockRouterImplementation(params); + + const exchange = new Exchange({ + baseConfig: { + environment: Environment.SANDBOX, + }, + chainId: IMMUTABLE_TESTNET_CHAIN_ID, + }); + + const result = await exchange.getUnsignedSwapTxFromAmountIn( + makeAddr('fromAddress'), + tokenIn.address, + tokenOut.address, + BigNumber.from(1), + ); + + expectToBeDefined(result.swap.gasFeeEstimate); + expect(result.swap.gasFeeEstimate.token).toEqual(TIMX_IMMUTABLE_TESTNET); + expect(result.swap.gasFeeEstimate.token.address).toEqual('0x0000000000000000000000000000000000001010'); + }); + }); + describe('When the swap transaction requires approval', () => { it('should include the unsigned approval transaction', async () => { const params = setupSwapTxTest(); @@ -117,11 +152,11 @@ describe('getUnsignedSwapTxFromAmountIn', () => { expectToBeDefined(tx.approval?.gasFeeEstimate); expect(tx.approval.gasFeeEstimate.value).toEqual(TEST_GAS_PRICE.mul(APPROVE_GAS_ESTIMATE)); - expect(tx.approval.gasFeeEstimate.token.chainId).toEqual(WIMX_TEST_TOKEN.chainId); - expect(tx.approval.gasFeeEstimate.token.address).toEqual(WIMX_TEST_TOKEN.address); - expect(tx.approval.gasFeeEstimate.token.decimals).toEqual(WIMX_TEST_TOKEN.decimals); - expect(tx.approval.gasFeeEstimate.token.symbol).toEqual(WIMX_TEST_TOKEN.symbol); - expect(tx.approval.gasFeeEstimate.token.name).toEqual(WIMX_TEST_TOKEN.name); + expect(tx.approval.gasFeeEstimate.token.chainId).toEqual(NATIVE_TEST_TOKEN.chainId); + expect(tx.approval.gasFeeEstimate.token.address).toEqual(''); + expect(tx.approval.gasFeeEstimate.token.decimals).toEqual(NATIVE_TEST_TOKEN.decimals); + expect(tx.approval.gasFeeEstimate.token.symbol).toEqual(NATIVE_TEST_TOKEN.symbol); + expect(tx.approval.gasFeeEstimate.token.name).toEqual(NATIVE_TEST_TOKEN.name); }); }); diff --git a/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountOut.test.ts b/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountOut.test.ts index 2819750598..47e878d592 100644 --- a/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountOut.test.ts +++ b/packages/internal/dex/sdk/src/exchange.getUnsignedSwapTxFromAmountOut.test.ts @@ -1,9 +1,9 @@ import { JsonRpcProvider } from '@ethersproject/providers'; import { Contract } from '@ethersproject/contracts'; import { BigNumber } from '@ethersproject/bignumber'; -import { SecondaryFee } from 'lib'; import { ethers } from 'ethers'; import { ERC20__factory } from 'contracts/types'; +import { SecondaryFee } from './types'; import { Exchange } from './exchange'; import { mockRouterImplementation, diff --git a/packages/internal/dex/sdk/src/exchange.ts b/packages/internal/dex/sdk/src/exchange.ts index 8880957c74..56e2e25bee 100644 --- a/packages/internal/dex/sdk/src/exchange.ts +++ b/packages/internal/dex/sdk/src/exchange.ts @@ -4,17 +4,40 @@ import assert from 'assert'; import { DuplicateAddressesError, InvalidAddressError, InvalidMaxHopsError, InvalidSlippageError } from 'errors'; import { fetchGasPrice } from 'lib/transactionUtils/gas'; import { getApproval, prepareApproval } from 'lib/transactionUtils/approval'; -import { getOurQuoteReqAmount, prepareUserQuote } from 'lib/transactionUtils/getQuote'; +import { applySlippage, getOurQuoteReqAmount, getQuoteAmountFromTradeType } from 'lib/transactionUtils/getQuote'; import { Fees } from 'lib/fees'; import { SecondaryFee__factory } from 'contracts/types'; import { NativeTokenService } from 'lib/nativeTokenService'; import { DEFAULT_DEADLINE, DEFAULT_MAX_HOPS, DEFAULT_SLIPPAGE, MAX_MAX_HOPS, MIN_MAX_HOPS } from './constants'; import { Router } from './lib/router'; -import { getERC20Decimals, isValidNonZeroAddress, newAmount } from './lib/utils'; -import { ERC20, ExchangeModuleConfiguration, Native, SecondaryFee, TransactionResponse } from './types'; +import { getERC20Decimals, isValidNonZeroAddress, newAmount, toPublicAmount } from './lib/utils'; +import { + Coin, + CoinAmount, + ERC20, + ExchangeModuleConfiguration, + Quote, + SecondaryFee, + TransactionResponse, +} from './types'; import { getSwap, adjustQuoteWithFees } from './lib/transactionUtils/swap'; import { ExchangeConfiguration } from './config'; +const toPublicQuote = ( + amount: CoinAmount, + amountWithMaxSlippage: CoinAmount, + slippage: number, + fees: Fees, +): Quote => ({ + amount, + amountWithMaxSlippage, + slippage, + fees: fees.withAmounts().map((fee) => ({ + ...fee, + amount: toPublicAmount(fee.amount), + })), +}); + export class Exchange { private provider: ethers.providers.JsonRpcProvider; @@ -22,7 +45,7 @@ export class Exchange { private chainId: number; - private nativeToken: Native; + private nativeToken: Coin; private wrappedNativeToken: ERC20; @@ -148,12 +171,16 @@ export class Exchange { this.nativeTokenService, ); - const userQuote = prepareUserQuote(otherToken, adjustedQuote, slippagePercent, fees, this.nativeTokenService); + const quotedAmount = getQuoteAmountFromTradeType(adjustedQuote); + const amountWithMaxSlippage = newAmount( + applySlippage(adjustedQuote.tradeType, quotedAmount.value, slippagePercent), + otherToken, + ); const preparedApproval = prepareApproval( tradeType, amountSpecified, - userQuote.amountWithMaxSlippage, + amountWithMaxSlippage, { routerAddress: this.routerContractAddress, secondaryFeeAddress: this.secondaryFeeContractAddress, @@ -162,13 +189,11 @@ export class Exchange { ); // preparedApproval always uses the tokenIn address because we are always selling the tokenIn - const approval = await getApproval(this.provider, fromAddress, preparedApproval, gasPrice, this.nativeTokenService); + const approval = await getApproval(this.provider, fromAddress, preparedApproval, gasPrice); - return { - approval, - swap, - quote: userQuote, - }; + const quote = toPublicQuote(quotedAmount, amountWithMaxSlippage, slippagePercent, fees); + + return { quote, approval, swap }; } /** diff --git a/packages/internal/dex/sdk/src/lib/fees.ts b/packages/internal/dex/sdk/src/lib/fees.ts index 1efb3d4ca7..dcf659cfcb 100644 --- a/packages/internal/dex/sdk/src/lib/fees.ts +++ b/packages/internal/dex/sdk/src/lib/fees.ts @@ -1,14 +1,12 @@ import { BASIS_POINT_PRECISION } from 'constants/router'; import { BigNumber } from 'ethers'; -import { - Amount, - SecondaryFee, newAmount, addAmount, Coin, subtractAmount, -} from 'lib'; +import { Coin, CoinAmount, SecondaryFee } from 'types'; +import { addAmount, newAmount, subtractAmount } from './utils'; export class Fees { private secondaryFees: SecondaryFee[]; - private amount: Amount; + private amount: CoinAmount; constructor(secondaryFees: SecondaryFee[], token: Coin) { this.secondaryFees = secondaryFees; @@ -19,15 +17,15 @@ export class Fees { return this.amount.token; } - addAmount(amount: Amount): void { + addAmount(amount: CoinAmount): void { this.amount = addAmount(this.amount, amount); } - amountWithFeesApplied(): Amount { + amountWithFeesApplied(): CoinAmount { return addAmount(this.amount, this.total()); } - amountLessFees(): Amount { + amountLessFees(): CoinAmount { return subtractAmount(this.amount, this.total()); } @@ -44,7 +42,7 @@ export class Fees { }); } - private total(): Amount { + private total(): CoinAmount { let totalFees = newAmount(BigNumber.from(0), this.amount.token); for (const fee of this.secondaryFees) { diff --git a/packages/internal/dex/sdk/src/lib/getQuotesForRoutes.ts b/packages/internal/dex/sdk/src/lib/getQuotesForRoutes.ts index 06a0edf45d..f8e296e484 100644 --- a/packages/internal/dex/sdk/src/lib/getQuotesForRoutes.ts +++ b/packages/internal/dex/sdk/src/lib/getQuotesForRoutes.ts @@ -2,7 +2,7 @@ import { Route, SwapQuoter } from '@uniswap/v3-sdk'; import { TradeType, Token } from '@uniswap/sdk-core'; import { BigNumber, ethers } from 'ethers'; import { ProviderCallError } from 'errors'; -import { Amount, ERC20 } from 'lib'; +import { CoinAmount, ERC20 } from 'types'; import { multicallMultipleCallDataSingContract, MulticallResponse } from './multicall'; import { newAmount, quoteReturnMapping, toCurrencyAmount, uniswapTokenToERC20, @@ -15,8 +15,8 @@ const gasEstimateIndex = 3; export type QuoteResult = { route: Route; gasEstimate: ethers.BigNumber - amountIn: Amount; - amountOut: Amount; + amountIn: CoinAmount; + amountOut: CoinAmount; tradeType: TradeType; }; @@ -24,7 +24,7 @@ export async function getQuotesForRoutes( multicallContract: Multicall, quoterContractAddress: string, routes: Route[], - amountSpecified: Amount, + amountSpecified: CoinAmount, tradeType: TradeType, ): Promise { const callData = routes.map( diff --git a/packages/internal/dex/sdk/src/lib/index.ts b/packages/internal/dex/sdk/src/lib/index.ts index 2db153784d..302a98192e 100644 --- a/packages/internal/dex/sdk/src/lib/index.ts +++ b/packages/internal/dex/sdk/src/lib/index.ts @@ -1,4 +1,3 @@ export * from './multicall'; export * from './router'; -export * from '../types'; export * from './utils'; diff --git a/packages/internal/dex/sdk/src/lib/nativeTokenService.test.ts b/packages/internal/dex/sdk/src/lib/nativeTokenService.test.ts index c5b2275c8a..6bb6a01d8a 100644 --- a/packages/internal/dex/sdk/src/lib/nativeTokenService.test.ts +++ b/packages/internal/dex/sdk/src/lib/nativeTokenService.test.ts @@ -15,6 +15,20 @@ describe('NativeTokenService', () => { expectERC20(wrappedEquivalent.token, nativeTokenService.wrappedToken.address); expect(formatAmount(wrappedEquivalent)).toEqual('1.0'); }); + + it('throws an error if the token is not a native amount', () => { + const erc20Amount = newAmountFromString('1', FUN_TEST_TOKEN); + expect(() => nativeTokenService.wrapAmount(erc20Amount)).toThrowError( + 'cannot wrap non-native token: 0xCc7bb2D219A0FC08033E130629C2B854b7bA9195', + ); + }); + + it('throws an error if the token is a wrapped native amount', () => { + const erc20Amount = newAmountFromString('1', nativeTokenService.wrappedToken); + expect(() => nativeTokenService.wrapAmount(erc20Amount)).toThrowError( + 'cannot wrap non-native token: 0xAf7cf5D4Af0BFAa85d384d42b8D410762Ccbce69', + ); + }); }); describe('unwrapAmount', () => { @@ -28,7 +42,7 @@ describe('NativeTokenService', () => { it('throws an error if the token is not a wrapped native amount', () => { const erc20Amount = newAmountFromString('1', FUN_TEST_TOKEN); expect(() => nativeTokenService.unwrapAmount(erc20Amount)).toThrowError( - 'token 0xCc7bb2D219A0FC08033E130629C2B854b7bA9195 is not wrapped', + 'cannot unwrap non-wrapped token 0xCc7bb2D219A0FC08033E130629C2B854b7bA9195', ); }); }); diff --git a/packages/internal/dex/sdk/src/lib/nativeTokenService.ts b/packages/internal/dex/sdk/src/lib/nativeTokenService.ts index 9171bace50..5d84e3a7c8 100644 --- a/packages/internal/dex/sdk/src/lib/nativeTokenService.ts +++ b/packages/internal/dex/sdk/src/lib/nativeTokenService.ts @@ -1,18 +1,21 @@ import { - Amount, Coin, ERC20, Native, + Coin, CoinAmount, ERC20, Native, } from 'types'; import { newAmount } from './utils'; export class NativeTokenService { - constructor(readonly nativeToken: Native, readonly wrappedToken: ERC20) {} + constructor(readonly nativeToken: Coin, readonly wrappedToken: ERC20) {} - wrapAmount(amount: Amount): Amount { + wrapAmount(amount: CoinAmount): CoinAmount { + if (!this.isNativeToken(amount.token)) { + throw new Error(`cannot wrap non-native token: ${amount.token.address}`); + } return newAmount(amount.value, this.wrappedToken); } - unwrapAmount(amount: Amount): Amount { + unwrapAmount(amount: CoinAmount): CoinAmount { if (amount.token !== this.wrappedToken) { - throw new Error(`token ${amount.token.address} is not wrapped`); + throw new Error(`cannot unwrap non-wrapped token ${amount.token.address}`); } return newAmount(amount.value, this.nativeToken); } @@ -24,7 +27,7 @@ export class NativeTokenService { return token as ERC20; } - maybeWrapAmount(amount: Amount): Amount { + maybeWrapAmount(amount: CoinAmount): CoinAmount { return newAmount(amount.value, this.maybeWrapToken(amount.token)); } diff --git a/packages/internal/dex/sdk/src/lib/router.ts b/packages/internal/dex/sdk/src/lib/router.ts index 0149d352c8..961aafbd67 100644 --- a/packages/internal/dex/sdk/src/lib/router.ts +++ b/packages/internal/dex/sdk/src/lib/router.ts @@ -2,7 +2,7 @@ import { ethers } from 'ethers'; import { Token, TradeType } from '@uniswap/sdk-core'; import { Pool, Route } from '@uniswap/v3-sdk'; import { NoRoutesAvailableError } from 'errors'; -import { Amount, ERC20 } from 'types'; +import { CoinAmount, ERC20 } from 'types'; import { erc20ToUniswapToken, poolEquals, uniswapTokenToERC20 } from './utils'; import { getQuotesForRoutes, QuoteResult } from './getQuotesForRoutes'; import { fetchValidPools } from './poolUtils/fetchValidPools'; @@ -29,7 +29,7 @@ export class Router { } public async findOptimalRoute( - amountSpecified: Amount, + amountSpecified: CoinAmount, otherToken: ERC20, tradeType: TradeType, maxHops: number = 2, @@ -69,7 +69,7 @@ export class Router { private async getBestQuoteFromRoutes( multicallContract: Multicall, routes: Route[], - amountSpecified: Amount, + amountSpecified: CoinAmount, tradeType: TradeType, ): Promise { const quotes = await getQuotesForRoutes( @@ -125,7 +125,7 @@ export class Router { // eslint-disable-next-line class-methods-use-this private determineERC20InAndERC20Out( tradeType: TradeType, - amountSpecified: Amount, + amountSpecified: CoinAmount, otherToken: ERC20, ): [ERC20, ERC20] { // If the trade type is EXACT INPUT then we have specified the amount for the tokenIn diff --git a/packages/internal/dex/sdk/src/lib/transactionUtils/approval.test.ts b/packages/internal/dex/sdk/src/lib/transactionUtils/approval.test.ts index d0942b76df..8b8caa4ed5 100644 --- a/packages/internal/dex/sdk/src/lib/transactionUtils/approval.test.ts +++ b/packages/internal/dex/sdk/src/lib/transactionUtils/approval.test.ts @@ -10,7 +10,7 @@ import { ERC20__factory } from 'contracts/types/factories/ERC20__factory'; import { ApproveError } from 'errors'; import { ethers } from 'ethers'; import { TradeType } from '@uniswap/sdk-core'; -import { SecondaryFee } from 'lib'; +import { SecondaryFee } from 'types'; import { getApproveGasEstimate, getApproveTransaction, prepareApproval } from './approval'; jest.mock('@ethersproject/providers'); diff --git a/packages/internal/dex/sdk/src/lib/transactionUtils/approval.ts b/packages/internal/dex/sdk/src/lib/transactionUtils/approval.ts index 117fde61e6..e491e9f823 100644 --- a/packages/internal/dex/sdk/src/lib/transactionUtils/approval.ts +++ b/packages/internal/dex/sdk/src/lib/transactionUtils/approval.ts @@ -4,16 +4,16 @@ import { ERC20__factory } from 'contracts/types/factories/ERC20__factory'; import { ApproveError, AlreadyApprovedError } from 'errors'; import { ethers } from 'ethers'; import { TradeType } from '@uniswap/sdk-core'; -import { newAmount } from 'lib/utils'; -import { NativeTokenService } from 'lib/nativeTokenService'; +import { newAmount, toPublicAmount } from 'lib/utils'; +import { CoinAmount, Coin, ERC20 } from 'types'; import { - Amount, ERC20, Native, SecondaryFee, TransactionDetails, + SecondaryFee, TransactionDetails, } from '../../types'; import { calculateGasFee } from './gas'; type PreparedApproval = { spender: string; - amount: Amount; + amount: CoinAmount; }; /** @@ -29,9 +29,9 @@ type PreparedApproval = { const getERC20AmountToApprove = async ( provider: JsonRpcProvider, ownerAddress: string, - tokenAmount: Amount, + tokenAmount: CoinAmount, spenderAddress: string, -): Promise> => { +): Promise> => { // create an instance of the ERC20 token contract const erc20Contract = ERC20__factory.connect(tokenAmount.token.address, provider); @@ -64,7 +64,7 @@ const getERC20AmountToApprove = async ( */ const getUnsignedERC20ApproveTransaction = ( ownerAddress: string, - tokenAmount: Amount, + tokenAmount: CoinAmount, spenderAddress: string, ): TransactionRequest => { if (ownerAddress === spenderAddress) { @@ -84,8 +84,8 @@ const getUnsignedERC20ApproveTransaction = ( export const prepareApproval = ( tradeType: TradeType, - amountSpecified: Amount, - amountWithSlippage: Amount, + amountSpecified: CoinAmount, + amountWithSlippage: CoinAmount, contracts: { routerAddress: string; secondaryFeeAddress: string; @@ -114,10 +114,10 @@ export const prepareApproval = ( export const getApproveTransaction = async ( provider: JsonRpcProvider, ownerAddress: string, - tokenAmount: Amount, + tokenAmount: CoinAmount, spenderAddress: string, ): Promise => { - let amountToApprove: Amount; + let amountToApprove: CoinAmount; try { amountToApprove = await getERC20AmountToApprove( provider, @@ -157,8 +157,7 @@ export const getApproval = async ( provider: JsonRpcProvider, ownerAddress: string, preparedApproval: PreparedApproval, - gasPrice: Amount | null, - nativeTokenService: NativeTokenService, + gasPrice: CoinAmount | null, ): Promise => { const approveTransaction = await getApproveTransaction( provider, @@ -182,7 +181,6 @@ export const getApproval = async ( return { transaction: approveTransaction, - // TODO: TP-1649: Remove the wrapping here - gasFeeEstimate: gasFeeEstimate ? nativeTokenService.maybeWrapAmount(gasFeeEstimate) : null, + gasFeeEstimate: gasFeeEstimate ? toPublicAmount(gasFeeEstimate) : null, }; }; diff --git a/packages/internal/dex/sdk/src/lib/transactionUtils/gas.ts b/packages/internal/dex/sdk/src/lib/transactionUtils/gas.ts index 633aa7c274..4eebf1a555 100644 --- a/packages/internal/dex/sdk/src/lib/transactionUtils/gas.ts +++ b/packages/internal/dex/sdk/src/lib/transactionUtils/gas.ts @@ -1,8 +1,9 @@ import { BigNumber } from 'ethers'; import { JsonRpcProvider, FeeData } from '@ethersproject/providers'; import { - Amount, newAmount, Native, + newAmount, } from 'lib'; +import { CoinAmount, Coin } from 'types'; type EIP1559FeeData = { maxFeePerGas: BigNumber; @@ -28,7 +29,7 @@ export const doesChainSupportEIP1559 = (fee: FeeData): fee is EIP1559FeeData => * @returns {Amount | null} - The gas price in the smallest denomination of the chain's currency, * or null if no gas price is available */ -export const fetchGasPrice = async (provider: JsonRpcProvider, nativeToken: Native): Promise | null> => { +export const fetchGasPrice = async (provider: JsonRpcProvider, nativeToken: Coin): Promise | null> => { const feeData = await provider.getFeeData().catch(() => null); if (!feeData) return null; @@ -46,6 +47,6 @@ export const fetchGasPrice = async (provider: JsonRpcProvider, nativeToken: Nati * @param {BigNumber} gasUsed - The total gas units that will be used for the transaction * @returns - The cost of the transaction in the gas token's smallest denomination (e.g. WEI) */ -export const calculateGasFee = (gasPrice: Amount, gasEstimate: BigNumber): Amount => +export const calculateGasFee = (gasPrice: CoinAmount, gasEstimate: BigNumber): CoinAmount => // eslint-disable-next-line implicit-arrow-linebreak newAmount(gasEstimate.mul(gasPrice.value), gasPrice.token); diff --git a/packages/internal/dex/sdk/src/lib/transactionUtils/getQuote.ts b/packages/internal/dex/sdk/src/lib/transactionUtils/getQuote.ts index 81b57c1e8f..6c082606fc 100644 --- a/packages/internal/dex/sdk/src/lib/transactionUtils/getQuote.ts +++ b/packages/internal/dex/sdk/src/lib/transactionUtils/getQuote.ts @@ -3,12 +3,10 @@ import { ethers } from 'ethers'; import { Fees } from 'lib/fees'; import { QuoteResult } from 'lib/getQuotesForRoutes'; import { NativeTokenService } from 'lib/nativeTokenService'; -import { - Amount, Coin, ERC20, Quote, -} from '../../types'; +import { Coin, CoinAmount, ERC20 } from 'types'; import { slippageToFraction } from './slippage'; -function getQuoteAmountFromTradeType(tradeInfo: QuoteResult): Amount { +export function getQuoteAmountFromTradeType(tradeInfo: QuoteResult): CoinAmount { if (tradeInfo.tradeType === TradeType.EXACT_INPUT) { return tradeInfo.amountOut; } @@ -28,36 +26,12 @@ export function applySlippage( return ethers.BigNumber.from(amountWithSlippage.toString()); } -export function prepareUserQuote( - otherToken: ERC20, - tradeInfo: QuoteResult, - slippage: number, - fees: Fees, - nativeTokenService: NativeTokenService, -): Quote { - const quote = getQuoteAmountFromTradeType(tradeInfo); - const amountWithSlippage = applySlippage(tradeInfo.tradeType, quote.value, slippage); - - return { - amount: quote, - amountWithMaxSlippage: { - token: otherToken, - value: amountWithSlippage, - }, - slippage, - fees: fees.withAmounts().map((fee) => ({ - ...fee, - amount: nativeTokenService.maybeWrapAmount(fee.amount), - })), - }; -} - export function getOurQuoteReqAmount( - amountSpecified: Amount, // the amount specified by the user, either exactIn or exactOut + amountSpecified: CoinAmount, // the amount specified by the user, either exactIn or exactOut fees: Fees, tradeType: TradeType, nativeTokenService: NativeTokenService, -): Amount { +): CoinAmount { if (tradeType === TradeType.EXACT_OUTPUT) { // For an exact output swap, we do not need to subtract fees from the given amount return nativeTokenService.maybeWrapAmount(amountSpecified); diff --git a/packages/internal/dex/sdk/src/lib/transactionUtils/swap.test.ts b/packages/internal/dex/sdk/src/lib/transactionUtils/swap.test.ts index bdfe1a5aa1..3c22f852d3 100644 --- a/packages/internal/dex/sdk/src/lib/transactionUtils/swap.test.ts +++ b/packages/internal/dex/sdk/src/lib/transactionUtils/swap.test.ts @@ -10,9 +10,10 @@ import { import { Pool, Route } from '@uniswap/v3-sdk'; import { Fees } from 'lib/fees'; import { - Coin, erc20ToUniswapToken, newAmount, uniswapTokenToERC20, + erc20ToUniswapToken, newAmount, uniswapTokenToERC20, } from 'lib'; import { QuoteResult } from 'lib/getQuotesForRoutes'; +import { Coin } from 'types'; import { getSwap, adjustQuoteWithFees } from './swap'; const UNISWAP_IMX = erc20ToUniswapToken(IMX_TEST_TOKEN); diff --git a/packages/internal/dex/sdk/src/lib/transactionUtils/swap.ts b/packages/internal/dex/sdk/src/lib/transactionUtils/swap.ts index 54cb55d604..23cb4e3300 100644 --- a/packages/internal/dex/sdk/src/lib/transactionUtils/swap.ts +++ b/packages/internal/dex/sdk/src/lib/transactionUtils/swap.ts @@ -9,8 +9,9 @@ import { Fees } from 'lib/fees'; import { toCurrencyAmount } from 'lib/utils'; import { QuoteResult } from 'lib/getQuotesForRoutes'; import { NativeTokenService } from 'lib/nativeTokenService'; +import { Coin, CoinAmount } from 'types'; import { - Amount, Coin, Native, SecondaryFee, TransactionDetails, + SecondaryFee, TransactionDetails, } from '../../types'; import { calculateGasFee } from './gas'; import { slippageToFraction } from './slippage'; @@ -200,7 +201,7 @@ export function getSwap( deadline: number, peripheryRouterAddress: string, secondaryFeesAddress: string, - gasPrice: Amount | null, + gasPrice: CoinAmount | null, secondaryFees: SecondaryFee[], nativeTokenService: NativeTokenService, ): TransactionDetails { @@ -229,7 +230,7 @@ export function getSwap( const adjustAmountIn = ( ourQuote: QuoteResult, - amountSpecified: Amount, + amountSpecified: CoinAmount, fees: Fees, nativeTokenService: NativeTokenService, ) => { @@ -258,7 +259,7 @@ const adjustAmountIn = ( */ export function adjustQuoteWithFees( ourQuote: QuoteResult, - amountSpecified: Amount, + amountSpecified: CoinAmount, fees: Fees, nativeTokenService: NativeTokenService, ): QuoteResult { diff --git a/packages/internal/dex/sdk/src/lib/utils.ts b/packages/internal/dex/sdk/src/lib/utils.ts index 1890750bb6..cb385c9d23 100644 --- a/packages/internal/dex/sdk/src/lib/utils.ts +++ b/packages/internal/dex/sdk/src/lib/utils.ts @@ -1,10 +1,8 @@ import { Pool } from '@uniswap/v3-sdk'; -import { CurrencyAmount, Token } from '@uniswap/sdk-core'; +import * as Uniswap from '@uniswap/sdk-core'; import { ethers } from 'ethers'; import { ProviderCallError } from 'errors'; -import { - Amount, Coin, ERC20, Native, -} from '../types'; +import { Amount, Coin, CoinAmount, ERC20, Native, Token } from 'types'; export const quoteReturnMapping: { [signature: string]: string[] } = { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -24,10 +22,8 @@ export const quoteReturnMapping: { [signature: string]: string[] } = { */ export function poolEquals(poolA: Pool, poolB: Pool): boolean { return ( - poolA === poolB - || (poolA.token0.equals(poolB.token0) - && poolA.token1.equals(poolB.token1) - && poolA.fee === poolB.fee) + poolA === poolB || + (poolA.token0.equals(poolB.token0) && poolA.token1.equals(poolB.token1) && poolA.fee === poolB.fee) ); } @@ -43,10 +39,7 @@ export async function getERC20Decimals( data: decimalsFunctionSig, }); - return parseInt( - decimalsResult, - 16, - ); + return parseInt(decimalsResult, 16); } catch (e) { const message = e instanceof Error ? e.message : 'Unknown Error'; throw new ProviderCallError(`failed to get ERC20 decimals: ${message}`); @@ -69,11 +62,11 @@ export function isValidNonZeroAddress(address: string): boolean { } } -export const erc20ToUniswapToken = (token: ERC20): Token => +export const erc20ToUniswapToken = (token: ERC20): Uniswap.Token => // eslint-disable-next-line implicit-arrow-linebreak - new Token(token.chainId, token.address, token.decimals, token.symbol, token.name); + new Uniswap.Token(token.chainId, token.address, token.decimals, token.symbol, token.name); -export const uniswapTokenToERC20 = (token: Token): ERC20 => ({ +export const uniswapTokenToERC20 = (token: Uniswap.Token): ERC20 => ({ chainId: token.chainId, address: token.address, decimals: token.decimals, @@ -82,40 +75,41 @@ export const uniswapTokenToERC20 = (token: Token): ERC20 => ({ type: 'erc20', }); -export const toBigNumber = (amount: CurrencyAmount): ethers.BigNumber => ( - ethers.BigNumber.from(amount.multiply(amount.decimalScale).toExact()) -); +export const toBigNumber = (amount: Uniswap.CurrencyAmount): ethers.BigNumber => + ethers.BigNumber.from(amount.multiply(amount.decimalScale).toExact()); -export const toAmount = (amount: CurrencyAmount): Amount => ({ +export const toAmount = (amount: Uniswap.CurrencyAmount): CoinAmount => ({ token: uniswapTokenToERC20(amount.currency), value: toBigNumber(amount), }); -export const toCurrencyAmount = (amount: Amount): CurrencyAmount => { +export const toCurrencyAmount = (amount: CoinAmount): Uniswap.CurrencyAmount => { const token = erc20ToUniswapToken(amount.token); - return CurrencyAmount.fromRawAmount(token, amount.value.toString()); + return Uniswap.CurrencyAmount.fromRawAmount(token, amount.value.toString()); }; -export const newAmount = (amount: ethers.BigNumber, token: T): Amount => ({ +export const newAmount = (amount: ethers.BigNumber, token: T): CoinAmount => ({ value: amount, token, }); -export const isERC20Amount = (amount: Amount): amount is Amount => amount.token.type === 'erc20'; -export const isNativeAmount = (amount: Amount): amount is Amount => amount.token.type === 'native'; +export const isERC20Amount = (amount: CoinAmount): amount is CoinAmount => amount.token.type === 'erc20'; -export const addERC20Amount = (a: Amount, b: Amount) => { +export const isNativeAmount = (amount: CoinAmount): amount is CoinAmount => + amount.token.type === 'native'; + +export const addERC20Amount = (a: CoinAmount, b: CoinAmount) => { // Make sure the ERC20s have the same address if (a.token.address !== b.token.address) throw new Error('Token mismatch: token addresses must be the same'); return { value: a.value.add(b.value), token: a.token }; }; -const addNativeAmount = (a: Amount, b: Amount) => ({ +const addNativeAmount = (a: CoinAmount, b: CoinAmount) => ({ value: a.value.add(b.value), token: a.token, }); -export const addAmount = (a: Amount, b: Amount) => { +export const addAmount = (a: CoinAmount, b: CoinAmount) => { if (isERC20Amount(a) && isERC20Amount(b)) { return addERC20Amount(a, b); } @@ -127,18 +121,18 @@ export const addAmount = (a: Amount, b: Amount) => { throw new Error('Token mismatch: token types must be the same'); }; -export const subtractERC20Amount = (a: Amount, b: Amount) => { +export const subtractERC20Amount = (a: CoinAmount, b: CoinAmount) => { // Make sure the ERC20s have the same address if (a.token.address !== b.token.address) throw new Error('Token mismatch: token addresses must be the same'); return { value: a.value.sub(b.value), token: a.token }; }; -const subtractNativeAmount = (a: Amount, b: Amount) => ({ +const subtractNativeAmount = (a: CoinAmount, b: CoinAmount) => ({ value: a.value.sub(b.value), token: a.token, }); -export const subtractAmount = (a: Amount, b: Amount) => { +export const subtractAmount = (a: CoinAmount, b: CoinAmount) => { if (isERC20Amount(a) && isERC20Amount(b)) { return subtractERC20Amount(a, b); } @@ -149,3 +143,28 @@ export const subtractAmount = (a: Amount, b: Amount) => { throw new Error('Token mismatch: token types must be the same'); }; + +/** + * Converts our internal token type which could be ERC20 or Native + * into a format consumable by Checkout. They require an address to be + * present. We populate the address with empty string if it's Native. + * If it's ERC20, we don't need to change it. + */ +export const toPublicTokenType = (token: Coin): Token => { + if (token.type === 'native') { + return { + address: '', + chainId: token.chainId, + decimals: token.decimals, + symbol: token.symbol, + name: token.name, + }; + } + + return token; +}; + +export const toPublicAmount = (amount: CoinAmount): Amount => ({ + token: toPublicTokenType(amount.token), + value: amount.value, +}); diff --git a/packages/internal/dex/sdk/src/test/utils.ts b/packages/internal/dex/sdk/src/test/utils.ts index 473a64ce50..5ea70244d6 100644 --- a/packages/internal/dex/sdk/src/test/utils.ts +++ b/packages/internal/dex/sdk/src/test/utils.ts @@ -8,17 +8,12 @@ import { IV3SwapRouter } from 'contracts/types/SecondaryFee'; import { PromiseOrValue } from 'contracts/types/common'; import { QuoteResult } from 'lib/getQuotesForRoutes'; import { NativeTokenService } from 'lib/nativeTokenService'; +import { ExchangeModuleConfiguration, SecondaryFee, CoinAmount, Coin, ERC20, Native, Amount } from 'types'; import { - Amount, - Coin, - ERC20, erc20ToUniswapToken, - ExchangeModuleConfiguration, - Native, newAmount, Router, RoutingContracts, - SecondaryFee, } from '../lib'; export const TEST_GAS_PRICE = BigNumber.from('1500000000'); // 1.5 gwei or 1500000000 wei @@ -353,7 +348,7 @@ type MockParams = { exchangeRate?: number; }; -export const amountOutFromAmountIn = (amountIn: Amount, tokenOut: ERC20, exchangeRate: number) => { +export const amountOutFromAmountIn = (amountIn: CoinAmount, tokenOut: ERC20, exchangeRate: number) => { let amountOut = amountIn.value.mul(exchangeRate); // 10 * 10^18 if (amountIn.token.decimals > tokenOut.decimals) { @@ -367,7 +362,7 @@ export const amountOutFromAmountIn = (amountIn: Amount, tokenOut: ERC20, return newAmount(amountOut, tokenOut); }; -export const amountInFromAmountOut = (amountOut: Amount, tokenIn: ERC20, exchangeRate: number) => { +export const amountInFromAmountOut = (amountOut: CoinAmount, tokenIn: ERC20, exchangeRate: number) => { let amountIn = amountOut.value.div(exchangeRate); // 1 * 10^6 if (tokenIn.decimals > amountOut.token.decimals) { @@ -384,7 +379,7 @@ export const amountInFromAmountOut = (amountOut: Amount, tokenIn: ERC20, export function mockRouterImplementation(params: MockParams) { const exchangeRate = params.exchangeRate ?? 10; // 1 TokenIn = 10 TokenOut const findOptimalRoute = jest.fn(( - amountSpecified: Amount, + amountSpecified: CoinAmount, otherToken: ERC20, tradeType: TradeType, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -456,7 +451,7 @@ export function makeAddr(str: string): string { return utils.keccak256(utils.toUtf8Bytes(str)).slice(0, 42); } -export function formatAmount(amount: Amount): string { +export function formatAmount(amount: CoinAmount | Amount): string { return utils.formatUnits(amount.value, amount.token.decimals); } @@ -471,7 +466,7 @@ export function formatEther(bn: PromiseOrValue): string { throw new Error('formatEther: bn is not a BigNumber'); } -export function newAmountFromString(amount: string, token: T): Amount { +export function newAmountFromString(amount: string, token: T): CoinAmount { const bn = utils.parseUnits(amount, token.decimals); return newAmount(bn, token); } diff --git a/packages/internal/dex/sdk/src/types/index.ts b/packages/internal/dex/sdk/src/types/index.ts index 1a9f162b8d..e99fb95fa1 100644 --- a/packages/internal/dex/sdk/src/types/index.ts +++ b/packages/internal/dex/sdk/src/types/index.ts @@ -3,24 +3,25 @@ import { ModuleConfiguration } from '@imtbl/config'; import { ExchangeContracts } from 'config'; /** - * Interface representing a Chain + * Type representing a Chain * @property {number} chainId - The chain ID * @property {string} rpcUrl - The RPC URL for the chain * @property {ExchangeContracts} contracts - The DEX contract addresses - * @property {Token[]} commonRoutingTokens - The tokens used to find available pools for a swap - * @property {TokenInfo} nativeToken - The native token of the chain + * @property {ERC20[]} commonRoutingTokens - The tokens used to find available pools for a swap + * @property {Coin} nativeToken - The native token of the chain + * @property {ERC20} wrappedNativeToken - The wrapped native token of the chain */ export type Chain = { chainId: number; rpcUrl: string; contracts: ExchangeContracts; commonRoutingTokens: ERC20[]; - nativeToken: Native; + nativeToken: Coin; wrappedNativeToken: ERC20; }; /** - * Interface representing the secondary fees for a swap + * Type representing the secondary fees for a swap * @property {string} recipient - The fee recipient address * @property {number} basisPoints - The fee percentage in basis points * @example 100 basis points = 1% @@ -31,7 +32,7 @@ export type SecondaryFee = { }; /** - * Interface representing the fees returned in the quote + * Type representing the fees returned in the quote * @property {string} recipient - The fee recipient address * @property {number} basisPoints - The fee percentage in basis points * @property {Amount} amount - The amount of the fee @@ -40,44 +41,45 @@ export type SecondaryFee = { export type Fee = { recipient: string; basisPoints: number; - amount: Amount; + amount: Amount; }; /** - * Interface representing an amount with the token information - * @property {TokenInfo} token - The token information - * @property {ethers.BigNumber} value - The amount + * Type representing an amount with the token information + * @property {Coin} token - The coin for the amount, either a {@link Native} or {@link ERC20} + * @property {ethers.BigNumber} value - The value of the amount in the token's smallest unit */ -export type Amount = { +export type CoinAmount = { token: T; value: ethers.BigNumber; }; /** - * Interface representing a quote for a swap - * @property {Amount} amount - The quoted amount - * @property {Amount} amountWithMaxSlippage - The quoted amount with the max slippage applied + * Type representing a quote for a swap + * @property {Amount} amount - The quoted amount with fees applied + * @property {Amount} amountWithMaxSlippage - The quoted amount with the max slippage and fees applied * @property {number} slippage - The slippage percentage used to calculate the quote + * @property {Fee[]} fees - The secondary fees applied to the swap */ export type Quote = { - amount: Amount; - amountWithMaxSlippage: Amount; + amount: Amount; + amountWithMaxSlippage: Amount; slippage: number; fees: Fee[]; }; /** - * Interface representing the details of a transaction + * Type representing the details of a transaction * @property {ethers.providers.TransactionRequest} transaction - The unsigned transaction * @property {Amount | null} gasFeeEstimate - The gas fee estimate or null if it is not available */ export type TransactionDetails = { transaction: ethers.providers.TransactionRequest; - gasFeeEstimate: Amount | null; + gasFeeEstimate: Amount | null; }; /** - * Interface representing the results of {@link Exchange.getUnsignedSwapTxFromAmountIn} {@link Exchange.getUnsignedSwapTxFromAmountOut} + * Type representing the results of {@link Exchange.getUnsignedSwapTxFromAmountIn} {@link Exchange.getUnsignedSwapTxFromAmountOut} * @property {TransactionDetails | null} approval - The approval transaction or null if it is not required * @property {TransactionDetails} swap - The swap transaction * @property {Quote} quote - The quote details for the swap @@ -89,7 +91,8 @@ export type TransactionResponse = { }; /** - * Interface representing an ERC20 token + * Type representing an ERC20 token + * @property {string} type - The token type, used to discriminate between {@link ERC20} and {@link Native} * @property {number} chainId - The chain ID * @property {string} address - The token address * @property {number} decimals - The token decimals @@ -106,7 +109,8 @@ export type ERC20 = { }; /** - * Interface representing a native token + * Type representing a native token + * @property {string} type - The token type, used to discriminate between {@link ERC20} and {@link Native} * @property {number} chainId - The chain ID * @property {number} decimals - The token decimals * @property {string | undefined} symbol - The token symbol or undefined if it is not available @@ -120,16 +124,58 @@ export type Native = { name?: string; }; +/** + * Type representing a token, either an {@link ERC20} or {@link Native} + */ export type Coin = ERC20 | Native; -export interface ExchangeOverrides { +/** + * Type representing a token + * @property {number} chainId - The chain ID + * @property {string} address - The token address, or the empty string for the native token + * @property {number} decimals - The token decimals + * @property {string | undefined} symbol - The token symbol or undefined if it is not available + * @property {string | undefined} name - The token name or undefined if it is not available + */ +export type Token = { + chainId: number; + address: string; + decimals: number; + symbol?: string; + name?: string; +}; + +/** + * Interface representing a token amount + * @property {Token} token - The token + * @property {ethers.BigNumber} value - The amount + */ +export type Amount = { + token: Token; + value: ethers.BigNumber; +}; + +/** + * Type representing the overrides for the {@link Exchange} module + * @property {string} rpcURL - The RPC URL for the chain + * @property {ExchangeContracts} exchangeContracts - The DEX contract addresses + * @property {ERC20[]} commonRoutingTokens - The tokens used to find available pools for a swap + * @property {Coin} nativeToken - The native token of the chain + * @property {ERC20} wrappedNativeToken - The wrapped native token of the chain + */ +export type ExchangeOverrides = { rpcURL: string; exchangeContracts: ExchangeContracts; commonRoutingTokens: ERC20[]; - nativeToken: Native; + nativeToken: Coin; wrappedNativeToken: ERC20; -} +}; +/** + * Interface representing the configuration for the {@link Exchange} module + * @property {number} chainId - The chain ID + * @property {SecondaryFee[]} secondaryFees - The secondary fees for a swap + */ export interface ExchangeModuleConfiguration extends ModuleConfiguration { chainId: number;