diff --git a/docs/calculations/routing.md b/docs/calculations/routing.md index 7419981..9220f57 100644 --- a/docs/calculations/routing.md +++ b/docs/calculations/routing.md @@ -1,3 +1,102 @@ -# ShadeSwap multi-hop routing +# ShadeSwap Multi-Hop Routing + +This page contains an example router that determines the optimal path for maximizing the users output tokens received + +## Router + +### Determine Possible Paths +Returns possible paths through one or multiple pools to complete a trade of two tokens. +```ts +function getPossiblePaths({ + inputTokenContractAddress, + outputTokenContractAddress, + maxHops, + pairs, +}:{ + inputTokenContractAddress:string, + outputTokenContractAddress:string + maxHops: number // max number of pairs used + pairs: BatchPairsInfo +}): string[][] +``` +See [Batch Pairs Query](../queries/swap.html#pairs-info) for input type BatchPairsInfo. + +::: warning +Increasing the max hops too much can cause decreased performance due to the large amount of computations required to calculate all routes. The current recommendation is 4 max based on the route paths available, but this is subject to change as more pairs are supported over time. +::: + +### Calculate Route +Calculates the output amount received for a given path + +```ts +function calculateRoute({ + inputTokenAmount, + inputTokenContractAddress, + path, + pairs, + tokens, +}:{ + inputTokenAmount: BigNumber, + inputTokenContractAddress: string, + path: string[], // path determined by getPossiblePaths + pairs: BatchPairsInfo, + tokens: TokensConfig, // list of all possible swap tokens +}): Route +``` + +::: tip +We pass in a list of all possible tokens (TokensConfig) so that we have access to their decimals for uDenom conversions. This is not the most elegant solution as it may be preferrable to reference your own data store for token data. In that case, you can create your own route calculator and use the ShadeJS one as a guide. +::: +```ts +type TokenConfig = { + tokenContractAddress: string, + decimals: number, +} + +type TokensConfig = TokenConfig[] +``` +**output** + +```ts +type Route = { + inputAmount: BigNumber, + quoteOutputAmount: BigNumber, + quoteShadeDaoFee: BigNumber, + quoteLPFee: BigNumber, + priceImpact: BigNumber, + inputTokenContractAddress: string, + outputTokenContractAddress: string, + path: string[], + gasMultiplier: number, // multiplier which is to be applied + // to the base gas cost of a swap +}; +``` + + +### Calculate All Possible Routes +Retrieves all potential route options and the outputs of each route. +Returns an array of routes in the order that will give the highest quoted +output amount. This function utilizes both getPossiblePaths and calculateRoute. + +```ts +function getRoutes({ + inputTokenAmount, + inputTokenContractAddress, + outputTokenContractAddress, + maxHops, + pairs, + tokens, +}: { + inputTokenAmount: BigNumber, + inputTokenContractAddress: string, + outputTokenContractAddress: string, + maxHops: number, + pairs: BatchPairsInfo, + tokens: TokensConfig, +}): Route[] +``` + +::: warning +This function optimizes for maximizing the output token amount only. It does NOT factor in the value of the gas for the extra hops (i.e. gas multiplier) relative to the value of tokens received. This will especially impact small trades where there is perceived arbitrage opportunities through certain paths, without factoring in the gas cost to perform the arbitrage. It is recommended that you create your own route calculator to handle token values and use the one provided here as a guide. +::: -Coming Soon... diff --git a/docs/calculations/swap.md b/docs/calculations/swap.md index e9f496d..8edf1a9 100644 --- a/docs/calculations/swap.md +++ b/docs/calculations/swap.md @@ -2,6 +2,9 @@ This page demonstrates how to calculate outputs of a single pair swap on ShadeSwap +## BigNumber.js +bignumber.js is used on all computations to prevent loss of precision that normally happens with standard javascript math + ## Constant Product (CPMM) ### Forward Swap @@ -47,7 +50,7 @@ function constantProductSwapToken1for0({ ``` ### Reverse Swap -Backwards calcuation for determining a token input amount for a given output amount. +Backwards calculation for determining a token input amount for a given output amount. ```ts /** * returns input of a simulated swap from token0 to token1 using the constant @@ -162,7 +165,7 @@ function stableSwapToken1for0({ ``` ### Reverse Swap -Backwards calcuation for determining a token input amount for a given output amount. +Backwards calculation for determining a token input amount for a given output amount. ```ts /** * returns input of a simulated swap of token0 for diff --git a/docs/queries/batch-query.md b/docs/queries/batch-query.md index 286cae2..64a54cb 100644 --- a/docs/queries/batch-query.md +++ b/docs/queries/batch-query.md @@ -2,7 +2,7 @@ This page demonstrates how to query using a smart contract batch query router. The purposes of a query router is to combine multiple queries to one or more contracts into a single http request. This reduces load on the node infrastructure and is faster for retrieving data than would otherwise be obtained by individual contract queries. -The batch query function is generalized to work with any contract queries. Miscellaneous ShadeJS services already implement the batch query router, for example the Pairs Info Query +The batch query function is generalized to work with any contract queries. Miscellaneous ShadeJS services already implement the batch query router, for example the Pairs Info Query ## Performance @@ -10,7 +10,7 @@ All of the following results are run with 500 total queries | Batch Size | RPC Queries | Time | Success | |------------|-------------|-------|---------| -| N/A | 500 | 5.56s | 26.6% | +| 1 | 500 | 5.56s | 26.6% | | 5 | 100 | 6.51s | 100% | | 10 | 50 | 9.24s | 100% | | 25 | 20 | 7.27s | 100% | diff --git a/docs/queries/swap.md b/docs/queries/swap.md index c02fe2b..3f889c1 100644 --- a/docs/queries/swap.md +++ b/docs/queries/swap.md @@ -327,18 +327,14 @@ type PairInfo = { isStable: boolean, token0Amount: string, token1Amount: string, - priceRatio: string | null, - pairSettings: { - lpFee: number, - daoFee: number, - stableLpFee: number, - stableDaoFee: number, - stableParams: StableParams | null - }, + lpFee: number, + daoFee: number, + stableParams: StableParams | null contractVersion: number, } type StableParams = { + priceRatio: string, alpha: string, gamma1: string, gamma2: string, @@ -413,14 +409,9 @@ console.log(output) "token0Amount": "4268251730565", "token1Amount": "365239579269", "lpTokenAmount": "487393298891", - "priceRatio": null, - "pairSettings": { - "lpFee": 0.0029, - "daoFee": 0.0001, - "stableLpFee": 0.000483, - "stableDaoFee": 0.000017, - "stableParams": null - }, + "lpFee": 0.0029, + "daoFee": 0.0001, + "stableParams": null "contractVersion": 1 } }, @@ -448,14 +439,9 @@ console.log(output) "token0Amount": "95199329571", "token1Amount": "377657768", "lpTokenAmount": "5708789507", - "priceRatio": null, - "pairSettings": { - "lpFee": 0.0029, - "daoFee": 0.0001, - "stableLpFee": 0.000483, - "stableDaoFee": 0.000017, - "stableParams": null - }, + "lpFee": 0.0029, + "daoFee": 0.0001, + "stableParams": null "contractVersion": 1 } } diff --git a/src/config.ts b/src/config.ts index 62a764f..01e4f9b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,5 @@ // default endpoint to be used when alternative endpoint is not provided -// this is a public endpoint and not guarantees can be provided about the performance +// this is a public endpoint and no guarantees can be provided about the performance const DEFAULT_SECRET_LCD_ENDPOINT = 'https://lcd.secret.express/'; const SECRET_MAINNET_CHAIN_ID = 'secret-4'; diff --git a/src/contracts/definitions/index.ts b/src/contracts/definitions/index.ts new file mode 100644 index 0000000..69c2f37 --- /dev/null +++ b/src/contracts/definitions/index.ts @@ -0,0 +1,4 @@ +export * from './batchQuery'; +export * from './oracle'; +export * from './snip20'; +export * from './swap'; diff --git a/src/contracts/definitions/snip20.test.ts b/src/contracts/definitions/snip20.test.ts index a12968e..b9daae9 100644 --- a/src/contracts/definitions/snip20.test.ts +++ b/src/contracts/definitions/snip20.test.ts @@ -136,13 +136,15 @@ test('it checks the shape of the snip20 increase allowance', () => { test('it checks the shape of the snip20 viewing', () => { const viewingKey = 'MOCK_VIEWING_KEY'; + const padding = 'PADDING'; const output = { msg: { set_viewing_key: { key: viewingKey, + padding, }, }, }; - expect(snip20.messages.createViewingKey(viewingKey)).toStrictEqual(output); + expect(snip20.messages.createViewingKey(viewingKey, padding)).toStrictEqual(output); }); diff --git a/src/contracts/definitions/snip20.ts b/src/contracts/definitions/snip20.ts index a86aea5..a491b3f 100644 --- a/src/contracts/definitions/snip20.ts +++ b/src/contracts/definitions/snip20.ts @@ -29,7 +29,7 @@ const snip20 = { recipientCodeHash?: string, amount: string, handleMsg: any, - padding: string, + padding?: string, }): Snip20MessageRequest { const msg = { send: { @@ -52,7 +52,7 @@ const snip20 = { }: { recipient: string, amount: string, - padding: string, + padding?: string, }): Snip20MessageRequest { const msg = { transfer: { @@ -85,7 +85,7 @@ const snip20 = { }:{ amount: string, denom: string, - padding: string, + padding?: string, }): Snip20MessageRequest { const msg = { redeem: { @@ -108,7 +108,7 @@ const snip20 = { spender: string, amount: string, expiration?: number, - padding: string, + padding?: string, }): Snip20MessageRequest { const msg = { increase_allowance: { @@ -123,10 +123,11 @@ const snip20 = { }; }, - createViewingKey(viewingKey: string): Snip20MessageRequest { + createViewingKey(viewingKey: string, padding?: string): Snip20MessageRequest { const msg = { set_viewing_key: { key: viewingKey, + padding, }, }; return { diff --git a/src/contracts/definitions/swap.test.ts b/src/contracts/definitions/swap.test.ts index 6cf83d8..69ae7e0 100644 --- a/src/contracts/definitions/swap.test.ts +++ b/src/contracts/definitions/swap.test.ts @@ -23,7 +23,7 @@ vi.mock('~/contracts/definitions/snip20', () => ({ })); vi.mock('~/lib/utils', () => ({ - randomPadding: vi.fn(() => 'RANDOM_PADDING'), + generatePadding: vi.fn(() => 'RANDOM_PADDING'), })); test('it tests the form of the query factory config msg', () => { diff --git a/src/contracts/definitions/swap.ts b/src/contracts/definitions/swap.ts index 2e8914f..d003eba 100644 --- a/src/contracts/definitions/swap.ts +++ b/src/contracts/definitions/swap.ts @@ -3,7 +3,7 @@ import { Path, Paths, } from '~/types/contracts/swap/model'; -import { randomPadding } from '~/lib/utils'; +import { generatePadding } from '~/lib/utils'; import { snip20 } from './snip20'; /** @@ -75,7 +75,7 @@ function msgSwap({ recipientCodeHash: routerCodeHash, amount: sendAmount, handleMsg: swapParamsMessage, - padding: randomPadding(), + padding: generatePadding(), }).msg; } diff --git a/src/contracts/index.ts b/src/contracts/index.ts new file mode 100644 index 0000000..09899e4 --- /dev/null +++ b/src/contracts/index.ts @@ -0,0 +1,2 @@ +export * from './definitions'; +export * from './services'; diff --git a/src/contracts/services/index.ts b/src/contracts/services/index.ts new file mode 100644 index 0000000..69c2f37 --- /dev/null +++ b/src/contracts/services/index.ts @@ -0,0 +1,4 @@ +export * from './batchQuery'; +export * from './oracle'; +export * from './snip20'; +export * from './swap'; diff --git a/src/contracts/services/swap.ts b/src/contracts/services/swap.ts index 0095331..829a9a6 100644 --- a/src/contracts/services/swap.ts +++ b/src/contracts/services/swap.ts @@ -187,39 +187,37 @@ function parsePairInfo( token0Amount: pairInfo.amount_0, token1Amount: pairInfo.amount_1, lpTokenAmount: pairInfo.total_liquidity, - priceRatio: stableInfo ? stableInfo.p : null, - pairSettings: { - lpFee: fees.lp_fee.nom / fees.lp_fee.denom, - daoFee: fees.shade_dao_fee.nom / fees.shade_dao_fee.denom, - stableLpFee: fees.stable_lp_fee.nom / fees.stable_lp_fee.denom, - stableDaoFee: fees.stable_shade_dao_fee.nom / fees.stable_shade_dao_fee.denom, - - stableParams: stableInfo ? { - alpha: stableInfo.stable_params.a, - gamma1: stableInfo.stable_params.gamma1, - gamma2: stableInfo.stable_params.gamma2, - oracle: { - address: stableInfo.stable_params.oracle.address, - codeHash: stableInfo.stable_params.oracle.code_hash, - }, - token0Data: { - oracleKey: stableInfo.stable_token0_data.oracle_key, - decimals: stableInfo.stable_token0_data.decimals, - }, - token1Data: { - oracleKey: stableInfo.stable_token1_data.oracle_key, - decimals: stableInfo.stable_token1_data.decimals, - }, - minTradeSizeXForY: stableInfo.stable_params.min_trade_size_x_for_y, - minTradeSizeYForX: stableInfo.stable_params.min_trade_size_y_for_x, - maxPriceImpactAllowed: stableInfo.stable_params.max_price_impact_allowed, - customIterationControls: stableInfo.stable_params.custom_iteration_controls ? { - epsilon: stableInfo.stable_params.custom_iteration_controls.epsilon, - maxIteratorNewton: stableInfo.stable_params.custom_iteration_controls.max_iter_newton, - maxIteratorBisect: stableInfo.stable_params.custom_iteration_controls.max_iter_bisect, - } : null, + lpFee: pairInfo.pair[2] ? fees.stable_lp_fee.nom / fees.stable_lp_fee.denom + : fees.lp_fee.nom / fees.lp_fee.denom, + daoFee: pairInfo.pair[2] ? fees.stable_shade_dao_fee.nom / fees.stable_shade_dao_fee.denom + : fees.shade_dao_fee.nom / fees.shade_dao_fee.denom, + stableParams: stableInfo ? { + priceRatio: stableInfo.p!, // if stable params exist, we know price ratio will be available + alpha: stableInfo.stable_params.a, + gamma1: stableInfo.stable_params.gamma1, + gamma2: stableInfo.stable_params.gamma2, + oracle: { + address: stableInfo.stable_params.oracle.address, + codeHash: stableInfo.stable_params.oracle.code_hash, + }, + token0Data: { + oracleKey: stableInfo.stable_token0_data.oracle_key, + decimals: stableInfo.stable_token0_data.decimals, + }, + token1Data: { + oracleKey: stableInfo.stable_token1_data.oracle_key, + decimals: stableInfo.stable_token1_data.decimals, + }, + minTradeSizeXForY: stableInfo.stable_params.min_trade_size_x_for_y, + minTradeSizeYForX: stableInfo.stable_params.min_trade_size_y_for_x, + maxPriceImpactAllowed: stableInfo.stable_params.max_price_impact_allowed, + customIterationControls: stableInfo.stable_params.custom_iteration_controls ? { + epsilon: stableInfo.stable_params.custom_iteration_controls.epsilon, + maxIteratorNewton: stableInfo.stable_params.custom_iteration_controls.max_iter_newton, + maxIteratorBisect: stableInfo.stable_params.custom_iteration_controls.max_iter_bisect, } : null, - }, + } : null, + contractVersion: pairInfo.contract_version, }; } diff --git a/src/index.ts b/src/index.ts index 7c7e43d..eb1d5c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,88 +1,12 @@ import '~/polyfills/index'; -import { - getSecretNetworkClient$, - getActiveQueryClient$, -} from '~/client'; -import { - queryPrice$, - queryPrices$, - queryPrice, - queryPrices, -} from '~/contracts/services/oracle'; -import { - batchQuery$, - batchQuery, -} from '~/contracts/services/batchQuery'; -import { - queryFactoryConfig$, - queryFactoryPairs$, - queryPairConfig$, - batchQueryPairsInfo$, - batchQueryStakingInfo$, - queryFactoryConfig, - queryFactoryPairs, - queryPairConfig, - batchQueryPairsInfo, - batchQueryStakingInfo, - parseSwapResponse, -} from '~/contracts/services/swap'; -import { - querySnip20TokenInfo$, - querySnip20TokenInfo, - querySnip20Balance, - querySnip20Balance$, -} from '~/contracts/services/snip20'; -import { msgSwap } from '~/contracts/definitions/swap'; -import { - constantProductSwapToken0for1, - constantProductSwapToken1for0, - stableSwapToken0for1, - stableSwapToken1for0, - constantProductPriceImpactToken0for1, - constantProductPriceImpactToken1for0, - stableSwapPriceImpactToken0For1, - stableSwapPriceImpactToken1For0, - constantProductReverseSwapToken0for1, - constantProductReverseSwapToken1for0, - stableReverseSwapToken0for1, - stableReverseSwapToken1for0, -} from '~/lib/swap/swapCalculations'; +export * from '~/contracts'; +export * from '~/client'; +export * from '~/lib/swap'; export { - getSecretNetworkClient$, - getActiveQueryClient$, - queryPrice$, - queryPrices$, - queryPrice, - queryPrices, - batchQuery$, - batchQuery, - queryFactoryConfig$, - queryFactoryPairs$, - queryPairConfig$, - batchQueryPairsInfo$, - batchQueryStakingInfo$, - queryFactoryConfig, - queryFactoryPairs, - queryPairConfig, - batchQueryPairsInfo, - batchQueryStakingInfo, - querySnip20TokenInfo$, - querySnip20TokenInfo, - querySnip20Balance, - querySnip20Balance$, - msgSwap, - parseSwapResponse, - constantProductSwapToken0for1, - constantProductSwapToken1for0, - stableSwapToken0for1, - stableSwapToken1for0, - constantProductPriceImpactToken0for1, - constantProductPriceImpactToken1for0, - stableSwapPriceImpactToken0For1, - stableSwapPriceImpactToken1For0, - constantProductReverseSwapToken0for1, - constantProductReverseSwapToken1for0, - stableReverseSwapToken0for1, - stableReverseSwapToken1for0, -}; + encodeJsonToB64, + decodeB64ToJson, + generatePadding, + convertCoinFromUDenom, + convertCoinToUDenom, +} from '~/lib/utils'; diff --git a/src/lib/swap/index.ts b/src/lib/swap/index.ts new file mode 100644 index 0000000..94eb50c --- /dev/null +++ b/src/lib/swap/index.ts @@ -0,0 +1,2 @@ +export * from './swapCalculations'; +export * from './router'; diff --git a/src/lib/swap/router.test.ts b/src/lib/swap/router.test.ts new file mode 100644 index 0000000..660f406 --- /dev/null +++ b/src/lib/swap/router.test.ts @@ -0,0 +1,552 @@ +import { + test, + expect, +} from 'vitest'; +import { + getPossiblePaths, + calculateRoute, + getRoutes, +} from '~/lib/swap/router'; +import BigNumber from 'bignumber.js'; +import { + batchPairsInfoMock, + batchPairsInfoMockForComplexRoute, + tokenConfigMock, +} from '~/test/mocks/swap/router'; +import { GasMultiplier } from '~/types/swap/router'; + +test('it can generate possible route paths', () => { + // Returns single pair + // + // pair1 + // (A-B) + const output1 = [['CONTRACT_ADDRESS_PAIR_1']]; + expect(getPossiblePaths({ + inputTokenContractAddress: 'TOKEN_A_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_B_CONTRACT_ADDRESS', + maxHops: 5, + pairs: batchPairsInfoMock, + })).toStrictEqual(output1); + + // ************************************************** + // Returns a linear path + // + // pair1 -> pair2 -> pair3 + // (A-B) -> (B-C) -> (D-C) + const output2 = [[ + 'CONTRACT_ADDRESS_PAIR_1', + 'CONTRACT_ADDRESS_PAIR_2', + 'CONTRACT_ADDRESS_PAIR_3', + ]]; + expect(getPossiblePaths({ + inputTokenContractAddress: 'TOKEN_A_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_D_CONTRACT_ADDRESS', + maxHops: 5, + pairs: batchPairsInfoMock, + })).toStrictEqual(output2); + + // ************************************************** + // Returns circular pair of routes + // + // E -- pair4 -- F -- pair5 -- G -- pair6 + // (E-F) (G-F) (G-H) + // | | + // F H + // | | + // pair7 -- I -- pair8 -- H -- pair9 -- J + // (F-I) (I-H) (H-J) + const output3 = [ + [ + 'CONTRACT_ADDRESS_PAIR_4', + 'CONTRACT_ADDRESS_PAIR_5', + 'CONTRACT_ADDRESS_PAIR_6', + 'CONTRACT_ADDRESS_PAIR_9', + ], + [ + 'CONTRACT_ADDRESS_PAIR_4', + 'CONTRACT_ADDRESS_PAIR_7', + 'CONTRACT_ADDRESS_PAIR_8', + 'CONTRACT_ADDRESS_PAIR_9', + ], + ]; + expect(getPossiblePaths({ + inputTokenContractAddress: 'TOKEN_E_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_J_CONTRACT_ADDRESS', + maxHops: 5, + pairs: batchPairsInfoMock, + })).toStrictEqual(output3); + + // ************************************************** + // A complex route + // F + // / \ + // / \ + // 1 2 + // (A-F) (F-D) + // / \ + // / 3 \ 4 + // A --- (A-D) --- D --- (D-G) --- G + // |\ / | + // | \ / | + // | 5 6 | + // | (A-C) (D-C) | + // | \ / | + // 7 \ / 8 + // (A-B) C (D-E) + // | / \ | + // | 9 10 | + // | (C-B) (C-E) | + // | / \ | + // | / 11 \ | + // B --- (B-E) --- E + + const output4 = [ + [ + 'CONTRACT_ADDRESS_PAIR_1', + 'CONTRACT_ADDRESS_PAIR_2', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_3', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_3', + 'CONTRACT_ADDRESS_PAIR_6', + 'CONTRACT_ADDRESS_PAIR_10', + 'CONTRACT_ADDRESS_PAIR_8', + 'CONTRACT_ADDRESS_PAIR_4', + ], + ['CONTRACT_ADDRESS_PAIR_3', + 'CONTRACT_ADDRESS_PAIR_8', + 'CONTRACT_ADDRESS_PAIR_10', + 'CONTRACT_ADDRESS_PAIR_6', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_5', + 'CONTRACT_ADDRESS_PAIR_6', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_5', + 'CONTRACT_ADDRESS_PAIR_10', + 'CONTRACT_ADDRESS_PAIR_8', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_5', + 'CONTRACT_ADDRESS_PAIR_9', + 'CONTRACT_ADDRESS_PAIR_11', + 'CONTRACT_ADDRESS_PAIR_8', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_5', + 'CONTRACT_ADDRESS_PAIR_9', + 'CONTRACT_ADDRESS_PAIR_7', + 'CONTRACT_ADDRESS_PAIR_3', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_7', + 'CONTRACT_ADDRESS_PAIR_9', + 'CONTRACT_ADDRESS_PAIR_6', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_7', + 'CONTRACT_ADDRESS_PAIR_11', + 'CONTRACT_ADDRESS_PAIR_8', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_7', + 'CONTRACT_ADDRESS_PAIR_11', + 'CONTRACT_ADDRESS_PAIR_10', + 'CONTRACT_ADDRESS_PAIR_6', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_7', + 'CONTRACT_ADDRESS_PAIR_9', + 'CONTRACT_ADDRESS_PAIR_10', + 'CONTRACT_ADDRESS_PAIR_8', + 'CONTRACT_ADDRESS_PAIR_4', + ], + [ + 'CONTRACT_ADDRESS_PAIR_7', + 'CONTRACT_ADDRESS_PAIR_9', + 'CONTRACT_ADDRESS_PAIR_5', + 'CONTRACT_ADDRESS_PAIR_3', + 'CONTRACT_ADDRESS_PAIR_4', + ], + ]; + expect(getPossiblePaths({ + inputTokenContractAddress: 'TOKEN_A_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_G_CONTRACT_ADDRESS', + maxHops: 5, + pairs: batchPairsInfoMockForComplexRoute, + }).sort()).toStrictEqual(output4.sort()); +}); + +test('it can calculate the output of a single pool swap route', () => { + // Single Pool calculations + // + // Pool 1: A-B + // + // Pool A-B params + // Liquidity A: 111,111,111 + // Liquidity B: 222,222,222 + // Dao Fee: 0.1 + // LP Fee: 0.2 + // + + const output = { + inputAmount: BigNumber('1000000000'), + quoteOutputAmount: BigNumber('1939982540'), + quoteShadeDaoFee: BigNumber(0.01), + quoteLPFee: BigNumber(0.02), + priceImpact: BigNumber('0.000009000000009'), + inputTokenContractAddress: 'TOKEN_A_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_B_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_1'], + gasMultiplier: GasMultiplier.CONSTANT_PRODUCT, + }; + expect(calculateRoute({ + inputTokenAmount: BigNumber('1000000000'), + inputTokenContractAddress: output.inputTokenContractAddress, + path: ['CONTRACT_ADDRESS_PAIR_1'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + })).toStrictEqual(output); +}); + +test('it can calculate the output of a multi-hop constant product swap route', () => { + // ************************************************** + // Constant Product Rule Only + // + // Pool 2 has order of token0 and token1 flipped + // i.e. A-B -> C-B -> C-D + // + // Pool A-B params + // Liquidity A: 111,111,111 + // Liquidity B: 222,222,222 + // dao Fee: 0.01 + // liquidity provider Fee: 0.02 + // + // Pool C-B params + // Liquidity C: 333,333,333 + // Liquidity B: 4,444,444,444 + // dao Fee: 0.01 + // liquidity provider Fee: 0.01 + // + // Pool C-B params + // Liquidity C: 5,555,555,555 + // Liquidity D: 66,666,666 + // dao Fee: 0.02 + // liquidity provider Fee: 0.02 + + // Pool A-B + const constantProductSwap1Output = calculateRoute({ + inputTokenAmount: BigNumber('1000000000'), + inputTokenContractAddress: 'TOKEN_A_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_1'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }).quoteOutputAmount; + + // pool C-B + const constantProductSwap2Output = calculateRoute({ + inputTokenAmount: constantProductSwap1Output, + inputTokenContractAddress: 'TOKEN_B_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_2'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }).quoteOutputAmount; + + // pool C-D + const constantProductSwap3Output = calculateRoute({ + inputTokenAmount: constantProductSwap2Output, + inputTokenContractAddress: 'TOKEN_C_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_3'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }).quoteOutputAmount; + + // combine into multi-hop routing (FORWARD) + const calulatedRouteResult = calculateRoute({ + inputTokenAmount: BigNumber('1000000000'), + inputTokenContractAddress: 'TOKEN_A_CONTRACT_ADDRESS', + path: [ + 'CONTRACT_ADDRESS_PAIR_1', + 'CONTRACT_ADDRESS_PAIR_2', + 'CONTRACT_ADDRESS_PAIR_3', + ], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }); + + const output = { + inputAmount: BigNumber('1000000000'), + quoteOutputAmount: BigNumber('1642621'), + quoteShadeDaoFee: BigNumber(0.04), + quoteLPFee: BigNumber(0.05), + priceImpact: BigNumber('0.000009462162038267'), + inputTokenContractAddress: 'TOKEN_A_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_D_CONTRACT_ADDRESS', + path: [ + 'CONTRACT_ADDRESS_PAIR_1', + 'CONTRACT_ADDRESS_PAIR_2', + 'CONTRACT_ADDRESS_PAIR_3', + ], + gasMultiplier: GasMultiplier.CONSTANT_PRODUCT * 3, + }; + + // Output Object validation + expect(calulatedRouteResult).toStrictEqual(output); + + // Output Amount validation + expect(calulatedRouteResult.quoteOutputAmount).toStrictEqual(constantProductSwap3Output); +}); + +test('it can calculate the output of a multi-hop stable swap route', () => { + // ************************************************** + // Stable Swaps in all pools + // + // 2nd has order of token0 and token1 flipped + // i.e. K-L -> M-L -> M-N + // + // Pool K-L params + // Liquidity K: 999,999,999 + // Liquidity L: 325,000,000 + // priceRatio: 3 + // a: 10, + // gamma1: 4, + // gamma2: 5, + // dao Fee: 0.01 + // liquidity provider Fee: 0.01 + // + // Pool M-L params + // Liquidity M: 999,999,999 + // Liquidity L: 325,000,000 + // priceRatio: 3 + // a: 10, + // gamma1: 4, + // gamma2: 5, + // dao Fee: 0.01 + // liquidity provider Fee: 0.01 + // + // Pool M-N params + // Liquidity M: 999,999,999 + // Liquidity N: 325,000,000 + // priceRatio: 3 + // a: 10, + // gamma1: 4, + // gamma2: 5, + // dao Fee: 0.01 + // liquidity provider Fee: 0.01 + + // First simulate indidual swaps using outputs as inputs of the following + + // Pool K-L + const stableSwap1Output = calculateRoute({ + inputTokenAmount: BigNumber('100000000'), + inputTokenContractAddress: 'TOKEN_K_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_10'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }).quoteOutputAmount; + + // pool M-L + const stableSwap2Output = calculateRoute({ + inputTokenAmount: stableSwap1Output, + inputTokenContractAddress: 'TOKEN_L_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_11'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }).quoteOutputAmount; + + // pool M-N + const stableSwap3Output = calculateRoute({ + inputTokenAmount: stableSwap2Output, + inputTokenContractAddress: 'TOKEN_M_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_12'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }).quoteOutputAmount; + + // combine into multi-hop routing + const calulatedStableRouteResult = calculateRoute({ + inputTokenAmount: BigNumber('100000000'), + inputTokenContractAddress: 'TOKEN_K_CONTRACT_ADDRESS', + path: [ + 'CONTRACT_ADDRESS_PAIR_10', + 'CONTRACT_ADDRESS_PAIR_11', + 'CONTRACT_ADDRESS_PAIR_12', + ], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }); + + // expected output object + const output = { + inputAmount: BigNumber('100000000'), + quoteOutputAmount: BigNumber('36551499'), + quoteShadeDaoFee: BigNumber('0.03'), + quoteLPFee: BigNumber('0.03'), + priceImpact: BigNumber('6.23997608623458059756756e-7'), + inputTokenContractAddress: 'TOKEN_K_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_N_CONTRACT_ADDRESS', + path: [ + 'CONTRACT_ADDRESS_PAIR_10', + 'CONTRACT_ADDRESS_PAIR_11', + 'CONTRACT_ADDRESS_PAIR_12', + ], + gasMultiplier: GasMultiplier.STABLE * 3, + }; + + // Output Object validation + expect(calulatedStableRouteResult).toStrictEqual(output); + + // Output Amount validation + expect(calulatedStableRouteResult.quoteOutputAmount).toStrictEqual(stableSwap3Output); +}); + +test('it can calculate the output of a multi-hop mixed swap route', () => { + // ************************************************** + // Mix of constant product -> stableswap -> constant product + // pools: O-L -> M-L -> M-P + // + // Pool O-L params + // Liquidity O: 111,111,111 + // Liquidity L: 222,222,222 + // dao Fee: 0.01 + // liquidity provider Fee: 0.02 + // + // Pool M-L params + // Liquidity M: 999,999,999 + // Liquidity L: 325,000,000 + // priceRatio: 3 + // a: 10, + // gamma1: 4, + // gamma2: 5, + // dao Fee: 0.01 + // liquidity provider Fee: 0.01 + // + // Pool M-P params + // Liquidity A: 111,111,111 + // Liquidity B: 222,222,222 + // dao Fee: 0.01 + // liquidity provider Fee: 0.02 + + // First simulate indidual swaps using outputs as inputs of the following + // Pool O-L + + const mixedSwapConstantProduct1Output = calculateRoute({ + inputTokenAmount: BigNumber('100000000'), + inputTokenContractAddress: 'TOKEN_O_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_13'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }).quoteOutputAmount; + + // pool M-L + const mixedSwapStableSwapOutput = calculateRoute({ + inputTokenAmount: mixedSwapConstantProduct1Output, + inputTokenContractAddress: 'TOKEN_L_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_11'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }).quoteOutputAmount; + + // pool M-P + const mixedSwapConstantProduct2Output = calculateRoute({ + inputTokenAmount: mixedSwapStableSwapOutput, + inputTokenContractAddress: 'TOKEN_M_CONTRACT_ADDRESS', + path: ['CONTRACT_ADDRESS_PAIR_14'], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }).quoteOutputAmount; + + // combined multi-hop + const mixedMultiHopResult = calculateRoute({ + inputTokenAmount: BigNumber('100000000'), + inputTokenContractAddress: 'TOKEN_O_CONTRACT_ADDRESS', + path: [ + 'CONTRACT_ADDRESS_PAIR_13', + 'CONTRACT_ADDRESS_PAIR_11', + 'CONTRACT_ADDRESS_PAIR_14', + ], + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }); + + expect(mixedMultiHopResult.quoteOutputAmount).toStrictEqual(mixedSwapConstantProduct2Output); + + const output = { + inputAmount: BigNumber('100000000'), + quoteOutputAmount: BigNumber('949728643'), + quoteShadeDaoFee: BigNumber('0.03'), + quoteLPFee: BigNumber('0.05'), + priceImpact: BigNumber('0.000006366091506753980711557256'), + inputTokenContractAddress: 'TOKEN_O_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_P_CONTRACT_ADDRESS', + path: [ + 'CONTRACT_ADDRESS_PAIR_13', + 'CONTRACT_ADDRESS_PAIR_11', + 'CONTRACT_ADDRESS_PAIR_14', + ], + gasMultiplier: GasMultiplier.CONSTANT_PRODUCT * 2 + GasMultiplier.STABLE, + }; + + // Output Object validation + expect(mixedMultiHopResult).toStrictEqual(output); + + // Output Amount validation + expect(mixedMultiHopResult.quoteOutputAmount).toStrictEqual(mixedSwapConstantProduct2Output); +}); + +test('it can calculate all routes for input + output token', () => { + const result = getRoutes({ + inputTokenAmount: BigNumber('100000000'), + inputTokenContractAddress: 'TOKEN_Q_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_T_CONTRACT_ADDRESS', + maxHops: 5, + pairs: batchPairsInfoMock, + tokens: tokenConfigMock, + }); + + const output1 = { + inputAmount: BigNumber('100000000'), + quoteOutputAmount: BigNumber('36551499'), + quoteShadeDaoFee: BigNumber('0.03'), + quoteLPFee: BigNumber('0.03'), + priceImpact: BigNumber('6.23997608623458059756756e-7'), + inputTokenContractAddress: 'TOKEN_Q_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_T_CONTRACT_ADDRESS', + path: [ + 'CONTRACT_ADDRESS_PAIR_15', + 'CONTRACT_ADDRESS_PAIR_16', + 'CONTRACT_ADDRESS_PAIR_17', + ], + gasMultiplier: GasMultiplier.STABLE * 3, + }; + + const output2 = { + inputAmount: BigNumber('100000000'), + quoteOutputAmount: BigNumber('96039896'), + quoteShadeDaoFee: BigNumber('0.02'), + quoteLPFee: BigNumber('0.02'), + priceImpact: BigNumber('0.000001081968422993497890830615'), + inputTokenContractAddress: 'TOKEN_Q_CONTRACT_ADDRESS', + outputTokenContractAddress: 'TOKEN_T_CONTRACT_ADDRESS', + path: [ + 'CONTRACT_ADDRESS_PAIR_18', + 'CONTRACT_ADDRESS_PAIR_17', + ], + gasMultiplier: GasMultiplier.STABLE * 2, + }; + + expect(result).toStrictEqual([output2, output1]); +}); diff --git a/src/lib/swap/router.ts b/src/lib/swap/router.ts new file mode 100644 index 0000000..15de5fb --- /dev/null +++ b/src/lib/swap/router.ts @@ -0,0 +1,406 @@ +import { + constantProductSwapToken0for1, + constantProductSwapToken1for0, + stableSwapToken0for1, + stableSwapToken1for0, + constantProductPriceImpactToken0for1, + constantProductPriceImpactToken1for0, + stableSwapPriceImpactToken0For1, + stableSwapPriceImpactToken1For0, +} from '~/lib/swap/swapCalculations'; +import BigNumber from 'bignumber.js'; +import { BatchPairsInfo } from '~/types/contracts/swap/model'; +import { + GasMultiplier, + Route, +} from '~/types/swap/router'; +import { TokensConfig } from '~/types/shared'; +import { + convertCoinToUDenom, + convertCoinFromUDenom, + getTokenDecimalsByTokenConfig, +} from '~/lib/utils'; + +/** +* retuns possible paths through one or multiple pools to complete a trade of two tokens +*/ +function getPossiblePaths({ + inputTokenContractAddress, + outputTokenContractAddress, + maxHops, + pairs, +}:{ + inputTokenContractAddress:string, + outputTokenContractAddress:string + maxHops: number + pairs: BatchPairsInfo +}) { + // keeps track of the current path we are exploring + const path: string[] = []; + // keeps track of all the paths found from the starting token to the ending token + const result: string[][] = []; + // keeps track of the pools that have been visited to avoid loops + const visited = new Set(); + + // depth-first search function + function dfs(tokenContractAddress: string, depth: number) { + // if the current depth exceeds the maximum number of hops, return + if (depth > maxHops) { + return; + } + + // if we have reached the ending token, add the current path to the result and return + if (tokenContractAddress === outputTokenContractAddress) { + result.push([...path]); + return; + } + + // iterate through all the pools + Object.values(pairs).forEach((pair) => { + const { + pairContractAddress, + pairInfo, + } = pair; + + // if the current pool has already been visited, return + if (visited.has(pairContractAddress)) { + return; + } + + // if the current pool contains the token we are currently exploring, + // add it to the path and mark it as visited + if (pairInfo.token0Contract.address === tokenContractAddress + || pairInfo.token1Contract.address === tokenContractAddress) { + path.push(pairContractAddress); + visited.add(pairContractAddress); + + // if the token we are currently exploring is token0 in the current pool, + // explore token1 tokenAddress + if (pairInfo.token0Contract.address === tokenContractAddress) { + dfs(pairInfo.token1Contract.address, depth + 1); + } else { + // if the token we are currently exploring is token1 in the current pool, + // explore token0 next + dfs(pairInfo.token0Contract.address, depth + 1); + } + + // backtrack by removing the current pool from the path and marking it as unvisited + visited.delete(pairContractAddress); + path.pop(); + } + }); + } + + // start exploring from the starting token + dfs(inputTokenContractAddress, 0); + return result; +} + +/** +* calculates the estimated output of swapping through a route given an input token amount +* and also transforms the data collected in each pool into the Route data model +*/ +function calculateRoute({ + inputTokenAmount, + inputTokenContractAddress, + path, + pairs, + tokens, +}:{ + inputTokenAmount: BigNumber, + inputTokenContractAddress: string, + path: string[], + pairs: BatchPairsInfo, + tokens: TokensConfig, // list of all possible swap tokens +}): Route { + // calculate output of the route + const routeCalculation = path.reduce((prev, poolContractAddress) => { + const { + // set previous pool swap output as the new input + outputTokenContractAddress: currentTokenContractAddress, + quoteOutputAmount: inputAmount, + quoteShadeDaoFee, + quotePriceImpact, + quoteLPFee, + gasMultiplier, + } = prev; + + let swapAmountOutput; + let swapPriceImpact; + let poolMultiplier; + + const pairArr = pairs.filter( + (pair) => pair.pairContractAddress === poolContractAddress, + ); + if (pairArr.length === 0) { + throw new Error(`Pair ${poolContractAddress} not available`); + } + + if (pairArr.length > 1) { + throw new Error(`Duplicate ${poolContractAddress} pairs found`); + } + + // at this point we have determined there is a single match + const pair = pairArr[0]; + + const { + pairInfo: { + token0Contract, + token1Contract, + token0Amount, + token1Amount, + lpFee, + daoFee, + isStable, + stableParams, + }, + } = pair; + // Convert pool liquidity from human readable to raw number for + // constant product swap calculations + // at this point we have determined there is a single match + const poolToken0Decimals = getTokenDecimalsByTokenConfig( + token0Contract.address, + tokens, + ); + const poolToken1Decimals = getTokenDecimalsByTokenConfig( + token1Contract.address, + tokens, + ); + + const poolToken0AmountHumanReadable = convertCoinFromUDenom( + token0Amount, + poolToken0Decimals, + ); + const poolToken1AmountHumanReadable = convertCoinFromUDenom( + token1Amount, + poolToken1Decimals, + ); + + // converts input amount from raw number to human readable for use as an input + // to the stableswap calculations. + const inputTokenDecimals = getTokenDecimalsByTokenConfig( + currentTokenContractAddress, + tokens, + ); + + const inputAmountHumanReadable = convertCoinFromUDenom( + inputAmount, + inputTokenDecimals, + ); + + // determine token id of the output token in the swap + let outputTokenContractAddress; + if (currentTokenContractAddress === token0Contract.address) { + outputTokenContractAddress = token1Contract.address; + } else { + outputTokenContractAddress = token0Contract.address; + } + + // determine decimals of the output token + const outputTokenDecimals = getTokenDecimalsByTokenConfig( + outputTokenContractAddress, + tokens, + ); + + // Stable Pool calculations + if (isStable && stableParams) { + poolMultiplier = GasMultiplier.STABLE; + + // token0 as the input + if (currentTokenContractAddress === token0Contract.address) { + const swapParams = { + inputToken0Amount: inputAmountHumanReadable, + poolToken0Amount: poolToken0AmountHumanReadable, + poolToken1Amount: poolToken1AmountHumanReadable, + priceRatio: BigNumber(stableParams.priceRatio), + alpha: BigNumber(stableParams.alpha), + gamma1: BigNumber(stableParams.gamma1), + gamma2: BigNumber(stableParams.gamma2), + liquidityProviderFee: BigNumber(lpFee), + daoFee: BigNumber(daoFee), + minTradeSizeToken0For1: BigNumber(stableParams.minTradeSizeXForY), + minTradeSizeToken1For0: BigNumber(stableParams.minTradeSizeYForX), + priceImpactLimit: BigNumber(stableParams.maxPriceImpactAllowed), + }; + + const swapAmountOutputHumanReadable = stableSwapToken0for1(swapParams); + + swapAmountOutput = BigNumber(convertCoinToUDenom( + swapAmountOutputHumanReadable, + outputTokenDecimals, + )); + swapPriceImpact = stableSwapPriceImpactToken0For1(swapParams); + // token1 as the input + } else if (currentTokenContractAddress === token1Contract.address) { + const swapParams = { + inputToken1Amount: inputAmountHumanReadable, + poolToken0Amount: poolToken0AmountHumanReadable, + poolToken1Amount: poolToken1AmountHumanReadable, + priceRatio: BigNumber(stableParams.priceRatio), + alpha: BigNumber(stableParams.alpha), + gamma1: BigNumber(stableParams.gamma1), + gamma2: BigNumber(stableParams.gamma2), + liquidityProviderFee: BigNumber(lpFee), + daoFee: BigNumber(daoFee), + minTradeSizeToken0For1: BigNumber(stableParams.minTradeSizeXForY), + minTradeSizeToken1For0: BigNumber(stableParams.minTradeSizeYForX), + priceImpactLimit: BigNumber(stableParams.maxPriceImpactAllowed), + }; + + const swapAmountOutputHumanReadable = stableSwapToken1for0(swapParams); + + swapAmountOutput = BigNumber(convertCoinToUDenom( + swapAmountOutputHumanReadable, + outputTokenDecimals, + )); + swapPriceImpact = stableSwapPriceImpactToken1For0(swapParams); + } else { + throw Error('stableswap parameter error'); + } + } else { + poolMultiplier = GasMultiplier.CONSTANT_PRODUCT; + // non-stable pools using constant product rule math + // token0 as the input + if (currentTokenContractAddress === token0Contract.address) { + swapAmountOutput = constantProductSwapToken0for1({ + token0LiquidityAmount: BigNumber(token0Amount), + token1LiquidityAmount: BigNumber(token1Amount), + token0InputAmount: inputAmount, + fee: BigNumber(lpFee).plus(daoFee), + }); + + swapPriceImpact = constantProductPriceImpactToken0for1({ + token0LiquidityAmount: BigNumber(token0Amount), + token1LiquidityAmount: BigNumber(token1Amount), + token0InputAmount: inputAmount, + }); + // non-stable pools using constant product rule math + // token1 as the input + } else if (currentTokenContractAddress === token1Contract.address) { + swapAmountOutput = constantProductSwapToken1for0({ + token0LiquidityAmount: BigNumber(token0Amount), + token1LiquidityAmount: BigNumber(token1Amount), + token1InputAmount: inputAmount, + fee: BigNumber(lpFee).plus(daoFee), + }); + + swapPriceImpact = constantProductPriceImpactToken1for0({ + token0LiquidityAmount: BigNumber(token0Amount), + token1LiquidityAmount: BigNumber(token1Amount), + token1InputAmount: inputAmount, + }); + } else { + throw Error('constant product rule swap parameter error'); + } + } + + // output data for the reduce function + return { + outputTokenContractAddress, + quoteOutputAmount: swapAmountOutput, + quoteShadeDaoFee: quoteShadeDaoFee.plus(daoFee), + quoteLPFee: quoteLPFee.plus(lpFee), + quotePriceImpact: quotePriceImpact.plus(swapPriceImpact), + gasMultiplier: gasMultiplier + poolMultiplier, + }; + + // reduce function starting values + }, { + outputTokenContractAddress: inputTokenContractAddress, + quoteOutputAmount: inputTokenAmount, + quoteShadeDaoFee: BigNumber(0), + quoteLPFee: BigNumber(0), + quotePriceImpact: BigNumber(0), + gasMultiplier: 0, + }); + + // formulate the Routes data model + const { + outputTokenContractAddress, + quoteOutputAmount, + quoteShadeDaoFee, + quoteLPFee, + quotePriceImpact, + gasMultiplier, + } = routeCalculation; + + return { + inputAmount: inputTokenAmount, + quoteOutputAmount, + quoteShadeDaoFee, + quoteLPFee, + priceImpact: quotePriceImpact, + inputTokenContractAddress, + outputTokenContractAddress, + path, + gasMultiplier, + }; +} + +/** +* retrieves all potential route options and the outputs of each route. +* returns an array of routes in the order that will give the highest quoted +* output amount +*/ +function getRoutes({ + inputTokenAmount, + inputTokenContractAddress, + outputTokenContractAddress, + maxHops, + pairs, + tokens, +}: { + inputTokenAmount: BigNumber, + inputTokenContractAddress: string, + outputTokenContractAddress: string, + maxHops: number, + pairs: BatchPairsInfo, + tokens: TokensConfig, +}) { + // generates possible routes as the swap path + const possiblePaths = getPossiblePaths({ + inputTokenContractAddress, + outputTokenContractAddress, + maxHops, + pairs, + }); + + if (possiblePaths.length === 0) { + return []; + } + + const routes = possiblePaths.reduce((prev, path) => { + try { + const newRoute = calculateRoute({ + inputTokenAmount, + inputTokenContractAddress, + path, + pairs, + tokens, + }); + prev.push(newRoute); + return prev; + // for any errors skip the path as a possible route + } catch { + return prev; + } + }, [] as Route[]); + + // returns routes in the order that maximizes the users output + return routes.sort((a: Route, b: Route) => { + // sort by output amounts + + if (a.quoteOutputAmount.isGreaterThan(b.quoteOutputAmount)) { + return -1; // sort a before b + } + if (a.quoteOutputAmount.isLessThan(b.quoteOutputAmount)) { + return 1; // sort a after b + } + return 0; // keep original order of a and b + }); +} +export { + getPossiblePaths, + calculateRoute, + getRoutes, +}; diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index 4659c3c..7a258ee 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -1,3 +1,4 @@ +import BigNumber from 'bignumber.js'; import { test, expect, @@ -6,7 +7,9 @@ import { import { encodeJsonToB64, decodeB64ToJson, - randomPadding, + generatePadding, + convertCoinFromUDenom, + convertCoinToUDenom, } from './utils'; test('It encodes a JSON into a base64 string', () => { @@ -28,13 +31,33 @@ test('It decodes a base64 string into JSON', () => { test('Generates random padding of length 8-15', () => { // checks midpoint vi.spyOn(global.Math, 'random').mockReturnValue(0.44351455); - expect(randomPadding()).toBe('bbbbbbbbbbb'); + expect(generatePadding()).toBe('bbbbbbbbbbb'); // checks upper bound vi.spyOn(global.Math, 'random').mockReturnValue(0.99999999); - expect(randomPadding()).toBe('999999999999999'); + expect(generatePadding()).toBe('999999999999999'); // checks lower bound vi.spyOn(global.Math, 'random').mockReturnValue(0); - expect(randomPadding()).toBe('AAAAAAAA'); + expect(generatePadding()).toBe('AAAAAAAA'); +}); + +test('It converts token from U denom V2', () => { + expect(convertCoinFromUDenom(1000, 2)).toStrictEqual(BigNumber(10)); + expect(convertCoinFromUDenom('1000', 2)).toStrictEqual(BigNumber(10)); + expect(convertCoinFromUDenom('1000000000000000000000000000', 2)).toStrictEqual(BigNumber('10000000000000000000000000')); + expect(convertCoinFromUDenom(1e100, 2)).toStrictEqual(BigNumber(1e98)); + expect(convertCoinFromUDenom('1e100', 2)).toStrictEqual(BigNumber(1e98)); + expect(convertCoinFromUDenom('100000000000000000000005555.123456789123456789', 2)).toStrictEqual(BigNumber('1000000000000000000000055.551234567891234568')); + expect(convertCoinFromUDenom(BigNumber('987654321987654321987654321'), 18)).toStrictEqual(BigNumber('987654321.987654321987654321')); +}); + +test('It converts token to U denom V2', () => { + const testBigNumber1 = BigNumber(1000); + expect(convertCoinToUDenom(testBigNumber1, 2)).toBe('100000'); + const testBigNumber2 = BigNumber('0.123456789123456789'); + expect(convertCoinToUDenom(testBigNumber2, 18)).toBe('123456789123456789'); + const testBigNumber3 = BigNumber('123456789123456789.123456789123456789'); + expect(convertCoinToUDenom(testBigNumber3, 18)).toBe('123456789123456789123456789123456789'); + expect(convertCoinToUDenom('1111.123456789101213141', 18)).toBe('1111123456789101213141'); }); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 677d517..150e551 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,3 +1,6 @@ +import BigNumber from 'bignumber.js'; +import { TokensConfig } from '~/types/shared'; + const encodeJsonToB64 = (toEncode:any) : string => Buffer.from(JSON.stringify(toEncode), 'utf8').toString('base64'); const decodeB64ToJson = (encodedData: string) => JSON.parse(Buffer.from(encodedData, 'base64').toString('utf8')); @@ -5,7 +8,7 @@ const decodeB64ToJson = (encodedData: string) => JSON.parse(Buffer.from(encodedD /** * Generates random string of characters, used to add entropy to TX data * */ -const randomPadding = ():string => { +const generatePadding = ():string => { enum length { MAX = 15, MIN = 8 @@ -20,8 +23,57 @@ const randomPadding = ():string => { return result; }; +/** + * Convert from uDenom to the human readable equivalent as BigNumber type + */ +const convertCoinFromUDenom = ( + amount: number | string | BigNumber, + decimals:number, +) => { + BigNumber.config({ DECIMAL_PLACES: 18 }); + return BigNumber( + amount, + ).dividedBy(BigNumber(10).pow(decimals)); +}; + +/** + * Convert BigNumber to the uDenom string type + */ +const convertCoinToUDenom = ( + amount: BigNumber | number | string, + decimals:number, +) => { + if (typeof amount === 'string' || typeof amount === 'number') { + return BigNumber(amount).multipliedBy(BigNumber(10).pow(decimals)).toFixed(0); + } + return amount.multipliedBy(BigNumber(10).pow(decimals)).toFixed(0); +}; + +/** + * function used to determine the decimals of a token given the contract address and a list + * of token configs + */ +function getTokenDecimalsByTokenConfig(tokenContractAddress: string, tokens: TokensConfig) { + const tokenConfigArr = tokens.filter( + (token) => token.tokenContractAddress === tokenContractAddress, + ); + + if (tokenConfigArr.length === 0) { + throw new Error(`token ${tokenContractAddress} not available`); + } + + if (tokenConfigArr.length > 1) { + throw new Error(`Duplicate ${tokenContractAddress} tokens found`); + } + // at this point we have determined there is a single match + return tokenConfigArr[0].decimals; +} + export { encodeJsonToB64, decodeB64ToJson, - randomPadding, + generatePadding, + convertCoinFromUDenom, + convertCoinToUDenom, + getTokenDecimalsByTokenConfig, }; diff --git a/src/test/mocks/swap/batchPairsInfoParsed.ts b/src/test/mocks/swap/batchPairsInfoParsed.ts index 469f34a..ee25486 100644 --- a/src/test/mocks/swap/batchPairsInfoParsed.ts +++ b/src/test/mocks/swap/batchPairsInfoParsed.ts @@ -23,14 +23,9 @@ const pairsInfoParsed = [ token0Amount: '4211390490855', token1Amount: '370196629477', lpTokenAmount: '487393298891', - priceRatio: null, - pairSettings: { - lpFee: 0.0029, - daoFee: 0.0001, - stableLpFee: 0.000483, - stableDaoFee: 0.000017, - stableParams: null, - }, + lpFee: 0.0029, + daoFee: 0.0001, + stableParams: null, contractVersion: 1, }, }, @@ -58,14 +53,9 @@ const pairsInfoParsed = [ token0Amount: '93237038002', token1Amount: '385657573', lpTokenAmount: '5708789507', - priceRatio: null, - pairSettings: { - lpFee: 0.0029, - daoFee: 0.0001, - stableLpFee: 0.000483, - stableDaoFee: 0.000017, - stableParams: null, - }, + lpFee: 0.0029, + daoFee: 0.0001, + stableParams: null, contractVersion: 1, }, }, diff --git a/src/test/mocks/swap/pairInfoParsed.ts b/src/test/mocks/swap/pairInfoParsed.ts index 6696507..2ce1f83 100644 --- a/src/test/mocks/swap/pairInfoParsed.ts +++ b/src/test/mocks/swap/pairInfoParsed.ts @@ -22,34 +22,29 @@ const pairInfoParsed: PairInfo = { token0Amount: '3218142110921700343525', token1Amount: '6366867216411002795778', lpTokenAmount: '4783477681443035000237', - priceRatio: '1.08921896906564985', - pairSettings: { - lpFee: 0.0005, - daoFee: 0.0005, - stableLpFee: 0.0005, - stableDaoFee: 0.0005, - stableParams: { - alpha: '150', - gamma1: '6', - gamma2: '50', - oracle: { - address: 'secret10n2xl5jmez6r9umtdrth78k0vwmce0l5m9f5dm', - codeHash: '32c4710842b97a526c243a68511b15f58d6e72a388af38a7221ff3244c754e91', - }, - token0Data: { - oracleKey: 'Stableswap Rate Base', - decimals: 18, - }, - token1Data: { - oracleKey: 'Stride INJ Rate', - decimals: 18, - }, - minTradeSizeXForY: '0.000000001', - minTradeSizeYForX: '0.000000001', - maxPriceImpactAllowed: '1000', - customIterationControls: null, + lpFee: 0.0005, + daoFee: 0.0005, + stableParams: { + priceRatio: '1.08921896906564985', + alpha: '150', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'secret10n2xl5jmez6r9umtdrth78k0vwmce0l5m9f5dm', + codeHash: '32c4710842b97a526c243a68511b15f58d6e72a388af38a7221ff3244c754e91', }, - + token0Data: { + oracleKey: 'Stableswap Rate Base', + decimals: 18, + }, + token1Data: { + oracleKey: 'Stride INJ Rate', + decimals: 18, + }, + minTradeSizeXForY: '0.000000001', + minTradeSizeYForX: '0.000000001', + maxPriceImpactAllowed: '1000', + customIterationControls: null, }, contractVersion: 1, }; diff --git a/src/test/mocks/swap/router.ts b/src/test/mocks/swap/router.ts new file mode 100644 index 0000000..b47ee20 --- /dev/null +++ b/src/test/mocks/swap/router.ts @@ -0,0 +1,1217 @@ +import { BatchPairsInfo } from '~/types/contracts/swap/model'; +import { TokensConfig } from '~/types/shared'; + +const batchPairsInfoMock: BatchPairsInfo = [ + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_1', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_1', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_1', + }, + token0Contract: { + address: 'TOKEN_A_CONTRACT_ADDRESS', + codeHash: 'TOKEN_A_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_B_CONTRACT_ADDRESS', + codeHash: 'TOKEN_B_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_1', + isStable: false, + token0Amount: '111111111000000', + token1Amount: '222222222000000', + lpFee: 0.02, + daoFee: 0.01, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_2', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_2', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_2', + }, + token0Contract: { + address: 'TOKEN_C_CONTRACT_ADDRESS', + codeHash: 'TOKEN_C_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_B_CONTRACT_ADDRESS', + codeHash: 'TOKEN_B_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_2', + isStable: false, + token0Amount: '333333333000000', + token1Amount: '4444444444000000', + lpFee: 0.01, + daoFee: 0.01, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_3', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_3', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_3', + }, + token0Contract: { + address: 'TOKEN_C_CONTRACT_ADDRESS', + codeHash: 'TOKEN_C_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_D_CONTRACT_ADDRESS', + codeHash: 'TOKEN_D_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_3', + isStable: false, + token0Amount: '5555555555000000', + token1Amount: '66666666000000', + lpFee: 0.02, + daoFee: 0.02, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_4', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_4', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_4', + }, + token0Contract: { + address: 'TOKEN_E_CONTRACT_ADDRESS', + codeHash: 'TOKEN_E_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_F_CONTRACT_ADDRESS', + codeHash: 'TOKEN_F_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_4', + isStable: true, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.01, + daoFee: 0.02, + stableParams: { + priceRatio: '1.1234567', + alpha: '200', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_4', + codeHash: 'ORACLE_CODE_HASH_PAIR_4', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_5', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_5', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_5', + }, + token0Contract: { + address: 'TOKEN_G_CONTRACT_ADDRESS', + codeHash: 'TOKEN_G_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_F_CONTRACT_ADDRESS', + codeHash: 'TOKEN_F_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_5', + isStable: true, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: { + priceRatio: '1.1234567', + alpha: '200', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_5', + codeHash: 'ORACLE_CODE_HASH_PAIR_5', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_6', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_6', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_6', + }, + token0Contract: { + address: 'TOKEN_G_CONTRACT_ADDRESS', + codeHash: 'TOKEN_G_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_H_CONTRACT_ADDRESS', + codeHash: 'TOKEN_H_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_6', + isStable: true, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: { + priceRatio: '1.1234567', + alpha: '200', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_6', + codeHash: 'ORACLE_CODE_HASH_PAIR_6', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_7', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_7', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_7', + }, + token0Contract: { + address: 'TOKEN_F_CONTRACT_ADDRESS', + codeHash: 'TOKEN_F_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_I_CONTRACT_ADDRESS', + codeHash: 'TOKEN_I_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_7', + isStable: false, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_8', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_8', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_8', + }, + token0Contract: { + address: 'TOKEN_I_CONTRACT_ADDRESS', + codeHash: 'TOKEN_I_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_H_CONTRACT_ADDRESS', + codeHash: 'TOKEN_H_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_8', + isStable: false, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_9', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_8', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_8', + }, + token0Contract: { + address: 'TOKEN_H_CONTRACT_ADDRESS', + codeHash: 'TOKEN_H_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_J_CONTRACT_ADDRESS', + codeHash: 'TOKEN_J_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_8', + isStable: false, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_10', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_10', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_10', + }, + token0Contract: { + address: 'TOKEN_K_CONTRACT_ADDRESS', + codeHash: 'TOKEN_K_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_L_CONTRACT_ADDRESS', + codeHash: 'TOKEN_L_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_10', + isStable: true, + token0Amount: '157485051000000', + token1Amount: '135613721000000', + lpFee: 0.01, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '10', + gamma1: '4', + gamma2: '5', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_10', + codeHash: 'ORACLE_CODE_HASH_PAIR_10', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_11', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_11', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_11', + }, + token0Contract: { + address: 'TOKEN_M_CONTRACT_ADDRESS', + codeHash: 'TOKEN_M_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_L_CONTRACT_ADDRESS', + codeHash: 'TOKEN_L_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_11', + isStable: true, + token0Amount: '157485051000000', + token1Amount: '135613721000000', + lpFee: 0.01, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '10', + gamma1: '4', + gamma2: '5', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_11', + codeHash: 'ORACLE_CODE_HASH_PAIR_11', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_12', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_12', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_12', + }, + token0Contract: { + address: 'TOKEN_M_CONTRACT_ADDRESS', + codeHash: 'TOKEN_M_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_N_CONTRACT_ADDRESS', + codeHash: 'TOKEN_N_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_12', + isStable: true, + token0Amount: '157485051000000', + token1Amount: '135613721000000', + lpFee: 0.01, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '10', + gamma1: '4', + gamma2: '5', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_12', + codeHash: 'ORACLE_CODE_HASH_PAIR_12', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_13', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_13', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_13', + }, + token0Contract: { + address: 'TOKEN_O_CONTRACT_ADDRESS', + codeHash: 'TOKEN_O_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_L_CONTRACT_ADDRESS', + codeHash: 'TOKEN_L_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_13', + isStable: false, + token0Amount: '111111111000000', + token1Amount: '222222222000000', + lpFee: 0.02, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '200', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_13', + codeHash: 'ORACLE_CODE_HASH_PAIR_13', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_14', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_14', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_14', + }, + token0Contract: { + address: 'TOKEN_M_CONTRACT_ADDRESS', + codeHash: 'TOKEN_M_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_P_CONTRACT_ADDRESS', + codeHash: 'TOKEN_P_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_14', + isStable: false, + token0Amount: '111111111000000', + token1Amount: '222222222000000', + lpFee: 0.02, + daoFee: 0.01, + stableParams: null, + + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_15', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_15', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_15', + }, + token0Contract: { + address: 'TOKEN_Q_CONTRACT_ADDRESS', + codeHash: 'TOKEN_Q_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_R_CONTRACT_ADDRESS', + codeHash: 'TOKEN_R_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_15', + isStable: true, + token0Amount: '157485051000000', + token1Amount: '135613721000000', + lpFee: 0.01, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '10', + gamma1: '4', + gamma2: '5', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_15', + codeHash: 'ORACLE_CODE_HASH_PAIR_15', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_16', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_16', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_16', + }, + token0Contract: { + address: 'TOKEN_S_CONTRACT_ADDRESS', + codeHash: 'TOKEN_S_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_R_CONTRACT_ADDRESS', + codeHash: 'TOKEN_R_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_16', + isStable: true, + token0Amount: '157485051000000', + token1Amount: '135613721000000', + lpFee: 0.01, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '10', + gamma1: '4', + gamma2: '5', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_16', + codeHash: 'ORACLE_CODE_HASH_PAIR_16', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_17', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_17', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_17', + }, + token0Contract: { + address: 'TOKEN_S_CONTRACT_ADDRESS', + codeHash: 'TOKEN_S_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_T_CONTRACT_ADDRESS', + codeHash: 'TOKEN_T_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_17', + isStable: true, + token0Amount: '157485051000000', + token1Amount: '135613721000000', + lpFee: 0.01, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '10', + gamma1: '4', + gamma2: '5', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_17', + codeHash: 'ORACLE_CODE_HASH_PAIR_17', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_18', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_18', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_18', + }, + token0Contract: { + address: 'TOKEN_S_CONTRACT_ADDRESS', + codeHash: 'TOKEN_S_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_Q_CONTRACT_ADDRESS', + codeHash: 'TOKEN_Q_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_18', + isStable: true, + token0Amount: '157485051000000', + token1Amount: '135613721000000', + lpFee: 0.01, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '10', + gamma1: '4', + gamma2: '5', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_18', + codeHash: 'ORACLE_CODE_HASH_PAIR_18', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, +]; + +const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_1', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_1', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_1', + }, + token0Contract: { + address: 'TOKEN_A_CONTRACT_ADDRESS', + codeHash: 'TOKEN_A_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_F_CONTRACT_ADDRESS', + codeHash: 'TOKEN_F_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_1', + isStable: false, + token0Amount: '111111111', + token1Amount: '222222222', + lpFee: 0.01, + daoFee: 0.02, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_2', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_2', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_2', + }, + token0Contract: { + address: 'TOKEN_F_CONTRACT_ADDRESS', + codeHash: 'TOKEN_F_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_D_CONTRACT_ADDRESS', + codeHash: 'TOKEN_D_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_2', + isStable: false, + token0Amount: '333333333', + token1Amount: '4444444444', + lpFee: 0.01, + daoFee: 0.01, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_3', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_3', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_3', + }, + token0Contract: { + address: 'TOKEN_A_CONTRACT_ADDRESS', + codeHash: 'TOKEN_A_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_D_CONTRACT_ADDRESS', + codeHash: 'TOKEN_D_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_3', + isStable: false, + token0Amount: '5555555555', + token1Amount: '66666666', + lpFee: 0.02, + daoFee: 0.02, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_4', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_4', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_4', + }, + token0Contract: { + address: 'TOKEN_D_CONTRACT_ADDRESS', + codeHash: 'TOKEN_D_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_G_CONTRACT_ADDRESS', + codeHash: 'TOKEN_G_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_4', + isStable: true, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.01, + daoFee: 0.02, + stableParams: { + priceRatio: '1.1234567', + alpha: '200', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_4', + codeHash: 'ORACLE_CODE_HASH_PAIR_4', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_5', + pairInfo: { + lpTokenAmount: '0', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_5', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_5', + }, + token0Contract: { + address: 'TOKEN_A_CONTRACT_ADDRESS', + codeHash: 'TOKEN_A_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_C_CONTRACT_ADDRESS', + codeHash: 'TOKEN_C_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_5', + isStable: false, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: { + priceRatio: '1.1234567', + alpha: '200', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_5', + codeHash: 'ORACLE_CODE_HASH_PAIR_5', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_6', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_6', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_6', + }, + token0Contract: { + address: 'TOKEN_D_CONTRACT_ADDRESS', + codeHash: 'TOKEN_D_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_C_CONTRACT_ADDRESS', + codeHash: 'TOKEN_C_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_6', + isStable: false, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: { + priceRatio: '1.1234567', + alpha: '200', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_6', + codeHash: 'ORACLE_CODE_HASH_PAIR_6', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_7', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_7', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_7', + }, + token0Contract: { + address: 'TOKEN_A_CONTRACT_ADDRESS', + codeHash: 'TOKEN_A_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_B_CONTRACT_ADDRESS', + codeHash: 'TOKEN_B_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_7', + isStable: false, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_8', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_8', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_8', + }, + token0Contract: { + address: 'TOKEN_D_CONTRACT_ADDRESS', + codeHash: 'TOKEN_D_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_E_CONTRACT_ADDRESS', + codeHash: 'TOKEN_E_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_8', + isStable: false, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_9', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_8', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_8', + }, + token0Contract: { + address: 'TOKEN_C_CONTRACT_ADDRESS', + codeHash: 'TOKEN_C_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_B_CONTRACT_ADDRESS', + codeHash: 'TOKEN_B_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_8', + isStable: false, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.04, + daoFee: 0.16, + stableParams: null, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_10', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_10', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_10', + }, + token0Contract: { + address: 'TOKEN_C_CONTRACT_ADDRESS', + codeHash: 'TOKEN_C_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_E_CONTRACT_ADDRESS', + codeHash: 'TOKEN_E_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_10', + isStable: true, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.01, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '200', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_10', + codeHash: 'ORACLE_CODE_HASH_PAIR_10', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, + { + pairContractAddress: 'CONTRACT_ADDRESS_PAIR_11', + pairInfo: { + lpTokenAmount: '145080056', + lpTokenContract: { + address: 'MOCK_LP_TOKEN_CONTRACT_ADDRESS_PAIR_11', + codeHash: 'MOCK_LP_TOKEN_CODE_HASH_PAIR_11', + }, + token0Contract: { + address: 'TOKEN_B_CONTRACT_ADDRESS', + codeHash: 'TOKEN_B_CODE_HASH', + }, + token1Contract: { + address: 'TOKEN_E_CONTRACT_ADDRESS', + codeHash: 'TOKEN_E_CODE_HASH', + }, + factoryContract: null, + daoContractAddress: 'MOCK_DAO_CONTRACT_ADDRESS_PAIR_11', + isStable: true, + token0Amount: '157485051', + token1Amount: '135613721', + lpFee: 0.01, + daoFee: 0.01, + stableParams: { + priceRatio: '3', + alpha: '200', + gamma1: '6', + gamma2: '50', + oracle: { + address: 'ORACLE_CONTRACT_ADDRESS_PAIR_11', + codeHash: 'ORACLE_CODE_HASH_PAIR_11', + }, + token0Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + token1Data: { + oracleKey: 'ORACLE_KEY', + decimals: 5, + }, + minTradeSizeXForY: '1e-9', + minTradeSizeYForX: '1e-9', + maxPriceImpactAllowed: '500', + customIterationControls: null, + }, + contractVersion: 1, + }, + }, +]; + +const tokenConfigMock:TokensConfig = [ + { + tokenContractAddress: 'TOKEN_A_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_B_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_C_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_D_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_E_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_F_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_G_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_H_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_I_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_J_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_K_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_L_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_M_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_N_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_O_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_P_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_Q_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_R_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_S_CONTRACT_ADDRESS', + decimals: 6, + }, + { + tokenContractAddress: 'TOKEN_T_CONTRACT_ADDRESS', + decimals: 6, + }, +]; + +export { + tokenConfigMock, + batchPairsInfoMock, + batchPairsInfoMockForComplexRoute, +}; diff --git a/src/types/contracts/swap/model.ts b/src/types/contracts/swap/model.ts index 39ebc5d..7885e56 100644 --- a/src/types/contracts/swap/model.ts +++ b/src/types/contracts/swap/model.ts @@ -60,6 +60,7 @@ type StableTokenData = { } type StableParams = { + priceRatio: string, alpha: string, gamma1: string, gamma2: string, @@ -82,14 +83,9 @@ type PairInfo = { isStable: boolean, token0Amount: string, token1Amount: string, - priceRatio: string | null, - pairSettings: { - lpFee: number, - daoFee: number, - stableLpFee: number, - stableDaoFee: number, - stableParams: StableParams | null - }, + lpFee: number, + daoFee: number, + stableParams: StableParams | null contractVersion: number, } diff --git a/src/types/shared.ts b/src/types/shared.ts new file mode 100644 index 0000000..5a06303 --- /dev/null +++ b/src/types/shared.ts @@ -0,0 +1,10 @@ +type TokenConfig = { + tokenContractAddress: string, + decimals: number, +} + +type TokensConfig = TokenConfig[] + +export type { + TokensConfig, +}; diff --git a/src/types/swap/router.ts b/src/types/swap/router.ts new file mode 100644 index 0000000..9a3ba87 --- /dev/null +++ b/src/types/swap/router.ts @@ -0,0 +1,26 @@ +import BigNumber from 'bignumber.js'; + +// Gas Multipliers based on the swap type +enum GasMultiplier { + STABLE = 2.7, + CONSTANT_PRODUCT = 1, +} + +type Route = { + inputAmount: BigNumber, + quoteOutputAmount: BigNumber, + quoteShadeDaoFee: BigNumber, + quoteLPFee: BigNumber, + priceImpact: BigNumber, + inputTokenContractAddress: string, + outputTokenContractAddress: string, + path: string[], + gasMultiplier: number, +}; + +export { + GasMultiplier, +}; +export type { + Route, +};