From b701bb590244be9760080ec978329d11a4f44be2 Mon Sep 17 00:00:00 2001 From: xrsv Date: Fri, 29 Nov 2024 17:38:24 -0800 Subject: [PATCH 01/17] fix(subgraph): include VIRTUAL/GAME pool in V2 Base subgraph (#768) * fix(subgraph): include VIRTUAL tokens in V2 subgraph * add chainId base check * space * only allow VIRTUAL/GAME for now --- src/providers/v2/subgraph-provider.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/providers/v2/subgraph-provider.ts b/src/providers/v2/subgraph-provider.ts index 7bd2ed0d8..21d6635b0 100644 --- a/src/providers/v2/subgraph-provider.ts +++ b/src/providers/v2/subgraph-provider.ts @@ -247,10 +247,14 @@ export class V2SubgraphProvider implements IV2SubgraphProvider { // TODO: Remove. Temporary fix to ensure tokens without trackedReserveETH are in the list. const FEI = '0x956f47f50a910163d8bf957cf5846d573e7f87ca'; + // TODO: Remove. Temporary fix to allow VIRTUAL/GAME V2 pool to be included in the list. + const VIRTUAL_GAME_3PCT_BASE = '0xD418dfE7670c21F682E041F34250c114DB5D7789'.toLowerCase(); + const tracked = pools.filter( (pool) => pool.token0.id == FEI || pool.token1.id == FEI || + (this.chainId === ChainId.BASE && pool.id.toLowerCase() === VIRTUAL_GAME_3PCT_BASE) || parseFloat(pool.trackedReserveETH) > this.trackedEthThreshold ); @@ -269,6 +273,7 @@ export class V2SubgraphProvider implements IV2SubgraphProvider { return ( pool.token0.id == FEI || pool.token1.id == FEI || + (this.chainId === ChainId.BASE && pool.id.toLowerCase() === VIRTUAL_GAME_3PCT_BASE) || parseFloat(pool.trackedReserveETH) > this.trackedEthThreshold || parseFloat(pool.reserveUSD) > this.untrackedUsdThreshold ); From e70a7fe893594c337b79305164ab906aadd6438c Mon Sep 17 00:00:00 2001 From: xrsv Date: Fri, 29 Nov 2024 20:35:09 -0800 Subject: [PATCH 02/17] chore: bump sor to 4.8.2 - fix(subgraph): include VIRTUAL/GAME pool in V2 Base subgraph (#769) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b0c9f46d..3492770e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.1", + "version": "4.8.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.8.1", + "version": "4.8.2", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index 989720e14..7e2c9cd6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.1", + "version": "4.8.2", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", From 019f47b7b3040bd97b4d611e0314bc05e49445fb Mon Sep 17 00:00:00 2001 From: xrsv Date: Mon, 2 Dec 2024 07:18:26 -0800 Subject: [PATCH 03/17] fix(subgraph): include top 20 VIRTUAL/* pools in V2 Base subgraph (#770) * fix(subgraph): include top 20 VIRTUAL/* pools in V2 Base subgraph * add test suite * update comment --- src/providers/v2/subgraph-provider.ts | 62 +++++- .../providers/v2/subgraph-provider.test.ts | 206 ++++++++++++++++++ 2 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 test/unit/providers/v2/subgraph-provider.test.ts diff --git a/src/providers/v2/subgraph-provider.ts b/src/providers/v2/subgraph-provider.ts index 21d6635b0..6b344557f 100644 --- a/src/providers/v2/subgraph-provider.ts +++ b/src/providers/v2/subgraph-provider.ts @@ -247,14 +247,11 @@ export class V2SubgraphProvider implements IV2SubgraphProvider { // TODO: Remove. Temporary fix to ensure tokens without trackedReserveETH are in the list. const FEI = '0x956f47f50a910163d8bf957cf5846d573e7f87ca'; - // TODO: Remove. Temporary fix to allow VIRTUAL/GAME V2 pool to be included in the list. - const VIRTUAL_GAME_3PCT_BASE = '0xD418dfE7670c21F682E041F34250c114DB5D7789'.toLowerCase(); - const tracked = pools.filter( (pool) => pool.token0.id == FEI || pool.token1.id == FEI || - (this.chainId === ChainId.BASE && pool.id.toLowerCase() === VIRTUAL_GAME_3PCT_BASE) || + this.isVirtualPairBaseV2Pool(pool.id) || parseFloat(pool.trackedReserveETH) > this.trackedEthThreshold ); @@ -273,7 +270,7 @@ export class V2SubgraphProvider implements IV2SubgraphProvider { return ( pool.token0.id == FEI || pool.token1.id == FEI || - (this.chainId === ChainId.BASE && pool.id.toLowerCase() === VIRTUAL_GAME_3PCT_BASE) || + this.isVirtualPairBaseV2Pool(pool.id) || parseFloat(pool.trackedReserveETH) > this.trackedEthThreshold || parseFloat(pool.reserveUSD) > this.untrackedUsdThreshold ); @@ -317,4 +314,59 @@ export class V2SubgraphProvider implements IV2SubgraphProvider { return poolsSanitized; } + + // This method checks if a given pool is part of the Virtual Pair Base V2 Pool IDs. + public isVirtualPairBaseV2Pool(poolId: string): boolean { + return ( + this.chainId === ChainId.BASE && + V2SubgraphProvider.VirtualPairBaseV2PoolIds.has(poolId.toLowerCase()) + ); + } + + // TODO: Remove. Temporary fix to allow VIRTUAL/* V2 top pools to be included in the list until the subgraph is re-indexed. + // Note: All addresses here should be in lowercase. + private static readonly VirtualPairBaseV2PoolIds: Set = new Set([ + // Pool ID for VIRTUAL / GAME + '0xd418dfe7670c21f682e041f34250c114db5d7789', + // Pool ID for VIRTUAL / AIXBT + '0x7464850cc1cfb54a2223229b77b1bca2f888d946', + // Pool ID for VIRTUAL / LUNA + '0xa8e64fb120ce8796594670bae72279c8aa1e5359', + // Pool ID for VIRTUAL / TOSHI + '0x368ea6f645deb6d1a8692fe93c550d2834150865', + // Pool ID for VIRTUAL / KEYCAT + '0x3b9ddad7459fdfcfdb7117d53301182dd46b7042', + // Pool ID for VIRTUAL / VADER + '0xa1dddb82501e8fe2d92ad0e8ba331313f501de72', + // Pool ID for VIRTUAL / SEKOIA + '0x9e046c706f9ea6ce8f9287d30a15438c07f42d64', + // Pool ID for VIRTUAL / SAINT + '0x93d65a935e7c0f1aa153780e0db3ad08b9448c89', + // Pool ID for VIRTUAL / $mfer + '0xcaabcb34b48d29631c2b2c1d947d669fd8117a51', + // Pool ID for VIRTUAL / AIINU + '0xd2d670c91d14d9abcca7749f65e3181b2124e12d', + // Pool ID for VIRTUAL / CONVO + '0x3268d7efa254e648ffe968664e47ca226e27cd04', + // Pool ID for VIRTUAL / POLY + '0xde40586745a4e21efc0e8e0073cdd431ae88876a', + // Pool ID for VIRTUAL / GUAN + '0xff5bbda63e87210edd0ab9bc424086589d92e0af', + // Pool ID for VIRTUAL / SAM + '0xd72a82022134ba0d417c8d733139057e9bb9ea3f', + // Pool ID for VIRTUAL / VIRTU + '0xd9436bdaaf364181f1a39bb4c5e0a307c7353a01', + // Pool ID for VIRTUAL / CYI + '0x9d89c0cb73143927761534143747e26c47a1589f', + // Pool ID for VIRTUAL / VU + '0xfa65a76655f3c0641b79e89de3f51459c3727823', + // Pool ID for VIRTUAL / ECHO + '0x75cf1180ceb1cbef7508a4a4a0de940e2db4f087', + // Pool ID for VIRTUAL / MUSIC + '0xcef84d17513ede0ab951f7be9829e638a20bfba4', + // Pool ID for VIRTUAL / MISATO + '0x863e3a73c604d5038d41e2512272a73585f70017', + // Pool ID for VIRTUAL / NIKITA + '0xb9d782f2cfb755d47e2a5fa1350100c2ce3287ee', + ]); } diff --git a/test/unit/providers/v2/subgraph-provider.test.ts b/test/unit/providers/v2/subgraph-provider.test.ts new file mode 100644 index 000000000..88c16d143 --- /dev/null +++ b/test/unit/providers/v2/subgraph-provider.test.ts @@ -0,0 +1,206 @@ +import dotenv from 'dotenv'; +import { GraphQLClient } from 'graphql-request'; +import sinon from 'sinon'; +import { + V2SubgraphProvider, +} from '../../../../src'; +import { ChainId } from '@uniswap/sdk-core'; + +dotenv.config(); + +describe('SubgraphProvider V2', () => { + + // VIRTUAL / AIXBT v2 pool + const virtualPairAddress = '0x7464850cc1cfb54a2223229b77b1bca2f888d946'; + // Random non-virtual pair address + const nonVirtualPairAddress = '0x7464850cc1cfb54a2223229b77b1bca2f888d947'; + + let requestStubMainnet: sinon.SinonStub; + let requestStubBase: sinon.SinonStub; + let subgraphProviderMainnet: V2SubgraphProvider; + let subgraphProviderBase: V2SubgraphProvider; + + beforeEach(() => {}); + + afterEach(() => { + sinon.restore(); + }); + + it('fetches subgraph pools if trackedReserveETH is above 0.025 threshold on Base', async () => { + requestStubBase = sinon.stub(GraphQLClient.prototype, 'request'); + subgraphProviderBase = new V2SubgraphProvider(ChainId.BASE, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); + + const firstCallResponse = { + pairs: [ + { + id: nonVirtualPairAddress, + token0: { id: '0xToken0', symbol: 'TOKEN0' }, + token1: { id: '0xToken1', symbol: 'TOKEN1' }, + totalSupply: '100000', + trackedReserveETH: '1', + reserveETH: '1', + reserveUSD: '0.001', + }, + ], + }; + const subsequentCallsResponse = { pairs: [] }; + + // Configure the stub: return mock data on the first call, empty array on subsequent calls + requestStubBase.onCall(0).resolves(firstCallResponse); + requestStubBase.onCall(1).resolves(subsequentCallsResponse); + + const pools = await subgraphProviderBase.getPools(); + expect(pools.length).toEqual(1); + expect(pools[0]!.id).toEqual(nonVirtualPairAddress); + }); + + it('fetches 0 subgraph pools if trackedReserveETH is below 0.025 threshold on Base', async () => { + requestStubBase = sinon.stub(GraphQLClient.prototype, 'request'); + subgraphProviderBase = new V2SubgraphProvider(ChainId.BASE, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); + + const firstCallResponse = { + pairs: [ + { + id: nonVirtualPairAddress, + token0: { id: '0xToken0', symbol: 'TOKEN0' }, + token1: { id: '0xToken1', symbol: 'TOKEN1' }, + totalSupply: '100000', + trackedReserveETH: '0.001', + reserveETH: '0.001', + reserveUSD: '0.001', + }, + ], + }; + const subsequentCallsResponse = { pairs: [] }; + + // Configure the stub: return mock data on the first call, empty array on subsequent calls + requestStubBase.onCall(0).resolves(firstCallResponse); + requestStubBase.onCall(1).resolves(subsequentCallsResponse); + + const pools = await subgraphProviderBase.getPools(); + expect(pools.length).toEqual(0); + }); + + it('fetches 1 subgraph pools if trackedReserveETH is below 0.025 threshold but Virtual pair on Base', async () => { + requestStubBase = sinon.stub(GraphQLClient.prototype, 'request'); + subgraphProviderBase = new V2SubgraphProvider(ChainId.BASE, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); + + const firstCallResponse = { + pairs: [ + { + id: virtualPairAddress, + token0: { id: '0xToken0', symbol: 'TOKEN0' }, + token1: { id: '0xToken1', symbol: 'TOKEN1' }, + totalSupply: '100000', + trackedReserveETH: '0.001', + reserveETH: '0.001', + reserveUSD: '0.001', + }, + ], + }; + const subsequentCallsResponse = { pairs: [] }; + + // Configure the stub: return mock data on the first call, empty array on subsequent calls + requestStubBase.onCall(0).resolves(firstCallResponse); + requestStubBase.onCall(1).resolves(subsequentCallsResponse); + + const pools = await subgraphProviderBase.getPools(); + expect(pools.length).toEqual(1); + expect(pools[0]!.id).toEqual(virtualPairAddress); + }); + + + it('fetches subgraph pools if trackedReserveETH is above 0.025 threshold on Mainnet', async () => { + requestStubMainnet = sinon.stub(GraphQLClient.prototype, 'request'); + subgraphProviderMainnet = new V2SubgraphProvider(ChainId.MAINNET, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); + + const firstCallResponse = { + pairs: [ + { + id: nonVirtualPairAddress, + token0: { id: '0xToken0', symbol: 'TOKEN0' }, + token1: { id: '0xToken1', symbol: 'TOKEN1' }, + totalSupply: '100000', + trackedReserveETH: '1', + reserveETH: '1', + reserveUSD: '0.001', + }, + ], + }; + const subsequentCallsResponse = { pairs: [] }; + + // Configure the stub: return mock data on the first call, empty array on subsequent calls + requestStubMainnet.onCall(0).resolves(firstCallResponse); + requestStubMainnet.onCall(1).resolves(subsequentCallsResponse); + + const pools = await subgraphProviderMainnet.getPools(); + expect(pools.length).toEqual(1); + expect(pools[0]!.id).toEqual(nonVirtualPairAddress); + }); + + it('fetches 0 subgraph pools if trackedReserveETH is below 0.025 threshold on Mainnet', async () => { + requestStubMainnet = sinon.stub(GraphQLClient.prototype, 'request'); + subgraphProviderMainnet = new V2SubgraphProvider(ChainId.MAINNET, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); + + const firstCallResponse = { + pairs: [ + { + id: nonVirtualPairAddress, + token0: { id: '0xToken0', symbol: 'TOKEN0' }, + token1: { id: '0xToken1', symbol: 'TOKEN1' }, + totalSupply: '100000', + trackedReserveETH: '0.001', + reserveETH: '0.001', + reserveUSD: '0.001', + }, + ], + }; + const subsequentCallsResponse = { pairs: [] }; + + // Configure the stub: return mock data on the first call, empty array on subsequent calls + requestStubMainnet.onCall(0).resolves(firstCallResponse); + requestStubMainnet.onCall(1).resolves(subsequentCallsResponse); + + const pools = await subgraphProviderMainnet.getPools(); + expect(pools.length).toEqual(0); + }); + + it('fetches 0 subgraph pools if trackedReserveETH is below 0.025 threshold but Virtual pair on Mainnet', async () => { + requestStubMainnet = sinon.stub(GraphQLClient.prototype, 'request'); + subgraphProviderMainnet = new V2SubgraphProvider(ChainId.MAINNET, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); + + const firstCallResponse = { + pairs: [ + { + id: virtualPairAddress, + token0: { id: '0xToken0', symbol: 'TOKEN0' }, + token1: { id: '0xToken1', symbol: 'TOKEN1' }, + totalSupply: '100000', + trackedReserveETH: '0.001', + reserveETH: '0.001', + reserveUSD: '0.001', + }, + ], + }; + const subsequentCallsResponse = { pairs: [] }; + + // Configure the stub: return mock data on the first call, empty array on subsequent calls + requestStubMainnet.onCall(0).resolves(firstCallResponse); + requestStubMainnet.onCall(1).resolves(subsequentCallsResponse); + + const pools = await subgraphProviderMainnet.getPools(); + expect(pools.length).toEqual(0); + }); + + it('isVirtualPairBaseV2Pool tests', async () => { + // virtual / non-virtual pair address on mainnet fails + const mainnetSubgraphProviderV2 = new V2SubgraphProvider(ChainId.MAINNET, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); + expect(mainnetSubgraphProviderV2.isVirtualPairBaseV2Pool(virtualPairAddress)).toBe(false); + expect(mainnetSubgraphProviderV2.isVirtualPairBaseV2Pool(nonVirtualPairAddress)).toBe(false); + + // virtual pair address on base succeeds / non-virtual fails + const baseSubgraphProviderV2 = new V2SubgraphProvider(ChainId.BASE, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); + expect(baseSubgraphProviderV2.isVirtualPairBaseV2Pool(virtualPairAddress)).toBe(true); + expect(baseSubgraphProviderV2.isVirtualPairBaseV2Pool(nonVirtualPairAddress)).toBe(false); + }); +}) From 75d103db24fb13ab1b2e7c72ac97f7faa9fd0658 Mon Sep 17 00:00:00 2001 From: xrsv Date: Mon, 2 Dec 2024 07:30:08 -0800 Subject: [PATCH 04/17] chore: bump sor to 4.8.3 - fix(subgraph): include top 20 VIRTUAL/* pools in V2 Base subgraph (#771) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3492770e8..c03c91490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.2", + "version": "4.8.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.8.2", + "version": "4.8.3", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index 7e2c9cd6c..e746d5d3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.2", + "version": "4.8.3", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", From e3688e6aaa5dbffc0dad33b729c960f72c95b6ff Mon Sep 17 00:00:00 2001 From: xrsv Date: Tue, 3 Dec 2024 09:14:50 -0800 Subject: [PATCH 05/17] fix(subgraph): include all VIRTUAL/* pools in V2 Base subgraph (#773) --- src/providers/v2/subgraph-provider.ts | 60 ++--------- .../providers/v2/subgraph-provider.test.ts | 99 ++++++------------- 2 files changed, 38 insertions(+), 121 deletions(-) diff --git a/src/providers/v2/subgraph-provider.ts b/src/providers/v2/subgraph-provider.ts index 6b344557f..231636c82 100644 --- a/src/providers/v2/subgraph-provider.ts +++ b/src/providers/v2/subgraph-provider.ts @@ -251,7 +251,7 @@ export class V2SubgraphProvider implements IV2SubgraphProvider { (pool) => pool.token0.id == FEI || pool.token1.id == FEI || - this.isVirtualPairBaseV2Pool(pool.id) || + this.isVirtualPairBaseV2Pool(pool) || parseFloat(pool.trackedReserveETH) > this.trackedEthThreshold ); @@ -270,7 +270,7 @@ export class V2SubgraphProvider implements IV2SubgraphProvider { return ( pool.token0.id == FEI || pool.token1.id == FEI || - this.isVirtualPairBaseV2Pool(pool.id) || + this.isVirtualPairBaseV2Pool(pool) || parseFloat(pool.trackedReserveETH) > this.trackedEthThreshold || parseFloat(pool.reserveUSD) > this.untrackedUsdThreshold ); @@ -315,58 +315,14 @@ export class V2SubgraphProvider implements IV2SubgraphProvider { return poolsSanitized; } - // This method checks if a given pool is part of the Virtual Pair Base V2 Pool IDs. - public isVirtualPairBaseV2Pool(poolId: string): boolean { + // This method checks if a given pool contains the VIRTUAL token. + public isVirtualPairBaseV2Pool(pool: RawV2SubgraphPool): boolean { + const virtualTokenAddress = + '0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b'.toLowerCase(); return ( this.chainId === ChainId.BASE && - V2SubgraphProvider.VirtualPairBaseV2PoolIds.has(poolId.toLowerCase()) + (pool.token0.id.toLowerCase() === virtualTokenAddress || + pool.token1.id.toLowerCase() === virtualTokenAddress) ); } - - // TODO: Remove. Temporary fix to allow VIRTUAL/* V2 top pools to be included in the list until the subgraph is re-indexed. - // Note: All addresses here should be in lowercase. - private static readonly VirtualPairBaseV2PoolIds: Set = new Set([ - // Pool ID for VIRTUAL / GAME - '0xd418dfe7670c21f682e041f34250c114db5d7789', - // Pool ID for VIRTUAL / AIXBT - '0x7464850cc1cfb54a2223229b77b1bca2f888d946', - // Pool ID for VIRTUAL / LUNA - '0xa8e64fb120ce8796594670bae72279c8aa1e5359', - // Pool ID for VIRTUAL / TOSHI - '0x368ea6f645deb6d1a8692fe93c550d2834150865', - // Pool ID for VIRTUAL / KEYCAT - '0x3b9ddad7459fdfcfdb7117d53301182dd46b7042', - // Pool ID for VIRTUAL / VADER - '0xa1dddb82501e8fe2d92ad0e8ba331313f501de72', - // Pool ID for VIRTUAL / SEKOIA - '0x9e046c706f9ea6ce8f9287d30a15438c07f42d64', - // Pool ID for VIRTUAL / SAINT - '0x93d65a935e7c0f1aa153780e0db3ad08b9448c89', - // Pool ID for VIRTUAL / $mfer - '0xcaabcb34b48d29631c2b2c1d947d669fd8117a51', - // Pool ID for VIRTUAL / AIINU - '0xd2d670c91d14d9abcca7749f65e3181b2124e12d', - // Pool ID for VIRTUAL / CONVO - '0x3268d7efa254e648ffe968664e47ca226e27cd04', - // Pool ID for VIRTUAL / POLY - '0xde40586745a4e21efc0e8e0073cdd431ae88876a', - // Pool ID for VIRTUAL / GUAN - '0xff5bbda63e87210edd0ab9bc424086589d92e0af', - // Pool ID for VIRTUAL / SAM - '0xd72a82022134ba0d417c8d733139057e9bb9ea3f', - // Pool ID for VIRTUAL / VIRTU - '0xd9436bdaaf364181f1a39bb4c5e0a307c7353a01', - // Pool ID for VIRTUAL / CYI - '0x9d89c0cb73143927761534143747e26c47a1589f', - // Pool ID for VIRTUAL / VU - '0xfa65a76655f3c0641b79e89de3f51459c3727823', - // Pool ID for VIRTUAL / ECHO - '0x75cf1180ceb1cbef7508a4a4a0de940e2db4f087', - // Pool ID for VIRTUAL / MUSIC - '0xcef84d17513ede0ab951f7be9829e638a20bfba4', - // Pool ID for VIRTUAL / MISATO - '0x863e3a73c604d5038d41e2512272a73585f70017', - // Pool ID for VIRTUAL / NIKITA - '0xb9d782f2cfb755d47e2a5fa1350100c2ce3287ee', - ]); } diff --git a/test/unit/providers/v2/subgraph-provider.test.ts b/test/unit/providers/v2/subgraph-provider.test.ts index 88c16d143..714e15f69 100644 --- a/test/unit/providers/v2/subgraph-provider.test.ts +++ b/test/unit/providers/v2/subgraph-provider.test.ts @@ -1,19 +1,28 @@ +import { ChainId } from '@uniswap/sdk-core'; import dotenv from 'dotenv'; import { GraphQLClient } from 'graphql-request'; import sinon from 'sinon'; -import { - V2SubgraphProvider, -} from '../../../../src'; -import { ChainId } from '@uniswap/sdk-core'; +import { V2SubgraphProvider } from '../../../../src'; dotenv.config(); describe('SubgraphProvider V2', () => { - // VIRTUAL / AIXBT v2 pool - const virtualPairAddress = '0x7464850cc1cfb54a2223229b77b1bca2f888d946'; - // Random non-virtual pair address - const nonVirtualPairAddress = '0x7464850cc1cfb54a2223229b77b1bca2f888d947'; + const virtualTokenAddress = '0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b'; + function constructPool(withVirtualToken: boolean, trackedReservedEth: string) { + return { + id: '0xAddress1', + token0: { + id: withVirtualToken ? virtualTokenAddress : '0xToken0', + symbol: withVirtualToken ? 'VIRTUAL' : 'TOKEN0' + }, + token1: { id: '0xToken1', symbol: 'TOKEN1' }, + totalSupply: '100000', + trackedReserveETH: trackedReservedEth, + reserveETH: trackedReservedEth, + reserveUSD: '0.001', + }; + } let requestStubMainnet: sinon.SinonStub; let requestStubBase: sinon.SinonStub; @@ -32,15 +41,7 @@ describe('SubgraphProvider V2', () => { const firstCallResponse = { pairs: [ - { - id: nonVirtualPairAddress, - token0: { id: '0xToken0', symbol: 'TOKEN0' }, - token1: { id: '0xToken1', symbol: 'TOKEN1' }, - totalSupply: '100000', - trackedReserveETH: '1', - reserveETH: '1', - reserveUSD: '0.001', - }, + constructPool(false, '1'), ], }; const subsequentCallsResponse = { pairs: [] }; @@ -51,7 +52,7 @@ describe('SubgraphProvider V2', () => { const pools = await subgraphProviderBase.getPools(); expect(pools.length).toEqual(1); - expect(pools[0]!.id).toEqual(nonVirtualPairAddress); + expect(pools[0]!.token0.id).not.toEqual(virtualTokenAddress); }); it('fetches 0 subgraph pools if trackedReserveETH is below 0.025 threshold on Base', async () => { @@ -60,15 +61,7 @@ describe('SubgraphProvider V2', () => { const firstCallResponse = { pairs: [ - { - id: nonVirtualPairAddress, - token0: { id: '0xToken0', symbol: 'TOKEN0' }, - token1: { id: '0xToken1', symbol: 'TOKEN1' }, - totalSupply: '100000', - trackedReserveETH: '0.001', - reserveETH: '0.001', - reserveUSD: '0.001', - }, + constructPool(false, '0.001'), ], }; const subsequentCallsResponse = { pairs: [] }; @@ -87,15 +80,7 @@ describe('SubgraphProvider V2', () => { const firstCallResponse = { pairs: [ - { - id: virtualPairAddress, - token0: { id: '0xToken0', symbol: 'TOKEN0' }, - token1: { id: '0xToken1', symbol: 'TOKEN1' }, - totalSupply: '100000', - trackedReserveETH: '0.001', - reserveETH: '0.001', - reserveUSD: '0.001', - }, + constructPool(true, '0.001'), ], }; const subsequentCallsResponse = { pairs: [] }; @@ -106,7 +91,7 @@ describe('SubgraphProvider V2', () => { const pools = await subgraphProviderBase.getPools(); expect(pools.length).toEqual(1); - expect(pools[0]!.id).toEqual(virtualPairAddress); + expect(pools[0]!.token0.id).toEqual(virtualTokenAddress); }); @@ -116,15 +101,7 @@ describe('SubgraphProvider V2', () => { const firstCallResponse = { pairs: [ - { - id: nonVirtualPairAddress, - token0: { id: '0xToken0', symbol: 'TOKEN0' }, - token1: { id: '0xToken1', symbol: 'TOKEN1' }, - totalSupply: '100000', - trackedReserveETH: '1', - reserveETH: '1', - reserveUSD: '0.001', - }, + constructPool(false, '1'), ], }; const subsequentCallsResponse = { pairs: [] }; @@ -135,7 +112,7 @@ describe('SubgraphProvider V2', () => { const pools = await subgraphProviderMainnet.getPools(); expect(pools.length).toEqual(1); - expect(pools[0]!.id).toEqual(nonVirtualPairAddress); + expect(pools[0]!.token0.id).not.toEqual(virtualTokenAddress); }); it('fetches 0 subgraph pools if trackedReserveETH is below 0.025 threshold on Mainnet', async () => { @@ -144,15 +121,7 @@ describe('SubgraphProvider V2', () => { const firstCallResponse = { pairs: [ - { - id: nonVirtualPairAddress, - token0: { id: '0xToken0', symbol: 'TOKEN0' }, - token1: { id: '0xToken1', symbol: 'TOKEN1' }, - totalSupply: '100000', - trackedReserveETH: '0.001', - reserveETH: '0.001', - reserveUSD: '0.001', - }, + constructPool(false, '0.001'), ], }; const subsequentCallsResponse = { pairs: [] }; @@ -171,15 +140,7 @@ describe('SubgraphProvider V2', () => { const firstCallResponse = { pairs: [ - { - id: virtualPairAddress, - token0: { id: '0xToken0', symbol: 'TOKEN0' }, - token1: { id: '0xToken1', symbol: 'TOKEN1' }, - totalSupply: '100000', - trackedReserveETH: '0.001', - reserveETH: '0.001', - reserveUSD: '0.001', - }, + constructPool(true, '0.001'), ], }; const subsequentCallsResponse = { pairs: [] }; @@ -195,12 +156,12 @@ describe('SubgraphProvider V2', () => { it('isVirtualPairBaseV2Pool tests', async () => { // virtual / non-virtual pair address on mainnet fails const mainnetSubgraphProviderV2 = new V2SubgraphProvider(ChainId.MAINNET, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); - expect(mainnetSubgraphProviderV2.isVirtualPairBaseV2Pool(virtualPairAddress)).toBe(false); - expect(mainnetSubgraphProviderV2.isVirtualPairBaseV2Pool(nonVirtualPairAddress)).toBe(false); + expect(mainnetSubgraphProviderV2.isVirtualPairBaseV2Pool(constructPool(true, '1'))).toBe(false); + expect(mainnetSubgraphProviderV2.isVirtualPairBaseV2Pool(constructPool(false, '1'))).toBe(false); // virtual pair address on base succeeds / non-virtual fails const baseSubgraphProviderV2 = new V2SubgraphProvider(ChainId.BASE, 2, 30000, true, 1000, 0.01, Number.MAX_VALUE, 'test_url'); - expect(baseSubgraphProviderV2.isVirtualPairBaseV2Pool(virtualPairAddress)).toBe(true); - expect(baseSubgraphProviderV2.isVirtualPairBaseV2Pool(nonVirtualPairAddress)).toBe(false); + expect(baseSubgraphProviderV2.isVirtualPairBaseV2Pool(constructPool(true, '1'))).toBe(true); + expect(baseSubgraphProviderV2.isVirtualPairBaseV2Pool(constructPool(false, '1'))).toBe(false); }); }) From edc92dd63cf1f03f1c148e09707d1f3e64120ccd Mon Sep 17 00:00:00 2001 From: xrsv Date: Tue, 3 Dec 2024 09:21:06 -0800 Subject: [PATCH 06/17] chore: bump sor to 4.8.4 - fix(subgraph): include all VIRTUAL/* pools in V2 Base subgraph (#774) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c03c91490..b24f5604f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.3", + "version": "4.8.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.8.3", + "version": "4.8.4", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index e746d5d3f..a59bdbedc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.3", + "version": "4.8.4", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", From 936a873b3c6fe5ff204546d6353953e1c429a66a Mon Sep 17 00:00:00 2001 From: xrsv Date: Tue, 3 Dec 2024 16:14:55 -0800 Subject: [PATCH 07/17] fix: Use syntheticGasCostInTermsOfQuoteToken only if within 30% of gasCostInTermsOfQuoteToken (#776) * fix: Use syntheticGasCostInTermsOfQuoteToken only if within 30% of gasCostInTermsOfQuoteToken * asFraction * multiply percent * increase gas test deviation percent --- .../gas-models/tick-based-heuristic-gas-model.ts | 10 +++++++--- .../alpha-router/alpha-router.integration.test.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/routers/alpha-router/gas-models/tick-based-heuristic-gas-model.ts b/src/routers/alpha-router/gas-models/tick-based-heuristic-gas-model.ts index 992f43310..fa4475b69 100644 --- a/src/routers/alpha-router/gas-models/tick-based-heuristic-gas-model.ts +++ b/src/routers/alpha-router/gas-models/tick-based-heuristic-gas-model.ts @@ -1,6 +1,6 @@ import { BigNumber } from '@ethersproject/bignumber'; import { BaseProvider } from '@ethersproject/providers'; -import { ChainId, Price } from '@uniswap/sdk-core'; +import { ChainId, Percent, Price } from '@uniswap/sdk-core'; import { Pool } from '@uniswap/v3-sdk'; import { CurrencyAmount, log, WRAPPED_NATIVE_CURRENCY } from '../../../util'; import { calculateL1GasFeesHelper } from '../../../util/gas-factory-helpers'; @@ -205,12 +205,16 @@ export abstract class TickBasedHeuristicGasModelFactory< // Note that the syntheticGasCost being lessThan the original quoted value is not always strictly better // e.g. the scenario where the amountToken/ETH pool is very illiquid as well and returns an extremely small number // however, it is better to have the gasEstimation be almost 0 than almost infinity, as the user will still receive a quote + // Only use syntheticGasCostInTermsOfQuoteToken if it's within 30% of the original gasCostInTermsOfQuoteToken as a safeguard. if ( syntheticGasCostInTermsOfQuoteToken !== null && - (gasCostInTermsOfQuoteToken === null || + (gasCostInTermsOfQuoteToken === null || ( syntheticGasCostInTermsOfQuoteToken.lessThan( gasCostInTermsOfQuoteToken.asFraction - )) + ) && + gasCostInTermsOfQuoteToken.subtract(syntheticGasCostInTermsOfQuoteToken) + .lessThan(gasCostInTermsOfQuoteToken.multiply(new Percent(30, 100)).asFraction) + )) ) { log.info( { diff --git a/test/integ/routers/alpha-router/alpha-router.integration.test.ts b/test/integ/routers/alpha-router/alpha-router.integration.test.ts index 8162f0f8a..27895d5eb 100644 --- a/test/integ/routers/alpha-router/alpha-router.integration.test.ts +++ b/test/integ/routers/alpha-router/alpha-router.integration.test.ts @@ -140,7 +140,7 @@ const GAS_ESTIMATE_DEVIATION_PERCENT: { [chainId in ChainId]: number } = { [ChainId.MAINNET]: 50, [ChainId.GOERLI]: 62, [ChainId.SEPOLIA]: 75, - [ChainId.OPTIMISM]: 71, + [ChainId.OPTIMISM]: 75, [ChainId.OPTIMISM_GOERLI]: 30, [ChainId.OPTIMISM_SEPOLIA]: 30, [ChainId.ARBITRUM_ONE]: 53, From 6f61a42d011c68793113ac75102cca2aadd80d90 Mon Sep 17 00:00:00 2001 From: xrsv Date: Tue, 3 Dec 2024 16:20:41 -0800 Subject: [PATCH 08/17] chore: bump sor to 4.8.5 - fix: Use syntheticGasCostInTermsOfQuoteToken only if within 30% of gasCostInTermsOfQuoteToken (#777) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b24f5604f..41fbb9ca0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.4", + "version": "4.8.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.8.4", + "version": "4.8.5", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index a59bdbedc..3f6c94df3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.4", + "version": "4.8.5", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", From bc99b7cf804398c3b31ae16c5d3491b9152e6c02 Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:05:32 -0800 Subject: [PATCH 09/17] fix: pass latest to tenderly node (#778) - **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) Bug fix - **What is the current behavior?** (You can also link to an open issue here) Tenderly node block can fall behind RPC node block, hence node simulation failed with "not found". - **What is the new behavior (if this is a feature change)?** We pass in latest for tenderly node simulation. - **Other information**: --- src/providers/tenderly-simulation-provider.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/providers/tenderly-simulation-provider.ts b/src/providers/tenderly-simulation-provider.ts index b78d63dac..0fd945914 100644 --- a/src/providers/tenderly-simulation-provider.ts +++ b/src/providers/tenderly-simulation-provider.ts @@ -781,9 +781,11 @@ export class TenderlySimulator extends Simulator { this.chainId, this.tenderlyNodeApiKey ); - const blockNumber = swap.block_number - ? BigNumber.from(swap.block_number).toHexString().replace('0x0', '0x') - : 'latest'; + // TODO: ROUTE-362 - Revisit tenderly node simulation hardcode latest block number + // https://linear.app/uniswap/issue/ROUTE-362/revisit-tenderly-node-simulation-hardcode-latest-block-number + const blockNumber = // swap.block_number + // ? BigNumber.from(swap.block_number).toHexString().replace('0x0', '0x') + 'latest'; const body: TenderlyNodeEstimateGasBundleBody = { id: 1, jsonrpc: '2.0', From 740ec6ad91532dddff75c0b98a2aa7aab845d4ee Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:14:06 -0800 Subject: [PATCH 10/17] 4.8.6 (#779) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 41fbb9ca0..8b3b51de0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.5", + "version": "4.8.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.8.5", + "version": "4.8.6", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index 3f6c94df3..ebcb38965 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.5", + "version": "4.8.6", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", From 94375a705c14f3390c1cd43bcbae98b4a089eb3c Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:45:06 -0800 Subject: [PATCH 11/17] chore: bump sor to 4.8.7 - feat: tenderly sim status SlippageTooLow (#780) * feat: tenderly sim status SlippageTooLow * add unit test * feedback * 4.8.7 --- package-lock.json | 4 +- package.json | 2 +- src/providers/simulation-provider.ts | 1 + src/providers/tenderly-simulation-provider.ts | 17 +++++++ .../tick-based-heuristic-gas-model.ts | 13 ++++-- src/util/tenderlySimulationErrorBreakDown.ts | 21 +++++++++ .../tenderlySimulationErrorBreakDown.test.ts | 46 +++++++++++++++++++ 7 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 src/util/tenderlySimulationErrorBreakDown.ts create mode 100644 test/unit/util/tenderlySimulationErrorBreakDown.test.ts diff --git a/package-lock.json b/package-lock.json index 8b3b51de0..265ead981 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.6", + "version": "4.8.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.8.6", + "version": "4.8.7", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index ebcb38965..e897978d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.6", + "version": "4.8.7", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/src/providers/simulation-provider.ts b/src/providers/simulation-provider.ts index 4ae6e1aec..b2143297e 100644 --- a/src/providers/simulation-provider.ts +++ b/src/providers/simulation-provider.ts @@ -32,6 +32,7 @@ export enum SimulationStatus { InsufficientBalance = 3, NotApproved = 4, SystemDown = 5, + SlippageTooLow = 6, } /** diff --git a/src/providers/tenderly-simulation-provider.ts b/src/providers/tenderly-simulation-provider.ts index 0fd945914..3e34790ff 100644 --- a/src/providers/tenderly-simulation-provider.ts +++ b/src/providers/tenderly-simulation-provider.ts @@ -31,6 +31,7 @@ import { initSwapRouteFromExisting, } from '../util/gas-factory-helpers'; +import { breakDownTenderlySimulationError } from '../util/tenderlySimulationErrorBreakDown'; import { EthEstimateGasSimulator } from './eth-estimate-gas-provider'; import { IPortionProvider } from './portion-provider'; import { @@ -464,6 +465,22 @@ export class TenderlySimulator extends Simulator { 2 )}.` ); + + if ( + resp && + resp.result && + resp.result.length >= 3 && + (resp.result[2] as JsonRpcError).error && + (resp.result[2] as JsonRpcError).error.data + ) { + return { + ...swapRoute, + simulationStatus: breakDownTenderlySimulationError( + (resp.result[2] as JsonRpcError).error.data + ), + }; + } + return { ...swapRoute, simulationStatus: SimulationStatus.Failed }; } diff --git a/src/routers/alpha-router/gas-models/tick-based-heuristic-gas-model.ts b/src/routers/alpha-router/gas-models/tick-based-heuristic-gas-model.ts index fa4475b69..1cbc15b84 100644 --- a/src/routers/alpha-router/gas-models/tick-based-heuristic-gas-model.ts +++ b/src/routers/alpha-router/gas-models/tick-based-heuristic-gas-model.ts @@ -208,13 +208,16 @@ export abstract class TickBasedHeuristicGasModelFactory< // Only use syntheticGasCostInTermsOfQuoteToken if it's within 30% of the original gasCostInTermsOfQuoteToken as a safeguard. if ( syntheticGasCostInTermsOfQuoteToken !== null && - (gasCostInTermsOfQuoteToken === null || ( - syntheticGasCostInTermsOfQuoteToken.lessThan( + (gasCostInTermsOfQuoteToken === null || + (syntheticGasCostInTermsOfQuoteToken.lessThan( gasCostInTermsOfQuoteToken.asFraction ) && - gasCostInTermsOfQuoteToken.subtract(syntheticGasCostInTermsOfQuoteToken) - .lessThan(gasCostInTermsOfQuoteToken.multiply(new Percent(30, 100)).asFraction) - )) + gasCostInTermsOfQuoteToken + .subtract(syntheticGasCostInTermsOfQuoteToken) + .lessThan( + gasCostInTermsOfQuoteToken.multiply(new Percent(30, 100)) + .asFraction + ))) ) { log.info( { diff --git a/src/util/tenderlySimulationErrorBreakDown.ts b/src/util/tenderlySimulationErrorBreakDown.ts new file mode 100644 index 000000000..17ce323b2 --- /dev/null +++ b/src/util/tenderlySimulationErrorBreakDown.ts @@ -0,0 +1,21 @@ +import { SimulationStatus } from '../providers'; + +export function breakDownTenderlySimulationError( + data?: string +): SimulationStatus { + if (data) { + switch (data) { + case '0x739dbe52': // V3TooMuchRequested + case '0x39d35496': // V3TooLittleReceived + case '0x849eaf98': // V2TooLittleReceived + case '0x8ab0bc16': // V2TooMuchRequested + case '0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000025556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54000000000000000000000000000000000000000000000000000000': // INSUFFICIENT_OUTPUT_AMOUNT + case '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034949410000000000000000000000000000000000000000000000000000000000': // IIA + return SimulationStatus.SlippageTooLow; + default: // we don't know why onchain execution reverted, just return generic failed. + return SimulationStatus.Failed; + } + } + + return SimulationStatus.Failed; +} diff --git a/test/unit/util/tenderlySimulationErrorBreakDown.test.ts b/test/unit/util/tenderlySimulationErrorBreakDown.test.ts new file mode 100644 index 000000000..e1a5ac326 --- /dev/null +++ b/test/unit/util/tenderlySimulationErrorBreakDown.test.ts @@ -0,0 +1,46 @@ +import { + breakDownTenderlySimulationError +} from '../../../src/util/tenderlySimulationErrorBreakDown'; +import { SimulationStatus } from '../../../build/main'; + +describe('tenderly simulation error break down', () => { + it('V3TooMuchRequested', async () => { + const simulationStatus = breakDownTenderlySimulationError('0x739dbe52') + expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) + }) + + it('V3TooLittleReceived', async () => { + const simulationStatus = breakDownTenderlySimulationError('0x39d35496') + expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) + }) + + it('V2TooLittleReceived', async () => { + const simulationStatus = breakDownTenderlySimulationError('0x849eaf98') + expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) + }) + + it('V2TooMuchRequested', async () => { + const simulationStatus = breakDownTenderlySimulationError('0x8ab0bc16') + expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) + }) + + it('INSUFFICIENT_OUTPUT_AMOUNT', async () => { + const simulationStatus = breakDownTenderlySimulationError('0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000025556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54000000000000000000000000000000000000000000000000000000'); + expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) + }) + + it('IIA', async () => { + const simulationStatus = breakDownTenderlySimulationError('0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034949410000000000000000000000000000000000000000000000000000000000'); + expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) + }) + + it('InsufficientToken', () => { + const simulationStatus = breakDownTenderlySimulationError('0x675cae38'); + expect(simulationStatus).toEqual(SimulationStatus.Failed); + }); + + it('unknown data', () => { + const simulationStatus = breakDownTenderlySimulationError(undefined); + expect(simulationStatus).toEqual(SimulationStatus.Failed); + }); +}); From cd071eedac678e88dac02d8202c0d1cd41bf23c0 Mon Sep 17 00:00:00 2001 From: xrsv Date: Mon, 9 Dec 2024 11:50:07 -0800 Subject: [PATCH 12/17] chore: add gas estimation quality metric (#767) * chore: add gas estimation quality metric * fix * metric in gas units * extra metric * fix test --- src/providers/eth-estimate-gas-provider.ts | 4 ++ src/providers/tenderly-simulation-provider.ts | 4 ++ src/util/gas-factory-helpers.ts | 59 +++++++++++++++++++ .../providers/simulation-provider.test.ts | 1 + 4 files changed, 68 insertions(+) diff --git a/src/providers/eth-estimate-gas-provider.ts b/src/providers/eth-estimate-gas-provider.ts index 0fdc24bf5..8a6cf545e 100644 --- a/src/providers/eth-estimate-gas-provider.ts +++ b/src/providers/eth-estimate-gas-provider.ts @@ -12,6 +12,7 @@ import { BEACON_CHAIN_DEPOSIT_ADDRESS, log } from '../util'; import { calculateGasUsed, initSwapRouteFromExisting, + logGasEstimationVsSimulationMetrics, } from '../util/gas-factory-helpers'; import { IPortionProvider } from './portion-provider'; @@ -125,6 +126,9 @@ export class EthEstimateGasSimulator extends Simulator { this.provider, providerConfig ); + + logGasEstimationVsSimulationMetrics(route, estimatedGasUsed, this.chainId); + return { ...initSwapRouteFromExisting( route, diff --git a/src/providers/tenderly-simulation-provider.ts b/src/providers/tenderly-simulation-provider.ts index 3e34790ff..75103d2c4 100644 --- a/src/providers/tenderly-simulation-provider.ts +++ b/src/providers/tenderly-simulation-provider.ts @@ -29,6 +29,7 @@ import { APPROVE_TOKEN_FOR_TRANSFER } from '../util/callData'; import { calculateGasUsed, initSwapRouteFromExisting, + logGasEstimationVsSimulationMetrics, } from '../util/gas-factory-helpers'; import { breakDownTenderlySimulationError } from '../util/tenderlySimulationErrorBreakDown'; @@ -707,6 +708,9 @@ export class TenderlySimulator extends Simulator { this.provider, providerConfig ); + + logGasEstimationVsSimulationMetrics(swapRoute, estimatedGasUsed, chainId); + return { ...initSwapRouteFromExisting( swapRoute, diff --git a/src/util/gas-factory-helpers.ts b/src/util/gas-factory-helpers.ts index d95e0efa2..7a4869108 100644 --- a/src/util/gas-factory-helpers.ts +++ b/src/util/gas-factory-helpers.ts @@ -19,6 +19,8 @@ import { GasModelProviderConfig, getQuoteThroughNativePool, MethodParameters, + metric, + MetricLoggerUnit, MixedRouteWithValidQuote, RouteWithValidQuote, SwapOptions, @@ -690,3 +692,60 @@ export const calculateL1GasFeesHelper = async ( return calculateArbitrumToL1FeeFromCalldata(data, gasData, chainId); } }; + +// Logs metrics about the diff between original estimatedGasUsed based on heuristics and the simulated gas used. +// This will help us track the quality of our gas estimation quality per chain. +export const logGasEstimationVsSimulationMetrics = ( + route: SwapRoute, + simulationGasUsed: BigNumber, + chainId: ChainId +) => { + try { + // Log the diff between original estimatedGasUsed and the simulated gas used + const estimatedGasUsed = route.estimatedGasUsed.toNumber(); + const simulatedGasUsed = simulationGasUsed.toNumber(); + const diff = estimatedGasUsed - simulatedGasUsed; + const absDiff = Math.abs(estimatedGasUsed - simulatedGasUsed); + const misEstimatePercent = (diff / estimatedGasUsed) * 100; + const misEstimateAbsPercent = (absDiff / estimatedGasUsed) * 100; + + log.info( + { + estimatedGasUsed: estimatedGasUsed, + simulatedGasUsed: simulatedGasUsed, + absDiff: absDiff, + diff: diff, + }, + 'Gas used diff between estimatedGasUsed and simulatedGasUsed' + ); + log.info( + { + misEstimateAbsPercent: misEstimateAbsPercent, + }, + 'Gas used mis-estimate percent' + ); + + metric.putMetric( + `TenderlySimulationGasUsed_AbsDiff_Chain_${chainId}`, + absDiff, + MetricLoggerUnit.Count + ); + metric.putMetric( + `TenderlySimulationGasUsed_MisEstimateAbsPercent_Chain_${chainId}`, + misEstimateAbsPercent, + MetricLoggerUnit.Count + ); + + const label = diff >= 0 ? 'OverEstimate' : 'UnderEstimate'; + metric.putMetric( + `TenderlySimulationGasUsed_${label}Percent_Chain_${chainId}`, + misEstimatePercent, + MetricLoggerUnit.Count + ); + } catch (err) { + log.error( + { err: err }, + 'Failed to log diff between original estimatedGasUsed and the simulated gas used' + ); + } +}; diff --git a/test/unit/providers/simulation-provider.test.ts b/test/unit/providers/simulation-provider.test.ts index 0b201117a..92d09aecf 100644 --- a/test/unit/providers/simulation-provider.test.ts +++ b/test/unit/providers/simulation-provider.test.ts @@ -69,6 +69,7 @@ jest.mock('../../../src/util/gas-factory-helpers', () => ({ quoteGasAdjusted, }; }, + logGasEstimationVsSimulationMetrics: jest.fn(), })); const provider = new JsonRpcProvider(); From 00d61cbcb4abc4fe5af578996d2c0448a57a1c33 Mon Sep 17 00:00:00 2001 From: xrsv Date: Tue, 10 Dec 2024 09:53:16 -0800 Subject: [PATCH 13/17] chore: bump sor to 4.8.8 - chore: add gas estimation quality metric (#781) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 265ead981..00c54a47a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.7", + "version": "4.8.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.8.7", + "version": "4.8.8", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index e897978d9..a4037779b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.7", + "version": "4.8.8", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", From c77d04d334cc1c6694bd74d88287cc5b6e3a7425 Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:26:33 -0800 Subject: [PATCH 14/17] chore: bump sor to 4.9.0 - feat: migrate all other L2s tenderly sim from API to node (#782) * feat: migrate all other L2s tenderly sim from API to node * 4.9.0 * add tenderly allowed L2s --- package-lock.json | 4 ++-- package.json | 2 +- src/providers/tenderly-simulation-provider.ts | 15 +++++++++++++++ .../alpha-router/alpha-router.integration.test.ts | 4 ++-- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00c54a47a..7350ed849 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.8", + "version": "4.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.8.8", + "version": "4.9.0", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index a4037779b..9d8b88abc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.8.8", + "version": "4.9.0", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/src/providers/tenderly-simulation-provider.ts b/src/providers/tenderly-simulation-provider.ts index 75103d2c4..da4c182e3 100644 --- a/src/providers/tenderly-simulation-provider.ts +++ b/src/providers/tenderly-simulation-provider.ts @@ -140,6 +140,18 @@ const TENDERLY_NODE_API = (chainId: ChainId, tenderlyNodeApiKey: string) => { return `https://mainnet.gateway.tenderly.co/${tenderlyNodeApiKey}`; case ChainId.BASE: return `https://base.gateway.tenderly.co/${tenderlyNodeApiKey}`; + case ChainId.ARBITRUM_ONE: + return `https://arbitrum-one.gateway.tenderly.co/${tenderlyNodeApiKey}`; + case ChainId.OPTIMISM: + return `https://optimism.gateway.tenderly.co/${tenderlyNodeApiKey}`; + case ChainId.POLYGON: + return `https://polygon.gateway.tenderly.co/${tenderlyNodeApiKey}`; + case ChainId.AVALANCHE: + return `https://avalanche.gateway.tenderly.co/${tenderlyNodeApiKey}`; + case ChainId.BLAST: + return `https://blast.gateway.tenderly.co/${tenderlyNodeApiKey}`; + case ChainId.WORLDCHAIN: + return `https://worldchain-mainnet.gateway.tenderly.co/${tenderlyNodeApiKey}`; default: throw new Error( `ChainId ${chainId} does not correspond to a tenderly node endpoint` @@ -151,6 +163,9 @@ export const TENDERLY_NOT_SUPPORTED_CHAINS = [ ChainId.CELO, ChainId.CELO_ALFAJORES, ChainId.ZKSYNC, + // tenderly node RPC supports BNB and ZORA upon request, we will make them available + ChainId.BNB, + ChainId.ZORA, ]; // We multiply tenderly gas limit by this to overestimate gas limit diff --git a/test/integ/routers/alpha-router/alpha-router.integration.test.ts b/test/integ/routers/alpha-router/alpha-router.integration.test.ts index 27895d5eb..bddaad42f 100644 --- a/test/integ/routers/alpha-router/alpha-router.integration.test.ts +++ b/test/integ/routers/alpha-router/alpha-router.integration.test.ts @@ -746,7 +746,7 @@ describe('alpha router integration', () => { // we will start using the new tenderly node endpoint in SOR integ-test suite at 100% 3000, 100, - [ChainId.MAINNET, ChainId.BASE] + [ChainId.MAINNET, ChainId.BASE, ChainId.ARBITRUM_ONE, ChainId.OPTIMISM, ChainId.POLYGON, ChainId.AVALANCHE, ChainId.BLAST, ChainId.WORLDCHAIN] ); const simulator = new FallbackTenderlySimulator( @@ -3653,7 +3653,7 @@ describe('quote for other networks', () => { // we will start using the new tenderly node endpoint in SOR integ-test suite at 100% 3000, 100, - [ChainId.MAINNET, ChainId.BASE] + [ChainId.MAINNET, ChainId.BASE, ChainId.ARBITRUM_ONE, ChainId.OPTIMISM, ChainId.POLYGON, ChainId.AVALANCHE, ChainId.BLAST, ChainId.WORLDCHAIN] ); const simulator = new FallbackTenderlySimulator( From 31ec714ae5513f5a8459649325e2ba3bc5005122 Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Thu, 12 Dec 2024 08:10:42 -0800 Subject: [PATCH 15/17] fix: arb tenderly node endpoint (#783) * fix: arb tenderly node endpoint * 4.9.1 --- package-lock.json | 4 ++-- package.json | 2 +- src/providers/tenderly-simulation-provider.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7350ed849..99547b9ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.9.0", + "version": "4.9.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.9.0", + "version": "4.9.1", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index 9d8b88abc..1dd2fcb58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.9.0", + "version": "4.9.1", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/src/providers/tenderly-simulation-provider.ts b/src/providers/tenderly-simulation-provider.ts index da4c182e3..238116be2 100644 --- a/src/providers/tenderly-simulation-provider.ts +++ b/src/providers/tenderly-simulation-provider.ts @@ -141,7 +141,7 @@ const TENDERLY_NODE_API = (chainId: ChainId, tenderlyNodeApiKey: string) => { case ChainId.BASE: return `https://base.gateway.tenderly.co/${tenderlyNodeApiKey}`; case ChainId.ARBITRUM_ONE: - return `https://arbitrum-one.gateway.tenderly.co/${tenderlyNodeApiKey}`; + return `https://arbitrum.gateway.tenderly.co/${tenderlyNodeApiKey}`; case ChainId.OPTIMISM: return `https://optimism.gateway.tenderly.co/${tenderlyNodeApiKey}`; case ChainId.POLYGON: From 69eed4addc9974d56ec2b4a0987b61ff7d3af5ab Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:20:37 -0800 Subject: [PATCH 16/17] fix: map virtual swap InsufficientToken as SlippageTooLow (#784) --- src/providers/tenderly-simulation-provider.ts | 4 +++ src/providers/token-provider.ts | 7 +++++ src/util/tenderlySimulationErrorBreakDown.ts | 17 ++++++++++- .../tenderlySimulationErrorBreakDown.test.ts | 28 +++++++++++++------ 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/providers/tenderly-simulation-provider.ts b/src/providers/tenderly-simulation-provider.ts index 238116be2..b2576fe7a 100644 --- a/src/providers/tenderly-simulation-provider.ts +++ b/src/providers/tenderly-simulation-provider.ts @@ -307,6 +307,8 @@ export class TenderlySimulator extends Simulator { ): Promise { const currencyIn = swapRoute.trade.inputAmount.currency; const tokenIn = currencyIn.wrapped; + const currencyOut = swapRoute.trade.outputAmount.currency; + const tokenOut = currencyOut.wrapped; const chainId = this.chainId; if (TENDERLY_NOT_SUPPORTED_CHAINS.includes(chainId)) { @@ -492,6 +494,8 @@ export class TenderlySimulator extends Simulator { return { ...swapRoute, simulationStatus: breakDownTenderlySimulationError( + tokenIn, + tokenOut, (resp.result[2] as JsonRpcError).error.data ), }; diff --git a/src/providers/token-provider.ts b/src/providers/token-provider.ts index 92f8e0dc7..036ee454d 100644 --- a/src/providers/token-provider.ts +++ b/src/providers/token-provider.ts @@ -601,6 +601,13 @@ export const USDC_NATIVE_BASE = new Token( 'USDbC', 'USD Base Coin' ); +export const VIRTUAL_BASE = new Token( + ChainId.BASE, + '0x0b3e328455c4059EEb9e3f84b5543F74E24e7E1b', + 18, + 'VIRTUAL', + 'Virtual Protocol' +); // Base Goerli Tokens export const USDC_BASE_GOERLI = new Token( diff --git a/src/util/tenderlySimulationErrorBreakDown.ts b/src/util/tenderlySimulationErrorBreakDown.ts index 17ce323b2..9eb82a286 100644 --- a/src/util/tenderlySimulationErrorBreakDown.ts +++ b/src/util/tenderlySimulationErrorBreakDown.ts @@ -1,6 +1,9 @@ -import { SimulationStatus } from '../providers'; +import { Token } from '@uniswap/sdk-core'; +import { SimulationStatus, VIRTUAL_BASE } from '../providers'; export function breakDownTenderlySimulationError( + tokenIn: Token, + tokenOut: Token, data?: string ): SimulationStatus { if (data) { @@ -12,6 +15,18 @@ export function breakDownTenderlySimulationError( case '0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000025556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54000000000000000000000000000000000000000000000000000000': // INSUFFICIENT_OUTPUT_AMOUNT case '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034949410000000000000000000000000000000000000000000000000000000000': // IIA return SimulationStatus.SlippageTooLow; + case '0x675cae38': // InsufficientToken + if ( + tokenIn.address.toLowerCase() === + VIRTUAL_BASE.address.toLowerCase() || + tokenOut.address.toLowerCase() === VIRTUAL_BASE.address.toLowerCase() + ) { + // if this is from virtual, we'd guess it's due to slippage too low, although it might be due to something else + return SimulationStatus.SlippageTooLow; + } + + // Otherwise we don't wanna guess, just return generic failed. + return SimulationStatus.Failed; default: // we don't know why onchain execution reverted, just return generic failed. return SimulationStatus.Failed; } diff --git a/test/unit/util/tenderlySimulationErrorBreakDown.test.ts b/test/unit/util/tenderlySimulationErrorBreakDown.test.ts index e1a5ac326..be223be84 100644 --- a/test/unit/util/tenderlySimulationErrorBreakDown.test.ts +++ b/test/unit/util/tenderlySimulationErrorBreakDown.test.ts @@ -1,46 +1,56 @@ import { breakDownTenderlySimulationError } from '../../../src/util/tenderlySimulationErrorBreakDown'; -import { SimulationStatus } from '../../../build/main'; +import { + SimulationStatus, + USDC_MAINNET, + USDT_MAINNET, + VIRTUAL_BASE +} from '../../../build/main'; describe('tenderly simulation error break down', () => { it('V3TooMuchRequested', async () => { - const simulationStatus = breakDownTenderlySimulationError('0x739dbe52') + const simulationStatus = breakDownTenderlySimulationError(USDC_MAINNET, USDT_MAINNET, '0x739dbe52') expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) }) it('V3TooLittleReceived', async () => { - const simulationStatus = breakDownTenderlySimulationError('0x39d35496') + const simulationStatus = breakDownTenderlySimulationError(USDC_MAINNET, USDT_MAINNET, '0x39d35496') expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) }) it('V2TooLittleReceived', async () => { - const simulationStatus = breakDownTenderlySimulationError('0x849eaf98') + const simulationStatus = breakDownTenderlySimulationError(USDC_MAINNET, USDT_MAINNET, '0x849eaf98') expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) }) it('V2TooMuchRequested', async () => { - const simulationStatus = breakDownTenderlySimulationError('0x8ab0bc16') + const simulationStatus = breakDownTenderlySimulationError(USDC_MAINNET, USDT_MAINNET, '0x8ab0bc16') expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) }) it('INSUFFICIENT_OUTPUT_AMOUNT', async () => { - const simulationStatus = breakDownTenderlySimulationError('0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000025556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54000000000000000000000000000000000000000000000000000000'); + const simulationStatus = breakDownTenderlySimulationError(USDC_MAINNET, USDT_MAINNET, '0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000025556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54000000000000000000000000000000000000000000000000000000'); expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) }) it('IIA', async () => { - const simulationStatus = breakDownTenderlySimulationError('0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034949410000000000000000000000000000000000000000000000000000000000'); + const simulationStatus = breakDownTenderlySimulationError(USDC_MAINNET, USDT_MAINNET, '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034949410000000000000000000000000000000000000000000000000000000000'); expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow) }) it('InsufficientToken', () => { - const simulationStatus = breakDownTenderlySimulationError('0x675cae38'); + const simulationStatus = breakDownTenderlySimulationError(USDC_MAINNET, USDT_MAINNET, '0x675cae38'); expect(simulationStatus).toEqual(SimulationStatus.Failed); }); + it('InsufficientToken Virtual', () => { + const simulationStatus = breakDownTenderlySimulationError(USDC_MAINNET, VIRTUAL_BASE, '0x675cae38'); + expect(simulationStatus).toEqual(SimulationStatus.SlippageTooLow); + }); + it('unknown data', () => { - const simulationStatus = breakDownTenderlySimulationError(undefined); + const simulationStatus = breakDownTenderlySimulationError(USDC_MAINNET, USDT_MAINNET, undefined); expect(simulationStatus).toEqual(SimulationStatus.Failed); }); }); From 6084c428e96dd65e8a2bc667dcb86454fc59461e Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:38:22 -0800 Subject: [PATCH 17/17] 4.9.2 (#785) - **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) Release https://github.com/Uniswap/smart-order-router/pull/784 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 99547b9ae..c13355f20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@uniswap/smart-order-router", - "version": "4.9.1", + "version": "4.9.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.9.1", + "version": "4.9.2", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", diff --git a/package.json b/package.json index 1dd2fcb58..fc9d7152f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.9.1", + "version": "4.9.2", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts",