diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 5d111eefa..321219553 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -20,9 +20,9 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: '16.x' + node-version: '18.x' - name: Install deps - run: yarn + run: yarn - name: Generate Schema run: yarn generate - name: Prisma Generate diff --git a/modules/balancer/balancer.gql b/modules/balancer/balancer.gql index 284b6d89d..228429a2f 100644 --- a/modules/balancer/balancer.gql +++ b/modules/balancer/balancer.gql @@ -1,7 +1,39 @@ +extend type Mutation { + balancerMutationTest: String! +} + extend type Query { - balancerQueryTest: String! + sorGetCowSwaps( + chain: GqlChain! + tokenIn: String! + tokenOut: String! + swapType: GqlSorSwapType! + swapAmount: BigDecimal! #expected in raw amount + ): GqlCowSwapApiResponse! } -extend type Mutation { - balancerMutationTest: String! +enum GqlSorSwapType { + EXACT_IN + EXACT_OUT +} + +type GqlCowSwapApiResponse { + tokenAddresses: [String!]! + swaps: [GqlSwap!]! + swapAmount: String! + swapAmountForSwaps: String! + returnAmount: String! + returnAmountFromSwaps: String! + returnAmountConsideringFees: String! + tokenIn: String! + tokenOut: String! + marketSp: String! +} + +type GqlSwap { + poolId: String! + assetInIndex: Int! + assetOutIndex: Int! + amount: String! + userData: String! } diff --git a/modules/balancer/balancer.resolvers.ts b/modules/balancer/balancer.resolvers.ts index 00723d41e..a7f0b1843 100644 --- a/modules/balancer/balancer.resolvers.ts +++ b/modules/balancer/balancer.resolvers.ts @@ -1,10 +1,16 @@ -import moment from 'moment'; import { Resolvers } from '../../schema'; +import { sorService } from '../sor/sor.service'; +import { getTokenAmountRaw } from '../sor/utils'; const balancerResolvers: Resolvers = { Query: { - balancerQueryTest: async (parent, {}, context) => { - return `${moment().utc().valueOf()}`; + sorGetCowSwaps: async (parent, args, context) => { + const amountToken = args.swapType === "EXACT_IN" ? args.tokenIn : args.tokenOut; + // Use TokenAmount to help follow scaling requirements in later logic + // args.swapAmount is RawScale, e.g. 1USDC should be passed as 1000000 + const amount = await getTokenAmountRaw(amountToken, args.swapAmount, args.chain); + const swaps = await sorService.getCowSwaps({ ...args, swapAmount: amount, swapOptions: {} }); + return { ...swaps, __typename: 'GqlCowSwapApiResponse' }; }, }, Mutation: { diff --git a/modules/beethoven/balancer-sdk.gql b/modules/beethoven/balancer-sdk.gql index e4ab912fc..a2d9e8d26 100644 --- a/modules/beethoven/balancer-sdk.gql +++ b/modules/beethoven/balancer-sdk.gql @@ -1,10 +1,12 @@ extend type Query { sorGetSwaps( + chain: GqlChain! tokenIn: String! tokenOut: String! swapType: GqlSorSwapType! swapAmount: BigDecimal! #expected in human readable form swapOptions: GqlSorSwapOptionsInput! + graphTraversalConfig: GqlGraphTraversalConfigInput ): GqlSorGetSwapsResponse! sorGetBatchSwapForTokensIn( tokensIn: [GqlTokenAmountHumanReadable!]! @@ -24,6 +26,14 @@ input GqlSorSwapOptionsInput { forceRefresh: Boolean #don't use any cached responses } +input GqlGraphTraversalConfigInput { + maxDepth: Int # default 6 + maxNonBoostedPathDepth: Int # default 3 + maxNonBoostedHopTokensInBoostedPath: Int # default 2 + approxPathsToReturn: Int # default 5 + poolIdsToInclude: [String] +} + type GqlSorGetSwapsResponse { tokenIn: String! tokenOut: String! @@ -77,3 +87,24 @@ type GqlSorGetBatchSwapForTokensInResponse { swaps: [GqlSorSwap!]! assets: [String!]! } + +type GqlCowSwapApiResponse { + tokenAddresses: [String!]! + swaps: [GqlSwap!]! + swapAmount: String! + swapAmountForSwaps: String! + returnAmount: String! + returnAmountFromSwaps: String! + returnAmountConsideringFees: String! + tokenIn: String! + tokenOut: String! + marketSp: String! +} + +type GqlSwap { + poolId: String! + assetInIndex: Int! + assetOutIndex: Int! + amount: String! + userData: String! +} diff --git a/modules/beethoven/balancer-sdk.resolvers.ts b/modules/beethoven/balancer-sdk.resolvers.ts index 3f889b5af..ccedce89b 100644 --- a/modules/beethoven/balancer-sdk.resolvers.ts +++ b/modules/beethoven/balancer-sdk.resolvers.ts @@ -1,13 +1,30 @@ import { Resolvers } from '../../schema'; import { balancerSorService } from './balancer-sor.service'; import { tokenService } from '../token/token.service'; +import { sorService } from '../sor/sor.service'; +import { getTokenAmountHuman } from '../sor/utils'; +import { GraphTraversalConfig } from '../sor/types'; const balancerSdkResolvers: Resolvers = { Query: { sorGetSwaps: async (parent, args, context) => { - const tokens = await tokenService.getTokens(); + const tokenIn = args.tokenIn.toLowerCase(); + const tokenOut = args.tokenOut.toLowerCase(); + const amountToken = args.swapType === 'EXACT_IN' ? tokenIn : tokenOut; + // Use TokenAmount to help follow scaling requirements in later logic + // args.swapAmount is HumanScale + const amount = await getTokenAmountHuman(amountToken, args.swapAmount, args.chain); + const graphTraversalConfig = args.graphTraversalConfig as GraphTraversalConfig; + + const swaps = await sorService.getBeetsSwaps({ + ...args, + tokenIn, + tokenOut, + graphTraversalConfig, + swapAmount: amount, + }); - return balancerSorService.getSwaps({ ...args, tokens }); + return { ...swaps, __typename: 'GqlSorGetSwapsResponse' }; }, sorGetBatchSwapForTokensIn: async (parent, args, context) => { const tokens = await tokenService.getTokens(); diff --git a/modules/beethoven/balancer-sor.service.ts b/modules/beethoven/balancer-sor.service.ts index 8f1f5888e..37106b79a 100644 --- a/modules/beethoven/balancer-sor.service.ts +++ b/modules/beethoven/balancer-sor.service.ts @@ -1,4 +1,4 @@ -import { GqlSorGetSwapsResponse, GqlSorSwapOptionsInput, GqlSorSwapType } from '../../schema'; +import { GqlSorGetSwapsResponse, GqlSorSwapOptionsInput, GqlSorSwapType, GqlPoolMinimal } from '../../schema'; import { formatFixed, parseFixed } from '@ethersproject/bignumber'; import { PrismaToken } from '@prisma/client'; import { poolService } from '../pool/pool.service'; @@ -17,6 +17,7 @@ import { DeploymentEnv } from '../network/network-config-types'; import * as Sentry from '@sentry/node'; import _ from 'lodash'; import { Logger } from '@ethersproject/logger'; +import { SwapInfoRoute } from '@balancer-labs/sor'; interface GetSwapsInput { tokenIn: string; @@ -55,35 +56,7 @@ export class BalancerSorService { let swapInfo = await this.querySor(swapType, tokenIn, tokenOut, swapAmountScaled, swapOptions); // no swaps found, return 0 if (swapInfo.swaps.length === 0) { - return { - ...swapInfo, - tokenIn: replaceZeroAddressWithEth(swapInfo.tokenIn), - tokenOut: replaceZeroAddressWithEth(swapInfo.tokenOut), - swapType, - tokenInAmount: swapType === 'EXACT_IN' ? swapAmount : BigNumber.from('0').toString(), - tokenOutAmount: swapType === 'EXACT_IN' ? BigNumber.from('0').toString() : swapAmount, - swapAmount: swapType === 'EXACT_IN' ? BigNumber.from('0').toString() : swapAmount, - swapAmountScaled: BigNumber.from('0').toString(), - swapAmountForSwaps: swapInfo.swapAmountForSwaps - ? BigNumber.from(swapInfo.swapAmountForSwaps).toString() - : undefined, - returnAmount: BigNumber.from('0').toString(), - returnAmountScaled: BigNumber.from('0').toString(), - returnAmountConsideringFees: BigNumber.from(swapInfo.returnAmountConsideringFees).toString(), - returnAmountFromSwaps: swapInfo.returnAmountFromSwaps - ? BigNumber.from(swapInfo.returnAmountFromSwaps).toString() - : undefined, - routes: swapInfo.routes.map((route) => ({ - ...route, - hops: route.hops.map((hop) => ({ - ...hop, - pool: pools.find((pool) => pool.id === hop.poolId)!, - })), - })), - effectivePrice: BigNumber.from('0').toString(), - effectivePriceReversed: BigNumber.from('0').toString(), - priceImpact: BigNumber.from('0').toString(), - }; + return this.zeroResponse(swapType, tokenIn, tokenOut, swapAmount); } let deltas: string[] = []; @@ -142,27 +115,70 @@ export class BalancerSorService { const tokenInAmount = BigNumber.from(deltas[swapInfo.tokenAddresses.indexOf(tokenIn)]); const tokenOutAmount = BigNumber.from(deltas[swapInfo.tokenAddresses.indexOf(tokenOut)]).abs(); - const swapAmountQuery = swapType === 'EXACT_OUT' ? tokenOutAmount : tokenInAmount; - const returnAmount = swapType === 'EXACT_IN' ? tokenOutAmount : tokenInAmount; - - const returnAmountFixed = formatFixed( - returnAmount, - this.getTokenDecimals(swapType === 'EXACT_IN' ? tokenOut : tokenIn, tokens), - ); - - const swapAmountQueryFixed = formatFixed( - swapAmountQuery, - this.getTokenDecimals(swapType === 'EXACT_OUT' ? tokenOut : tokenIn, tokens), - ); + return this.formatResponse({ + tokenIn: swapInfo.tokenIn, + tokenOut: swapInfo.tokenOut, + tokens, + tokenInAmtEvm: tokenInAmount.toString(), + tokenOutAmtEvm: tokenOutAmount.toString(), + swapAmountForSwaps: BigNumber.from(swapInfo.swapAmountForSwaps).toString(), + returnAmountConsideringFees: BigNumber.from(swapInfo.returnAmountConsideringFees).toString(), + returnAmountFromSwaps: BigNumber.from(swapInfo.returnAmountFromSwaps).toString(), + routes: swapInfo.routes, + pools, + marketSp: swapInfo.marketSp, + swaps: swapInfo.swaps, + tokenAddresses: swapInfo.tokenAddresses, + swapType, + }); + } - const tokenInAmountFixed = formatFixed(tokenInAmount, this.getTokenDecimals(tokenIn, tokens)); - const tokenOutAmountFixed = formatFixed(tokenOutAmount, this.getTokenDecimals(tokenOut, tokens)); + formatResponse(swapData: { + tokenIn: string; + tokenOut: string; + swapType: GqlSorSwapType; + tokens: PrismaToken[]; + tokenInAmtEvm: string; + tokenOutAmtEvm: string; + swapAmountForSwaps: string; + returnAmountConsideringFees: string; + returnAmountFromSwaps: string; + routes: SwapInfoRoute[]; + pools: GqlPoolMinimal[]; + marketSp: string; + swaps: SwapV2[]; + tokenAddresses: string[]; + }): GqlSorGetSwapsResponse { + const { + tokenIn, + tokenOut, + swapType, + tokens, + tokenInAmtEvm, + tokenOutAmtEvm, + swapAmountForSwaps, + returnAmountConsideringFees, + returnAmountFromSwaps, + routes, + pools, + marketSp, + swaps, + tokenAddresses, + } = swapData; + + const tokenInAmountFixed = formatFixed(tokenInAmtEvm, this.getTokenDecimals(tokenIn, tokens)); + const tokenOutAmountFixed = formatFixed(tokenOutAmtEvm, this.getTokenDecimals(tokenOut, tokens)); + + const swapAmountQuery = swapType === 'EXACT_OUT' ? tokenOutAmtEvm : tokenInAmtEvm; + const returnAmount = swapType === 'EXACT_IN' ? tokenOutAmtEvm : tokenInAmtEvm; + const swapAmountQueryFixed = swapType === 'EXACT_OUT' ? tokenOutAmountFixed : tokenInAmountFixed; + const returnAmountFixed = swapType === 'EXACT_IN' ? tokenOutAmountFixed : tokenInAmountFixed; const effectivePrice = oldBnum(tokenInAmountFixed).div(tokenOutAmountFixed); const effectivePriceReversed = oldBnum(tokenOutAmountFixed).div(tokenInAmountFixed); - const priceImpact = effectivePrice.div(swapInfo.marketSp).minus(1); + const priceImpact = effectivePrice.div(marketSp).minus(1); - for (const route of swapInfo.routes) { + for (const route of routes) { route.tokenInAmount = oldBnum(tokenInAmountFixed) .multipliedBy(route.share) .dp(this.getTokenDecimals(tokenIn, tokens)) @@ -174,24 +190,22 @@ export class BalancerSorService { } return { - ...swapInfo, - tokenIn: replaceZeroAddressWithEth(swapInfo.tokenIn), - tokenOut: replaceZeroAddressWithEth(swapInfo.tokenOut), + swaps, + marketSp, + tokenAddresses, + tokenIn: replaceZeroAddressWithEth(tokenIn), + tokenOut: replaceZeroAddressWithEth(tokenOut), swapType, tokenInAmount: tokenInAmountFixed, tokenOutAmount: tokenOutAmountFixed, swapAmount: swapAmountQueryFixed, - swapAmountScaled: swapAmountQuery.toString(), - swapAmountForSwaps: swapInfo.swapAmountForSwaps - ? BigNumber.from(swapInfo.swapAmountForSwaps).toString() - : undefined, + swapAmountScaled: swapAmountQuery, + swapAmountForSwaps: swapAmountForSwaps ? BigNumber.from(swapAmountForSwaps).toString() : undefined, returnAmount: returnAmountFixed, - returnAmountScaled: returnAmount.toString(), - returnAmountConsideringFees: BigNumber.from(swapInfo.returnAmountConsideringFees).toString(), - returnAmountFromSwaps: swapInfo.returnAmountFromSwaps - ? BigNumber.from(swapInfo.returnAmountFromSwaps).toString() - : undefined, - routes: swapInfo.routes.map((route) => ({ + returnAmountScaled: returnAmount, + returnAmountConsideringFees: BigNumber.from(returnAmountConsideringFees).toString(), + returnAmountFromSwaps: returnAmountFromSwaps ? BigNumber.from(returnAmountFromSwaps).toString() : undefined, + routes: routes.map((route) => ({ ...route, hops: route.hops.map((hop) => ({ ...hop, @@ -204,6 +218,35 @@ export class BalancerSorService { }; } + zeroResponse( + swapType: GqlSorSwapType, + tokenIn: string, + tokenOut: string, + swapAmount: string, + ): GqlSorGetSwapsResponse { + return { + marketSp: '0', + tokenAddresses: [], + swaps: [], + tokenIn: replaceZeroAddressWithEth(tokenIn), + tokenOut: replaceZeroAddressWithEth(tokenOut), + swapType, + tokenInAmount: swapType === 'EXACT_IN' ? swapAmount : '0', + tokenOutAmount: swapType === 'EXACT_IN' ? '0' : swapAmount, + swapAmount: swapType === 'EXACT_IN' ? '0' : swapAmount, + swapAmountScaled: '0', + swapAmountForSwaps: '0', + returnAmount: '0', + returnAmountScaled: '0', + returnAmountConsideringFees: '0', + returnAmountFromSwaps: '0', + routes: [], + effectivePrice: '0', + effectivePriceReversed: '0', + priceImpact: '0', + }; + } + private async querySor( swapType: string, tokenIn: string, @@ -293,11 +336,13 @@ export class BalancerSorService { tokenAddress = tokenAddress.toLowerCase(); const match = tokens.find((token) => token.address === tokenAddress); - if (!match) { - throw new Error('Unknown token: ' + tokenAddress); + let decimals = match?.decimals; + if (!decimals) { + console.error(`Unknown token: ${tokenAddress}`); + decimals = 18; } - return match.decimals; + return decimals; } private batchSwaps(assetArray: string[][], swaps: SwapV2[][]): { swaps: SwapV2[]; assets: string[] } { diff --git a/modules/beethoven/balancer-sor.test.ts b/modules/beethoven/balancer-sor.test.ts new file mode 100644 index 000000000..0a7261f9e --- /dev/null +++ b/modules/beethoven/balancer-sor.test.ts @@ -0,0 +1,299 @@ +import { BalancerSorService } from './balancer-sor.service'; +import { tokenService } from '../token/token.service'; +import { poolService } from '../pool/pool.service'; + +import { GqlSorGetSwapsResponse, GqlSorSwapOptionsInput, GqlSorSwapType, GqlPoolMinimal } from '../../schema'; + +// npx jest --testPathPattern=modules/beethoven/balancer-sor.test.ts +describe('SmartOrderRouter', () => { + test('swap with mixed decimals', async () => { + const tokens = await tokenService.getTokens(); + const pools = await poolService.getGqlPools({ + where: { idIn: ['0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015'] }, + }); + + const sor = new BalancerSorService(); + const out = { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + swapType: 'EXACT_IN' as GqlSorSwapType, + tokens, + tokenInAmtEvm: '1000000000000000000000', + tokenOutAmtEvm: '21524991', + swapAmountForSwaps: '1000000000000000000000', + returnAmountConsideringFees: '21524991', + returnAmountFromSwaps: '21524991', + routes: [ + { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenInAmount: '1000', + tokenOutAmount: '21.524991694650096005', + share: 1, + hops: [ + { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenInAmount: '1000', + tokenOutAmount: '21.524991694650096005', + poolId: '0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015', + }, + ], + }, + ], + pools, + marketSp: '46.44498985968269', + swaps: [ + { + poolId: '0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015', + assetInIndex: 0, + assetOutIndex: 1, + amount: '1000000000000000000000', + userData: '0x', + returnAmount: '21524991', + }, + ], + tokenAddresses: [ + '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + ], + }; + const result = sor.formatResponse(out); + const actual = { + swapAmount: '1000.0', + swapAmountForSwaps: '1000000000000000000000', + returnAmount: '21.524991', + returnAmountFromSwaps: '21524991', + returnAmountConsideringFees: '21524991', + swaps: [ + { + poolId: '0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015', + assetInIndex: 0, + assetOutIndex: 1, + amount: '1000000000000000000000', + userData: '0x', + returnAmount: '21524991', + }, + ], + tokenAddresses: [ + '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + ], + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + marketSp: '46.44498985968269', + routes: [ + { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenInAmount: '1000', + tokenOutAmount: '21.524991', + share: 1, + hops: [ + { + tokenIn: '0xf24bcf4d1e507740041c9cfd2dddb29585adce1e', + tokenOut: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenInAmount: '1000', + tokenOutAmount: '21.524991694650096005', + poolId: '0x03c6b3f09d2504606936b1a4decefad204687890000200000000000000000015', + pool: pools[0], + }, + ], + }, + ], + swapType: 'EXACT_IN', + tokenInAmount: '1000.0', + tokenOutAmount: '21.524991', + swapAmountScaled: '1000000000000000000000', + returnAmountScaled: '21524991', + effectivePrice: '46.45762685800890694914', + effectivePriceReversed: '0.021524991', + priceImpact: '0.00027208528550431865', + }; + expect(result).toEqual(actual); + }); + test('swap with native asset', async () => { + const tokens = await tokenService.getTokens(); + const pools = await poolService.getGqlPools({ + where: { + idIn: [ + '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + ], + }, + }); + + const sor = new BalancerSorService(); + const out = { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0x0000000000000000000000000000000000000000', + swapType: 'EXACT_IN' as GqlSorSwapType, + tokens, + tokenInAmtEvm: '10000000', + tokenOutAmtEvm: '34434611675857195780', + swapAmountForSwaps: '10000000', + returnAmountConsideringFees: '34434650939050394265', + returnAmountFromSwaps: '34434650939080394265', + routes: [ + { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83', + tokenInAmount: '10', + tokenOutAmount: '34.434650939080394265', + share: 1, + hops: [ + { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + tokenInAmount: '10', + tokenOutAmount: '9.953043613661588446', + poolId: '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + }, + { + tokenIn: '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + tokenOut: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + tokenInAmount: '9.953043613661588446', + tokenOutAmount: '34.355661787346070666', + poolId: '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + }, + { + tokenIn: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + tokenOut: '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83', + tokenInAmount: '34.355661787346070666', + tokenOutAmount: '34.434650939080394265', + poolId: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + }, + ], + }, + ], + pools, + marketSp: '0.29021486756308521004963850902453996332730905029153527', + swaps: [ + { + poolId: '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + assetInIndex: 0, + assetOutIndex: 1, + amount: '10000000', + userData: '0x', + returnAmount: '9953043613661588446', + }, + { + poolId: '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + assetInIndex: 1, + assetOutIndex: 2, + amount: '0', + userData: '0x', + returnAmount: '34355661787346070666', + }, + { + poolId: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + assetInIndex: 2, + assetOutIndex: 3, + amount: '0', + userData: '0x', + returnAmount: '34434650939080394265', + }, + ], + tokenAddresses: [ + '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + '0x0000000000000000000000000000000000000000', + ], + }; + const result = sor.formatResponse(out); + const actual = { + swapAmount: '10.0', + swapAmountForSwaps: '10000000', + returnAmount: '34.43461167585719578', + returnAmountFromSwaps: '34434650939080394265', + returnAmountConsideringFees: '34434650939050394265', + swaps: [ + { + poolId: '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + assetInIndex: 0, + assetOutIndex: 1, + amount: '10000000', + userData: '0x', + returnAmount: '9953043613661588446', + }, + { + poolId: '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + assetInIndex: 1, + assetOutIndex: 2, + amount: '0', + userData: '0x', + returnAmount: '34355661787346070666', + }, + { + poolId: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + assetInIndex: 2, + assetOutIndex: 3, + amount: '0', + userData: '0x', + returnAmount: '34434650939080394265', + }, + ], + tokenAddresses: [ + '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + '0x0000000000000000000000000000000000000000', + ], + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + marketSp: '0.29021486756308521004963850902453996332730905029153527', + routes: [ + { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83', + tokenInAmount: '10', + tokenOutAmount: '34.43461167585719578', + share: 1, + hops: [ + { + tokenIn: '0x04068da6c83afcfa0e13ba15a6696662335d5b75', + tokenOut: '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + tokenInAmount: '10', + tokenOutAmount: '9.953043613661588446', + poolId: '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + pool: pools.find( + (p) => p.id === '0xc385e76e575b2d71eb877c27dcc1608f77fada99000000000000000000000719', + ), + }, + { + tokenIn: '0xc385e76e575b2d71eb877c27dcc1608f77fada99', + tokenOut: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + tokenInAmount: '9.953043613661588446', + tokenOutAmount: '34.355661787346070666', + poolId: '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + pool: pools.find( + (p) => p.id === '0x7449f09c8f0ed490472d7c14b4eef235620d027000010000000000000000072d', + ), + }, + { + tokenIn: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f', + tokenOut: '0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83', + tokenInAmount: '34.355661787346070666', + tokenOutAmount: '34.434650939080394265', + poolId: '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + pool: pools.find( + (p) => p.id === '0x92502cd8e00f5b8e737b2ba203fdd7cd27b23c8f000000000000000000000718', + ), + }, + ], + }, + ], + swapType: 'EXACT_IN', + tokenInAmount: '10.0', + tokenOutAmount: '34.43461167585719578', + swapAmountScaled: '10000000', + returnAmountScaled: '34434611675857195780', + effectivePrice: '0.29040548196485696673', + effectivePriceReversed: '3.443461167585719578', + priceImpact: '0.00065680439934843116', + }; + expect(result).toEqual(actual); + }); +}); diff --git a/modules/metrics/sor.metric.ts b/modules/metrics/sor.metric.ts new file mode 100644 index 000000000..bac38295e --- /dev/null +++ b/modules/metrics/sor.metric.ts @@ -0,0 +1,12 @@ +import { Chain } from '@prisma/client'; +import { CloudwatchMetricsPublisher } from './metrics.client'; + +const publishers: Record = {}; + +export function getSorMetricsPublisher(chain: Chain): CloudwatchMetricsPublisher { + if (!publishers[chain]) { + console.log(`Creating new SOR publisher for ${chain}`); + publishers[chain] = new CloudwatchMetricsPublisher(`Backend-${chain}/Sor`); + } + return publishers[chain]; +} diff --git a/modules/network/arbitrum.ts b/modules/network/arbitrum.ts index 20bb523f5..5db4ebdcc 100644 --- a/modules/network/arbitrum.ts +++ b/modules/network/arbitrum.ts @@ -97,6 +97,7 @@ const arbitrumNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -104,6 +105,7 @@ const arbitrumNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, ibAprConfig: { diff --git a/modules/network/avalanche.ts b/modules/network/avalanche.ts index f696e5005..504f3b373 100644 --- a/modules/network/avalanche.ts +++ b/modules/network/avalanche.ts @@ -91,6 +91,7 @@ const avalancheNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -98,6 +99,7 @@ const avalancheNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, ibAprConfig: { diff --git a/modules/network/base.ts b/modules/network/base.ts index c25d9b91e..b2ddf1291 100644 --- a/modules/network/base.ts +++ b/modules/network/base.ts @@ -96,6 +96,7 @@ const baseNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -103,6 +104,7 @@ const baseNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, monitoring: { diff --git a/modules/network/fantom.ts b/modules/network/fantom.ts index a266d331c..ca6112e98 100644 --- a/modules/network/fantom.ts +++ b/modules/network/fantom.ts @@ -164,6 +164,7 @@ const fantomNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://mep53ds2noe6rhicd67q7raqhq0dkupc.lambda-url.eu-central-1.on.aws/', @@ -171,6 +172,7 @@ const fantomNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, ibAprConfig: { diff --git a/modules/network/gnosis.ts b/modules/network/gnosis.ts index d00aa09f0..d5312029c 100644 --- a/modules/network/gnosis.ts +++ b/modules/network/gnosis.ts @@ -92,6 +92,7 @@ const gnosisNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: '', @@ -99,6 +100,7 @@ const gnosisNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, ibAprConfig: { diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 986c83a63..2210eedd2 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -110,6 +110,10 @@ const data: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [ + "0xbfa413a2ff0f20456d57b643746133f54bfe0cd20000000000000000000004c3", + "0xdc063deafce952160ec112fa382ac206305657e60000000000000000000004c4", // Linear pools that cause issues with new b-sdk + ], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -117,6 +121,10 @@ const data: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [ + "0xbfa413a2ff0f20456d57b643746133f54bfe0cd20000000000000000000004c3", + "0xdc063deafce952160ec112fa382ac206305657e60000000000000000000004c4", // Linear pools that cause issues with new b-sdk + ], }, }, ibAprConfig: { diff --git a/modules/network/network-config-types.ts b/modules/network/network-config-types.ts index 10c8e33dd..71a566434 100644 --- a/modules/network/network-config-types.ts +++ b/modules/network/network-config-types.ts @@ -156,6 +156,7 @@ export interface NetworkData { forceRefresh: boolean; gasPrice: BigNumber; swapGas: BigNumber; + poolIdsToExclude: string[]; }; }; datastudio?: { diff --git a/modules/network/network-config.ts b/modules/network/network-config.ts index 7cc0786da..74ec636e3 100644 --- a/modules/network/network-config.ts +++ b/modules/network/network-config.ts @@ -9,7 +9,6 @@ import { zkevmNetworkConfig } from './zkevm'; import { avalancheNetworkConfig } from './avalanche'; import { baseNetworkConfig } from './base'; import { Chain } from '@prisma/client'; -import { keyBy, pickBy } from 'lodash'; export const AllNetworkConfigs: { [chainId: string]: NetworkConfig } = { '250': fantomNetworkConfig, @@ -37,3 +36,8 @@ export const AllNetworkConfigsKeyedOnChain: { [chain in Chain]: NetworkConfig } export const BalancerChainIds = ['1', '137', '42161', '100', '1101', '43114', '8453']; export const BeethovenChainIds = ['250', '10']; + +export const chainToIdMap = Object.values(AllNetworkConfigs).reduce((acc, config) => { + acc[config.data.chain.gqlId] = String(config.data.chain.id); + return acc; +}, {} as { [chain in Chain]: string }); diff --git a/modules/network/optimism.ts b/modules/network/optimism.ts index af9a1a00d..c0f3beccb 100644 --- a/modules/network/optimism.ts +++ b/modules/network/optimism.ts @@ -109,6 +109,7 @@ const optimismNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://svlitjilcr5qtp7iolimlrlg7e0ipupj.lambda-url.eu-central-1.on.aws/', @@ -116,6 +117,7 @@ const optimismNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, ibAprConfig: { diff --git a/modules/network/polygon.ts b/modules/network/polygon.ts index 8fd44b1fd..e641bd715 100644 --- a/modules/network/polygon.ts +++ b/modules/network/polygon.ts @@ -97,6 +97,7 @@ const polygonNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -104,6 +105,7 @@ const polygonNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, ibAprConfig: { diff --git a/modules/network/zkevm.ts b/modules/network/zkevm.ts index 77ed05875..32c89798e 100644 --- a/modules/network/zkevm.ts +++ b/modules/network/zkevm.ts @@ -93,6 +93,7 @@ const zkevmNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, canary: { url: 'https://ksa66wlkjbvteijxmflqjehsay0jmekw.lambda-url.eu-central-1.on.aws/', @@ -100,6 +101,7 @@ const zkevmNetworkData: NetworkData = { forceRefresh: false, gasPrice: BigNumber.from(10), swapGas: BigNumber.from('1000000'), + poolIdsToExclude: [], }, }, ibAprConfig: { diff --git a/modules/pool/lib/pool-creator.service.ts b/modules/pool/lib/pool-creator.service.ts index 2b632c652..9235162e1 100644 --- a/modules/pool/lib/pool-creator.service.ts +++ b/modules/pool/lib/pool-creator.service.ts @@ -16,7 +16,7 @@ export class PoolCreatorService { } private get chain() { - return networkContext.chain + return networkContext.chain; } public async syncAllPoolsFromSubgraph(blockNumber: number): Promise { @@ -36,6 +36,39 @@ export class PoolCreatorService { await this.createPoolRecord(subgraphPool, blockNumber); poolIds.push(subgraphPool.id); + } else if (subgraphPool.poolType?.includes('Gyro')) { + await prisma.prismaPool.update({ + data: { + gyroData: { + update: { + id: subgraphPool.id, + alpha: subgraphPool.alpha || '', + beta: subgraphPool.beta || '', + sqrtAlpha: subgraphPool.sqrtAlpha || '', + sqrtBeta: subgraphPool.sqrtBeta || '', + root3Alpha: subgraphPool.root3Alpha || '', + c: subgraphPool.c || '', + s: subgraphPool.s || '', + lambda: subgraphPool.lambda || '', + tauAlphaX: subgraphPool.tauAlphaX || '', + tauAlphaY: subgraphPool.tauAlphaY || '', + tauBetaX: subgraphPool.tauBetaX || '', + tauBetaY: subgraphPool.tauBetaY || '', + u: subgraphPool.u || '', + v: subgraphPool.v || '', + w: subgraphPool.w || '', + z: subgraphPool.z || '', + dSq: subgraphPool.dSq || '', + }, + }, + }, + where: { + id_chain: { + id: subgraphPool.id, + chain: this.chain, + }, + }, + }); } } @@ -256,6 +289,21 @@ export class PoolCreatorService { id: pool.id, alpha: pool.alpha || '', beta: pool.beta || '', + sqrtAlpha: pool.sqrtAlpha || '', + sqrtBeta: pool.sqrtBeta || '', + root3Alpha: pool.root3Alpha || '', + c: pool.c || '', + s: pool.s || '', + lambda: pool.lambda || '', + tauAlphaX: pool.tauAlphaX || '', + tauAlphaY: pool.tauAlphaY || '', + tauBetaX: pool.tauBetaX || '', + tauBetaY: pool.tauBetaY || '', + u: pool.u || '', + v: pool.v || '', + w: pool.w || '', + z: pool.z || '', + dSq: pool.dSq || '', }, } : undefined, diff --git a/modules/pool/lib/pool-gql-loader.service.ts b/modules/pool/lib/pool-gql-loader.service.ts index 1e90acefc..3c5ac2126 100644 --- a/modules/pool/lib/pool-gql-loader.service.ts +++ b/modules/pool/lib/pool-gql-loader.service.ts @@ -13,6 +13,7 @@ import { GqlBalancePoolAprSubItem, GqlPoolDynamicData, GqlPoolFeaturedPoolGroup, + GqlPoolGyro, GqlPoolInvestConfig, GqlPoolInvestOption, GqlPoolLinear, @@ -112,6 +113,16 @@ export class PoolGqlLoaderService { return pools.map((pool) => this.mapPoolToGqlPool(pool)) as GqlPoolLinear[]; } + public async getGyroPools(): Promise { + const pools = await prisma.prismaPool.findMany({ + where: { type: { in: ['GYRO', 'GYRO3', 'GYROE'] }, chain: networkContext.chain }, + orderBy: { dynamicData: { totalLiquidity: 'desc' } }, + include: prismaPoolWithExpandedNesting.include, + }); + + return pools.map((pool) => this.mapPoolToGqlPool(pool)) as GqlPoolGyro[]; + } + public mapToMinimalGqlPool( pool: PrismaPoolMinimal, userWalletbalances: PrismaUserWalletBalance[] = [], @@ -457,6 +468,21 @@ export class PoolGqlLoaderService { type: mappedData.type, alpha: pool.gyroData?.alpha || '', beta: pool.gyroData?.beta || '', + sqrtAlpha: pool.gyroData?.sqrtAlpha || '', + sqrtBeta: pool.gyroData?.sqrtBeta || '', + root3Alpha: pool.gyroData?.root3Alpha || '', + c: pool.gyroData?.c || '', + s: pool.gyroData?.s || '', + lambda: pool.gyroData?.lambda || '', + tauAlphaX: pool.gyroData?.tauAlphaX || '', + tauAlphaY: pool.gyroData?.tauAlphaY || '', + tauBetaX: pool.gyroData?.tauBetaX || '', + tauBetaY: pool.gyroData?.tauBetaY || '', + u: pool.gyroData?.u || '', + v: pool.gyroData?.v || '', + w: pool.gyroData?.w || '', + z: pool.gyroData?.z || '', + dSq: pool.gyroData?.dSq || '', }; case 'GYRO3': return { @@ -465,6 +491,21 @@ export class PoolGqlLoaderService { type: mappedData.type, alpha: pool.gyroData?.alpha || '', beta: pool.gyroData?.beta || '', + sqrtAlpha: pool.gyroData?.sqrtAlpha || '', + sqrtBeta: pool.gyroData?.sqrtBeta || '', + root3Alpha: pool.gyroData?.root3Alpha || '', + c: pool.gyroData?.c || '', + s: pool.gyroData?.s || '', + lambda: pool.gyroData?.lambda || '', + tauAlphaX: pool.gyroData?.tauAlphaX || '', + tauAlphaY: pool.gyroData?.tauAlphaY || '', + tauBetaX: pool.gyroData?.tauBetaX || '', + tauBetaY: pool.gyroData?.tauBetaY || '', + u: pool.gyroData?.u || '', + v: pool.gyroData?.v || '', + w: pool.gyroData?.w || '', + z: pool.gyroData?.z || '', + dSq: pool.gyroData?.dSq || '', }; case 'GYROE': return { @@ -473,6 +514,21 @@ export class PoolGqlLoaderService { type: mappedData.type, alpha: pool.gyroData?.alpha || '', beta: pool.gyroData?.beta || '', + sqrtAlpha: pool.gyroData?.sqrtAlpha || '', + sqrtBeta: pool.gyroData?.sqrtBeta || '', + root3Alpha: pool.gyroData?.root3Alpha || '', + c: pool.gyroData?.c || '', + s: pool.gyroData?.s || '', + lambda: pool.gyroData?.lambda || '', + tauAlphaX: pool.gyroData?.tauAlphaX || '', + tauAlphaY: pool.gyroData?.tauAlphaY || '', + tauBetaX: pool.gyroData?.tauBetaX || '', + tauBetaY: pool.gyroData?.tauBetaY || '', + u: pool.gyroData?.u || '', + v: pool.gyroData?.v || '', + w: pool.gyroData?.w || '', + z: pool.gyroData?.z || '', + dSq: pool.gyroData?.dSq || '', }; } diff --git a/modules/pool/pool.gql b/modules/pool/pool.gql index f6d231cf3..2d1b84e59 100644 --- a/modules/pool/pool.gql +++ b/modules/pool/pool.gql @@ -23,6 +23,7 @@ extend type Query { poolGetSnapshots(id: String!, chain: GqlChain, range: GqlPoolSnapshotDataRange!): [GqlPoolSnapshot!]! poolGetAllPoolsSnapshots(chains: [GqlChain!], range: GqlPoolSnapshotDataRange!): [GqlPoolSnapshot!]! poolGetLinearPools(chains: [GqlChain!]): [GqlPoolLinear!]! + poolGetGyroPools(chains: [GqlChain!]): [GqlPoolGyro!]! } extend type Mutation { @@ -247,6 +248,21 @@ type GqlPoolGyro implements GqlPoolBase { alpha: String! beta: String! + sqrtAlpha: String! + sqrtBeta: String! + root3Alpha: String! + c: String! + s: String! + lambda: String! + tauAlphaX: String! + tauAlphaY: String! + tauBetaX: String! + tauBetaY: String! + u: String! + v: String! + w: String! + z: String! + dSq: String! tokens: [GqlPoolTokenUnion!]! nestingType: GqlPoolNestingType! diff --git a/modules/pool/pool.prisma b/modules/pool/pool.prisma index 9244c46b5..6781f3727 100644 --- a/modules/pool/pool.prisma +++ b/modules/pool/pool.prisma @@ -96,6 +96,21 @@ model PrismaPoolGyroData{ alpha String beta String + sqrtAlpha String? + sqrtBeta String? + root3Alpha String? + c String? + s String? + lambda String? + tauAlphaX String? + tauAlphaY String? + tauBetaX String? + tauBetaY String? + u String? + v String? + w String? + z String? + dSq String? } model PrismaPoolDynamicData { diff --git a/modules/pool/pool.resolvers.ts b/modules/pool/pool.resolvers.ts index e9fe7431b..bb7387b93 100644 --- a/modules/pool/pool.resolvers.ts +++ b/modules/pool/pool.resolvers.ts @@ -103,6 +103,9 @@ const balancerResolvers: Resolvers = { } return poolService.getGqlLinearPools(chains); }, + poolGetGyroPools: async () => { + return poolService.getGqlGyroPools(); + } }, Mutation: { poolSyncAllPoolsFromSubgraph: async (parent, {}, context) => { diff --git a/modules/pool/pool.service.ts b/modules/pool/pool.service.ts index 468a8f201..704d56364 100644 --- a/modules/pool/pool.service.ts +++ b/modules/pool/pool.service.ts @@ -7,6 +7,7 @@ import { GqlChain, GqlPoolBatchSwap, GqlPoolFeaturedPoolGroup, + GqlPoolGyro, GqlPoolJoinExit, GqlPoolLinear, GqlPoolMinimal, @@ -84,6 +85,10 @@ export class PoolService { return this.poolGqlLoaderService.getLinearPools(chains); } + public async getGqlGyroPools(): Promise { + return this.poolGqlLoaderService.getGyroPools(); + } + public async getPoolsCount(args: QueryPoolGetPoolsArgs): Promise { return this.poolGqlLoaderService.getPoolsCount(args); } diff --git a/modules/sor/constants.ts b/modules/sor/constants.ts new file mode 100644 index 000000000..0fa69e5be --- /dev/null +++ b/modules/sor/constants.ts @@ -0,0 +1,355 @@ +import { TokenAmount } from '@balancer/sdk'; +import { GqlCowSwapApiResponse, GqlSorSwapType } from '../../schema'; + +export const EMPTY_COWSWAP_RESPONSE = (assetIn: string, assetOut: string, amount: TokenAmount): GqlCowSwapApiResponse => { + return { + marketSp: '0', + returnAmount: '0', + returnAmountConsideringFees: '0', + returnAmountFromSwaps: '0', + swapAmount: amount.amount.toString(), + swapAmountForSwaps: '0', + swaps: [], + tokenAddresses: [], + tokenIn: assetIn, + tokenOut: assetOut, + }; +}; + +// These are pools related to Aug `23 vulnerability/mitigation and should be ignored for swaps +export const poolsToIgnore = [ + '0x00c2a4be503869fa751c2dbcb7156cc970b5a8da000000000000000000000477', + '0x02d928e68d8f10c0358566152677db51e1e2dc8c00000000000000000000051e', + '0x04248aabca09e9a1a3d5129a7ba05b7f17de768400000000000000000000050e', + '0x05513ca725b6ce035ca2641075474eb469f05f4c00020000000000000000041f', + '0x0a0fb4ff697de5ac5b6770cd8ee1b72af80b57cf000000000000000000000496', + '0x0afbd58beca09545e4fb67772faf3858e610bcd00000000000000000000004b9', + '0x0d05aac44ac7dd3c7ba5d50be93eb884a057d23400000000000000000000051c', + '0x11839d635e2f0270da37e8ef4324d4d5d54329570002000000000000000004d8', + '0x126e7643235ec0ab9c103c507642dc3f4ca23c66000000000000000000000468', + '0x133d241f225750d2c92948e464a5a80111920331000000000000000000000476', + '0x159cb00338fb63f263fd6f621df619cef71da9540000000000000000000004d5', + '0x173063a30e095313eee39411f07e95a8a806014e0002000000000000000003ab', + '0x1bd2f176a812e312077bca87e37c08432bb09f3e0000000000000000000005a1', + '0x20b156776114e8a801e9767d90c6ccccc8adf398000000000000000000000499', + '0x246ffb4d928e394a02e45761fecdba6c2e79b8eb000000000000000000000541', + '0x25accb7943fd73dda5e23ba6329085a3c24bfb6a000200000000000000000387', + '0x26c2b83fc8535deead276f5cc3ad9c1a2192e02700020000000000000000056b', + '0x2b218683178d029bab6c9789b1073aa6c96e517600000000000000000000058c', + '0x2ba7aa2213fa2c909cd9e46fed5a0059542b36b00000000000000000000003a3', + '0x2bbf681cc4eb09218bee85ea2a5d3d13fa40fc0c0000000000000000000000fd', + '0x2e52c64fd319e380cdbcfc4577ea1fda558a32e40002000000000000000005ba', + '0x2f4eb100552ef93840d5adc30560e5513dfffacb000000000000000000000334', + '0x2ff1a9dbdacd55297452cfd8a4d94724bc22a5f7000000000000000000000484', + '0x3035917be42af437cbdd774be26b9ec90a2bd677000200000000000000000543', + '0x331d50e0b00fc1c32742f151e56b9b616227e23e00000000000000000000047c', + '0x334c96d792e4b26b841d28f53235281cec1be1f200020000000000000000038a', + '0x335d1709d4da9aca59d16328db5cd4ea66bfe06b0000000000000000000004d6', + '0x395d8a1d9ad82b5abe558f8abbfe183b27138af40000000000000000000004e5', + '0x3bb22fc9033b802f2ac47c18885f63476f158afc000000000000000000000483', + '0x3c640f0d3036ad85afa2d5a9e32be651657b874f00000000000000000000046b', + '0x3cdae4f12a67ba563499e102f309c73213cb241c000000000000000000000335', + '0x3dbb8d974b82e82ce79c20c0f5995f4f1f533ede000000000000000000000470', + '0x3f7a7fd7f214be45ec26820fd01ac3be4fc75aa70002000000000000000004c5', + '0x3fcb7085b8f2f473f80bf6d879cae99ea4de934400000000000000000000056d', + '0x41503c9d499ddbd1dcdf818a1b05e9774203bf46000000000000000000000594', + '0x4228290ee9cab692938ff0b4ba303fbcdb68e9f200020000000000000000057d', + '0x454ed96955d04d2f5cdd05e0fd1c77975bfe5307000000000000000000000410', + '0x481c5fc05d63a58aa2f0f2aa417c021b5d419cb200000000000000000000056a', + '0x483006684f422a9448023b2382615c57c5ecf18f000000000000000000000488', + '0x4a82b580365cff9b146281ab72500957a849abdc000000000000000000000494', + '0x4c81255cc9ed7062180ea99962fe05ac0d57350b0000000000000000000005a3', + '0x4c8d2e60863e8d7e1033eda2b3d84e92a641802000000000000000000000040f', + '0x4cbde5c4b4b53ebe4af4adb85404725985406163000000000000000000000595', + '0x4ce0bd7debf13434d3ae127430e9bd4291bfb61f00020000000000000000038b', + '0x4ce277df0feb5b4d07a0ca2adcf5326e4005239d000000000000000000000518', + '0x4fd4687ec38220f805b6363c3c1e52d0df3b5023000200000000000000000473', + '0x4fd63966879300cafafbb35d157dc5229278ed230000000000000000000000e9', + '0x50cf90b954958480b8df7958a9e965752f62712400000000000000000000046f', + '0x53bc3cba3832ebecbfa002c12023f8ab1aa3a3a0000000000000000000000411', + '0x5a6a8cffb4347ff7fc484bf5f0f8a2e234d34255000200000000000000000275', + '0x5b3240b6be3e7487d61cd1afdfc7fe4fa1d81e6400000000000000000000037b', + '0x60683b05e9a39e3509d8fdb9c959f23170f8a0fa000000000000000000000489', + '0x60d604890feaa0b5460b28a424407c24fe89374a0000000000000000000004fc', + '0x639883476960a23b38579acfd7d71561a0f408cf000200000000000000000505', + '0x652d486b80c461c397b0d95612a404da936f3db30000000000000000000000e7', + '0x6667c6fa9f2b3fc1cc8d85320b62703d938e43850000000000000000000004fb', + '0x6a1eb2e9b45e772f55bd9a34659a04b6f75da68700000000000000000000040d', + '0x6c56e72c551b5ac4bf54a620a76077ca768c8fe40002000000000000000004da', + '0x70b7d3b3209a59fb0400e17f67f3ee8c37363f4900020000000000000000018f', + '0x7337224d59cb16c2dc6938cd45a7b2c60c865d6a0000000000000000000004d4', + '0x74cbfaf94a3577c539a9dcee9870a6349a33b34f000000000000000000000534', + '0x779d01f939d78a918a3de18cc236ee89221dfd4e0000000000000000000004c7', + '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe', + '0x804cdb9116a10bb78768d3252355a1b18067bf8f0000000000000000000000fb', + '0x813e3fe1761f714c502d1d2d3a7cceb33f37f59d00000000000000000000040c', + '0x82698aecc9e28e9bb27608bd52cf57f704bd1b83000000000000000000000336', + '0x8a6b25e33b12d1bb6929a8793961076bd1f9d3eb0002000000000000000003e8', + '0x8e6ec57a822c2f527f2df7c7d7d361df3e7530a1000000000000000000000498', + '0x8f4063446f5011bc1c9f79a819efe87776f23704000000000000000000000197', + '0x9001cbbd96f54a658ff4e6e65ab564ded76a543100000000000000000000050a', + '0x9210f1204b5a24742eba12f710636d76240df3d00000000000000000000000fc', + '0x9516a2d25958edb8da246a320f2c7d94a0dbe25d000000000000000000000519', + '0x959216bb492b2efa72b15b7aacea5b5c984c3cca000200000000000000000472', + '0x968024662b9566b42d78af23a0f441bc8723fa83000200000000000000000418', + '0x99c88ad7dc566616548adde8ed3effa730eb6c3400000000000000000000049a', + '0x9b1c8407a360443a9e5eca004713e4088fab8ac0000000000000000000000497', + '0x9b692f571b256140a39a34676bffa30634c586e100000000000000000000059d', + '0x9d7f992c900fbea0ec314bdd71b7cc1becf76a33000200000000000000000573', + '0x9fb771d530b0ceba5160f7bfe2dd1e8b8aa1340300000000000000000000040e', + '0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d', + '0xa1697f9af0875b63ddc472d6eebada8c1fab85680000000000000000000004f9', + '0xa3823e50f20982656557a4a6a9c06ba5467ae9080000000000000000000000e6', + '0xa718042e5622099e5f0ace4e7122058ab39e1bbe000200000000000000000475', + '0xa8b103a10a94f4f2d7ed2fdcd5545e807557330700000000000000000000048e', + '0xac5b4ef7ede2f2843a704e96dcaa637f4ba3dc3f00000000000000000000051d', + '0xac976bb42cb0c85635644e8c7c74d0e0286aa61c0000000000000000000003cb', + '0xae37d54ae477268b9997d4161b96b8200755935c000000000000000000000337', + '0xae8535c23afedda9304b03c68a3563b75fc8f92b0000000000000000000005a0', + '0xb0f75e97a114a4eb4a425edc48990e6760726709000000000000000000000198', + '0xb5e3de837f869b0248825e0175da73d4e8c3db6b000200000000000000000474', + '0xb841b062ea8ccf5c4cb78032e91de4ae875560420002000000000000000005b7', + '0xb9bd68a77ccf8314c0dfe51bc291c77590c4e9e6000200000000000000000385', + '0xbb6881874825e60e1160416d6c426eae65f2459e000000000000000000000592', + '0xbc0f2372008005471874e426e86ccfae7b4de79d000000000000000000000485', + '0xbf2ef8bdc2fc0f3203b3a01778e3ec5009aeef3300000000000000000000058d', + '0xbfa413a2ff0f20456d57b643746133f54bfe0cd20000000000000000000004c3', + '0xc2b021133d1b0cf07dba696fd5dd89338428225b000000000000000000000598', + '0xc443c15033fcb6cf72cc24f1bda0db070ddd9786000000000000000000000593', + '0xc50d4347209f285247bda8a09fc1c12ce42031c3000000000000000000000590', + '0xc5dc1316ab670a2eed5716d7f19ced321191f38200000000000000000000056e', + '0xc8c79fcd0e859e7ec81118e91ce8e4379a481ee6000000000000000000000196', + '0xcaa052584b462198a5a9356c28bce0634d65f65c0000000000000000000004db', + '0xcbfa4532d8b2ade2c261d3dd5ef2a2284f7926920000000000000000000004fa', + '0xcfae6e251369467f465f13836ac8135bd42f8a56000000000000000000000591', + '0xd4e7c1f3da1144c9e2cfd1b015eda7652b4a439900000000000000000000046a', + '0xd6e355036f41dc261b3f1ed3bbc6003e87aadb4f000000000000000000000495', + '0xd7edb56f63b2a0191742aea32df1f98ca81ed9c600000000000000000000058e', + '0xd997f35c9b1281b82c8928039d14cddab5e13c2000000000000000000000019c', + '0xdba274b4d04097b90a72b62467d828cefd708037000000000000000000000486', + '0xdc063deafce952160ec112fa382ac206305657e60000000000000000000004c4', + '0xdec02e6642e2c999af429f5ce944653cad15e093000000000000000000000469', + '0xe03af00fabe8401560c1ff7d242d622a5b601573000000000000000000000493', + '0xe0fcbf4d98f0ad982db260f86cf28b49845403c5000000000000000000000504', + '0xe2d16b0a39f3fbb4389a0e8f1efcbecfb3d1e6e10000000000000000000005a7', + '0xe4dc3c1998ac693d68f4c77476d7c815694c3e94000200000000000000000416', + '0xe6bcc79f328eec93d4ec8f7ed35534d9ab549faa0000000000000000000000e8', + '0xe8c56405bc405840154d9b572927f4197d110de10000000000000000000005a4', + '0xeb486af868aeb3b6e53066abc9623b1041b42bc000000000000000000000046c', + '0xeb567dde03f3da7fe185bdacd5ab495ab220769d000000000000000000000548', + '0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d', + '0xf22ff21e17157340575158ad7394e068048dd98b0000000000000000000004b8', + '0xf57c794f42da72b38c8f610ff3b5e8502e48cbde00000000000000000000055c', + '0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa00000000000000000000046e', + '0xfa24a90a3f2bbe5feea92b95cd0d14ce709649f900000000000000000000058f', + '0xfd11ccdbdb7ab91cb9427a6d6bf570c95876d1950000000000000000000004c2', + '0xfebb0bbf162e64fb9d0dfe186e517d84c395f016000000000000000000000502', + '0xfef969638c52899f91781f1be594af6f40b99bad00000000000000000000047b', + '0x02e139d53ebf4033bf78ab66c6a1e7f1f204487f0002000000000000000009f9', + '0x03090a9811181a2afe830a3a0b467698ccf3a8b1000000000000000000000bf5', + '0x0320c1c5b6df19a194d48882aaec1c72940081d9000000000000000000000a7d', + '0x04b54ea92d73de2d62d651db7d9778f0c49157d8000200000000000000000ba2', + '0x0503dd6b2d3dd463c9bef67fb5156870af63393e00000000000000000000042e', + '0x0889b240a5876aae745ac19f1771853671dc5d36000000000000000000000b3f', + '0x0bc54e914f53f98d16035f4f0d948f3e09c2fac0000200000000000000000bac', + '0x0c06e87c7b88d998f645b91c1f53b51294b12bca000100000000000000000bb9', + '0x10b040038f87219d9b42e025e3bd9b8095c87dd9000000000000000000000b11', + '0x117a3d474976274b37b7b94af5dcade5c90c6e85000000000000000000000aca', + '0x11884da90fb4221b3aa288a7741c51ec4fc43b2f000000000000000000000a5f', + '0x1379b816b9be611431d693290289c204720ca56d000100000000000000000b6f', + '0x150e7b885bdfce974f2abe88a72fdbd692175c6f0002000000000000000009fd', + '0x178e029173417b1f9c8bc16dcec6f697bc323746000000000000000000000758', + '0x1aafc31091d93c3ff003cff5d2d8f7ba2e7284250000000000000000000003b3', + '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + '0x216d6db0c28204014618482c369d7fbf0a8f3232000100000000000000000b60', + '0x230ecdb2a7cee56d6889965a023aa0473d6da507000000000000000000000bf3', + '0x252ff6a3a6fd7b5e8e999de8e3f5c3b306ed1401000200000000000000000bec', + '0x25e57f4612912614e6c99616bd2abb9b5ae71e99000000000000000000000bf0', + '0x2645b13fd2c5295296e94a76280b968bdcbbdfed000000000000000000000c11', + '0x284eb68520c8fa83361c1a3a5910aec7f873c18b000000000000000000000ac9', + '0x2c8dbe8eb86135d9f2f26d196748c088d47f73e7000200000000000000000a29', + '0x31bccf9e28b94e5dacebaa67fe8bc1603cecd904000000000000000000000a01', + '0x341068a547c3cde3c09e338714010dd01b32f93f000200000000000000000a34', + '0x3db543faf7a92052de7860c5c9debabee59ed5bd000000000000000000000a62', + '0x3dd0843a028c86e0b760b1a76929d1c5ef93a2dd00000000000000000000070d', + '0x3efb91c4f9b103ee45885695c67794591916f34e000200000000000000000b43', + '0x402cfdb7781fa85d52f425352661128250b79e12000000000000000000000be3', + '0x43894de14462b421372bcfe445fa51b1b4a0ff3d000000000000000000000b36', + '0x4739e50b59b552d490d3fdc60d200977a38510c0000000000000000000000b10', + '0x48e6b98ef6329f8f0a30ebb8c7c960330d64808500000000000000000000075b', + '0x4a0b73f0d13ff6d43e304a174697e3d5cfd310a400020000000000000000091c', + '0x4a77ef015ddcd972fd9ba2c7d5d658689d090f1a000000000000000000000b38', + '0x4ae3661afa119892f0cc8c43edaf6a94989ac171000000000000000000000c06', + '0x4ccb966d8246240afb7a1a24628efb930870b1c40002000000000000000009fc', + '0x52cc8389c6b93d740325729cc7c958066cee4262000000000000000000000b0f', + '0x5b77107fcdf2b41903bab2bc555d4fc14cf7667d000000000000000000000b32', + '0x5bae72b75caab1f260d21bc028c630140607d6e8000000000000000000000ac6', + '0x600bd01b6526611079e12e1ff93aba7a3e34226f0000000000000000000009e4', + '0x63ce19ccd39930725b8a3d2733627804718ab83d000000000000000000000bf2', + '0x64efad69f099813021b41f4cac6e749fd55e188f000000000000000000000b39', + '0x6933ec1ca55c06a894107860c92acdfd2dd8512f000000000000000000000428', + '0x6abe4e7a497b8293c258389c1b00d177e4f257ed00010000000000000000080d', + '0x6c8c7fc50247a47038015eb1fd5dc105d05dafba000200000000000000000ba0', + '0x7079a25dec33be61bbd81b2fb69b468e80d3e72c0000000000000000000009ff', + '0x71bd10c2a590b5858f5576550c163976a48af906000000000000000000000b27', + '0x7c82a23b4c48d796dee36a9ca215b641c6a8709d000000000000000000000acd', + '0x7f4f4942f2a14b6ab7b08b10ada1aacede4ee8d4000200000000000000000b44', + '0x86aef31951e0a3a54333bd9e72f9a95587d058c5000200000000000000000912', + '0x882c7a84231484b3e9f3fd45ac04b1eb5d35b076000200000000000000000a91', + '0x894c82800526e0391e709c0983a5aea3718b7f6d000000000000000000000ac5', + '0x89b28a9494589b09dbccb69911c189f74fdadc5a000000000000000000000b33', + '0x89bb15076c9f2d86aa98ec6cffc1a71e31c38953000000000000000000000bf1', + '0x89f1146fee52b5d9166e9c83cc388b6d8f69f1380001000000000000000009e7', + '0x8a819a4cabd6efcb4e5504fe8679a1abd831dd8f00000000000000000000042d', + '0x8b58a1e7fff52001c22386c2918d45938a6a9be30001000000000000000008d9', + '0x8b8225bfedebaf1708c55743acb4ad43fd4d0f21000200000000000000000918', + '0x8fbd0f8e490735cfc3abf4f29cbddd5c3289b9a7000000000000000000000b5b', + '0x8fd39252d683fdb60bddd4df4b53c9380b496d59000200000000000000000b45', + '0x9321e2250767d79bab5aa06daa8606a2b3b7b4c5000000000000000000000bf4', + '0x949a12b95ec5b80c375b98963a5d6b33b0d0efff0002000000000000000009fe', + '0x9a020bdc2faff5bd24c6acc2020d01ff9f2c627a000000000000000000000ae2', + '0x9cf9358300e34bf9373d30129a1e718d8d058b54000200000000000000000913', + '0x9e34631547adcf2f8cefa0f5f223955c7b137571000000000000000000000ad5', + '0xa5a935833f6a5312715f182733eab088452335d7000100000000000000000bee', + '0xa5fe91dde37d8bf2dacacc0168b115d28ed03f84000000000000000000000b35', + '0xa8bf1c584519be0184311c48adbdc4c15cb2e8c1000000000000000000000bf6', + '0xab269164a10fab22bc87c39946da06c870b172d6000000000000000000000bfc', + '0xac2cae8d2f78a4a8f92f20dbe74042cd0a8d5af3000000000000000000000be2', + '0xae646817e458c0be890b81e8d880206710e3c44e000000000000000000000acb', + '0xaef2c171dbe64b0c18977e16e70bfd29d4ee0256000000000000000000000ac8', + '0xb0c830dceb4ef55a60192472c20c8bf19df03488000000000000000000000be1', + '0xb266ac3b7c98d7bcb28731dac0ef42dba1b276be000000000000000000000be4', + '0xb371aa09f5a110ab69b39a84b5469d29f9b22b76000000000000000000000b37', + '0xb3d658d5b95bf04e2932370dd1ff976fe18dd66a000000000000000000000ace', + '0xb54b2125b711cd183edd3dd09433439d5396165200000000000000000000075e', + '0xb59be8f3c85a9dd6e2899103b6fbf6ea405b99a4000000000000000000000b34', + '0xb878ecce26838fbba4f78cb5b791a0e09152c067000000000000000000000427', + '0xb973ca96a3f0d61045f53255e319aedb6ed4924000000000000000000000042f', + '0xbd4e35784c832d0f9049b54cb3609e5907c5b495000100000000000000000b14', + '0xc55ec796a4debe625d95436a3531f4950b11bdcf000000000000000000000b3e', + '0xc7e6389e364f4275eb442ef215ed21877028e2af000000000000000000000ac7', + '0xc83b55bbd005f1f84906545fcdb145dee53523e0000200000000000000000b30', + '0xcb21a9e647c95127ed784626485b3104cb28d0e7000000000000000000000425', + '0xd00f9ca46ce0e4a63067c4657986f0167b0de1e5000000000000000000000b42', + '0xd2f3b9e67c69762dd1c88f1d3dadd1649a190761000200000000000000000bf7', + '0xd4accb350f9cf59fe3cf7a5ee6ed9ace6a568ea9000200000000000000000b75', + '0xda1cd1711743e57dd57102e9e61b75f3587703da000000000000000000000acc', + '0xdae301690004946424e41051ace1791083be42a1000000000000000000000b40', + '0xde0a77ab6689b980c30306b10f9131a007e1af81000200000000000000000ba1', + '0xe051605a83deae38d26a7346b100ef1ac2ef8a0b0000000000000000000003ce', + '0xe1fb90d0d3b47e551d494d7ebe8f209753526b01000000000000000000000ac4', + '0xe2272cddb2cc408e79e02a73d1db9acc24a843d5000200000000000000000ba7', + '0xe2dc0e0f2c358d6e31836dee69a558ab8d1390e70000000000000000000009fa', + '0xe4885ed2818cc9e840a25f94f9b2a28169d1aea7000000000000000000000b29', + '0xe6909c2f18a29d97217a6146f045e1780606991f000100000000000000000bfe', + '0xe78b25c06db117fdf8f98583cdaaa6c92b79e917000000000000000000000b2b', + '0xea11645ac7d8f2def94c9d8d86bd766296c9b6b6000000000000000000000b3a', + '0xeb480dbbdd921cd6c359e4cc4c65ddea6395e2a1000200000000000000000946', + '0xed35f28f837e96f81240ebb82e0e3f518c7e8a2f000100000000000000000bb5', + '0xf0211cceebe6fcc45052b4e57ee95d233f5669d2000100000000000000000c01', + '0xf22a66046b5307842f21b311ecb4c462c24c0635000000000000000000000b15', + '0xf28f17be00f8ca3c9b7f66a4aad5513757fb3341000200000000000000000b5a', + '0xf42ed61450458ee4620f5ef4f29adb25a6ef0fb6000000000000000000000bf8', + '0xf48f01dcb2cbb3ee1f6aab0e742c2d3941039d56000000000000000000000445', + '0xf93579002dbe8046c43fefe86ec78b1112247bb8000000000000000000000759', + '0xf984eb2b8a7ef780245a797a2fccd82f346409ca000000000000000000000a59', + '0xfa2c0bd8327c99db5bde4c9e9e5cbf30946351bb000000000000000000000948', + '0xff4ce5aaab5a627bf82f4a571ab1ce94aa365ea600000000000000000000075a', + '0x1ac55c31dac78ca943cb8ebfca5945ce09e036e2000000000000000000000024', + '0x225e0047671939a8d78e08ebd692788abe63f15c000000000000000000000009', + '0x41211bba6d37f5a74b22e667533f080c7c7f3f1300000000000000000000000b', + '0x4de21b365d6543661d0e105e579a34b963862497000200000000000000000045', + '0x581ec1f5e7ced12b186deae32256adb53bdd5b08000000000000000000000001', + '0x66f33ae36dd80327744207a48122f874634b3ada000100000000000000000013', + '0xa3ed6f78edc29f69df8a0d16b1d1ccf9871f918c000000000000000000000032', + '0xa611a551b95b205ccd9490657acf7899daee5db700000000000000000000002e', + '0xb95829adbacd8af89e291dee78bc09e24de51d6b000000000000000000000043', + '0xb973ca96a3f0d61045f53255e319aedb6ed49240000200000000000000000011', + '0xba1a5b19d09a79dada039b1f974015c5a989d5fd000100000000000000000046', + '0xbb9cd48d33033f5effbedec9dd700c7d7e1dcf5000000000000000000000000e', + '0xd16f72b02da5f51231fde542a8b9e2777a478c8800000000000000000000000f', + '0xd4015683b8153666190e0b2bec352580ebc4caca00000000000000000000000d', + '0xe15cac1df3621e001f76210ab12a7f1a1691481f000000000000000000000044', + '0xe7f88d7d4ef2eb18fcf9dd7216ba7da1c46f3dd600000000000000000000000a', + '0xf48f01dcb2cbb3ee1f6aab0e742c2d3941039d56000200000000000000000012', + '0xfedb19ec000d38d92af4b21436870f115db22725000000000000000000000010', + '0xffff76a3280e95dc855696111c2562da09db2ac000000000000000000000000c', + '0x00fcd3d55085e998e291a0005cedecf58ac14c4000020000000000000000047f', + '0x077794c30afeccdf5ad2abc0588e8cee7197b71a000000000000000000000352', + '0x117a3d474976274b37b7b94af5dcade5c90c6e85000000000000000000000381', + '0x11884da90fb4221b3aa288a7741c51ec4fc43b2f000000000000000000000353', + '0x19b1c92631405a0a9495ccba0becf4f2e8e908bd000000000000000000000410', + '0x1e550b7764da9638fdd32c8a701364de31f45ee800000000000000000000047c', + '0x1fa7f727934226aedab636d62a084931b97d366b000000000000000000000411', + '0x23ca0306b21ea71552b148cf3c4db4fc85ae19290000000000000000000000c9', + '0x284eb68520c8fa83361c1a3a5910aec7f873c18b000000000000000000000380', + '0x2a96254ca32020b20ed3506f8f75318da24709f9000200000000000000000456', + '0x36942963e3b6f37ecc45a4e72349558514233f0000000000000000000000048a', + '0x3f53a862919ccfa023cb6ace91378a79fb0f6bf500000000000000000000040f', + '0x40af308e3d07ec769d85eb80afb116525ff4ac99000000000000000000000485', + '0x418de00ae109e6f874d872658767866d680eaa1900000000000000000000047d', + '0x45c4d1376943ab28802b995acffc04903eb5223f000000000000000000000470', + '0x4689122d360c4725d244c5cfea22861333d862e6000100000000000000000468', + '0x4739e50b59b552d490d3fdc60d200977a38510c0000000000000000000000409', + '0x49a0e3334496442a9706e481617724e7e37eaa080000000000000000000003ff', + '0x519cce718fcd11ac09194cff4517f12d263be067000000000000000000000382', + '0x52cc8389c6b93d740325729cc7c958066cee4262000000000000000000000408', + '0x567ecfcb22205d279bb8eed3e066989902bf03d5000000000000000000000452', + '0x585d95df0231fa08aeee35ff0c16b92fd0ecdc3300020000000000000000045f', + '0x5a7f39435fd9c381e4932fa2047c9a5136a5e3e7000000000000000000000400', + '0x5bae72b75caab1f260d21bc028c630140607d6e8000000000000000000000350', + '0x6cb787a419c3e6ee2e9ff365856c29cd10659113000000000000000000000474', + '0x7c82a23b4c48d796dee36a9ca215b641c6a8709d000000000000000000000406', + '0x81fc12c60ee5b753cf5fd0adc342dfb5f3817e3200000000000000000000035d', + '0x894c82800526e0391e709c0983a5aea3718b7f6d00000000000000000000034f', + '0x970712708a08e8fb152be4d81b2dc586923f5369000200000000000000000479', + '0x9bf7c3b63c77b4b4f2717776f15a4bec1b532a280000000000000000000000c8', + '0x9cebf13bb702f253abf1579294694a1edad00eaa000000000000000000000486', + '0x9e34631547adcf2f8cefa0f5f223955c7b137571000000000000000000000407', + '0x9fb7d6dcac7b6aa20108bad226c35b85a9e31b63000200000000000000000412', + '0xa1ea76c42b2938cfa9abea12357881006c52851300000000000000000000048f', + '0xa50f89e9f439fde2a6fe05883721a00475da3c4500000000000000000000048b', + '0xa612b6aed2e7ca1a3a4f23fbca9128461bbb7718000000000000000000000274', + '0xa8af146d79ac0bb981e4e0d8b788ec5711b1d5d000000000000000000000047b', + '0xad28940024117b442a9efb6d0f25c8b59e1c950b00000000000000000000046f', + '0xae646817e458c0be890b81e8d880206710e3c44e00000000000000000000039d', + '0xaef2c171dbe64b0c18977e16e70bfd29d4ee0256000000000000000000000351', + '0xbbf9d705b75f408cfcaee91da32966124d2c6f7d00000000000000000000047e', + '0xbd724eb087d4cc0f61a5fed1fffaf937937e14de000000000000000000000473', + '0xbe0f30217be1e981add883848d0773a86d2d2cd4000000000000000000000471', + '0xc46be4b8bb6b5a3d3120660efae9c5416318ed40000000000000000000000472', + '0xc69771058481551261709d8db44977e9afde645000010000000000000000042a', + '0xc6eee8cb7643ec2f05f46d569e9ec8ef8b41b389000000000000000000000475', + '0xcba9ff45cfb9ce238afde32b0148eb82cbe635620000000000000000000003fd', + '0xcf8b555b7754556cf2ac2165e77ee23ed8517d7900020000000000000000045e', + '0xd0dc20e6342db2de82692b8dc842301ff9121805000200000000000000000454', + '0xd3d5d45f4edf82ba0dfaf061d230766032a10e07000200000000000000000413', + '0xd6d20527c7b0669989ee082b9d3a1c63af742290000000000000000000000483', + '0xda1cd1711743e57dd57102e9e61b75f3587703da0000000000000000000003fc', + '0xe1fb90d0d3b47e551d494d7ebe8f209753526b0100000000000000000000034e', + '0xee02583596aee94cccb7e8ccd3921d955f17982a00000000000000000000040a', + '0xf984eb2b8a7ef780245a797a2fccd82f346409ca00000000000000000000034d', + '0xff8f84e8c87532af96aef5582ee451572233678b000200000000000000000478', + '0x054e7b0c73e1ee5aed6864fa511658fc2b54bcaa000000000000000000000015', + '0x3f1a2c4a3a751f6626bd90ef16e104f0772d4d6b00020000000000000000001b', + '0x7275c131b1f67e8b53b4691f92b0e35a4c1c6e22000000000000000000000010', + '0xa154009870e9b6431305f19b09f9cfd7284d4e7a000000000000000000000013', + '0xa1d14d922a575232066520eda11e27760946c991000000000000000000000012', + '0xa826a114b0c7db4d1ff4a4be845a78998c64564c000000000000000000000008', + '0xea67626e1f0b59e0d172a04f5702ef90bcdf440c00000000000000000000000f', + '0xeb496161099d45b3ea4892408ef745c6182eb56e00000000000000000000000e', + '0xece571847897fd61e764d455dc15cf1cd9de8d6f000000000000000000000014', + '0xed3e2f496cbcd8e212192fb8d1499842f04a0d19000000000000000000000009', + '0x02c9dcb975262719a61f9b40bdf0987ead9add3a000000000000000000000006', + '0x16c9a4d841e88e52b51936106010f27085a529ec00000000000000000000000c', + '0x32be2d0ddeaf3333501b24a28668ce373ba8e763000200000000000000000014', + '0x32f03464fdf909fdf3798f87ff3712b10c59bd86000000000000000000000005', + '0x4b718e0e2fea1da68b763cd50c446fba03ceb2ea00000000000000000000000b', + '0x68a69c596b3839023c0e08d09682314f582314e5000200000000000000000011', + '0x6f34a44fce1506352a171232163e7716dd073ade000200000000000000000015', + '0x9e2d87f904862671eb49cb358e74284762cc9f42000200000000000000000013', + '0xac4b72c01072a52b73ca71105504f1372efcce0d000000000000000000000003', + '0xbfd65c6160cfd638a85c645e6e6d8acac5dac935000000000000000000000004', + '0xe274c9deb6ed34cfe4130f8d0a8a948dea5bb28600000000000000000000000d', +]; + diff --git a/modules/sor/sor.service.ts b/modules/sor/sor.service.ts new file mode 100644 index 000000000..1602e1d34 --- /dev/null +++ b/modules/sor/sor.service.ts @@ -0,0 +1,180 @@ +import { GqlCowSwapApiResponse, GqlSorSwapType, GqlSorGetSwapsResponse, GqlSorSwapOptionsInput } from '../../schema'; +import { sorV1BalancerService } from './sorV1Balancer/sorV1Balancer.service'; +import { sorV1BeetsService } from './sorV1Beets/sorV1Beets.service'; +import { sorV2Service } from './sorV2/sorV2.service'; +import { GetSwapsInput, SwapResult, SwapService } from './types'; +import { EMPTY_COWSWAP_RESPONSE } from './constants'; +import { getSorMetricsPublisher } from '../metrics/sor.metric'; +import { Chain } from '@prisma/client'; +import { parseUnits, formatUnits } from '@ethersproject/units'; +import { tokenService } from '../token/token.service'; + +export class SorService { + async getCowSwaps(input: GetSwapsInput): Promise { + const swap = await this.getSwap({ ...input, swapOptions: {} }); + const emptyResponse = EMPTY_COWSWAP_RESPONSE(input.tokenIn, input.tokenOut, input.swapAmount); + + if (!swap) return emptyResponse; + + try { + // Updates with latest onchain data before returning + return await swap.getCowSwapResponse(input.chain, true); + } catch (err) { + console.log(`Error Retrieving QuerySwap`, err); + return emptyResponse; + } + } + + async getBeetsSwaps(input: GetSwapsInput): Promise { + const swap = await this.getSwap(input, sorV1BeetsService); + const emptyResponse = sorV1BeetsService.zeroResponse( + input.swapType, + input.tokenIn, + input.tokenOut, + input.swapAmount, + ); + + if (!swap) return emptyResponse; + + try { + // Updates with latest onchain data before returning + return swap.getBeetsSwapResponse(true); + } catch (err) { + console.log(`Error Retrieving QuerySwap`, err); + return emptyResponse; + } + } + + private async getSwap(input: GetSwapsInput, v1Service: SwapService = sorV1BalancerService) { + const v1Start = +new Date(); + const swapV1 = await v1Service.getSwapResult(input); + const v1Time = +new Date() - v1Start; + + const v2Start = +new Date(); + const swapV2 = await sorV2Service.getSwapResult(input); + const v2Time = +new Date() - v2Start; + + const version = this.getBestSwap(swapV1, swapV2, input.swapType); + + await this.logResult( + version, + swapV1, + swapV2, + input.swapType, + input.tokenIn, + input.tokenOut, + input.chain, + v1Time, + v2Time, + ); + + if (!version) return null; + + return version === 'V1' ? swapV1 : swapV2; + } + + /** + * Find best swap result for V1 vs V2 and return in CowSwap API format. Log if V1 wins. + * @param v1 + * @param v2 + * @param swapType + * @returns + */ + private getBestSwap(v1: SwapResult, v2: SwapResult, swapType: GqlSorSwapType, debugOut = false) { + // Useful for comparing + if (debugOut) { + console.log(`------ DEBUG`); + console.log(v1); + console.log(v2); + } + + if (!v1.isValid && !v2.isValid) return null; + + let isV1 = false; + if (!v1.isValid || !v2.isValid) { + isV1 = v1.isValid ? true : false; + } else if (swapType === 'EXACT_IN') { + if (v2.outputAmount < v1.outputAmount) { + isV1 = true; + } + } else { + if (v2.inputAmount > v1.inputAmount) { + isV1 = true; + } + } + + if (isV1 === true) { + return 'V1'; + } + + return 'V2'; + } + + private async logResult( + version: string | null, + v1: SwapResult, + v2: SwapResult, + swapType: GqlSorSwapType, + assetIn: string, + assetOut: string, + chain: Chain, + v1Time: number, + v2Time: number, + ) { + const sorMetricsPublisher = getSorMetricsPublisher(chain); + await sorMetricsPublisher.publish(`SOR_VALID_V1`, v1.isValid ? 1 : 0); + await sorMetricsPublisher.publish(`SOR_VALID_V2`, v2.isValid ? 1 : 0); + + if (!version) return; + + let v1ResultAmount = v1.inputAmount; + let v2ResultAmount = v2.inputAmount < 0 ? -v2.inputAmount : v2.inputAmount; + let tradeAmount = v1.outputAmount; + let userToken = assetOut; + let resultToken = assetIn; + if (swapType === 'EXACT_IN') { + v1ResultAmount = v1.outputAmount; + v2ResultAmount = v2.outputAmount < 0 ? -v2.outputAmount : v2.outputAmount; + tradeAmount = v1.inputAmount; + userToken = assetIn; + resultToken = assetOut; + } + + const fp = (a: bigint, d: number) => Number(formatUnits(String(a), d)); + const bn = (a: string, d: number) => BigInt(String(parseUnits(a, d))); + const prismaToken = await tokenService.getToken(resultToken, chain); + const decimals = prismaToken!.decimals; + let v2Perf = + version === 'V1' + ? 1 - fp(v1ResultAmount, decimals) / fp(v2ResultAmount, decimals) // negative perf means V1 is better + : fp(v2ResultAmount, decimals) / fp(v1ResultAmount, decimals) - 1; // positive perf means V2 is better + + v2Perf = Math.max(-1, Math.min(1, v2Perf)); + let diffN = fp(v2ResultAmount, decimals) - fp(v1ResultAmount, decimals); + let diff = bn(diffN.toFixed(decimals), decimals); + let bestResultAmount = version === 'V1' ? v1ResultAmount : v2ResultAmount; + + await sorMetricsPublisher.publish(`SOR_TIME_V1`, v1Time); + await sorMetricsPublisher.publish(`SOR_TIME_V2`, v2Time); + await sorMetricsPublisher.publish(`SOR_V2_PERFORMACE`, v2Perf); + + console.log( + [ + 'SOR_RESULT', + v1Time, + v2Time, + chain, + version, + swapType, + userToken, + resultToken, + String(tradeAmount), + String(bestResultAmount), + String(diff), + v2Perf.toFixed(8), + ].join(','), + ); + } +} + +export const sorService = new SorService(); diff --git a/modules/sor/sorV1Balancer/sorV1Balancer.service.ts b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts new file mode 100644 index 000000000..73ad3b5ab --- /dev/null +++ b/modules/sor/sorV1Balancer/sorV1Balancer.service.ts @@ -0,0 +1,136 @@ +import axios from 'axios'; +import * as Sentry from '@sentry/node'; +import { AddressZero } from '@ethersproject/constants'; +import { Contract } from '@ethersproject/contracts'; +import { GqlSorSwapType, GqlCowSwapApiResponse, GqlSorGetSwapsResponse } from '../../../schema'; +import { GetSwapsInput, SwapService, SwapResult } from '../types'; +import { FundManagement, SwapTypes, SwapV2 } from '@balancer-labs/sdk'; +import { env } from '../../../app/env'; +import { networkContext } from '../../network/network-context.service'; +import { AllNetworkConfigs, AllNetworkConfigsKeyedOnChain, chainToIdMap } from '../../network/network-config'; +import { DeploymentEnv } from '../../network/network-config-types'; + +import VaultAbi from '../../pool/abi/Vault.json'; +import { BigNumber } from 'ethers'; +import { TokenAmount } from '@balancer/sdk'; +import { Chain } from '@prisma/client'; + +type CowSwapSwapType = 'buy' | 'sell'; + +class SwapResultV1 implements SwapResult { + public inputAmount: bigint = BigInt(0); + public outputAmount: bigint = BigInt(0); + public isValid: boolean; + + constructor(private swap: GqlCowSwapApiResponse | null, private swapType: GqlSorSwapType) { + if (swap === null) { + this.isValid = false; + this.swap = null; + } else { + this.inputAmount = swapType === 'EXACT_IN' ? BigInt(swap.swapAmount) : BigInt(swap.returnAmount); + this.outputAmount = swapType === 'EXACT_IN' ? BigInt(swap.returnAmount) : BigInt(swap.swapAmount); + this.isValid = swap.swaps.length === 0 ? false : true; + } + } + + async getCowSwapResponse(chain = networkContext.chain, queryFirst = false): Promise { + if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); + + if (queryFirst) { + const swapType = this.mapSwapType(this.swapType); + const deltas = await this.queryBatchSwap(swapType, this.swap.swaps, this.swap.tokenAddresses, chain); + const tokenInAmount = deltas[this.swap.tokenAddresses.indexOf(this.swap.tokenIn)].toString(); + const tokenOutAmount = deltas[this.swap.tokenAddresses.indexOf(this.swap.tokenOut)].abs().toString(); + // console.log(`UPDATE:`, this.inputAmount, this.outputAmount, tokenInAmount, tokenOutAmount, deltas.toString()); + return { + ...this.swap, + returnAmount: swapType === SwapTypes.SwapExactIn ? tokenOutAmount : tokenInAmount, + swapAmount: swapType === SwapTypes.SwapExactIn ? tokenInAmount : tokenOutAmount, + }; + } + return this.swap; + } + + async getBeetsSwapResponse(queryFirst: boolean): Promise { + throw new Error('Use Beets service.'); + } + + private queryBatchSwap(swapType: SwapTypes, swaps: SwapV2[], assets: string[], chain: Chain): Promise { + const vault = AllNetworkConfigsKeyedOnChain[chain].data.balancer.vault; + const provider = AllNetworkConfigsKeyedOnChain[chain].provider; + + const vaultContract = new Contract(vault, VaultAbi, provider); + const funds: FundManagement = { + sender: AddressZero, + recipient: AddressZero, + fromInternalBalance: false, + toInternalBalance: false, + }; + + return vaultContract.queryBatchSwap(swapType, swaps, assets, funds); + } + + private mapSwapType(swapType: GqlSorSwapType): SwapTypes { + return swapType === 'EXACT_IN' ? SwapTypes.SwapExactIn : SwapTypes.SwapExactOut; + } +} +export class SorV1BalancerService implements SwapService { + public async getSwapResult({ chain, tokenIn, tokenOut, swapType, swapAmount }: GetSwapsInput): Promise { + try { + const swap = await this.querySorBalancer(chain, swapType, tokenIn, tokenOut, swapAmount); + return new SwapResultV1(swap, swapType); + } catch (err: any) { + console.error( + `SOR_V1_ERROR ${err.message} - tokenIn: ${tokenIn} - tokenOut: ${tokenOut} - swapAmount: ${swapAmount.amount} - swapType: ${swapType} - chain: ${chain}`, + ); + Sentry.captureException(err.message, { + tags: { + service: 'sorV1', + tokenIn, + tokenOut, + swapAmount: swapAmount.amount, + swapType, + chain, + }, + }); + return new SwapResultV1(null, swapType); + } + } + + /** + * Query Balancer API CowSwap/SOR endpoint. + * @param swapType + * @param tokenIn + * @param tokenOut + * @param swapAmountScaled + * @param swapOptions + * @returns + */ + private async querySorBalancer( + chain: Chain, + swapType: GqlSorSwapType, + tokenIn: string, + tokenOut: string, + swapAmount: TokenAmount, + ): Promise { + const chainId = chainToIdMap[chain]; + const endPoint = `https://api.balancer.fi/sor/${chainId}`; + const gasPrice = AllNetworkConfigs[chainId].data.sor[env.DEPLOYMENT_ENV as DeploymentEnv].gasPrice.toString(); + const swapData = { + orderKind: this.mapSwapType(swapType), + sellToken: tokenIn, + buyToken: tokenOut, + amount: swapAmount.amount.toString(), + gasPrice, + }; + + const { data } = await axios.post(endPoint, swapData); + return data; + } + + private mapSwapType(swapType: GqlSorSwapType): CowSwapSwapType { + return swapType === 'EXACT_IN' ? 'sell' : 'buy'; + } +} + +export const sorV1BalancerService = new SorV1BalancerService(); diff --git a/modules/sor/sorV1Beets/sorV1Beets.service.ts b/modules/sor/sorV1Beets/sorV1Beets.service.ts new file mode 100644 index 000000000..067e5f87c --- /dev/null +++ b/modules/sor/sorV1Beets/sorV1Beets.service.ts @@ -0,0 +1,70 @@ +import { formatEther } from 'viem'; +import { GqlSorSwapType, GqlCowSwapApiResponse, GqlSorGetSwapsResponse, GqlSorSwapOptionsInput } from '../../../schema'; +import { GetSwapsInput, SwapService, SwapResult } from '../types'; +import { BalancerSorService } from '../../beethoven/balancer-sor.service'; +import { tokenService } from '../../token/token.service'; +import { TokenAmount } from '@balancer/sdk'; + +class SwapResultV1 implements SwapResult { + public inputAmount: bigint = BigInt(0); + public outputAmount: bigint = BigInt(0); + public isValid: boolean; + + constructor(private swap: GqlSorGetSwapsResponse | null, private swapType: GqlSorSwapType) { + if (swap === null) { + this.isValid = false; + this.swap = null; + } else { + this.inputAmount = + swapType === 'EXACT_IN' ? BigInt(swap.swapAmountScaled) : BigInt(swap.returnAmountScaled); + this.outputAmount = + swapType === 'EXACT_IN' ? BigInt(swap.returnAmountScaled) : BigInt(swap.swapAmountScaled); + this.isValid = swap.swaps.length === 0 ? false : true; + } + } + + async getCowSwapResponse(chain = 'MAINNET', queryFirst = false): Promise { + throw new Error('Use Balancer Service'); + } + + async getBeetsSwapResponse(queryFirst: boolean): Promise { + if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); + // Beets service is already querying onchain + return this.swap; + } +} +export class SorV1BeetsService implements SwapService { + sorService: BalancerSorService; + + constructor() { + this.sorService = new BalancerSorService(); + } + + public async getSwapResult(input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }): Promise { + try { + const swap = await this.querySorBeets(input); + return new SwapResultV1(swap, input.swapType); + } catch (err) { + console.log(`sorV1 Service Error`, err); + return new SwapResultV1(null, input.swapType); + } + } + + public zeroResponse( + swapType: GqlSorSwapType, + tokenIn: string, + tokenOut: string, + swapAmount: TokenAmount, + ): GqlSorGetSwapsResponse { + return this.sorService.zeroResponse(swapType, tokenIn, tokenOut, formatEther(swapAmount.scale18)); + } + + private async querySorBeets( + input: GetSwapsInput & { swapOptions: GqlSorSwapOptionsInput }, + ): Promise { + const tokens = await tokenService.getTokens(); + return await this.sorService.getSwaps({ ...input, tokens, swapAmount: formatEther(input.swapAmount.scale18) }); + } +} + +export const sorV1BeetsService = new SorV1BeetsService(); diff --git a/modules/sor/sorV2/beetsHelpers.test.ts b/modules/sor/sorV2/beetsHelpers.test.ts new file mode 100644 index 000000000..cdd0621f9 --- /dev/null +++ b/modules/sor/sorV2/beetsHelpers.test.ts @@ -0,0 +1,432 @@ +import { SingleSwap, SwapKind, BatchSwapStep } from '@balancer/sdk'; +import { GqlPoolMinimal, GqlSorSwapRoute } from '../../../schema'; +import { mapBatchSwap, mapRoutes, splitPaths } from './beetsHelpers'; +import { poolService } from '../../pool/pool.service'; + +// npx jest --testPathPattern=modules/sor/sorV2/beetsHelpers.test.ts +describe('sorV2 Service - Routes', () => { + describe('SingleSwap', () => { + let singleSwap: SingleSwap; + let pools: GqlPoolMinimal[]; + let expectedRoute: GqlSorSwapRoute[]; + const assetIn = '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4'; + const assetOut = '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7'; + const assets = ['0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7']; + + beforeAll(async () => { + pools = await poolService.getGqlPools({ + where: { idIn: ['0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c'] }, + }); + singleSwap = { + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + kind: SwapKind.GivenIn, + assetIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + assetOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + amount: BigInt(0), + userData: '0x', + }; + expectedRoute = [ + { + hops: [ + { + pool: pools[0], + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: '', + tokenOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenOutAmount: '', + }, + ], + share: 1, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: '', + tokenOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenOutAmount: '', + }, + ]; + }); + test('GivenIn', () => { + const amountIn = '123456789112345678'; + const amountOut = '876543210987654321'; + singleSwap.kind = SwapKind.GivenIn; + singleSwap.amount = BigInt(amountIn); + expectedRoute[0].tokenInAmount = amountIn; + expectedRoute[0].tokenOutAmount = amountOut; + expectedRoute[0].hops[0].tokenInAmount = amountIn; + expectedRoute[0].hops[0].tokenOutAmount = amountOut; + const mappedRoute = mapRoutes( + singleSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenIn, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); + test('GivenOut', () => { + const amountIn = '876543210987654321'; + const amountOut = '123456789112345678'; + singleSwap.kind = SwapKind.GivenOut; + singleSwap.amount = BigInt(amountOut); + expectedRoute[0].tokenInAmount = amountIn; + expectedRoute[0].tokenOutAmount = amountOut; + expectedRoute[0].hops[0].tokenInAmount = amountIn; + expectedRoute[0].hops[0].tokenOutAmount = amountOut; + const mappedRoute = mapRoutes( + singleSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenOut, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); + }); + describe('BatchSwap', () => { + describe('ExactIn', () => { + let pools: GqlPoolMinimal[]; + let paths: BatchSwapStep[][]; + const assetIn = '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4'; + const assetOut = '0x8159462d255C1D24915CB51ec361F700174cD994'; + const amountIn = '111111111111111111'; + const amountOut = '222222222222222222'; + const batchSwap: BatchSwapStep[] = [ + { + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + assetInIndex: BigInt(0), + assetOutIndex: BigInt(1), + amount: BigInt(amountIn), + userData: '0x', + }, + { + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + assetInIndex: BigInt(1), + assetOutIndex: BigInt(2), + amount: BigInt(0), + userData: '0x', + }, + ]; + const assets = [ + '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + '0x8159462d255C1D24915CB51ec361F700174cD994', + ]; + const expectedRoute: GqlSorSwapRoute[] = [ + { + hops: [ + { + pool: {} as GqlPoolMinimal, + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenOutAmount: '0', // TODO - Are we expecting to get the values for intermmediate steps? May need b-sdk to return that? + }, + { + pool: {} as GqlPoolMinimal, + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + tokenIn: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenInAmount: '0', + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ], + share: 1, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ]; + beforeAll(async () => { + pools = await poolService.getGqlPools({ + where: { + idIn: [ + '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ], + }, + }); + expectedRoute[0].hops[0].pool = pools.find( + (p) => p.id === '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + ) as GqlPoolMinimal; + expectedRoute[0].hops[1].pool = pools.find( + (p) => p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ) as GqlPoolMinimal; + }); + describe('Single Path', () => { + test('splitPaths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenIn); + expect(paths.length).toEqual(1); + expect(paths[0].length).toEqual(2); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap(paths[0], amountIn, amountOut, SwapKind.GivenIn, assets, pools); + expect(route).toEqual(expectedRoute[0]); + }); + test('GivenIn', () => { + const mappedRoute = mapRoutes( + batchSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenIn, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); + }); + describe('Multiple Paths', () => { + beforeAll(() => { + batchSwap.push({ + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + assetInIndex: BigInt(0), + assetOutIndex: BigInt(2), + amount: BigInt(amountIn), + userData: '0x', + }); + expectedRoute[0].share = 0.5; + expectedRoute.push({ + hops: [ + { + pool: pools.find( + (p) => + p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ) as GqlPoolMinimal, + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ], + share: 0.5, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }); + }); + test('splitPaths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenIn); + expect(paths.length).toEqual(2); + expect(paths[0].length).toEqual(2); + expect(paths[1].length).toEqual(1); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap( + paths[0], + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + SwapKind.GivenIn, + assets, + pools, + ); + expect(route).toEqual(expectedRoute[0]); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap( + paths[1], + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + SwapKind.GivenIn, + assets, + pools, + ); + expect(route).toEqual(expectedRoute[1]); + }); + test('GivenIn', () => { + const mappedRoute = mapRoutes( + batchSwap, + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenIn, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); + }); + }); + describe('ExactOut', () => { + let pools: GqlPoolMinimal[]; + let paths: BatchSwapStep[][]; + const assetIn = '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4'; + const assetOut = '0x8159462d255C1D24915CB51ec361F700174cD994'; + const amountIn = '111111111111111111'; + const amountOut = '222222222222222222'; + const batchSwap: BatchSwapStep[] = [ + { + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + assetInIndex: BigInt(1), + assetOutIndex: BigInt(2), + amount: BigInt(amountOut), + userData: '0x', + }, + { + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + assetInIndex: BigInt(0), + assetOutIndex: BigInt(1), + amount: BigInt(0), + userData: '0x', + }, + ]; + const assets = [ + '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + '0x8159462d255C1D24915CB51ec361F700174cD994', + ]; + const expectedRoute: GqlSorSwapRoute[] = [ + { + hops: [ + { + pool: {} as GqlPoolMinimal, + poolId: '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenOutAmount: '0', // TODO - Are we expecting to get the values for intermmediate steps? May need b-sdk to return that? + }, + { + pool: {} as GqlPoolMinimal, + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + tokenIn: '0xE4885Ed2818Cc9E840A25f94F9b2A28169D1AEA7', + tokenInAmount: '0', + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ], + share: 1, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ]; + + beforeAll(async () => { + pools = await poolService.getGqlPools({ + where: { + idIn: [ + '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ], + }, + }); + expectedRoute[0].hops[0].pool = pools.find( + (p) => p.id === '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + ) as GqlPoolMinimal; + expectedRoute[0].hops[1].pool = pools.find( + (p) => p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ) as GqlPoolMinimal; + }); + describe('Single Path', () => { + test('split paths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenOut); + expect(paths.length).toEqual(1); + expect(paths[0].length).toEqual(2); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap(paths[0], amountIn, amountOut, SwapKind.GivenOut, assets, pools); + expect(route).toEqual(expectedRoute[0]); + }); + test('GivenOut', () => { + const mappedRoute = mapRoutes( + batchSwap, + amountIn, + amountOut, + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenOut, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); + }); + describe('Multi Paths', () => { + beforeAll(() => { + batchSwap.push({ + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + assetInIndex: BigInt(0), + assetOutIndex: BigInt(2), + amount: BigInt(amountOut), + userData: '0x', + }); + expectedRoute[0].share = 0.5; + expectedRoute.unshift({ + hops: [ + { + pool: pools.find( + (p) => + p.id === '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + ) as GqlPoolMinimal, + poolId: '0x8159462d255c1d24915cb51ec361f700174cd99400000000000000000000075d', + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }, + ], + share: 0.5, + tokenIn: '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4', + tokenInAmount: amountIn, + tokenOut: '0x8159462d255C1D24915CB51ec361F700174cD994', + tokenOutAmount: amountOut, + }); + }); + test('splitPaths', () => { + paths = splitPaths(batchSwap, assetIn, assetOut, assets, SwapKind.GivenOut); + expect(paths.length).toEqual(2); + expect(paths[0].length).toEqual(1); + expect(paths[1].length).toEqual(2); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap( + paths[0], + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + SwapKind.GivenOut, + assets, + pools, + ); + expect(route).toEqual(expectedRoute[0]); + }); + test('mapBatchSwap', () => { + const route = mapBatchSwap( + paths[1], + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + SwapKind.GivenOut, + assets, + pools, + ); + expect(route).toEqual(expectedRoute[1]); + }); + test('GivenOut', () => { + const mappedRoute = mapRoutes( + batchSwap, + (BigInt(amountIn) * BigInt(2)).toString(), + (BigInt(amountOut) * BigInt(2)).toString(), + pools, + assetIn, + assetOut, + assets, + SwapKind.GivenOut, + ); + expect(mappedRoute).toEqual(expectedRoute); + }); + }); + }); + }); +}); diff --git a/modules/sor/sorV2/beetsHelpers.ts b/modules/sor/sorV2/beetsHelpers.ts new file mode 100644 index 000000000..135276c23 --- /dev/null +++ b/modules/sor/sorV2/beetsHelpers.ts @@ -0,0 +1,116 @@ +import { BatchSwapStep, SingleSwap, SwapKind } from '@balancer/sdk'; +import { GqlPoolMinimal, GqlSorSwapRoute, GqlSorSwapRouteHop } from '../../../schema'; +import { formatFixed } from '@ethersproject/bignumber'; + +export function mapRoutes( + swaps: BatchSwapStep[] | SingleSwap, + amountIn: string, + amountOut: string, + pools: GqlPoolMinimal[], + assetIn: string, + assetOut: string, + assets: string[], + kind: SwapKind, +): GqlSorSwapRoute[] { + const isBatchSwap = Array.isArray(swaps); + if (!isBatchSwap) { + const pool = pools.find((p) => p.id === swaps.poolId); + if (!pool) throw new Error('Pool not found while mapping route'); + return [mapSingleSwap(swaps, amountIn, amountOut, pool)]; + } + const paths = splitPaths(swaps, assetIn, assetOut, assets, kind); + return paths.map((p) => mapBatchSwap(p, amountIn, amountOut, kind, assets, pools)); +} + +export function mapBatchSwap( + swaps: BatchSwapStep[], + amountIn: string, + amountOut: string, + kind: SwapKind, + assets: string[], + pools: GqlPoolMinimal[], +): GqlSorSwapRoute { + const exactIn = kind === SwapKind.GivenIn; + const first = swaps[0]; + const last = swaps[swaps.length - 1]; + let tokenInAmount: string; + let tokenOutAmount: string; + let share: bigint; + const one = BigInt(1e18); + if (exactIn) { + tokenInAmount = first.amount.toString(); + share = (BigInt(tokenInAmount) * one) / BigInt(amountIn); + tokenOutAmount = ((BigInt(amountOut) * share) / one).toString(); + } else { + tokenOutAmount = last.amount.toString(); + share = (BigInt(tokenOutAmount) * one) / BigInt(amountOut); + tokenInAmount = ((BigInt(amountIn) * share) / one).toString(); + } + + return { + tokenIn: assets[Number(first.assetInIndex)], + tokenOut: assets[Number(last.assetOutIndex)], + tokenInAmount, + tokenOutAmount, + share: Number(formatFixed(share.toString(), 18)), + hops: swaps.map((swap, i) => { + return { + tokenIn: assets[Number(swap.assetInIndex)], + tokenOut: assets[Number(swap.assetOutIndex)], + tokenInAmount: i === 0 ? tokenInAmount : '0', + tokenOutAmount: i === swaps.length - 1 ? tokenOutAmount : '0', + poolId: swap.poolId, + pool: pools.find((p) => p.id === swap.poolId) as GqlPoolMinimal, + }; + }), + }; +} + +export function splitPaths( + swaps: BatchSwapStep[], + assetIn: string, + assetOut: string, + assets: string[], + kind: SwapKind, +): BatchSwapStep[][] { + const swapsCopy = [...swaps]; + if (kind === SwapKind.GivenOut) { + swapsCopy.reverse(); + } + const assetInIndex = BigInt(assets.indexOf(assetIn)); + const assetOutIndex = BigInt(assets.indexOf(assetOut)); + let path: BatchSwapStep[]; + let paths: BatchSwapStep[][] = []; + swapsCopy.forEach((swap) => { + if (swap.assetInIndex === assetInIndex && swap.assetOutIndex === assetOutIndex) { + paths.push([swap]); + } else if (swap.assetInIndex === assetInIndex) { + path = [swap]; + } else if (swap.assetOutIndex === assetOutIndex) { + path.push(swap); + paths.push(path); + } else { + path.push(swap); + } + }); + return paths; +} + +function mapSingleSwap(swap: SingleSwap, amountIn: string, amountOut: string, pool: GqlPoolMinimal): GqlSorSwapRoute { + const hop: GqlSorSwapRouteHop = { + pool, + poolId: swap.poolId, + tokenIn: swap.assetIn, + tokenInAmount: amountIn, + tokenOut: swap.assetOut, + tokenOutAmount: amountOut, + }; + return { + share: 1, + tokenIn: swap.assetIn, + tokenOut: swap.assetOut, + tokenInAmount: amountIn, + tokenOutAmount: amountOut, + hops: [hop], + } as GqlSorSwapRoute; +} diff --git a/modules/sor/sorV2/sorV2.service.ts b/modules/sor/sorV2/sorV2.service.ts new file mode 100644 index 000000000..04b7892b0 --- /dev/null +++ b/modules/sor/sorV2/sorV2.service.ts @@ -0,0 +1,518 @@ +import { + BasePool, + sorGetSwapsWithPools, + Token, + Address, + SwapKind, + sorParseRawPools, + RawStablePool, + RawWeightedPool, + RawMetaStablePool, + Swap as SwapSdk, + RawPool, + TokenAmount, + RawGyro2Pool, + RawGyro3Pool, + RawGyroEPool, +} from '@balancer/sdk'; +import { GqlSorSwapType, GqlSwap, GqlSorGetSwapsResponse, GqlPoolMinimal, GqlSorSwapRoute } from '../../../schema'; +import { Chain, PrismaPoolType } from '@prisma/client'; +import { GetSwapsInput, SwapResult, SwapService } from '../types'; +import { tokenService } from '../../token/token.service'; +import { networkContext } from '../../network/network-context.service'; +import { prisma } from '../../../prisma/prisma-client'; +import { PrismaPoolWithDynamic, prismaPoolWithDynamic } from '../../../prisma/prisma-types'; +import { HumanAmount, SupportedRawPoolTypes } from '@balancer/sdk'; +import { env } from '../../../app/env'; +import { DeploymentEnv } from '../../network/network-config-types'; +import { Cache, CacheClass } from 'memory-cache'; +import { GqlCowSwapApiResponse } from '../../../schema'; +import { BalancerSorService } from '../../beethoven/balancer-sor.service'; +import { poolService } from '../../pool/pool.service'; +import { BatchSwapStep } from '@balancer/sdk'; +import { SingleSwap } from '@balancer/sdk'; +import { SwapInfoRoute, SwapTypes, Swap, bnum, SwapInfoRouteHop } from '@balancer-labs/sor'; +import { BigNumber } from 'ethers'; +import { oldBnumScale } from '../../big-number/old-big-number'; +import { mapRoutes } from './beetsHelpers'; +import { poolsToIgnore } from '../constants'; +import { AllNetworkConfigsKeyedOnChain, chainToIdMap } from '../../network/network-config'; +import * as Sentry from '@sentry/node'; + +const ALL_BASEPOOLS_CACHE_KEY = `basePools:all`; + +class SwapResultV2 implements SwapResult { + private swap: SwapSdk | null; + public inputAmount: bigint = BigInt(0); + public outputAmount: bigint = BigInt(0); + public isValid: boolean; + + constructor(swap: SwapSdk | null) { + if (swap === null) { + this.isValid = false; + this.swap = null; + } else { + this.isValid = true; + this.swap = swap; + this.inputAmount = swap.inputAmount.amount; + this.outputAmount = swap.outputAmount.amount; + } + } + + async getCowSwapResponse(chain = networkContext.chain, queryFirst = false): Promise { + if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); + + if (!queryFirst) return this.mapResultToCowSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); + else { + const rpcUrl = AllNetworkConfigsKeyedOnChain[chain].data.rpcUrl; + // Needs node >= 18 (https://github.com/wagmi-dev/viem/discussions/147) + const updatedResult = await this.swap.query(rpcUrl); + // console.log(`UPDATE:`, this.swap.quote.amount.toString(), updatedResult.amount.toString()); + + const ip = this.swap.swapKind === SwapKind.GivenIn ? this.swap.inputAmount : updatedResult; + const op = this.swap.swapKind === SwapKind.GivenIn ? updatedResult : this.swap.outputAmount; + + return this.mapResultToCowSwap(this.swap, ip, op); + } + } + + async getBeetsSwapResponse(queryFirst: boolean): Promise { + if (!this.isValid || this.swap === null) throw new Error('No Response - Invalid Swap'); + + return await this.mapResultToBeetsSwap(this.swap, this.swap.inputAmount, this.swap.outputAmount); + } + + private async mapResultToBeetsSwap( + swap: SwapSdk, + inputAmount: TokenAmount, + outputAmount: TokenAmount, + ): Promise { + const sor = new BalancerSorService(); + const tokens = await tokenService.getTokens(); + let poolIds: string[]; + if (swap.isBatchSwap) { + const swaps = swap.swaps as BatchSwapStep[]; + poolIds = swaps.map((swap) => swap.poolId); + } else { + const singleSwap = swap.swaps as SingleSwap; + poolIds = [singleSwap.poolId]; + } + const pools = await poolService.getGqlPools({ + where: { idIn: poolIds }, + }); + + const swapAmountForSwaps = + swap.swapKind === SwapKind.GivenIn ? inputAmount.amount.toString() : outputAmount.amount.toString(); + const returnAmountFromSwaps = + swap.swapKind === SwapKind.GivenIn ? outputAmount.amount.toString() : inputAmount.amount.toString(); + + const swapData = { + tokenIn: inputAmount.token.address.toString(), + tokenOut: outputAmount.token.address.toString(), + tokens, + swapType: this.mapSwapKind(swap.swapKind), + tokenInAmtEvm: inputAmount.amount.toString(), + tokenOutAmtEvm: outputAmount.amount.toString(), + swapAmountForSwaps, + returnAmountFromSwaps, + returnAmountConsideringFees: returnAmountFromSwaps, + routes: this.mapRoutes( + swap.swaps, + inputAmount.amount.toString(), + outputAmount.amount.toString(), + pools, + swap.inputAmount.token.address, + swap.outputAmount.token.address, + swap.assets, + swap.swapKind, + ), + pools, + marketSp: '0', // Daniel confirmed returning 0 should be fine here + swaps: this.mapSwaps(swap.swaps, swap.assets), + tokenAddresses: swap.assets, + }; + return sor.formatResponse(swapData); + } + + private mapSwaps(swaps: BatchSwapStep[] | SingleSwap, assets: string[]): GqlSwap[] { + if (Array.isArray(swaps)) { + return swaps.map((swap) => { + return { + ...swap, + assetInIndex: Number(swap.assetInIndex.toString()), + assetOutIndex: Number(swap.assetOutIndex.toString()), + amount: swap.amount.toString(), + }; + }); + } else { + const assetInIndex = assets.indexOf(swaps.assetIn); + const assetOutIndex = assets.indexOf(swaps.assetOut); + return [ + { + ...swaps, + assetInIndex, + assetOutIndex, + amount: swaps.amount.toString(), + }, + ]; + } + } + + private mapSwapKind(kind: SwapKind): GqlSorSwapType { + return kind === SwapKind.GivenIn ? 'EXACT_IN' : 'EXACT_OUT'; + } + + private mapRoutes( + swaps: BatchSwapStep[] | SingleSwap, + inputAmount: string, + outputAmount: string, + pools: GqlPoolMinimal[], + assetIn: string, + assetOut: string, + assets: string[], + kind: SwapKind, + ): GqlSorSwapRoute[] { + return mapRoutes(swaps, inputAmount, outputAmount, pools, assetIn, assetOut, assets, kind); + } + + /** + * Formats a sequence of swaps to a format that is useful for displaying the routes in user interfaces. + * Taken directly from Beets SOR: https://github.com/beethovenxfi/balancer-sor/blob/beethovenx-master/src/formatSwaps.ts#L167 + * @dev The swaps are converted to an array of routes, where each route has an array of hops + * @param swapType - exact in or exact out + * @param routes - The original Swaps + * @param swapAmount - The total amount being swapped + * @returns SwapInfoRoute[] - The swaps formatted as routes with hops + */ + private formatRoutesSOR(swapType: SwapTypes, routes: Swap[][], swapAmount: BigNumber): SwapInfoRoute[] { + const exactIn = swapType === SwapTypes.SwapExactIn; + + return routes.map((swaps) => { + const first = swaps[0]; + const last = swaps[swaps.length - 1]; + const tokenInAmount = (exactIn ? first.swapAmount : last.swapAmountOut) || '0'; + const tokenOutAmount = (exactIn ? last.swapAmountOut : first.swapAmount) || '0'; + const tokenInAmountScaled = oldBnumScale(bnum(tokenInAmount), first.tokenInDecimals); + + return { + tokenIn: first.tokenIn, + tokenOut: last.tokenOut, + tokenInAmount, + tokenOutAmount, + share: tokenInAmountScaled.div(bnum(swapAmount.toString())).toNumber(), + hops: swaps.map((swap): SwapInfoRouteHop => { + return { + tokenIn: swap.tokenIn, + tokenOut: swap.tokenOut, + tokenInAmount: (exactIn ? swap.swapAmount : swap.swapAmountOut) || '0', + tokenOutAmount: (exactIn ? swap.swapAmountOut : swap.swapAmount) || '0', + poolId: swap.pool, + }; + }), + }; + }); + } + + /** + * Maps Swap to GqlCowSwapApiResponse which is what current CowSwap Solver uses. + * @param swap + * @returns + */ + private mapResultToCowSwap( + swap: SwapSdk, + inputAmount: TokenAmount, + outputAmount: TokenAmount, + ): GqlCowSwapApiResponse { + let swaps: GqlSwap[]; + if (swap.swaps instanceof Array) { + swaps = swap.swaps.map((swap) => { + return { + ...swap, + amount: swap.amount.toString(), + assetInIndex: Number(swap.assetInIndex), + assetOutIndex: Number(swap.assetOutIndex), + }; + }); + } else { + swaps = [ + { + amount: inputAmount.amount.toString(), + assetInIndex: swap.assets.indexOf(swap.swaps.assetIn), + assetOutIndex: swap.assets.indexOf(swap.swaps.assetOut), + poolId: swap.swaps.poolId, + userData: swap.swaps.userData, + }, + ]; + } + const returnAmount = + swap.swapKind === SwapKind.GivenIn ? outputAmount.amount.toString() : inputAmount.amount.toString(); + const swapAmount = + swap.swapKind === SwapKind.GivenIn ? inputAmount.amount.toString() : outputAmount.amount.toString(); + return { + marketSp: '', // TODO - Check if CowSwap actually use this? Could this be calculate using out/in? + returnAmount, + returnAmountConsideringFees: returnAmount, // TODO - Check if CowSwap actually use this? + returnAmountFromSwaps: returnAmount, // TODO - Check if CowSwap actually use this? + swapAmount, + swapAmountForSwaps: swapAmount, // TODO - Check if CowSwap actually use this? + swaps, + tokenAddresses: swap.assets, + tokenIn: swap.inputAmount.token.address, + tokenOut: swap.outputAmount.token.address, + }; + } +} + +export class SorV2Service implements SwapService { + cache: CacheClass; + + constructor() { + this.cache = new Cache(); + } + + public async getSwapResult({ + chain, + tokenIn, + tokenOut, + swapType, + swapAmount, + graphTraversalConfig, + }: GetSwapsInput): Promise { + try { + const poolsFromDb = await this.getBasePools(chain); + const tIn = await this.getToken(tokenIn as Address, chain); + const tOut = await this.getToken(tokenOut as Address, chain); + const swapKind = this.mapSwapType(swapType); + const swap = await sorGetSwapsWithPools(tIn, tOut, swapKind, swapAmount, poolsFromDb, { + graphTraversalConfig, + }); + return new SwapResultV2(swap); + } catch (err: any) { + console.error( + `SOR_V2_ERROR ${err.message} - tokenIn: ${tokenIn} - tokenOut: ${tokenOut} - swapAmount: ${swapAmount.amount} - swapType: ${swapType} - chain: ${chain}`, + ); + Sentry.captureException(err.message, { + tags: { + service: 'sorV2', + tokenIn, + tokenOut, + swapAmount: swapAmount.amount, + swapType, + chain, + }, + }); + return new SwapResultV2(null); + } + } + + /** + * Gets a b-sdk Token based off tokenAddr. + * @param address + * @param chain + * @returns + */ + private async getToken(address: Address, chain: Chain): Promise { + const token = await tokenService.getToken(address, chain); + if (!token) { + throw new Error('Unknown token: ' + address); + } + const chainId = Number(chainToIdMap[chain]); + return new Token(chainId, address, token.decimals, token.symbol); + } + + private mapSwapType(swapType: GqlSorSwapType): SwapKind { + return swapType === 'EXACT_IN' ? SwapKind.GivenIn : SwapKind.GivenOut; + } + + private async getBasePools(chain: Chain): Promise { + let basePools: BasePool[] | null = this.cache.get(`${ALL_BASEPOOLS_CACHE_KEY}:${chain}`); + if (!basePools) { + basePools = await this.getBasePoolsFromDb(chain); + this.cache.put(`${ALL_BASEPOOLS_CACHE_KEY}:${chain}`, basePools, 5 * 60 * 1000); + } + return basePools; + } + + /** + * Fetch pools from Prisma and map to b-sdk BasePool. + * @returns + */ + private async getBasePoolsFromDb(chain: Chain): Promise { + const { poolIdsToExclude } = AllNetworkConfigsKeyedOnChain[chain].data.sor[env.DEPLOYMENT_ENV as DeploymentEnv]; + const pools = await prisma.prismaPool.findMany({ + where: { + chain, + dynamicData: { + totalSharesNum: { + gt: 0.000000000001, + }, + swapEnabled: true, + }, + id: { + notIn: [...poolIdsToExclude, ...poolsToIgnore], + }, + type: { + notIn: [ + 'LINEAR', // Linear pools are sunset so ignore to avoid issues related to lack of support + 'LIQUIDITY_BOOTSTRAPPING', // not supported by b-sdk + 'ELEMENT', // not supported by b-sdk + 'UNKNOWN', // not supported by b-sdk + 'INVESTMENT', // not supported by b-sdk + ], + }, + AND: { + NOT: { + // not supported by b-sdk + type: 'STABLE', + version: { + in: [1, 2], + }, + }, + }, + }, + include: prismaPoolWithDynamic.include, + }); + const rawPools = this.mapToRawPools(pools); + return this.mapToBasePools(rawPools, chain); + } + + /** + * Map Prisma pools to b-sdk RawPool. + * @param pools + * @returns + */ + private mapToRawPools(pools: PrismaPoolWithDynamic[]): RawPool[] { + return pools.map((prismaPool) => { + // b-sdk: src/data/types.ts + let rawPool: RawPool = { + id: prismaPool.id as Address, + address: prismaPool.address as Address, + poolType: this.mapRawPoolType(prismaPool.type), + poolTypeVersion: prismaPool.version, + tokensList: prismaPool.tokens.map((t) => t.address as Address), + swapEnabled: prismaPool.dynamicData!.swapEnabled, + swapFee: prismaPool.dynamicData!.swapFee as unknown as HumanAmount, + totalShares: prismaPool.dynamicData!.totalShares as unknown as HumanAmount, + liquidity: prismaPool.dynamicData!.totalLiquidity as unknown as HumanAmount, + tokens: prismaPool.tokens.map((t) => { + return { + address: t.token.address as Address, + index: t.index, + symbol: t.token.symbol, + name: t.token.name, + decimals: t.token.decimals, + balance: t.dynamicData?.balance as unknown as HumanAmount, + }; + }), + isPaused: !!prismaPool.dynamicData?.isPaused, + inRecoveryMode: !!prismaPool.dynamicData?.isInRecoveryMode, + name: 'n/a', + }; + if (['Weighted', 'Investment', 'LiquidityBootstrapping'].includes(rawPool.poolType)) { + rawPool = { + ...rawPool, + tokens: rawPool.tokens.map((t, i) => { + return { ...t, weight: prismaPool.tokens[i].dynamicData?.weight }; + }), + } as RawWeightedPool; + } + if (rawPool.poolType === 'Stable') { + rawPool = { + ...rawPool, + amp: prismaPool.stableDynamicData?.amp, + } as RawStablePool; + } + if (['MetaStable', 'ComposableStable'].includes(rawPool.poolType)) { + rawPool = { + ...rawPool, + amp: prismaPool.stableDynamicData?.amp.split('.')[0], // Taken b-sdk onChainPoolDataEnricher.ts + tokens: rawPool.tokens.map((t, i) => { + return { ...t, priceRate: prismaPool.tokens[i].dynamicData?.priceRate }; + }), + } as RawMetaStablePool; + } + if (rawPool.poolType === 'Gyro2') { + rawPool = { + ...rawPool, + alpha: prismaPool.gyroData?.alpha, + beta: prismaPool.gyroData?.beta, + sqrtAlpha: prismaPool.gyroData?.sqrtAlpha, + sqrtBeta: prismaPool.gyroData?.sqrtBeta, + } as RawGyro2Pool; + } + if (rawPool.poolType === 'Gyro3') { + rawPool = { + ...rawPool, + root3Alpha: prismaPool.gyroData?.root3Alpha, + } as RawGyro3Pool; + } + if (rawPool.poolType === 'GyroE') { + rawPool = { + ...rawPool, + alpha: prismaPool.gyroData?.alpha, + beta: prismaPool.gyroData?.beta, + c: prismaPool.gyroData?.c, + s: prismaPool.gyroData?.s, + lambda: prismaPool.gyroData?.lambda, + tauAlphaX: prismaPool.gyroData?.tauAlphaX, + tauAlphaY: prismaPool.gyroData?.tauAlphaY, + tauBetaX: prismaPool.gyroData?.tauBetaX, + tauBetaY: prismaPool.gyroData?.tauBetaY, + u: prismaPool.gyroData?.u, + v: prismaPool.gyroData?.v, + w: prismaPool.gyroData?.w, + z: prismaPool.gyroData?.z, + dSq: prismaPool.gyroData?.dSq, + tokenRates: prismaPool.tokens.map((t) => t.dynamicData?.priceRate), + } as RawGyroEPool; + } + return rawPool; + }); + } + + /** + * Map b-sdk RawPools to BasePools. + * @param pools + * @returns + */ + private mapToBasePools(pools: RawPool[], chain: Chain): BasePool[] { + const chainId = Number(chainToIdMap[chain]); + return sorParseRawPools(chainId, pools); + } + + /** + * Map Prisma pool type to b-sdk Raw pool type. + * @param type + * @returns + */ + private mapRawPoolType(type: PrismaPoolType): SupportedRawPoolTypes | string { + // From b-sdk: + // - type LinearPoolType = `${string}Linear`; + // - LinearPoolType | 'Weighted' | 'Investment' | 'LiquidityBootstrapping' | 'Stable' | 'MetaStable' | 'ComposableStable' | 'StablePhantom' | 'Element'; + switch (type) { + case PrismaPoolType.WEIGHTED: + return 'Weighted'; + case PrismaPoolType.INVESTMENT: + return 'Investment'; + case PrismaPoolType.LIQUIDITY_BOOTSTRAPPING: + return 'LiquidityBootstrapping'; + case PrismaPoolType.STABLE: + return 'Stable'; + case PrismaPoolType.META_STABLE: + return 'MetaStable'; + case PrismaPoolType.PHANTOM_STABLE: + // Composablestables are PHANTOM_STABLE in Prisma. b-sdk treats Phantoms as ComposableStable. + return 'ComposableStable'; + case PrismaPoolType.GYRO: + return 'Gyro2'; + case PrismaPoolType.GYRO3: + return 'Gyro3'; + case PrismaPoolType.GYROE: + return 'GyroE'; + default: + return type; + } + } +} + +export const sorV2Service = new SorV2Service(); diff --git a/modules/sor/types.ts b/modules/sor/types.ts new file mode 100644 index 000000000..564647c84 --- /dev/null +++ b/modules/sor/types.ts @@ -0,0 +1,31 @@ +import { Chain } from '@prisma/client'; +import { GqlCowSwapApiResponse, GqlSorSwapType, GqlSorGetSwapsResponse, GqlSorSwapOptionsInput } from '../../schema'; +import { TokenAmount } from '@balancer/sdk'; +export interface GetSwapsInput { + chain: Chain; + tokenIn: string; + tokenOut: string; + swapType: GqlSorSwapType; + swapAmount: TokenAmount; + swapOptions: GqlSorSwapOptionsInput; + graphTraversalConfig?: GraphTraversalConfig; +} + +export interface GraphTraversalConfig { + approxPathsToReturn?: number; + maxDepth?: number; + maxNonBoostedHopTokensInBoostedPath?: number; + maxNonBoostedPathDepth?: number; +} + +export interface SwapResult { + getCowSwapResponse(chain: Chain, queryFirst: boolean): Promise; + getBeetsSwapResponse(queryFirst: boolean): Promise; + isValid: boolean; + outputAmount: bigint; + inputAmount: bigint; +} + +export interface SwapService { + getSwapResult(inputs: GetSwapsInput): Promise; +} diff --git a/modules/sor/utils.ts b/modules/sor/utils.ts new file mode 100644 index 000000000..1be0ad8ba --- /dev/null +++ b/modules/sor/utils.ts @@ -0,0 +1,20 @@ +import { TokenAmount, Token, Address } from '@balancer/sdk'; +import { tokenService } from '../token/token.service'; +import { Chain } from '@prisma/client'; +import { chainToIdMap } from '../network/network-config'; + +export async function getTokenAmountHuman(tokenAddr: string, humanAmount: string, chain: Chain): Promise { + const chainId = Number(chainToIdMap[chain]); + const prismaToken = await tokenService.getToken(tokenAddr, chain); + if (!prismaToken) throw Error(`Missing token from tokenService ${tokenAddr}`); + const token = new Token(chainId, prismaToken.address as Address, prismaToken.decimals); + return TokenAmount.fromHumanAmount(token, humanAmount as `${number}`); +} + +export async function getTokenAmountRaw(tokenAddr: string, rawAmount: string, chain: Chain): Promise { + const chainId = Number(chainToIdMap[chain]); + const prismaToken = await tokenService.getToken(tokenAddr, chain); + if (!prismaToken) throw Error(`Missing token from tokenService ${tokenAddr}`); + const token = new Token(chainId, prismaToken.address as Address, prismaToken.decimals); + return TokenAmount.fromRawAmount(token, rawAmount); +} diff --git a/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql b/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql index 8c37e682f..f154888d1 100644 --- a/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql +++ b/modules/subgraphs/balancer-subgraph/balancer-subgraph-queries.graphql @@ -182,6 +182,21 @@ fragment BalancerPool on Pool { amp alpha beta + sqrtAlpha + sqrtBeta + root3Alpha + c + s + lambda + tauAlphaX + tauAlphaY + tauBetaX + tauBetaY + u + v + w + z + dSq tokens { ...BalancerPoolToken diff --git a/modules/token/token.service.ts b/modules/token/token.service.ts index 4e1eb374c..21aff4d98 100644 --- a/modules/token/token.service.ts +++ b/modules/token/token.service.ts @@ -27,22 +27,22 @@ export class TokenService { await networkContext.config.contentService.syncTokenContentData(); } - public async getToken(address: string): Promise { + public async getToken(address: string, chain = networkContext.chain): Promise { return prisma.prismaToken.findUnique({ where: { address_chain: { address: address.toLowerCase(), - chain: networkContext.chain, + chain, }, }, }); } - public async getTokens(addresses?: string[]): Promise { - let tokens: PrismaToken[] | null = this.cache.get(`${ALL_TOKENS_CACHE_KEY}:${networkContext.chain}`); + public async getTokens(addresses?: string[], chain = networkContext.chain): Promise { + let tokens: PrismaToken[] | null = this.cache.get(`${ALL_TOKENS_CACHE_KEY}:${chain}`); if (!tokens) { - tokens = await prisma.prismaToken.findMany({ where: { chain: networkContext.chain } }); - this.cache.put(`${ALL_TOKENS_CACHE_KEY}:${networkContext.chain}`, tokens, 5 * 60 * 1000); + tokens = await prisma.prismaToken.findMany({ where: { chain: chain } }); + this.cache.put(`${ALL_TOKENS_CACHE_KEY}:${chain}`, tokens, 5 * 60 * 1000); } if (addresses) { return tokens.filter((token) => addresses.includes(token.address)); diff --git a/package.json b/package.json index f02ca2c23..368ab00db 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@aws-sdk/client-secrets-manager": "^3.195.0", "@aws-sdk/client-sqs": "^3.137.0", "@balancer-labs/sdk": "github:beethovenxfi/balancer-sdk#beethovenx-master", + "@balancer/sdk": "^0.3.0", "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.6.0", "@ethersproject/constants": "^5.6.0", @@ -89,7 +90,7 @@ "testcontainers": "^8.0.0", "ts-jest": "^28.0.7", "ts-node": "^10.4.0", - "typescript": "^4.5.3", + "typescript": "^5.2.2", "vitest": "^0.32.4", "vitest-mock-extended": "^1.1.3" } diff --git a/prisma/migrations/20230905102723_gyro_sor_fields/migration.sql b/prisma/migrations/20230905102723_gyro_sor_fields/migration.sql new file mode 100644 index 000000000..4202ecbdf --- /dev/null +++ b/prisma/migrations/20230905102723_gyro_sor_fields/migration.sql @@ -0,0 +1,13 @@ +-- AlterTable +ALTER TABLE "PrismaPoolGyroData" ADD COLUMN "c" TEXT, +ADD COLUMN "dSq" TEXT, +ADD COLUMN "lambda" TEXT, +ADD COLUMN "s" TEXT, +ADD COLUMN "tauAlphaX" TEXT, +ADD COLUMN "tauAlphaY" TEXT, +ADD COLUMN "tauBetaX" TEXT, +ADD COLUMN "tauBetaY" TEXT, +ADD COLUMN "u" TEXT, +ADD COLUMN "v" TEXT, +ADD COLUMN "w" TEXT, +ADD COLUMN "z" TEXT; diff --git a/prisma/migrations/20230907135550_gyro_missing_fields/migration.sql b/prisma/migrations/20230907135550_gyro_missing_fields/migration.sql new file mode 100644 index 000000000..c3f5623b1 --- /dev/null +++ b/prisma/migrations/20230907135550_gyro_missing_fields/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "PrismaPoolGyroData" ADD COLUMN "root3Alpha" TEXT, +ADD COLUMN "sqrtAlpha" TEXT, +ADD COLUMN "sqrtBeta" TEXT; diff --git a/prisma/prisma-types.ts b/prisma/prisma-types.ts index 56663de91..6f830da7b 100644 --- a/prisma/prisma-types.ts +++ b/prisma/prisma-types.ts @@ -276,3 +276,22 @@ export const prismaPoolBatchSwapWithSwaps = Prisma.validator; + +export const prismaPoolWithDynamic = Prisma.validator()({ + include: { + stableDynamicData: true, + dynamicData: true, + linearDynamicData: true, + linearData: true, + gyroData: true, + tokens: { + orderBy: { index: 'asc' }, + include: { + token: true, + dynamicData: true, + }, + }, + } +}); + +export type PrismaPoolWithDynamic = Prisma.PrismaPoolGetPayload; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 988be5d51..6e3103627 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -142,6 +142,21 @@ model PrismaPoolGyroData{ alpha String beta String + sqrtAlpha String? + sqrtBeta String? + root3Alpha String? + c String? + s String? + lambda String? + tauAlphaX String? + tauAlphaY String? + tauBetaX String? + tauBetaY String? + u String? + v String? + w String? + z String? + dSq String? } model PrismaPoolDynamicData { diff --git a/tsconfig.json b/tsconfig.json index 817776afd..a9c4bc4b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,16 @@ { - "compilerOptions": { - "esModuleInterop": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "lib": ["es2018"], - "module": "commonjs", - "strict": true, - "target": "ES2020", - "resolveJsonModule": true, - "outDir": "./dist", - "skipLibCheck": true - }, - "exclude": [ - "node_modules", - "debug", - "**/*.spec.ts", - "**/*.test.ts" - ] + "compilerOptions": { + "esModuleInterop": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2018"], + "module": "commonjs", + "strict": true, + "target": "ES2020", + "resolveJsonModule": true, + "outDir": "./dist", + "skipLibCheck": true, + "sourceMap": true + }, + "exclude": ["node_modules", "debug", "**/*.spec.ts", "**/*.test.ts"] } diff --git a/worker/worker.ts b/worker/worker.ts index 45cd2e0ed..c5da70337 100644 --- a/worker/worker.ts +++ b/worker/worker.ts @@ -17,7 +17,7 @@ export async function startWorker() { new ProfilingIntegration(), ], tracesSampleRate: 0.2, - profilesSampleRate: 1.0, + profilesSampleRate: 0.1, }); app.use(Sentry.Handlers.requestHandler()); diff --git a/yarn.lock b/yarn.lock index 4c8f3ad55..245f5a8c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -2451,6 +2456,16 @@ graphology "^0.24.1" isomorphic-fetch "^2.2.1" +"@balancer/sdk@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@balancer/sdk/-/sdk-0.3.1.tgz#ca70a0b0082a809d2b0889277dff37c1676d46ef" + integrity sha512-eyhfgjPlVR5nZ6+Az1bDVnmycjkVa2wx7uO5bs/ntb/HYvheeJt+mcoHUOTojMRYvWUSal0WrenfbxWy0pn9Pg== + dependencies: + async-retry "^1.3.3" + decimal.js-light "^2.5.1" + pino "^8.11.0" + viem "^1.9.3" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -3851,6 +3866,18 @@ resolved "https://registry.yarnpkg.com/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz#defaebdd31f625bee49e6745934f36312532b2bc" integrity sha512-BTpWy1e+FxN82RnLz4x1+JcEewVdfmUhV1C6/XYD5AjS7PQp9QFF7K8bCD6gzPTr2l+prvqOyVueQhFJxB1vfg== +"@noble/curves@1.2.0", "@noble/curves@~1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + +"@noble/hashes@1.3.2", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -4014,6 +4041,28 @@ resolved "https://registry.yarnpkg.com/@sanity/timed-out/-/timed-out-4.0.2.tgz#c9f61f9a1609baa1eb3e4235a24ea2a775022cdf" integrity sha512-NBDKGj14g9Z+bopIvZcQKWCzJq5JSrdmzRR1CS+iyA3Gm8SnIWBfZa7I3mTg2X6Nu8LQXG0EPKXdOGozLS4i3w== +"@scure/base@~1.1.0", "@scure/base@~1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" + integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== + +"@scure/bip32@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" + integrity sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA== + dependencies: + "@noble/curves" "~1.2.0" + "@noble/hashes" "~1.3.2" + "@scure/base" "~1.1.2" + +"@scure/bip39@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" + integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== + dependencies: + "@noble/hashes" "~1.3.0" + "@scure/base" "~1.1.0" + "@sentry-internal/tracing@7.56.0": version "7.56.0" resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.56.0.tgz#ba709258f2f0f3d8a36f9740403088b39212b843" @@ -5027,6 +5076,11 @@ abbrev@1, abbrev@^1.0.0: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abitype@0.9.8: + version "0.9.8" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" + integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -5354,7 +5408,7 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== -async-retry@^1.2.1: +async-retry@^1.2.1, async-retry@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== @@ -5742,6 +5796,14 @@ buffer@^5.5.0, buffer@^5.7.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" @@ -6439,6 +6501,11 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js-light@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decimal.js@^10.3.1: version "10.3.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" @@ -7099,6 +7166,11 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-redact@^3.1.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.3.0.tgz#7c83ce3a7be4898241a46560d51de10f653f7634" + integrity sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ== + fast-text-encoding@^1.0.0: version "1.0.6" resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" @@ -7872,7 +7944,7 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -8392,6 +8464,11 @@ isomorphic-ws@4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isows@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" + integrity sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -9860,6 +9937,11 @@ obliterator@^2.0.2: resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -10128,6 +10210,36 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pino-abstract-transport@v1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz#083d98f966262164504afb989bccd05f665937a8" + integrity sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + +pino-std-serializers@^6.0.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz#d9a9b5f2b9a402486a5fc4db0a737570a860aab3" + integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== + +pino@^8.11.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.16.0.tgz#2465012a1d11fa2e7a0545032f636e203990ae26" + integrity sha512-UUmvQ/7KTZt/vHjhRrnyS7h+J7qPBQnpG80V56xmIC+o9IqYmQOw/UIny9S9zYDfRBR0ClouCr464EkBMIT7Fw== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport v1.1.0 + pino-std-serializers "^6.0.0" + process-warning "^2.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.7.0" + thread-stream "^2.0.0" + pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -10214,6 +10326,16 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-warning@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.2.0.tgz#008ec76b579820a8e5c35d81960525ca64feb626" + integrity sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + progress-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-2.0.0.tgz#fac63a0b3d11deacbb0969abcc93b214bce19ed5" @@ -10307,6 +10429,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + random-words@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/random-words/-/random-words-1.3.0.tgz#00715efb8dd787d244f963c994367707c1e95676" @@ -10378,6 +10505,17 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^4.0.0: + version "4.4.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.2.tgz#e6aced27ad3b9d726d8308515b9a1b98dc1b9d13" + integrity sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + readdir-glob@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" @@ -10392,6 +10530,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" @@ -10636,6 +10779,11 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" +safe-stable-stringify@^2.3.1: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -10863,6 +11011,13 @@ sonic-boom@^2.1.0: dependencies: atomic-sleep "^1.0.0" +sonic-boom@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.7.0.tgz#b4b7b8049a912986f4a92c51d4660b721b11f2f2" + integrity sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg== + dependencies: + atomic-sleep "^1.0.0" + source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -10909,6 +11064,11 @@ split-ca@^1.0.1: resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + sponge-case@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sponge-case/-/sponge-case-1.0.1.tgz#260833b86453883d974f84854cdb63aecc5aef4c" @@ -11061,7 +11221,7 @@ string.prototype.trimstart@^1.0.5: define-properties "^1.1.4" es-abstract "^1.19.5" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -11279,6 +11439,13 @@ testcontainers@^8.0.0: ssh-remote-port-forward "^1.0.4" tar-fs "^2.1.1" +thread-stream@^2.0.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.4.1.tgz#6d588b14f0546e59d3f306614f044bc01ce43351" + integrity sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg== + dependencies: + real-require "^0.2.0" + through2@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -11486,10 +11653,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.5.3: - version "4.5.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.3.tgz#afaa858e68c7103317d89eb90c5d8906268d353c" - integrity sha512-eVYaEHALSt+s9LbvgEv4Ef+Tdq7hBiIZgii12xXJnukryt3pMgJf6aKhoCZ3FWQsu6sydEnkg11fYXLzhLBjeQ== +typescript@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== ua-parser-js@^0.7.30: version "0.7.31" @@ -11682,6 +11849,20 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +viem@^1.9.3: + version "1.19.6" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.19.6.tgz#daf1ebf4774a5bb9e87822ab25fcc67420471847" + integrity sha512-WSBHBMurWIWQk2yisOD8hqSA5S56cZu6onty3hzauVjiHMildtVWujF7YT0xjoU40GpFODvJASRR2RFdzgvUUg== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "0.9.8" + isows "1.0.3" + ws "8.13.0" + vite-node@0.32.4: version "0.32.4" resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.32.4.tgz#7b3f94af5a87c631fbc380ba662914bafbd04d80" @@ -11894,6 +12075,11 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + ws@8.2.3: version "8.2.3" resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"