From 1bd65bac7658350b4112a8b65068365a0961485a Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Thu, 30 Jan 2025 18:17:11 -0800 Subject: [PATCH 01/13] fix(router-sdk): fix MixedRoute tokenpath (#287) ## PR Scope Please title your PR according to the following types and scopes following [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/): - `fix(SDK name):` will trigger a patch version - `chore():` will not trigger any release and should be used for internal repo changes - `(public):` will trigger a patch version for non-code changes (e.g. README changes) - `feat(SDK name):` will trigger a minor version - `feat(breaking):` will trigger a major version for a breaking change ## Description _[Summary of the change, motivation, and context]_ ## How Has This Been Tested? _[e.g. Manually, E2E tests, unit tests, Storybook]_ ## Are there any breaking changes? _[e.g. Type definitions, API definitions]_ If there are breaking changes, please ensure you bump the major version Bump the major version (by using the title `feat(breaking): ...`), post a notice in #eng-sdks, and explicitly notify all Uniswap Labs consumers of the SDK. ## (Optional) Feedback Focus _[Specific parts of this PR you'd like feedback on, or that reviewers should pay closer attention to]_ ## (Optional) Follow Ups _[Things that weren't addressed in this PR, ways you plan to build on this work, or other ways this work could be extended]_ --- .../router-sdk/src/entities/mixedRoute/route.test.ts | 12 +++++++++--- sdks/router-sdk/src/entities/mixedRoute/route.ts | 12 ++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/sdks/router-sdk/src/entities/mixedRoute/route.test.ts b/sdks/router-sdk/src/entities/mixedRoute/route.test.ts index 0887147fe..4ca7b0b67 100644 --- a/sdks/router-sdk/src/entities/mixedRoute/route.test.ts +++ b/sdks/router-sdk/src/entities/mixedRoute/route.test.ts @@ -20,6 +20,7 @@ describe('MixedRoute', () => { const pool_v4_0_weth = new V4Pool(token0, weth, FeeAmount.MEDIUM, 60, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0, []) const pool_v4_1_eth = new V4Pool(token1, ETHER, FeeAmount.MEDIUM, 60, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0, []) const pool_v4_0_1 = new V4Pool(token0, token1, FeeAmount.MEDIUM, 60, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0, []) + const pool_v4_weth_eth = new V4Pool(weth, ETHER, 0, 0, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0) const pool_v3_0_1 = new V3Pool(token0, token1, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, []) const pool_v3_0_weth = new V3Pool(token0, weth, FeeAmount.MEDIUM, encodeSqrtRatioX96(1, 1), 0, 0, []) @@ -34,6 +35,11 @@ describe('MixedRoute', () => { const pair_2_3 = new Pair(CurrencyAmount.fromRawAmount(token2, '100'), CurrencyAmount.fromRawAmount(token3, '200')) describe('path', () => { + it('real v3 weth pool and fake v4 eth/weth pool', () => { + const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_weth_eth], token0, ETHER) + expect(route.path).toEqual([token0, weth, ETHER]) + }) + it('wraps pure v3 route object and successfully constructs a path from the tokens', () => { /// @dev since the MixedRoute sdk object lives here in router-sdk we don't need to wrap it const routeOriginal = new MixedRouteSDK([pool_v3_0_1], token0, token1) @@ -84,9 +90,9 @@ describe('MixedRoute', () => { }) it('wraps mixed route object with mixed v4 route that converts WETH -> ETH ', () => { - const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_1_eth], token0, token1) - expect(route.pools).toEqual([pool_v3_0_weth, pool_v4_1_eth]) - expect(route.path).toEqual([token0, weth, token1]) + const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_weth_eth, pool_v4_1_eth], token0, token1) + expect(route.pools).toEqual([pool_v3_0_weth, pool_v4_weth_eth, pool_v4_1_eth]) + expect(route.path).toEqual([token0, weth, ETHER, token1]) expect(route.input).toEqual(token0) expect(route.output).toEqual(token1) expect(route.pathInput).toEqual(token0) diff --git a/sdks/router-sdk/src/entities/mixedRoute/route.ts b/sdks/router-sdk/src/entities/mixedRoute/route.ts index f308d38fa..7de3018f4 100644 --- a/sdks/router-sdk/src/entities/mixedRoute/route.ts +++ b/sdks/router-sdk/src/entities/mixedRoute/route.ts @@ -54,9 +54,17 @@ export class MixedRouteSDK { const prevPool = pools[i - 1] const pool = pools[i] const inputToken = tokenPath[i] - const outputToken = pool.token0.wrapped.equals(inputToken.wrapped) ? pool.token1 : pool.token0 - invariant(isValidTokenPath(prevPool, pool, inputToken), 'PATH') + const outputToken = + pool instanceof V4Pool + ? pool.token0.equals(inputToken) + ? pool.token1 + : pool.token0 + : pool.token0.wrapped.equals(inputToken.wrapped) + ? pool.token1 + : pool.token0 + + invariant(isValidTokenPath(prevPool, pool, inputToken), `PATH`) tokenPath.push(outputToken) } From 7ff3e1c4da1ca111e1761e901b531a3b8fc2acba Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Fri, 31 Jan 2025 06:18:37 -0800 Subject: [PATCH 02/13] fix(v4-sdk): loosen v4 last pool check invariant (#288) ## PR Scope Please title your PR according to the following types and scopes following [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/): - `fix(SDK name):` will trigger a patch version - `chore():` will not trigger any release and should be used for internal repo changes - `(public):` will trigger a patch version for non-code changes (e.g. README changes) - `feat(SDK name):` will trigger a minor version - `feat(breaking):` will trigger a major version for a breaking change ## Description _[Summary of the change, motivation, and context]_ ## How Has This Been Tested? _[e.g. Manually, E2E tests, unit tests, Storybook]_ ## Are there any breaking changes? _[e.g. Type definitions, API definitions]_ If there are breaking changes, please ensure you bump the major version Bump the major version (by using the title `feat(breaking): ...`), post a notice in #eng-sdks, and explicitly notify all Uniswap Labs consumers of the SDK. ## (Optional) Feedback Focus _[Specific parts of this PR you'd like feedback on, or that reviewers should pay closer attention to]_ ## (Optional) Follow Ups _[Things that weren't addressed in this PR, ways you plan to build on this work, or other ways this work could be extended]_ --- sdks/v4-sdk/src/entities/pool.test.ts | 34 ++++++++++++++++++++++++++- sdks/v4-sdk/src/entities/pool.ts | 14 +++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/sdks/v4-sdk/src/entities/pool.test.ts b/sdks/v4-sdk/src/entities/pool.test.ts index 24fc13e23..5ad1a600a 100644 --- a/sdks/v4-sdk/src/entities/pool.test.ts +++ b/sdks/v4-sdk/src/entities/pool.test.ts @@ -1,4 +1,4 @@ -import { Token, CurrencyAmount, WETH9 } from '@uniswap/sdk-core' +import { Token, CurrencyAmount, WETH9, Ether, ChainId } from '@uniswap/sdk-core' import { Pool, DYNAMIC_FEE_FLAG } from './pool' import JSBI from 'jsbi' import { nearestUsableTick, encodeSqrtRatioX96, TickMath } from '@uniswap/v3-sdk' @@ -261,6 +261,38 @@ describe('Pool', () => { expect(pool.involvesCurrency(WETH9[1])).toEqual(false) }) + describe('#v4InvolvesToken', () => { + const pool = new Pool( + Ether.onChain(ChainId.MAINNET), + DAI, + FEE_AMOUNT_LOW, + TICK_SPACING_TEN, + ADDRESS_ZERO, + encodeSqrtRatioX96(1, 1), + 0, + 0, + [] + ) + expect(pool.v4InvolvesToken(Ether.onChain(ChainId.MAINNET))).toEqual(true) + expect(pool.v4InvolvesToken(DAI)).toEqual(true) + expect(pool.v4InvolvesToken(WETH9[1])).toEqual(true) + + const pool2 = new Pool( + Ether.onChain(ChainId.MAINNET).wrapped, + DAI, + FEE_AMOUNT_LOW, + TICK_SPACING_TEN, + ADDRESS_ZERO, + encodeSqrtRatioX96(1, 1), + 0, + 0, + [] + ) + expect(pool2.v4InvolvesToken(Ether.onChain(ChainId.MAINNET))).toEqual(true) + expect(pool2.v4InvolvesToken(DAI)).toEqual(true) + expect(pool2.v4InvolvesToken(WETH9[1])).toEqual(true) + }) + describe('swaps', () => { let pool: Pool let poolWithSwapHook: Pool diff --git a/sdks/v4-sdk/src/entities/pool.ts b/sdks/v4-sdk/src/entities/pool.ts index c66454218..9ddc01361 100644 --- a/sdks/v4-sdk/src/entities/pool.ts +++ b/sdks/v4-sdk/src/entities/pool.ts @@ -160,6 +160,20 @@ export class Pool { return this.involvesCurrency(currency) } + /** + * v4-only involvesToken convenience method, used for mixed route ETH <-> WETH connection only + * @param currency + */ + public v4InvolvesToken(currency: Currency): boolean { + return ( + this.involvesCurrency(currency) || + currency.wrapped.equals(this.currency0) || + currency.wrapped.equals(this.currency1) || + currency.wrapped.equals(this.currency0.wrapped) || + currency.wrapped.equals(this.currency1.wrapped) + ) + } + /** * Returns the current mid price of the pool in terms of currency0, i.e. the ratio of currency1 over currency0 */ From cc85a14829685138985f242df29e809a49855ca3 Mon Sep 17 00:00:00 2001 From: Sara Reynolds <30504811+snreynolds@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:05:24 -0500 Subject: [PATCH 03/13] fix(router-sdk): special case eth, weth fake pool (#282) --- sdks/router-sdk/package.json | 2 +- .../src/utils/encodeMixedRouteToPath.test.ts | 41 +++++++++++++++++++ .../src/utils/encodeMixedRouteToPath.ts | 24 +++++++---- yarn.lock | 10 ++--- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/sdks/router-sdk/package.json b/sdks/router-sdk/package.json index 451dd9934..d2bdaa31b 100644 --- a/sdks/router-sdk/package.json +++ b/sdks/router-sdk/package.json @@ -25,7 +25,7 @@ "@uniswap/swap-router-contracts": "^1.3.0", "@uniswap/v2-sdk": "^4.13.0", "@uniswap/v3-sdk": "^3.24.0", - "@uniswap/v4-sdk": "^1.18.0" + "@uniswap/v4-sdk": "^1.18.1" }, "devDependencies": { "@types/jest": "^24.0.25", diff --git a/sdks/router-sdk/src/utils/encodeMixedRouteToPath.test.ts b/sdks/router-sdk/src/utils/encodeMixedRouteToPath.test.ts index 500f5ee18..a409ae009 100644 --- a/sdks/router-sdk/src/utils/encodeMixedRouteToPath.test.ts +++ b/sdks/router-sdk/src/utils/encodeMixedRouteToPath.test.ts @@ -31,6 +31,16 @@ describe('#encodeMixedRouteToPath', () => { 0, [] ) + const fake_v4_eth_weth_pool = new V4Pool( + weth, + ETHER, + FeeAmount.MEDIUM, + 0, + ADDRESS_ZERO, + encodeSqrtRatioX96(1, 1), + 0, + 0 + ) const pair_0_1 = new Pair(CurrencyAmount.fromRawAmount(token0, '100'), CurrencyAmount.fromRawAmount(token1, '200')) const pair_1_2 = new Pair(CurrencyAmount.fromRawAmount(token1, '150'), CurrencyAmount.fromRawAmount(token2, '150')) @@ -61,6 +71,12 @@ describe('#encodeMixedRouteToPath', () => { const route_eth_V4_0_V3_1 = new MixedRouteSDK([pool_V4_0_eth, pool_V3_0_1_medium], ETHER, token1) const route_eth_V3_0_V4_1 = new MixedRouteSDK([pool_V3_0_weth, pool_V4_0_1], ETHER, token1) + const route_1_v2_weth_v0_eth_v4_token0 = new MixedRouteSDK( + [pair_1_weth, fake_v4_eth_weth_pool, pool_V4_0_eth], + token1, + token0 + ) + describe('pure V3', () => { it('packs them for exact input single hop', () => { expect(encodeMixedRouteToPath(route_0_V3_1)).toEqual( @@ -181,5 +197,30 @@ describe('#encodeMixedRouteToPath', () => { '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2300bb80000000000000000000000000000000000000001400bb800001e00000000000000000000000000000000000000000000000000000000000000000000000000000002' ) }) + + it('encodes the mixed route with an unwrap, token1 v2 -> v4 token0 through an unwrap', () => { + expect(encodeMixedRouteToPath(route_1_v2_weth_v0_eth_v4_token0)).toEqual( + '0x000000000000000000000000000000000000000220c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000400bb800001e00000000000000000000000000000000000000000000000000000000000000000000000000000001' + ) + // comments left for future reference, to show special cased eth-weth v4 (version0) encoding in the mixed route quoter + // // first path address - token1 + // 0x0000000000000000000000000000000000000002 + // // first path fee - v2 "version" + // 0x20 + // // first path second address - weth + // 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 + // // second path - fake v4 pool, "version" + // 0x00 + // // second path - fake v4 pool, second address - eth + // 0x0000000000000000000000000000000000000000 + // // last path - v4 pool, with Fee.MEDIUM + // 0x400bb8 + // // last path - v4, tick spacing of 30 + // 0x00001e + // // last path - v4, hook address + // 0x0000000000000000000000000000000000000000 + // // last path address - v4 pool, token0 + // 0x0000000000000000000000000000000000000001 + }) }) }) diff --git a/sdks/router-sdk/src/utils/encodeMixedRouteToPath.ts b/sdks/router-sdk/src/utils/encodeMixedRouteToPath.ts index eca13741e..f7088f98b 100644 --- a/sdks/router-sdk/src/utils/encodeMixedRouteToPath.ts +++ b/sdks/router-sdk/src/utils/encodeMixedRouteToPath.ts @@ -34,14 +34,22 @@ export function encodeMixedRouteToPath(route: MixedRouteSDK) const currencyOut = currencyIn.equals(pool.token0) ? pool.token1 : pool.token0 if (pool instanceof V4Pool) { - const v4Fee = pool.fee + MIXED_QUOTER_V2_V4_FEE_PATH_PLACEHOLDER - path.push( - v4Fee, - pool.tickSpacing, - pool.hooks, - currencyOut.isNative ? ADDRESS_ZERO : currencyOut.wrapped.address - ) - types.push('uint24', 'uint24', 'address', 'address') + // a tickSpacing of 0 indicates a "fake" v4 pool where the quote actually requires a wrap or unwrap + // the fake v4 pool will always have native as token0 and wrapped native as token1 + if (pool.tickSpacing === 0) { + const wrapOrUnwrapEncoding = 0 + path.push(wrapOrUnwrapEncoding, currencyOut.isNative ? ADDRESS_ZERO : currencyOut.wrapped.address) + types.push('uint8', 'address') + } else { + const v4Fee = pool.fee + MIXED_QUOTER_V2_V4_FEE_PATH_PLACEHOLDER + path.push( + v4Fee, + pool.tickSpacing, + pool.hooks, + currencyOut.isNative ? ADDRESS_ZERO : currencyOut.wrapped.address + ) + types.push('uint24', 'uint24', 'address', 'address') + } } else if (pool instanceof V3Pool) { const v3Fee = pool.fee + MIXED_QUOTER_V2_V3_FEE_PATH_PLACEHOLDER path.push(v3Fee, currencyOut.wrapped.address) diff --git a/yarn.lock b/yarn.lock index 2dd2f9e45..3328e3e3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4533,7 +4533,7 @@ __metadata: "@uniswap/swap-router-contracts": ^1.3.0 "@uniswap/v2-sdk": ^4.13.0 "@uniswap/v3-sdk": ^3.24.0 - "@uniswap/v4-sdk": ^1.18.0 + "@uniswap/v4-sdk": ^1.18.1 prettier: ^2.4.1 tsdx: ^0.14.1 languageName: unknown @@ -4791,16 +4791,16 @@ __metadata: languageName: node linkType: hard -"@uniswap/v4-sdk@npm:^1.18.0": - version: 1.18.0 - resolution: "@uniswap/v4-sdk@npm:1.18.0" +"@uniswap/v4-sdk@npm:^1.18.0, @uniswap/v4-sdk@npm:^1.18.1": + version: 1.18.1 + resolution: "@uniswap/v4-sdk@npm:1.18.1" dependencies: "@ethersproject/solidity": ^5.0.9 "@uniswap/sdk-core": ^7.5.0 "@uniswap/v3-sdk": 3.24.0 tiny-invariant: ^1.1.0 tiny-warning: ^1.0.3 - checksum: 37d2ebd3781eb9d5f5e0e875d7788d2369720fd30a53dba9abb7bb8df97eb89ddad2baab99c99aab566b350e1efbfe4e0d26765a9a49da38aaab767be05eafb8 + checksum: b107e7afec5f9422472bb0752a117b17f98b2671a6824a216d557e8d179ce2af9eacd21c8591b7f946f0f4cf933e2adda6d4dfd3747067576fb16435714dde0c languageName: node linkType: hard From c2fb0463f84f6dbd1c3951a1d415a805652b4d59 Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Fri, 31 Jan 2025 07:29:32 -0800 Subject: [PATCH 04/13] feat(universal-router-sdk): bump router-sdk to 1.21.3 and v4-sdk to 1.18.1 (#291) ## PR Scope Please title your PR according to the following types and scopes following [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/): - `fix(SDK name):` will trigger a patch version - `chore():` will not trigger any release and should be used for internal repo changes - `(public):` will trigger a patch version for non-code changes (e.g. README changes) - `feat(SDK name):` will trigger a minor version - `feat(breaking):` will trigger a major version for a breaking change ## Description _[Summary of the change, motivation, and context]_ ## How Has This Been Tested? _[e.g. Manually, E2E tests, unit tests, Storybook]_ ## Are there any breaking changes? _[e.g. Type definitions, API definitions]_ If there are breaking changes, please ensure you bump the major version Bump the major version (by using the title `feat(breaking): ...`), post a notice in #eng-sdks, and explicitly notify all Uniswap Labs consumers of the SDK. ## (Optional) Feedback Focus _[Specific parts of this PR you'd like feedback on, or that reviewers should pay closer attention to]_ ## (Optional) Follow Ups _[Things that weren't addressed in this PR, ways you plan to build on this work, or other ways this work could be extended]_ --- sdks/universal-router-sdk/package.json | 4 ++-- yarn.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sdks/universal-router-sdk/package.json b/sdks/universal-router-sdk/package.json index c9fcf1c2b..8455e127c 100644 --- a/sdks/universal-router-sdk/package.json +++ b/sdks/universal-router-sdk/package.json @@ -31,14 +31,14 @@ "dependencies": { "@openzeppelin/contracts": "4.7.0", "@uniswap/permit2-sdk": "^1.3.0", - "@uniswap/router-sdk": "^1.21.1", + "@uniswap/router-sdk": "^1.21.3", "@uniswap/sdk-core": "^7.5.0", "@uniswap/universal-router": "2.0.0-beta.2", "@uniswap/v2-core": "^1.0.1", "@uniswap/v2-sdk": "^4.13.0", "@uniswap/v3-core": "1.0.0", "@uniswap/v3-sdk": "^3.24.0", - "@uniswap/v4-sdk": "^1.18.0", + "@uniswap/v4-sdk": "^1.18.1", "bignumber.js": "^9.0.2", "ethers": "^5.7.0" }, diff --git a/yarn.lock b/yarn.lock index 3328e3e3e..f6a77a58f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4509,17 +4509,17 @@ __metadata: languageName: unknown linkType: soft -"@uniswap/router-sdk@npm:^1.21.1": - version: 1.21.1 - resolution: "@uniswap/router-sdk@npm:1.21.1" +"@uniswap/router-sdk@npm:^1.21.3": + version: 1.21.3 + resolution: "@uniswap/router-sdk@npm:1.21.3" dependencies: "@ethersproject/abi": ^5.5.0 "@uniswap/sdk-core": ^7.5.0 "@uniswap/swap-router-contracts": ^1.3.0 "@uniswap/v2-sdk": ^4.13.0 "@uniswap/v3-sdk": ^3.24.0 - "@uniswap/v4-sdk": ^1.18.0 - checksum: 06f72be88361ee961b8121f3be52894de05243bd18d12762ad947805322046ca9df4405e93b26b017828daaff3bf97dc43435e53ddf431b459670213800829d2 + "@uniswap/v4-sdk": ^1.18.1 + checksum: e11c4557785b36781e5dfe8015dbebc9a7392a910f3461c9e0cef8e34c15c7bec7cd7c5e61ace9788417f763399de1da15a10b98a9a7f6ca74184c7e49d8847d languageName: node linkType: hard @@ -4647,14 +4647,14 @@ __metadata: "@types/node": ^18.7.16 "@types/node-fetch": ^2.6.2 "@uniswap/permit2-sdk": ^1.3.0 - "@uniswap/router-sdk": ^1.21.1 + "@uniswap/router-sdk": ^1.21.3 "@uniswap/sdk-core": ^7.5.0 "@uniswap/universal-router": 2.0.0-beta.2 "@uniswap/v2-core": ^1.0.1 "@uniswap/v2-sdk": ^4.13.0 "@uniswap/v3-core": 1.0.0 "@uniswap/v3-sdk": ^3.24.0 - "@uniswap/v4-sdk": ^1.18.0 + "@uniswap/v4-sdk": ^1.18.1 bignumber.js: ^9.0.2 chai: ^4.3.6 dotenv: ^16.0.3 @@ -4791,7 +4791,7 @@ __metadata: languageName: node linkType: hard -"@uniswap/v4-sdk@npm:^1.18.0, @uniswap/v4-sdk@npm:^1.18.1": +"@uniswap/v4-sdk@npm:^1.18.1": version: 1.18.1 resolution: "@uniswap/v4-sdk@npm:1.18.1" dependencies: From d975cc7356c6fce67577e77dae2daf61c2758211 Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Fri, 31 Jan 2025 07:54:10 -0800 Subject: [PATCH 05/13] fix(router-sdk): mixed route last pool v4 check use v4InvolvesToken (#292) ## PR Scope Please title your PR according to the following types and scopes following [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/): - `fix(SDK name):` will trigger a patch version - `chore():` will not trigger any release and should be used for internal repo changes - `(public):` will trigger a patch version for non-code changes (e.g. README changes) - `feat(SDK name):` will trigger a minor version - `feat(breaking):` will trigger a major version for a breaking change ## Description _[Summary of the change, motivation, and context]_ ## How Has This Been Tested? _[e.g. Manually, E2E tests, unit tests, Storybook]_ ## Are there any breaking changes? _[e.g. Type definitions, API definitions]_ If there are breaking changes, please ensure you bump the major version Bump the major version (by using the title `feat(breaking): ...`), post a notice in #eng-sdks, and explicitly notify all Uniswap Labs consumers of the SDK. ## (Optional) Feedback Focus _[Specific parts of this PR you'd like feedback on, or that reviewers should pay closer attention to]_ ## (Optional) Follow Ups _[Things that weren't addressed in this PR, ways you plan to build on this work, or other ways this work could be extended]_ --- sdks/router-sdk/src/entities/mixedRoute/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/router-sdk/src/entities/mixedRoute/route.ts b/sdks/router-sdk/src/entities/mixedRoute/route.ts index 7de3018f4..0ed0dff84 100644 --- a/sdks/router-sdk/src/entities/mixedRoute/route.ts +++ b/sdks/router-sdk/src/entities/mixedRoute/route.ts @@ -39,7 +39,7 @@ export class MixedRouteSDK { invariant(pools[0].involvesToken(this.pathInput as Token), 'INPUT') const lastPool = pools[pools.length - 1] if (lastPool instanceof V4Pool) { - invariant(lastPool.involvesToken(output) || lastPool.involvesToken(output.wrapped), 'OUTPUT') + invariant(lastPool.v4InvolvesToken(output) || lastPool.v4InvolvesToken(output.wrapped), 'OUTPUT') } else { invariant(lastPool.involvesToken(output.wrapped as Token), 'OUTPUT') } From 68e538c1b42874949c0685c6ad398679f396385a Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Fri, 31 Jan 2025 08:14:21 -0800 Subject: [PATCH 06/13] fix(universal-router-sdk): bump router-sdk 1.21.4 (#293) ## PR Scope Please title your PR according to the following types and scopes following [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/): - `fix(SDK name):` will trigger a patch version - `chore():` will not trigger any release and should be used for internal repo changes - `(public):` will trigger a patch version for non-code changes (e.g. README changes) - `feat(SDK name):` will trigger a minor version - `feat(breaking):` will trigger a major version for a breaking change ## Description _[Summary of the change, motivation, and context]_ ## How Has This Been Tested? _[e.g. Manually, E2E tests, unit tests, Storybook]_ ## Are there any breaking changes? _[e.g. Type definitions, API definitions]_ If there are breaking changes, please ensure you bump the major version Bump the major version (by using the title `feat(breaking): ...`), post a notice in #eng-sdks, and explicitly notify all Uniswap Labs consumers of the SDK. ## (Optional) Feedback Focus _[Specific parts of this PR you'd like feedback on, or that reviewers should pay closer attention to]_ ## (Optional) Follow Ups _[Things that weren't addressed in this PR, ways you plan to build on this work, or other ways this work could be extended]_ --- sdks/universal-router-sdk/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdks/universal-router-sdk/package.json b/sdks/universal-router-sdk/package.json index 8455e127c..41e6df2da 100644 --- a/sdks/universal-router-sdk/package.json +++ b/sdks/universal-router-sdk/package.json @@ -31,7 +31,7 @@ "dependencies": { "@openzeppelin/contracts": "4.7.0", "@uniswap/permit2-sdk": "^1.3.0", - "@uniswap/router-sdk": "^1.21.3", + "@uniswap/router-sdk": "^1.21.4", "@uniswap/sdk-core": "^7.5.0", "@uniswap/universal-router": "2.0.0-beta.2", "@uniswap/v2-core": "^1.0.1", diff --git a/yarn.lock b/yarn.lock index f6a77a58f..e0567dd22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4509,9 +4509,9 @@ __metadata: languageName: unknown linkType: soft -"@uniswap/router-sdk@npm:^1.21.3": - version: 1.21.3 - resolution: "@uniswap/router-sdk@npm:1.21.3" +"@uniswap/router-sdk@npm:^1.21.4": + version: 1.21.4 + resolution: "@uniswap/router-sdk@npm:1.21.4" dependencies: "@ethersproject/abi": ^5.5.0 "@uniswap/sdk-core": ^7.5.0 @@ -4519,7 +4519,7 @@ __metadata: "@uniswap/v2-sdk": ^4.13.0 "@uniswap/v3-sdk": ^3.24.0 "@uniswap/v4-sdk": ^1.18.1 - checksum: e11c4557785b36781e5dfe8015dbebc9a7392a910f3461c9e0cef8e34c15c7bec7cd7c5e61ace9788417f763399de1da15a10b98a9a7f6ca74184c7e49d8847d + checksum: 9bf06ab7f1183548a1ad217707ed36ae7fd0fec2047fb9845be12a6347af920e2669228acc3d182b276c48ba373dd5d12f80dfca89503d3473fdbc8a04abbd18 languageName: node linkType: hard @@ -4647,7 +4647,7 @@ __metadata: "@types/node": ^18.7.16 "@types/node-fetch": ^2.6.2 "@uniswap/permit2-sdk": ^1.3.0 - "@uniswap/router-sdk": ^1.21.3 + "@uniswap/router-sdk": ^1.21.4 "@uniswap/sdk-core": ^7.5.0 "@uniswap/universal-router": 2.0.0-beta.2 "@uniswap/v2-core": ^1.0.1 From 3d0f767a6f08a71c5721b5a2e257ef5cfea0ad97 Mon Sep 17 00:00:00 2001 From: diana Date: Tue, 4 Feb 2025 10:11:32 -0500 Subject: [PATCH 07/13] feat(v4-sdk): migrate weth to eth by unwrapping (#278) --- sdks/v4-sdk/src/PositionManager.test.ts | 71 ++++++++++++++++++++++--- sdks/v4-sdk/src/PositionManager.ts | 53 +++++++++++------- sdks/v4-sdk/src/internalConstants.ts | 5 +- sdks/v4-sdk/src/utils/v4Planner.ts | 8 ++- 4 files changed, 109 insertions(+), 28 deletions(-) diff --git a/sdks/v4-sdk/src/PositionManager.test.ts b/sdks/v4-sdk/src/PositionManager.test.ts index b258efcd3..8e3cb336e 100644 --- a/sdks/v4-sdk/src/PositionManager.test.ts +++ b/sdks/v4-sdk/src/PositionManager.test.ts @@ -5,7 +5,8 @@ import { EMPTY_HOOK, FeeAmount, CANNOT_BURN, - NO_NATIVE, + NATIVE_NOT_SET, + OPEN_DELTA, SQRT_PRICE_1_1, TICK_SPACINGS, ZERO_LIQUIDITY, @@ -106,7 +107,7 @@ describe('PositionManager', () => { ).toThrow('ZERO_LIQUIDITY') }) - it('throws if pool does not involve ether and useNative is true', () => { + it('throws if pool does not involve ether and useNative is set', () => { expect(() => V4PositionManager.addCallParameters( new Position({ @@ -115,9 +116,23 @@ describe('PositionManager', () => { tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM], liquidity: 8888888, }), - { recipient, slippageTolerance, deadline, useNative: Ether.onChain(1) } + { recipient, slippageTolerance, deadline, useNative: currency_native } ) - ).toThrow(NO_NATIVE) + ).toThrow(NATIVE_NOT_SET) + }) + + it('throws if pool involves ether and useNative is not set', () => { + expect(() => + V4PositionManager.addCallParameters( + new Position({ + pool: pool_1_eth, + tickLower: -TICK_SPACINGS[FeeAmount.MEDIUM], + tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM], + liquidity: 8888888, + }), + { recipient, slippageTolerance, deadline } // useNative not set + ) + ).toThrow(NATIVE_NOT_SET) }) it('throws if createPool is true but there is no sqrtPrice defined', () => { @@ -237,7 +252,7 @@ describe('PositionManager', () => { expect(value).toEqual('0x00') }) - it('succeeds when useNative is true', () => { + it('succeeds when useNative is set', () => { const position: Position = new Position({ pool: pool_1_eth, tickLower: -TICK_SPACINGS[FeeAmount.MEDIUM], @@ -248,7 +263,7 @@ describe('PositionManager', () => { recipient, slippageTolerance, deadline, - useNative: Ether.onChain(1), + useNative: currency_native, }) // Rebuild the data with the planner for the expected mint. MUST sweep since we are using the native currency. @@ -303,8 +318,8 @@ describe('PositionManager', () => { EMPTY_BYTES, ]) - planner.addAction(Actions.SETTLE, [toAddress(pool_0_1.currency0), 0, false]) - planner.addAction(Actions.SETTLE, [toAddress(pool_0_1.currency1), 0, false]) + planner.addAction(Actions.SETTLE, [toAddress(pool_0_1.currency0), OPEN_DELTA, false]) + planner.addAction(Actions.SETTLE, [toAddress(pool_0_1.currency1), OPEN_DELTA, false]) planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency0), recipient]) planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency1), recipient]) expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline)) @@ -312,6 +327,46 @@ describe('PositionManager', () => { expect(value).toEqual('0x00') }) + it('succeeds when migrating to an eth position', () => { + const position: Position = new Position({ + pool: pool_1_eth, + tickLower: -TICK_SPACINGS[FeeAmount.MEDIUM], + tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM], + liquidity: 1, + }) + const { calldata, value } = V4PositionManager.addCallParameters(position, { + recipient, + slippageTolerance, + deadline, + migrate: true, + useNative: currency_native, + }) + + // Rebuild the data with the planner for the expected mint. MUST sweep since we are using the native currency. + const planner = new V4Planner() + const { amount0: amount0Max, amount1: amount1Max } = position.mintAmountsWithSlippage(slippageTolerance) + // Expect position to be minted correctly + planner.addAction(Actions.MINT_POSITION, [ + pool_1_eth.poolKey, + -TICK_SPACINGS[FeeAmount.MEDIUM], + TICK_SPACINGS[FeeAmount.MEDIUM], + 1, + toHex(amount0Max), + toHex(amount1Max), + recipient, + EMPTY_BYTES, + ]) + + planner.addAction(Actions.UNWRAP, [OPEN_DELTA]) + planner.addAction(Actions.SETTLE, [toAddress(pool_1_eth.currency0), OPEN_DELTA, false]) + planner.addAction(Actions.SETTLE, [toAddress(pool_1_eth.currency1), OPEN_DELTA, false]) + planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency0.wrapped), recipient]) + planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency1), recipient]) + expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline)) + + expect(value).toEqual('0x00') + }) + it('succeeds for batchPermit', () => { const position: Position = new Position({ pool: pool_0_1, diff --git a/sdks/v4-sdk/src/PositionManager.ts b/sdks/v4-sdk/src/PositionManager.ts index 003468a70..c3943ec34 100644 --- a/sdks/v4-sdk/src/PositionManager.ts +++ b/sdks/v4-sdk/src/PositionManager.ts @@ -1,4 +1,4 @@ -import { BigintIsh, Percent, validateAndParseAddress, Currency, NativeCurrency } from '@uniswap/sdk-core' +import { BigintIsh, Percent, validateAndParseAddress, NativeCurrency } from '@uniswap/sdk-core' import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' import JSBI from 'jsbi' import { Position } from './entities/position' @@ -11,9 +11,10 @@ import invariant from 'tiny-invariant' import { EMPTY_BYTES, CANNOT_BURN, - NO_NATIVE, + NATIVE_NOT_SET, NO_SQRT_PRICE, ONE, + OPEN_DELTA, PositionFunctions, ZERO, ZERO_LIQUIDITY, @@ -240,6 +241,13 @@ export abstract class V4PositionManager { calldataList.push(V4PositionManager.encodeInitializePool(position.pool.poolKey, options.sqrtPriceX96!)) } + // position.pool.currency0 is native if and only if options.useNative is set + invariant( + position.pool.currency0 === options.useNative || + (!position.pool.currency0.isNative && options.useNative === undefined), + NATIVE_NOT_SET + ) + // adjust for slippage const maximumAmounts = position.mintAmountsWithSlippage(options.slippageTolerance) const amount0Max = toHex(maximumAmounts.amount0) @@ -274,27 +282,36 @@ export abstract class V4PositionManager { planner.addIncrease(options.tokenId, position.liquidity, amount0Max, amount1Max, options.hookData) } + let value: string = toHex(0) + // If migrating, we need to settle and sweep both currencies individually if (isMint(options) && options.migrate) { - // payer is v4 positiion manager - planner.addSettle(position.pool.currency0, false) - planner.addSettle(position.pool.currency1, false) - planner.addSweep(position.pool.currency0, options.recipient) - planner.addSweep(position.pool.currency1, options.recipient) + if (options.useNative) { + // unwrap the exact amount needed to send to the pool manager + planner.addUnwrap(OPEN_DELTA) + // payer is v4 position manager + planner.addSettle(position.pool.currency0, false) + planner.addSettle(position.pool.currency1, false) + // sweep any leftover wrapped native that was not unwrapped + planner.addSweep(position.pool.currency0.wrapped, options.recipient) + planner.addSweep(position.pool.currency1, options.recipient) + } else { + // payer is v4 position manager + planner.addSettle(position.pool.currency0, false) + planner.addSettle(position.pool.currency1, false) + planner.addSweep(position.pool.currency0, options.recipient) + planner.addSweep(position.pool.currency1, options.recipient) + } } else { // need to settle both currencies when minting / adding liquidity (user is the payer) planner.addSettlePair(position.pool.currency0, position.pool.currency1) - } - - // Any sweeping must happen after the settling. - let value: string = toHex(0) - if (options.useNative) { - invariant(position.pool.currency0.isNative || position.pool.currency1.isNative, NO_NATIVE) - let nativeCurrency: Currency = position.pool.currency0.isNative - ? position.pool.currency0 - : position.pool.currency1 - value = position.pool.currency0.isNative ? toHex(amount0Max) : toHex(amount1Max) - planner.addSweep(nativeCurrency, MSG_SENDER) + // When not migrating and adding native currency, add a final sweep + if (options.useNative) { + // Any sweeping must happen after the settling. + // native currency will always be currency0 in v4 + value = toHex(amount0Max) + planner.addSweep(position.pool.currency0, MSG_SENDER) + } } calldataList.push(V4PositionManager.encodeModifyLiquidities(planner.finalize(), options.deadline)) diff --git a/sdks/v4-sdk/src/internalConstants.ts b/sdks/v4-sdk/src/internalConstants.ts index 5df7b3a80..a91ffb9d6 100644 --- a/sdks/v4-sdk/src/internalConstants.ts +++ b/sdks/v4-sdk/src/internalConstants.ts @@ -24,6 +24,9 @@ export const TICK_SPACING_SIXTY = 60 // used in position manager math export const MIN_SLIPPAGE_DECREASE = 0 +// used when unwrapping weth in positon manager +export const OPEN_DELTA = constants.Zero + // default prices export const SQRT_PRICE_1_1 = encodeSqrtRatioX96(1, 1) @@ -31,7 +34,7 @@ export const SQRT_PRICE_1_1 = encodeSqrtRatioX96(1, 1) export const EMPTY_HOOK = '0x0000000000000000000000000000000000000000' // error constants -export const NO_NATIVE = 'NO_NATIVE' +export const NATIVE_NOT_SET = 'NATIVE_NOT_SET' export const ZERO_LIQUIDITY = 'ZERO_LIQUIDITY' export const NO_SQRT_PRICE = 'NO_SQRT_PRICE' export const CANNOT_BURN = 'CANNOT_BURN' diff --git a/sdks/v4-sdk/src/utils/v4Planner.ts b/sdks/v4-sdk/src/utils/v4Planner.ts index 4e2e7dddd..c7bbce1ee 100644 --- a/sdks/v4-sdk/src/utils/v4Planner.ts +++ b/sdks/v4-sdk/src/utils/v4Planner.ts @@ -46,7 +46,7 @@ export enum Actions { // for wrapping/unwrapping native // WRAP = 0x15, - // UNWRAP = 0x16, + UNWRAP = 0x16, } export enum Subparser { @@ -160,6 +160,7 @@ export const V4_BASE_ACTIONS_ABI_DEFINITION: { [key in Actions]: readonly ParamT { name: 'currency', type: 'address' }, { name: 'recipient', type: 'address' }, ], + [Actions.UNWRAP]: [{ name: 'amount', type: 'uint256' }], } const FULL_DELTA_AMOUNT = 0 @@ -221,6 +222,11 @@ export class V4Planner { return this } + addUnwrap(amount: BigNumber): V4Planner { + this.addAction(Actions.UNWRAP, [amount]) + return this + } + finalize(): string { return defaultAbiCoder.encode(['bytes', 'bytes[]'], [this.actions, this.params]) } From e03acbf746d14e41122388aea058dd4ea9018094 Mon Sep 17 00:00:00 2001 From: diana Date: Tue, 4 Feb 2025 12:48:17 -0500 Subject: [PATCH 08/13] fix(v4-sdk): fix sweep recipient (#296) --- sdks/v4-sdk/src/PositionManager.test.ts | 8 ++++---- sdks/v4-sdk/src/PositionManager.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdks/v4-sdk/src/PositionManager.test.ts b/sdks/v4-sdk/src/PositionManager.test.ts index 8e3cb336e..9d712697b 100644 --- a/sdks/v4-sdk/src/PositionManager.test.ts +++ b/sdks/v4-sdk/src/PositionManager.test.ts @@ -320,8 +320,8 @@ describe('PositionManager', () => { planner.addAction(Actions.SETTLE, [toAddress(pool_0_1.currency0), OPEN_DELTA, false]) planner.addAction(Actions.SETTLE, [toAddress(pool_0_1.currency1), OPEN_DELTA, false]) - planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency0), recipient]) - planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency1), recipient]) + planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency0), MSG_SENDER]) + planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency1), MSG_SENDER]) expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline)) expect(value).toEqual('0x00') @@ -360,8 +360,8 @@ describe('PositionManager', () => { planner.addAction(Actions.UNWRAP, [OPEN_DELTA]) planner.addAction(Actions.SETTLE, [toAddress(pool_1_eth.currency0), OPEN_DELTA, false]) planner.addAction(Actions.SETTLE, [toAddress(pool_1_eth.currency1), OPEN_DELTA, false]) - planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency0.wrapped), recipient]) - planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency1), recipient]) + planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency0.wrapped), MSG_SENDER]) + planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency1), MSG_SENDER]) expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline)) expect(value).toEqual('0x00') diff --git a/sdks/v4-sdk/src/PositionManager.ts b/sdks/v4-sdk/src/PositionManager.ts index c3943ec34..fab921af7 100644 --- a/sdks/v4-sdk/src/PositionManager.ts +++ b/sdks/v4-sdk/src/PositionManager.ts @@ -293,14 +293,14 @@ export abstract class V4PositionManager { planner.addSettle(position.pool.currency0, false) planner.addSettle(position.pool.currency1, false) // sweep any leftover wrapped native that was not unwrapped - planner.addSweep(position.pool.currency0.wrapped, options.recipient) - planner.addSweep(position.pool.currency1, options.recipient) + planner.addSweep(position.pool.currency0.wrapped, MSG_SENDER) + planner.addSweep(position.pool.currency1, MSG_SENDER) } else { // payer is v4 position manager planner.addSettle(position.pool.currency0, false) planner.addSettle(position.pool.currency1, false) - planner.addSweep(position.pool.currency0, options.recipient) - planner.addSweep(position.pool.currency1, options.recipient) + planner.addSweep(position.pool.currency0, MSG_SENDER) + planner.addSweep(position.pool.currency1, MSG_SENDER) } } else { // need to settle both currencies when minting / adding liquidity (user is the payer) From 17c345d77e5895b80c83255a50ed84fd5a1c029c Mon Sep 17 00:00:00 2001 From: diana Date: Tue, 4 Feb 2025 14:41:26 -0500 Subject: [PATCH 09/13] fix(v4-sdk): switch back recipient (#297) --- sdks/v4-sdk/src/PositionManager.test.ts | 8 ++++---- sdks/v4-sdk/src/PositionManager.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/sdks/v4-sdk/src/PositionManager.test.ts b/sdks/v4-sdk/src/PositionManager.test.ts index 9d712697b..8e3cb336e 100644 --- a/sdks/v4-sdk/src/PositionManager.test.ts +++ b/sdks/v4-sdk/src/PositionManager.test.ts @@ -320,8 +320,8 @@ describe('PositionManager', () => { planner.addAction(Actions.SETTLE, [toAddress(pool_0_1.currency0), OPEN_DELTA, false]) planner.addAction(Actions.SETTLE, [toAddress(pool_0_1.currency1), OPEN_DELTA, false]) - planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency0), MSG_SENDER]) - planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency1), MSG_SENDER]) + planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency0), recipient]) + planner.addAction(Actions.SWEEP, [toAddress(pool_0_1.currency1), recipient]) expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline)) expect(value).toEqual('0x00') @@ -360,8 +360,8 @@ describe('PositionManager', () => { planner.addAction(Actions.UNWRAP, [OPEN_DELTA]) planner.addAction(Actions.SETTLE, [toAddress(pool_1_eth.currency0), OPEN_DELTA, false]) planner.addAction(Actions.SETTLE, [toAddress(pool_1_eth.currency1), OPEN_DELTA, false]) - planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency0.wrapped), MSG_SENDER]) - planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency1), MSG_SENDER]) + planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency0.wrapped), recipient]) + planner.addAction(Actions.SWEEP, [toAddress(pool_1_eth.currency1), recipient]) expect(calldata).toEqual(V4PositionManager.encodeModifyLiquidities(planner.finalize(), deadline)) expect(value).toEqual('0x00') diff --git a/sdks/v4-sdk/src/PositionManager.ts b/sdks/v4-sdk/src/PositionManager.ts index fab921af7..869fe3057 100644 --- a/sdks/v4-sdk/src/PositionManager.ts +++ b/sdks/v4-sdk/src/PositionManager.ts @@ -293,14 +293,16 @@ export abstract class V4PositionManager { planner.addSettle(position.pool.currency0, false) planner.addSettle(position.pool.currency1, false) // sweep any leftover wrapped native that was not unwrapped - planner.addSweep(position.pool.currency0.wrapped, MSG_SENDER) - planner.addSweep(position.pool.currency1, MSG_SENDER) + // recipient will be same as the v4 lp token recipient + planner.addSweep(position.pool.currency0.wrapped, options.recipient) + planner.addSweep(position.pool.currency1, options.recipient) } else { // payer is v4 position manager planner.addSettle(position.pool.currency0, false) planner.addSettle(position.pool.currency1, false) - planner.addSweep(position.pool.currency0, MSG_SENDER) - planner.addSweep(position.pool.currency1, MSG_SENDER) + // recipient will be same as the v4 lp token recipient + planner.addSweep(position.pool.currency0, options.recipient) + planner.addSweep(position.pool.currency1, options.recipient) } } else { // need to settle both currencies when minting / adding liquidity (user is the payer) From 13a909ca747a53805dd1e80c963bc0c6f08dfd69 Mon Sep 17 00:00:00 2001 From: diana Date: Tue, 4 Feb 2025 18:34:09 -0500 Subject: [PATCH 10/13] feat(ur-sdk): allow migrating to eth position (#279) --- sdks/universal-router-sdk/package.json | 2 +- sdks/universal-router-sdk/src/swapRouter.ts | 19 +- .../test/forge/MigratorCallParameters.t.sol | 223 +++++++++++++- .../test/forge/interop.json | 30 +- .../test/uniswapTrades.test.ts | 275 ++++++++++++++++-- yarn.lock | 10 +- 6 files changed, 508 insertions(+), 51 deletions(-) diff --git a/sdks/universal-router-sdk/package.json b/sdks/universal-router-sdk/package.json index 41e6df2da..3866f15b0 100644 --- a/sdks/universal-router-sdk/package.json +++ b/sdks/universal-router-sdk/package.json @@ -38,7 +38,7 @@ "@uniswap/v2-sdk": "^4.13.0", "@uniswap/v3-core": "1.0.0", "@uniswap/v3-sdk": "^3.24.0", - "@uniswap/v4-sdk": "^1.18.1", + "@uniswap/v4-sdk": "^1.19.2", "bignumber.js": "^9.0.2", "ethers": "^5.7.0" }, diff --git a/sdks/universal-router-sdk/src/swapRouter.ts b/sdks/universal-router-sdk/src/swapRouter.ts index a1154ebcf..7aedd8303 100644 --- a/sdks/universal-router-sdk/src/swapRouter.ts +++ b/sdks/universal-router-sdk/src/swapRouter.ts @@ -82,14 +82,25 @@ export abstract class SwapRouter { positionManagerOverride?: string ): MethodParameters { const v4Pool: V4Pool = options.outputPosition.pool - const token0 = options.inputPosition.pool.token0 - const token1 = options.inputPosition.pool.token1 + const v3Token0 = options.inputPosition.pool.token0 + const v3Token1 = options.inputPosition.pool.token1 const v4PositionManagerAddress = positionManagerOverride ?? CHAIN_TO_ADDRESSES_MAP[v4Pool.chainId as SupportedChainsType].v4PositionManagerAddress + // owner of the v3 nft must be the receiver of the v4 nft + // validate the parameters - invariant(token0 === v4Pool.token0, 'TOKEN0_MISMATCH') - invariant(token1 === v4Pool.token1, 'TOKEN1_MISMATCH') + if (v4Pool.currency0.isNative) { + invariant( + (v4Pool.currency0.wrapped.equals(v3Token0) && v4Pool.currency1.equals(v3Token1)) || + (v4Pool.currency0.wrapped.equals(v3Token1) && v4Pool.currency1.equals(v3Token0)), + 'TOKEN_MISMATCH' + ) + } else { + invariant(v3Token0 === v4Pool.token0, 'TOKEN0_MISMATCH') + invariant(v3Token1 === v4Pool.token1, 'TOKEN1_MISMATCH') + } + invariant( options.v3RemoveLiquidityOptions.liquidityPercentage.equalTo(new Percent(100, 100)), 'FULL_REMOVAL_REQUIRED' diff --git a/sdks/universal-router-sdk/test/forge/MigratorCallParameters.t.sol b/sdks/universal-router-sdk/test/forge/MigratorCallParameters.t.sol index 5331b59b5..3c3c07987 100644 --- a/sdks/universal-router-sdk/test/forge/MigratorCallParameters.t.sol +++ b/sdks/universal-router-sdk/test/forge/MigratorCallParameters.t.sol @@ -29,8 +29,8 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter { vm.deal(from, BALANCE); } - function test_migrate_withoutPermit() public { - MethodParameters memory params = readFixture(json, "._MIGRATE_WITHOUT_PERMIT"); + function test_migrate_toEth_withoutPermit() public { + MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ETH_WITHOUT_PERMIT"); // add the position to v3 so we have something to migrate assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0); @@ -42,6 +42,16 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter { vm.prank(from); INonfungiblePositionManager(V3_POSITION_MANAGER).setApprovalForAll(MAINNET_ROUTER, true); + // pool manager balance before + uint256 ethBalanceBefore = address(poolManager).balance; + uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager)); + uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager)); + + // recipient balance before + uint256 recipientBalanceBefore = address(RECIPIENT).balance; + uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT); + uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT); + assertEq(params.value, 0); vm.prank(from); (bool success,) = address(router).call(params.data); @@ -50,16 +60,28 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter { // all funds were swept out of contracts assertEq(USDC.balanceOf(MAINNET_ROUTER), 0); assertEq(WETH.balanceOf(MAINNET_ROUTER), 0); + assertEq(address(MAINNET_ROUTER).balance, 0); assertEq(USDC.balanceOf(address(v4PositionManager)), 0); assertEq(WETH.balanceOf(address(v4PositionManager)), 0); + assertEq(address(v4PositionManager).balance, 0); + + // pool manager balance after, eth and usdc deposited + assertGt(address(poolManager).balance, ethBalanceBefore); + assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore); + assertEq(WETH.balanceOf(address(poolManager)), wethBalanceBefore); + + // recipient balance after + assertEq(address(RECIPIENT).balance, recipientBalanceBefore); + assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore); + assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore); // old position burned, new position minted assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT"); assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED"); } - function test_migrate_withPermit() public { - MethodParameters memory params = readFixture(json, "._MIGRATE_WITH_PERMIT"); + function test_migrate_toErc20_withoutPermit() public { + MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ERC20_WITHOUT_PERMIT"); // add the position to v3 so we have something to migrate assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0); @@ -67,6 +89,20 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter { mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18); assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1); + // approve the UniversalRouter to access the position (instead of permit) + vm.prank(from); + INonfungiblePositionManager(V3_POSITION_MANAGER).setApprovalForAll(MAINNET_ROUTER, true); + + // pool manager balance before + uint256 ethBalanceBefore = address(poolManager).balance; + uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager)); + uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager)); + + // recipient balance before + uint256 recipientBalanceBefore = address(RECIPIENT).balance; + uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT); + uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT); + assertEq(params.value, 0); vm.prank(from); (bool success,) = address(router).call(params.data); @@ -75,16 +111,28 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter { // all funds were swept out of contracts assertEq(USDC.balanceOf(MAINNET_ROUTER), 0); assertEq(WETH.balanceOf(MAINNET_ROUTER), 0); + assertEq(address(MAINNET_ROUTER).balance, 0); assertEq(USDC.balanceOf(address(v4PositionManager)), 0); assertEq(WETH.balanceOf(address(v4PositionManager)), 0); + assertEq(address(v4PositionManager).balance, 0); + + // pool manager balance after, weth and usdc deposited + assertEq(address(poolManager).balance, ethBalanceBefore); + assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore); + assertGt(WETH.balanceOf(address(poolManager)), wethBalanceBefore); + + // recipient balance after + assertEq(address(RECIPIENT).balance, recipientBalanceBefore); + assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore); + assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore); // old position burned, new position minted assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT"); assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED"); } - function test_migrate_withPermitAndPoolInitialize() public { - MethodParameters memory params = readFixture(json, "._MIGRATE_WITH_PERMIT_AND_POOL_INITIALIZE"); + function test_migrate_toEth_withPermit() public { + MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ETH_WITH_PERMIT"); // add the position to v3 so we have something to migrate assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0); @@ -92,6 +140,16 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter { mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18); assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1); + // pool manager balance before + uint256 ethBalanceBefore = address(poolManager).balance; + uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager)); + uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager)); + + // recipient balance before + uint256 recipientBalanceBefore = address(RECIPIENT).balance; + uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT); + uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT); + assertEq(params.value, 0); vm.prank(from); (bool success,) = address(router).call(params.data); @@ -100,8 +158,161 @@ contract MigratorCallParametersTest is Test, Interop, DeployRouter { // all funds were swept out of contracts assertEq(USDC.balanceOf(MAINNET_ROUTER), 0); assertEq(WETH.balanceOf(MAINNET_ROUTER), 0); + assertEq(address(MAINNET_ROUTER).balance, 0); assertEq(USDC.balanceOf(address(v4PositionManager)), 0); assertEq(WETH.balanceOf(address(v4PositionManager)), 0); + assertEq(address(v4PositionManager).balance, 0); + + // pool manager balance after, eth and usdc deposited + assertGt(address(poolManager).balance, ethBalanceBefore); + assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore); + assertEq(WETH.balanceOf(address(poolManager)), wethBalanceBefore); + + // recipient balance after + assertEq(address(RECIPIENT).balance, recipientBalanceBefore); + assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore); + assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore); + + // old position burned, new position minted + assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT"); + assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED"); + } + + function test_migrate_toErc20_withPermit() public { + MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ERC20_WITH_PERMIT"); + + // add the position to v3 so we have something to migrate + assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0); + // USDC < WETH + mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18); + assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1); + + // pool manager balance before + uint256 ethBalanceBefore = address(poolManager).balance; + uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager)); + uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager)); + + // recipient balance before + uint256 recipientBalanceBefore = address(RECIPIENT).balance; + uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT); + uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT); + + assertEq(params.value, 0); + vm.prank(from); + (bool success,) = address(router).call(params.data); + require(success, "call failed"); + + // all funds were swept out of contracts + assertEq(USDC.balanceOf(MAINNET_ROUTER), 0); + assertEq(WETH.balanceOf(MAINNET_ROUTER), 0); + assertEq(address(MAINNET_ROUTER).balance, 0); + assertEq(USDC.balanceOf(address(v4PositionManager)), 0); + assertEq(WETH.balanceOf(address(v4PositionManager)), 0); + assertEq(address(v4PositionManager).balance, 0); + + // pool manager balance after, weth and usdc deposited + assertEq(address(poolManager).balance, ethBalanceBefore); + assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore); + assertGt(WETH.balanceOf(address(poolManager)), wethBalanceBefore); + + // recipient balance after + assertEq(address(RECIPIENT).balance, recipientBalanceBefore); + assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore); + assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore); + + // old position burned, new position minted + assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT"); + assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED"); + } + + function test_migrate_toEth_withPermitAndPoolInitialize() public { + MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ETH_WITH_PERMIT_AND_POOL_INITIALIZE"); + + // add the position to v3 so we have something to migrate + assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0); + // USDC < WETH + mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18); + assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1); + + // pool manager balance before + uint256 ethBalanceBefore = address(poolManager).balance; + uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager)); + uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager)); + + // recipient balance before + uint256 recipientBalanceBefore = address(RECIPIENT).balance; + uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT); + uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT); + + assertEq(params.value, 0); + vm.prank(from); + (bool success,) = address(router).call(params.data); + require(success, "call failed"); + + // all funds were swept out of contracts + assertEq(USDC.balanceOf(MAINNET_ROUTER), 0); + assertEq(WETH.balanceOf(MAINNET_ROUTER), 0); + assertEq(address(MAINNET_ROUTER).balance, 0); + assertEq(USDC.balanceOf(address(v4PositionManager)), 0); + assertEq(WETH.balanceOf(address(v4PositionManager)), 0); + assertEq(address(v4PositionManager).balance, 0); + + // pool manager balance after, eth and usdc deposited + assertGt(address(poolManager).balance, ethBalanceBefore); + assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore); + assertEq(WETH.balanceOf(address(poolManager)), wethBalanceBefore); + + // recipient balance after + assertEq(address(RECIPIENT).balance, recipientBalanceBefore); + assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore); + assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore); + + // old position burned, new position minted + assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT"); + assertEq(v4PositionManager.balanceOf(RECIPIENT), 1, "V4 NOT MINTED"); + } + + function test_migrate_toErc20_withPermitAndPoolInitialize() public { + MethodParameters memory params = readFixture(json, "._MIGRATE_TO_ERC20_WITH_PERMIT_AND_POOL_INITIALIZE"); + + // add the position to v3 so we have something to migrate + assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0); + // USDC < WETH + mintV3Position(address(USDC), address(WETH), 3000, 2500e6, 1e18); + assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 1); + + // pool manager balance before + uint256 ethBalanceBefore = address(poolManager).balance; + uint256 usdcBalanceBefore = USDC.balanceOf(address(poolManager)); + uint256 wethBalanceBefore = WETH.balanceOf(address(poolManager)); + + // recipient balance before + uint256 recipientBalanceBefore = address(RECIPIENT).balance; + uint256 recipientUSDCBalanceBefore = USDC.balanceOf(RECIPIENT); + uint256 recipientWETHBalanceBefore = WETH.balanceOf(RECIPIENT); + + assertEq(params.value, 0); + vm.prank(from); + (bool success,) = address(router).call(params.data); + require(success, "call failed"); + + // all funds were swept out of contracts + assertEq(USDC.balanceOf(MAINNET_ROUTER), 0); + assertEq(WETH.balanceOf(MAINNET_ROUTER), 0); + assertEq(address(MAINNET_ROUTER).balance, 0); + assertEq(USDC.balanceOf(address(v4PositionManager)), 0); + assertEq(WETH.balanceOf(address(v4PositionManager)), 0); + assertEq(address(v4PositionManager).balance, 0); + + // pool manager balance after, weth and usdc deposited + assertEq(address(poolManager).balance, ethBalanceBefore); + assertGt(USDC.balanceOf(address(poolManager)), usdcBalanceBefore); + assertGt(WETH.balanceOf(address(poolManager)), wethBalanceBefore); + + // recipient balance after + assertEq(address(RECIPIENT).balance, recipientBalanceBefore); + assertGe(USDC.balanceOf(RECIPIENT), recipientUSDCBalanceBefore); + assertGe(WETH.balanceOf(RECIPIENT), recipientWETHBalanceBefore); // old position burned, new position minted assertEq(INonfungiblePositionManager(V3_POSITION_MANAGER).balanceOf(from), 0, "V3 NOT BURNT"); diff --git a/sdks/universal-router-sdk/test/forge/interop.json b/sdks/universal-router-sdk/test/forge/interop.json index 63e3d0ee2..e0a0fa0a0 100644 --- a/sdks/universal-router-sdk/test/forge/interop.json +++ b/sdks/universal-router-sdk/test/forge/interop.json @@ -151,6 +151,10 @@ "calldata": "0x24856bc300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000310050b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003090b0e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000e92596fd62900000000000000000000000000000000000000000000000000000f647b8513b70838000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb00000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa8000000000000000000000000000000000000000000000000000000000000000", "value": "0" }, + "_UNISWAP_V4_ETH_FOR_1000_USDC": { + "calldata": "0x24856bc3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002100400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003090b0e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000003ef64b1300000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", + "value": "1056328467" + }, "_UNISWAP_V4_UNWRAP_WETH_TO_ETH_FOR_1000_USDC": { "calldata": "0x24856bc3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004020c100b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000006200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000003ef64b1300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003090b0e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000003ef64b1300000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa8000000000000000000000000000000000000000000000000000000000000000", "value": "0" @@ -203,20 +207,28 @@ "calldata": "0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000050b08000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000004600000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000029a2241af62c0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000ceebdd6e", "value": "3000000000000000000" }, - "_MIGRATE_WITH_PERMIT": { - "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000051112121214000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000000c47ac2ff7b00000000000000000000000066a9893cc07d91d95644aedd05d03f95e1dba8af000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000001ce3b052b9759a89085d422f47135a6b07afb2401ef4918b5f52b0bda503b9222b01001d6e7addb742ecf373558f59ed21358d74c1ea2f1cfcab80f4c5941a0e510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000041b5de3f36ba0000000000000000000000000000000000000000000000000000000091637ff800000000000000000000000000000000000000000000000005e085d5c8a18a7e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005c4740000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000524dd46508f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005020b0b1414000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030d6800000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", + "_MIGRATE_TO_ETH_WITH_PERMIT": { + "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000051112121214000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000000c47ac2ff7b00000000000000000000000066a9893cc07d91d95644aedd05d03f95e1dba8af000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000001ce3b052b9759a89085d422f47135a6b07afb2401ef4918b5f52b0bda503b9222b01001d6e7addb742ecf373558f59ed21358d74c1ea2f1cfcab80f4c5941a0e510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000041b5de3f36ba0000000000000000000000000000000000000000000000000000000091637ff800000000000000000000000000000000000000000000000005e085d5c8a18a7e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005c4740000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000584dd46508f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000602160b0b14140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf29800000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000190c60000000000000000000000000000000000000000000000000000000000019041000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", "value": "0" }, - "_MIGRATE_WITHOUT_PERMIT": { - "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000412121214000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000041b5de3f36ba0000000000000000000000000000000000000000000000000000000091637ff800000000000000000000000000000000000000000000000005e085d5c8a18a7e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005c4740000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000524dd46508f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005020b0b1414000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030d6800000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", + "_MIGRATE_TO_ERC20_WITH_PERMIT": { + "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000051112121214000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000000c47ac2ff7b00000000000000000000000066a9893cc07d91d95644aedd05d03f95e1dba8af000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000001ce3b052b9759a89085d422f47135a6b07afb2401ef4918b5f52b0bda503b9222b01001d6e7addb742ecf373558f59ed21358d74c1ea2f1cfcab80f4c5941a0e510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000041b5de3f36ba0000000000000000000000000000000000000000000000000000000091637ff800000000000000000000000000000000000000000000000005e085d5c8a18a7e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005c4740000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000524dd46508f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005020b0b1414000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf29800000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000190c60000000000000000000000000000000000000000000000000000000000019041000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", "value": "0" }, - "_MIGRATE_WITH_PERMIT_AND_POOL_INITIALIZE": { - "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000061311121212140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000c47ac2ff7b00000000000000000000000066a9893cc07d91d95644aedd05d03f95e1dba8af000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000001ce3b052b9759a89085d422f47135a6b07afb2401ef4918b5f52b0bda503b9222b01001d6e7addb742ecf373558f59ed21358d74c1ea2f1cfcab80f4c5941a0e510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000041b5de3f36ba0000000000000000000000000000000000000000000000000000000091637ff800000000000000000000000000000000000000000000000005e085d5c8a18a7e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005c4740000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000524dd46508f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005020b0b1414000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030d6800000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", + "_MIGRATE_TO_ETH_WITHOUT_PERMIT": { + "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000412121214000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000041b5de3f36ba0000000000000000000000000000000000000000000000000000000091637ff800000000000000000000000000000000000000000000000005e085d5c8a18a7e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005c4740000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000584dd46508f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000602160b0b14140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf29800000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000190c60000000000000000000000000000000000000000000000000000000000019041000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", "value": "0" }, - "_UNISWAP_V4_ETH_FOR_1000_USDC": { - "calldata": "0x24856bc3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002100400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003090b0e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000003ef64b1300000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000000", - "value": "1056328467" + "_MIGRATE_TO_ERC20_WITHOUT_PERMIT": { + "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000412121214000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000041b5de3f36ba0000000000000000000000000000000000000000000000000000000091637ff800000000000000000000000000000000000000000000000005e085d5c8a18a7e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005c4740000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000524dd46508f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005020b0b1414000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000bb8000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf29800000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000190c60000000000000000000000000000000000000000000000000000000000019041000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", + "value": "0" + }, + "_MIGRATE_TO_ETH_WITH_PERMIT_AND_POOL_INITIALIZE": { + "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000061311121212140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000c47ac2ff7b00000000000000000000000066a9893cc07d91d95644aedd05d03f95e1dba8af000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000001ce3b052b9759a89085d422f47135a6b07afb2401ef4918b5f52b0bda503b9222b01001d6e7addb742ecf373558f59ed21358d74c1ea2f1cfcab80f4c5941a0e510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000041b5de3f36ba0000000000000000000000000000000000000000000000000000000091637ff800000000000000000000000000000000000000000000000005e085d5c8a18a7e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005c4740000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000584dd46508f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000602160b0b14140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf29800000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000190c60000000000000000000000000000000000000000000000000000000000019041000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", + "value": "0" + }, + "_MIGRATE_TO_ERC20_WITH_PERMIT_AND_POOL_INITIALIZE": { + "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000061311121212140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000c47ac2ff7b00000000000000000000000066a9893cc07d91d95644aedd05d03f95e1dba8af000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000001ce3b052b9759a89085d422f47135a6b07afb2401ef4918b5f52b0bda503b9222b01001d6e7addb742ecf373558f59ed21358d74c1ea2f1cfcab80f4c5941a0e510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000041b5de3f36ba0000000000000000000000000000000000000000000000000000000091637ff800000000000000000000000000000000000000000000000005e085d5c8a18a7e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005c4740000000000000000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002442966c68000000000000000000000000000000000000000000000000000000000005c474000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000524dd46508f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005020b0b1414000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf29800000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000190c60000000000000000000000000000000000000000000000000000000000019041000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000000000000000", + "value": "0" } } diff --git a/sdks/universal-router-sdk/test/uniswapTrades.test.ts b/sdks/universal-router-sdk/test/uniswapTrades.test.ts index 67f122014..a48e96a66 100644 --- a/sdks/universal-router-sdk/test/uniswapTrades.test.ts +++ b/sdks/universal-router-sdk/test/uniswapTrades.test.ts @@ -71,6 +71,7 @@ describe('Uniswap', () => { let ETH_USDC_V4: V4Pool let WETH_USDC_V4: V4Pool let WETH_USDC_V4_LOW_FEE: V4Pool + let ETH_USDC_V4_LOW_FEE: V4Pool let USDC_DAI_V4: V4Pool before(async () => { @@ -117,6 +118,18 @@ describe('Uniswap', () => { tickProviderMock ) + ETH_USDC_V4_LOW_FEE = new V4Pool( + ETHER, + USDC, + FeeAmount.LOW, + tickSpacing, + ZERO_ADDRESS, + encodeSqrtRatioX96(1, 1), + liquidity, + 0, + tickProviderMock + ) + ETH_USDC_V4 = new V4Pool( ETHER, USDC, @@ -1578,7 +1591,7 @@ describe('Uniswap', () => { }) describe('migrate', () => { - it('encodes a migration', async () => { + it('encodes a migration to eth', async () => { // sign a permit for the token const tokenId = 377972 const permit = { @@ -1603,10 +1616,11 @@ describe('Uniswap', () => { tickLower: 200040, tickUpper: 300000, }), + // in range (current tick = 0) outputPosition: new V4Position({ - pool: WETH_USDC_V4, + pool: ETH_USDC_V4, liquidity: 100000, - tickLower: 200040, + tickLower: -200040, tickUpper: 300000, }), v3RemoveLiquidityOptions: { @@ -1633,14 +1647,32 @@ describe('Uniswap', () => { migrate: true, slippageTolerance: new Percent(5, 100), recipient: TEST_RECIPIENT_ADDRESS, + useNative: ETHER, }, }) const methodParameters = SwapRouter.migrateV3ToV4CallParameters(opts, FORGE_V4_POSITION_MANAGER) - registerFixture('_MIGRATE_WITH_PERMIT', methodParameters) + registerFixture('_MIGRATE_TO_ETH_WITH_PERMIT', methodParameters) expect(hexToDecimalString(methodParameters.value)).to.eq('0') }) - it('encodes a migration if no v3 permit', async () => { + it('encodes a migration from erc20 to erc20', async () => { + // sign a permit for the token + const tokenId = 377972 + const permit = { + spender: UNIVERSAL_ROUTER_ADDRESS(UniversalRouterVersion.V2_0, 1), + tokenId, + deadline: MAX_UINT160.toString(), + nonce: 0, + } + const { domain, types, values } = NonfungiblePositionManager.getPermitData( + permit, + NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[ChainId.MAINNET], + ChainId.MAINNET + ) + + const signature: Signature = splitSignature(await wallet._signTypedData(domain, types, values)) + + // create migrate options const opts = Object.assign({ inputPosition: new Position({ pool: WETH_USDC_V3, @@ -1648,12 +1680,99 @@ describe('Uniswap', () => { tickLower: 200040, tickUpper: 300000, }), + // in range (current tick = 0) outputPosition: new V4Position({ pool: WETH_USDC_V4, liquidity: 100000, + tickLower: -200040, + tickUpper: 300000, + }), + v3RemoveLiquidityOptions: { + tokenId, + liquidityPercentage: new Percent(100, 100), + slippageTolerance: new Percent(5, 100), + deadline: MAX_UINT160, + burnToken: true, + collectOptions: { + expectedCurrencyOwed0: CurrencyAmount.fromRawAmount(USDC, 0), + expectedCurrencyOwed1: CurrencyAmount.fromRawAmount(WETH, 0), + recipient: FORGE_V4_POSITION_MANAGER, + }, + permit: { + v: signature.v, + r: signature.r, + s: signature.s, + deadline: permit.deadline, + spender: permit.spender, + }, + }, + v4AddLiquidityOptions: { + deadline: MAX_UINT160, + migrate: true, + slippageTolerance: new Percent(5, 100), + recipient: TEST_RECIPIENT_ADDRESS, + }, + }) + const methodParameters = SwapRouter.migrateV3ToV4CallParameters(opts, FORGE_V4_POSITION_MANAGER) + registerFixture('_MIGRATE_TO_ERC20_WITH_PERMIT', methodParameters) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + }) + + it('encodes a migration from erc20 to eth if no v3 permit', async () => { + const opts = Object.assign({ + inputPosition: new Position({ + pool: WETH_USDC_V3, + liquidity: 72249373570746, + tickLower: 200040, + tickUpper: 300000, + }), + // in range (current tick = 0) + outputPosition: new V4Position({ + pool: ETH_USDC_V4, + liquidity: 100000, + tickLower: -200040, + tickUpper: 300000, + }), + v3RemoveLiquidityOptions: { + tokenId: 377972, + liquidityPercentage: new Percent(100, 100), + slippageTolerance: new Percent(5, 100), + deadline: MAX_UINT160, + burnToken: true, + collectOptions: { + expectedCurrencyOwed0: CurrencyAmount.fromRawAmount(USDC, 0), + expectedCurrencyOwed1: CurrencyAmount.fromRawAmount(WETH, 0), + recipient: FORGE_V4_POSITION_MANAGER, + }, + }, + v4AddLiquidityOptions: { + deadline: MAX_UINT160, + migrate: true, + slippageTolerance: new Percent(5, 100), + recipient: TEST_RECIPIENT_ADDRESS, + useNative: ETHER, + }, + }) + const methodParameters = SwapRouter.migrateV3ToV4CallParameters(opts, FORGE_V4_POSITION_MANAGER) + registerFixture('_MIGRATE_TO_ETH_WITHOUT_PERMIT', methodParameters) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + }) + + it('encodes a migration from erc20 to erc20 if no v3 permit', async () => { + const opts = Object.assign({ + inputPosition: new Position({ + pool: WETH_USDC_V3, + liquidity: 72249373570746, tickLower: 200040, tickUpper: 300000, }), + // in range (current tick = 0) + outputPosition: new V4Position({ + pool: WETH_USDC_V4, + liquidity: 100000, + tickLower: -200040, + tickUpper: 300000, + }), v3RemoveLiquidityOptions: { tokenId: 377972, liquidityPercentage: new Percent(100, 100), @@ -1674,11 +1793,11 @@ describe('Uniswap', () => { }, }) const methodParameters = SwapRouter.migrateV3ToV4CallParameters(opts, FORGE_V4_POSITION_MANAGER) - registerFixture('_MIGRATE_WITHOUT_PERMIT', methodParameters) + registerFixture('_MIGRATE_TO_ERC20_WITHOUT_PERMIT', methodParameters) expect(hexToDecimalString(methodParameters.value)).to.eq('0') }) - it('encodes a migration including pool initialization', async () => { + it('encodes a migration from erc20 to eth including pool initialization', async () => { // sign a permit for the token const tokenId = 377972 const permit = { @@ -1703,12 +1822,78 @@ describe('Uniswap', () => { tickLower: 200040, tickUpper: 300000, }), + // in range (current tick = 0) outputPosition: new V4Position({ - pool: WETH_USDC_V4_LOW_FEE, // migrate to LOW pool, which hasn't been initialized + pool: ETH_USDC_V4_LOW_FEE, liquidity: 100000, + tickLower: -200040, + tickUpper: 300000, + }), + v3RemoveLiquidityOptions: { + tokenId, + liquidityPercentage: new Percent(100, 100), + slippageTolerance: new Percent(5, 100), + deadline: MAX_UINT160, + burnToken: true, + collectOptions: { + expectedCurrencyOwed0: CurrencyAmount.fromRawAmount(USDC, 0), + expectedCurrencyOwed1: CurrencyAmount.fromRawAmount(WETH, 0), + recipient: FORGE_V4_POSITION_MANAGER, + }, + permit: { + v: signature.v, + r: signature.r, + s: signature.s, + deadline: permit.deadline, + spender: permit.spender, + }, + }, + v4AddLiquidityOptions: { + deadline: MAX_UINT160, + migrate: true, + slippageTolerance: new Percent(5, 100), + recipient: TEST_RECIPIENT_ADDRESS, + createPool: true, // boolean to signal pool creation + useNative: ETHER, + }, + }) + const methodParameters = SwapRouter.migrateV3ToV4CallParameters(opts, FORGE_V4_POSITION_MANAGER) + registerFixture('_MIGRATE_TO_ETH_WITH_PERMIT_AND_POOL_INITIALIZE', methodParameters) + expect(hexToDecimalString(methodParameters.value)).to.eq('0') + }) + + it('encodes a migration from erc20 to erc20 including pool initialization', async () => { + // sign a permit for the token + const tokenId = 377972 + const permit = { + spender: UNIVERSAL_ROUTER_ADDRESS(UniversalRouterVersion.V2_0, 1), + tokenId, + deadline: MAX_UINT160.toString(), + nonce: 0, + } + const { domain, types, values } = NonfungiblePositionManager.getPermitData( + permit, + NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[ChainId.MAINNET], + ChainId.MAINNET + ) + + const signature: Signature = splitSignature(await wallet._signTypedData(domain, types, values)) + + // create migrate options + const opts = Object.assign({ + inputPosition: new Position({ + pool: WETH_USDC_V3, + liquidity: 72249373570746, tickLower: 200040, tickUpper: 300000, }), + // in range (current tick = 0) + outputPosition: new V4Position({ + pool: WETH_USDC_V4_LOW_FEE, // migrate to LOW pool, which hasn't been initialized + liquidity: 100000, + tickLower: -200040, + tickUpper: 300000, + }), v3RemoveLiquidityOptions: { tokenId, liquidityPercentage: new Percent(100, 100), @@ -1737,7 +1922,7 @@ describe('Uniswap', () => { }, }) const methodParameters = SwapRouter.migrateV3ToV4CallParameters(opts, FORGE_V4_POSITION_MANAGER) - registerFixture('_MIGRATE_WITH_PERMIT_AND_POOL_INITIALIZE', methodParameters) + registerFixture('_MIGRATE_TO_ERC20_WITH_PERMIT_AND_POOL_INITIALIZE', methodParameters) expect(hexToDecimalString(methodParameters.value)).to.eq('0') }) @@ -1896,16 +2081,16 @@ describe('Uniswap', () => { it('throws if not minting when migrating', async () => { const opts = Object.assign({ inputPosition: new Position({ - pool: WETH_USDC_V3, + pool: USDC_DAI_V3, liquidity: 1, - tickLower: -WETH_USDC_V3.tickSpacing, - tickUpper: WETH_USDC_V3.tickSpacing, + tickLower: -USDC_DAI_V3.tickSpacing, + tickUpper: USDC_DAI_V3.tickSpacing, }), outputPosition: new V4Position({ - pool: WETH_USDC_V4, + pool: USDC_DAI_V4, liquidity: 1, - tickLower: -WETH_USDC_V4.tickSpacing, - tickUpper: WETH_USDC_V4.tickSpacing, + tickLower: -USDC_DAI_V4.tickSpacing, + tickUpper: USDC_DAI_V4.tickSpacing, }), v3RemoveLiquidityOptions: { tokenId: 1, @@ -1920,7 +2105,7 @@ describe('Uniswap', () => { }, }, v4AddLiquidityOptions: { - tokenId: 1, + migrate: true, deadline: 1, slippageTolerance: new Percent(5, 100), sqrtPriceX96: encodeSqrtRatioX96(1, 1), @@ -1929,7 +2114,7 @@ describe('Uniswap', () => { expect(() => SwapRouter.migrateV3ToV4CallParameters(opts)).to.throw('MINT_REQUIRED') }) - it('throws if migrating flag not set', async () => { + it('throws if migrating weth to eth with token mismatch', async () => { const opts = Object.assign({ inputPosition: new Position({ pool: WETH_USDC_V3, @@ -1938,10 +2123,48 @@ describe('Uniswap', () => { tickUpper: WETH_USDC_V3.tickSpacing, }), outputPosition: new V4Position({ - pool: WETH_USDC_V4, + pool: ETH_DAI_V4, liquidity: 1, - tickLower: -WETH_USDC_V4.tickSpacing, - tickUpper: WETH_USDC_V4.tickSpacing, + tickLower: -ETH_DAI_V4.tickSpacing, + tickUpper: ETH_DAI_V4.tickSpacing, + }), + v3RemoveLiquidityOptions: { + tokenId: 1, + liquidityPercentage: new Percent(100, 100), + slippageTolerance: new Percent(5, 100), + deadline: 1, + burnToken: true, + collectOptions: { + expectedCurrencyOwed0: CurrencyAmount.fromRawAmount(USDC, 0), + expectedCurrencyOwed1: CurrencyAmount.fromRawAmount(WETH, 0), + recipient: CHAIN_TO_ADDRESSES_MAP[ChainId.MAINNET].v4PositionManagerAddress, + }, + }, + v4AddLiquidityOptions: { + migrate: true, + deadline: 1, + slippageTolerance: new Percent(5, 100), + sqrtPriceX96: encodeSqrtRatioX96(1, 1), + useNative: ETHER, + recipient: TEST_RECIPIENT_ADDRESS, + }, + }) + expect(() => SwapRouter.migrateV3ToV4CallParameters(opts)).to.throw('TOKEN_MISMATCH') + }) + + it('throws if migrating flag not set', async () => { + const opts = Object.assign({ + inputPosition: new Position({ + pool: USDC_DAI_V3, + liquidity: 1, + tickLower: -USDC_DAI_V3.tickSpacing, + tickUpper: USDC_DAI_V3.tickSpacing, + }), + outputPosition: new V4Position({ + pool: USDC_DAI_V4, + liquidity: 1, + tickLower: -USDC_DAI_V4.tickSpacing, + tickUpper: USDC_DAI_V4.tickSpacing, }), v3RemoveLiquidityOptions: { tokenId: 1, @@ -1968,16 +2191,16 @@ describe('Uniswap', () => { it('throws if not permitting the Universal router', async () => { const opts = Object.assign({ inputPosition: new Position({ - pool: WETH_USDC_V3, + pool: USDC_DAI_V3, liquidity: 1, - tickLower: -WETH_USDC_V3.tickSpacing, - tickUpper: WETH_USDC_V3.tickSpacing, + tickLower: -USDC_DAI_V3.tickSpacing, + tickUpper: USDC_DAI_V3.tickSpacing, }), outputPosition: new V4Position({ - pool: WETH_USDC_V4, + pool: USDC_DAI_V4, liquidity: 1, - tickLower: -WETH_USDC_V4.tickSpacing, - tickUpper: WETH_USDC_V4.tickSpacing, + tickLower: -USDC_DAI_V4.tickSpacing, + tickUpper: USDC_DAI_V4.tickSpacing, }), v3RemoveLiquidityOptions: { tokenId: 1, diff --git a/yarn.lock b/yarn.lock index e0567dd22..72847ecfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4654,7 +4654,7 @@ __metadata: "@uniswap/v2-sdk": ^4.13.0 "@uniswap/v3-core": 1.0.0 "@uniswap/v3-sdk": ^3.24.0 - "@uniswap/v4-sdk": ^1.18.1 + "@uniswap/v4-sdk": ^1.19.2 bignumber.js: ^9.0.2 chai: ^4.3.6 dotenv: ^16.0.3 @@ -4791,16 +4791,16 @@ __metadata: languageName: node linkType: hard -"@uniswap/v4-sdk@npm:^1.18.1": - version: 1.18.1 - resolution: "@uniswap/v4-sdk@npm:1.18.1" +"@uniswap/v4-sdk@npm:^1.18.1, @uniswap/v4-sdk@npm:^1.19.2": + version: 1.19.2 + resolution: "@uniswap/v4-sdk@npm:1.19.2" dependencies: "@ethersproject/solidity": ^5.0.9 "@uniswap/sdk-core": ^7.5.0 "@uniswap/v3-sdk": 3.24.0 tiny-invariant: ^1.1.0 tiny-warning: ^1.0.3 - checksum: b107e7afec5f9422472bb0752a117b17f98b2671a6824a216d557e8d179ce2af9eacd21c8591b7f946f0f4cf933e2adda6d4dfd3747067576fb16435714dde0c + checksum: aa789689bfa98c021f2355f234f8b9108cb27c1d6abbff59169326ecadd78a381fd50ba4cd267a3552a139f42b9c2c3f58cbbb1baf1af012124c010eb1c1bb6c languageName: node linkType: hard From 210d614ff64e6efd3cf86db56992461e6ec64bee Mon Sep 17 00:00:00 2001 From: Eric Zhong Date: Thu, 6 Feb 2025 12:07:18 -0500 Subject: [PATCH 11/13] feat(router-sdk): add getter for amounts which returns native amounts in a trade (#284) --- sdks/router-sdk/src/entities/route.test.ts | 7 + sdks/router-sdk/src/entities/route.ts | 22 ++ sdks/router-sdk/src/entities/trade.test.ts | 253 ++++++++++++++++++++- sdks/router-sdk/src/entities/trade.ts | 48 +++- 4 files changed, 325 insertions(+), 5 deletions(-) diff --git a/sdks/router-sdk/src/entities/route.test.ts b/sdks/router-sdk/src/entities/route.test.ts index 07104929f..ee9306134 100644 --- a/sdks/router-sdk/src/entities/route.test.ts +++ b/sdks/router-sdk/src/entities/route.test.ts @@ -237,4 +237,11 @@ describe('RouteV2', () => { expect(route.input).toEqual(token0) expect(route.output).toEqual(ETHER) }) + + it('assigns pathInput and pathOutput correctly', () => { + const routeOriginal = new V2RouteSDK([pair_0_weth], token0, ETHER) + const route = new RouteV2(routeOriginal) + expect(route.pathInput).toEqual(token0) + expect(route.pathOutput).toEqual(weth) + }) }) diff --git a/sdks/router-sdk/src/entities/route.ts b/sdks/router-sdk/src/entities/route.ts index fe38aa88c..79bcd5d57 100644 --- a/sdks/router-sdk/src/entities/route.ts +++ b/sdks/router-sdk/src/entities/route.ts @@ -7,6 +7,18 @@ import { Protocol } from './protocol' import { Currency, Price, Token } from '@uniswap/sdk-core' import { MixedRouteSDK } from './mixedRoute/route' +// Helper function to get the pathInput and pathOutput for a V2 / V3 route +// currency could be native so we check against the wrapped version as they don't support native ETH in path +export function getPathToken(currency: Currency, pool: Pair | V3Pool): Token { + if (pool.token0.wrapped.equals(currency.wrapped)) { + return pool.token0 + } else if (pool.token1.wrapped.equals(currency.wrapped)) { + return pool.token1 + } else { + throw new Error(`Expected token ${currency.symbol} to be either ${pool.token0.symbol} or ${pool.token1.symbol}`) + } +} + export interface IRoute { protocol: Protocol // array of pools if v3 or pairs if v2 @@ -15,6 +27,8 @@ export interface IRoute input: TInput output: TOutput + pathInput: Currency + pathOutput: Currency } // V2 route wrapper @@ -24,10 +38,14 @@ export class RouteV2 { public readonly protocol: Protocol = Protocol.V2 public readonly pools: Pair[] + public pathInput: Currency + public pathOutput: Currency constructor(v2Route: V2RouteSDK) { super(v2Route.pairs, v2Route.input, v2Route.output) this.pools = this.pairs + this.pathInput = getPathToken(v2Route.input, this.pairs[0]) + this.pathOutput = getPathToken(v2Route.output, this.pairs[this.pairs.length - 1]) } } @@ -38,10 +56,14 @@ export class RouteV3 { public readonly protocol: Protocol = Protocol.V3 public readonly path: Token[] + public pathInput: Currency + public pathOutput: Currency constructor(v3Route: V3RouteSDK) { super(v3Route.pools, v3Route.input, v3Route.output) this.path = v3Route.tokenPath + this.pathInput = getPathToken(v3Route.input, this.pools[0]) + this.pathOutput = getPathToken(v3Route.output, this.pools[this.pools.length - 1]) } } diff --git a/sdks/router-sdk/src/entities/trade.test.ts b/sdks/router-sdk/src/entities/trade.test.ts index 82ea737de..d40642896 100644 --- a/sdks/router-sdk/src/entities/trade.test.ts +++ b/sdks/router-sdk/src/entities/trade.test.ts @@ -1,7 +1,7 @@ import { sqrt, Token, CurrencyAmount, TradeType, WETH9, Ether, Percent, Price } from '@uniswap/sdk-core' import { BigNumber } from '@ethersproject/bignumber' import JSBI from 'jsbi' -import { MixedRoute, RouteV2, RouteV3 } from './route' +import { MixedRoute, RouteV2, RouteV3, RouteV4 } from './route' import { Trade } from './trade' import { Route as V3RouteSDK, @@ -14,6 +14,8 @@ import { } from '@uniswap/v3-sdk' import { Pair, Route as V2RouteSDK } from '@uniswap/v2-sdk' import { MixedRouteSDK } from './mixedRoute/route' +import { Route as V4RouteSDK, Pool as V4Pool } from '@uniswap/v4-sdk' +import { ADDRESS_ZERO } from '../constants' describe('Trade', () => { const ETHER = Ether.onChain(1) @@ -22,6 +24,8 @@ describe('Trade', () => { const token1 = new Token(1, '0x0000000000000000000000000000000000000002', 18, 't1', 'token1') const token2 = new Token(1, '0x0000000000000000000000000000000000000003', 18, 't2', 'token2') const token3 = new Token(1, '0x0000000000000000000000000000000000000004', 18, 't3', 'token3') + const SQRT_RATIO_ONE = encodeSqrtRatioX96(1, 1) + const token4WithTax = new Token( 1, '0x0000000000000000000000000000000000000005', @@ -147,6 +151,29 @@ describe('Trade', () => { CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100000)) ) + const pool_v4_1_eth = new V4Pool( + token1, + ETHER, + FeeAmount.MEDIUM, + 60, + ADDRESS_ZERO, + SQRT_RATIO_ONE, + JSBI.BigInt(10000000000000), + 0, + [ + { + index: nearestUsableTick(TickMath.MIN_TICK, TICK_SPACINGS[FeeAmount.MEDIUM]), + liquidityNet: JSBI.BigInt(10000000000000), + liquidityGross: JSBI.BigInt(10000000000000), + }, + { + index: nearestUsableTick(TickMath.MAX_TICK, TICK_SPACINGS[FeeAmount.MEDIUM]), + liquidityNet: JSBI.multiply(JSBI.BigInt(10000000000000), JSBI.BigInt(-1)), + liquidityGross: JSBI.BigInt(10000000000000), + }, + ] + ) + describe('#fromRoute', () => { it('can contain only a v3 route', async () => { const routeOriginal = new V3RouteSDK([pool_0_1], token0, token1) @@ -164,6 +191,9 @@ describe('Trade', () => { expect(trade.swaps.length).toEqual(1) expect(trade.routes.length).toEqual(1) expect(trade.tradeType).toEqual(TradeType.EXACT_INPUT) + + expect(trade.amounts.inputAmountNative).toEqual(undefined) + expect(trade.amounts.outputAmountNative).toEqual(undefined) }) it('can contain only a v2 route', async () => { @@ -182,6 +212,9 @@ describe('Trade', () => { expect(trade.swaps.length).toEqual(1) expect(trade.routes.length).toEqual(1) expect(trade.tradeType).toEqual(TradeType.EXACT_OUTPUT) + + expect(trade.amounts.inputAmountNative).toEqual(undefined) + expect(trade.amounts.outputAmountNative).toEqual(undefined) }) it('can contain only a mixed route', async () => { @@ -200,6 +233,9 @@ describe('Trade', () => { expect(trade.swaps.length).toEqual(1) expect(trade.routes.length).toEqual(1) expect(trade.tradeType).toEqual(TradeType.EXACT_INPUT) + + expect(trade.amounts.inputAmountNative).toEqual(undefined) + expect(trade.amounts.outputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as input for a V3 Route exact input swap', async () => { @@ -210,6 +246,10 @@ describe('Trade', () => { const trade = await Trade.fromRoute(route, amount, TradeType.EXACT_INPUT) expect(trade.inputAmount.currency).toEqual(ETHER) expect(trade.outputAmount.currency).toEqual(token0) + + expect(trade.amounts.inputAmountNative).toBeDefined() + expect(trade.amounts.inputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.outputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as input for a V3 Route exact output swap', async () => { @@ -220,6 +260,10 @@ describe('Trade', () => { const trade = await Trade.fromRoute(route, amount, TradeType.EXACT_OUTPUT) expect(trade.inputAmount.currency).toEqual(ETHER) expect(trade.outputAmount.currency).toEqual(token0) + + expect(trade.amounts.inputAmountNative).toBeDefined() + expect(trade.amounts.inputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.outputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as output for a V3 Route exact output swap', async () => { @@ -232,6 +276,10 @@ describe('Trade', () => { expect(trade.outputAmount.currency).toEqual(ETHER) expect(trade.outputAmount).toEqual(amount) expect(trade.inputAmount).toEqual(expectedIn[0]) + + expect(trade.amounts.outputAmountNative).toBeDefined() + expect(trade.amounts.outputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.inputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as output for a V3 Route exact input swap', async () => { @@ -244,6 +292,10 @@ describe('Trade', () => { expect(trade.outputAmount.currency).toEqual(ETHER) expect(trade.inputAmount).toEqual(amount) expect(trade.outputAmount.wrapped).toEqual(expectedOut[0]) + + expect(trade.amounts.outputAmountNative).toBeDefined() + expect(trade.amounts.outputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.inputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as input for a V2 Route exact input swap', async () => { @@ -254,6 +306,10 @@ describe('Trade', () => { const trade = await Trade.fromRoute(route, amount, TradeType.EXACT_INPUT) expect(trade.inputAmount.currency).toEqual(ETHER) expect(trade.outputAmount.currency).toEqual(token2) + + expect(trade.amounts.inputAmountNative).toBeDefined() + expect(trade.amounts.inputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.outputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as input for a V2 Route exact output swap', async () => { @@ -264,6 +320,10 @@ describe('Trade', () => { const trade = await Trade.fromRoute(route, amount, TradeType.EXACT_OUTPUT) expect(trade.inputAmount.currency).toEqual(ETHER) expect(trade.outputAmount.currency).toEqual(token2) + + expect(trade.amounts.inputAmountNative).toBeDefined() + expect(trade.amounts.inputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.outputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as output for a V2 Route exact output swap', async () => { @@ -274,6 +334,10 @@ describe('Trade', () => { const trade = await Trade.fromRoute(route, amount, TradeType.EXACT_OUTPUT) expect(trade.inputAmount.currency).toEqual(token2) expect(trade.outputAmount.currency).toEqual(ETHER) + + expect(trade.amounts.outputAmountNative).toBeDefined() + expect(trade.amounts.outputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.inputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as output for a V2 Route exact input swap', async () => { @@ -284,6 +348,10 @@ describe('Trade', () => { const trade = await Trade.fromRoute(route, amount, TradeType.EXACT_INPUT) expect(trade.inputAmount.currency).toEqual(token2) expect(trade.outputAmount.currency).toEqual(ETHER) + + expect(trade.amounts.outputAmountNative).toBeDefined() + expect(trade.amounts.outputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.inputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as input for a Mixed Route exact input swap', async () => { @@ -294,6 +362,10 @@ describe('Trade', () => { const trade = await Trade.fromRoute(route, amount, TradeType.EXACT_INPUT) expect(trade.inputAmount.currency).toEqual(ETHER) expect(trade.outputAmount.currency).toEqual(token0) + + expect(trade.amounts.inputAmountNative).toBeDefined() + expect(trade.amounts.inputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.outputAmountNative).toEqual(undefined) }) it('can be constructed with ETHER as output for a Mixed Route exact input swap', async () => { @@ -306,6 +378,10 @@ describe('Trade', () => { expect(trade.outputAmount.currency).toEqual(ETHER) expect(trade.inputAmount).toEqual(amount) expect(trade.outputAmount.wrapped).toEqual(expectedOut[0]) + + expect(trade.amounts.outputAmountNative).toBeDefined() + expect(trade.amounts.outputAmountNative?.equalTo(0)).toBe(true) + expect(trade.amounts.inputAmountNative).toEqual(undefined) }) it('throws if input currency does not match for V2 Route', async () => { @@ -552,6 +628,12 @@ describe('Trade', () => { expect(trade.inputAmount.currency).toEqual(ETHER) expect(trade.outputAmount.currency).toEqual(token1) expect(trade.swaps.length).toEqual(3) + // Expect all input amounts to be native + expect(trade.swaps.every((swap) => swap.inputAmount.currency.isNative)).toBe(true) + // Expect all route inputs to be ETH + expect(trade.swaps.every((swap) => swap.route.input.isNative)).toBe(true) + // Expect all route path inputs to be WETH, can't use pathInput because not supported in older SDKs + expect(trade.swaps.every((swap) => swap.route.pools[0].involvesToken(weth))).toBe(true) expect(trade.routes.length).toEqual(3) expect(trade.tradeType).toEqual(TradeType.EXACT_INPUT) }) @@ -627,6 +709,175 @@ describe('Trade', () => { expect(trade.tradeType).toEqual(TradeType.EXACT_INPUT) }) + it('can be constructed with ETHER as input for exact input swap, with V4 eth route and V2 weth route', async () => { + const routeOriginalV2 = new V2RouteSDK([pair_weth_0, pair_0_1], ETHER, token1) + const routev2 = new RouteV2(routeOriginalV2) + const amountv2 = CurrencyAmount.fromRawAmount(ETHER, JSBI.BigInt(100)) + + const routeOriginalV4 = new V4RouteSDK([pool_v4_1_eth], ETHER, token1) + const routev4 = new RouteV4(routeOriginalV4) + const amountv4 = CurrencyAmount.fromRawAmount(ETHER, JSBI.BigInt(1000)) + + const trade = await Trade.fromRoutes( + [{ routev2, amount: amountv2 }], + [], + TradeType.EXACT_INPUT, + [], + [{ routev4, amount: amountv4 }] + ) + + expect(trade.tradeType).toEqual(TradeType.EXACT_INPUT) + expect(trade.inputAmount.currency).toEqual(ETHER) + expect(trade.outputAmount.currency).toEqual(token1) + expect(trade.swaps.length).toEqual(2) + expect(trade.routes.length).toEqual(2) + // Expect all swap input amounts to be native + expect(trade.swaps.every((swap) => swap.inputAmount.currency.isNative)).toBe(true) + // Expect all route inputs to be ETH + expect(trade.swaps.every((swap) => swap.route.input.isNative)).toBe(true) + // However, expect the routes to be preserved (v2 using WETH and v4 using ETH) + expect(trade.swaps[0].route.pathInput).toEqual(weth) + expect(trade.swaps[1].route.pathInput).toEqual(ETHER) + + // Expect inputAmount to be the sum of the input amounts of the swaps + expect(trade.amounts.inputAmount.equalTo(trade.amounts.inputAmount)).toBe(true) + expect(trade.amounts.inputAmount.equalTo(amountv2.add(amountv4))).toBe(true) + // Expect inputAmountNative to correctly track only the amount required for the ETH input V4 route + expect(trade.amounts.inputAmountNative).toBeDefined() + expect(trade.amounts.inputAmountNative?.equalTo(amountv4)).toBe(true) + // Expect outputAmount to be the sum of the output amounts of the swaps + expect(trade.amounts.outputAmount.equalTo(trade.amounts.outputAmount)).toBe(true) + // Expect outputAmountNative to be undefined because there is no ETH output path + expect(trade.amounts.outputAmountNative).toBeUndefined() + }) + + it('can be constructed with ETHER as input for exact output swap, with V4 eth route and V2 weth route', async () => { + const routeOriginalV2 = new V2RouteSDK([pair_weth_0, pair_0_1], ETHER, token1) + const routev2 = new RouteV2(routeOriginalV2) + const amountv2 = CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100)) + + const routeOriginalV4 = new V4RouteSDK([pool_v4_1_eth], ETHER, token1) + const routev4 = new RouteV4(routeOriginalV4) + const amountv4 = CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(1000)) + + const trade = await Trade.fromRoutes( + [{ routev2, amount: amountv2 }], + [], + TradeType.EXACT_OUTPUT, + [], + [{ routev4, amount: amountv4 }] + ) + + expect(trade.tradeType).toEqual(TradeType.EXACT_OUTPUT) + expect(trade.inputAmount.currency).toEqual(ETHER) + expect(trade.outputAmount.currency).toEqual(token1) + expect(trade.swaps.length).toEqual(2) + expect(trade.routes.length).toEqual(2) + // Expect all swap input amounts to be native + expect(trade.swaps.every((swap) => swap.inputAmount.currency.isNative)).toBe(true) + // Expect all route inputs to be ETH + expect(trade.swaps.every((swap) => swap.route.input.isNative)).toBe(true) + // However, expect the routes to be preserved (v2 using WETH and v4 using ETH) + expect(trade.swaps[0].route.pathInput).toEqual(weth) + expect(trade.swaps[1].route.pathInput).toEqual(ETHER) + + // Expect inputAmount to be the sum of the input amounts of the swaps + expect(trade.amounts.inputAmount.equalTo(trade.amounts.inputAmount)).toBe(true) + // Expect inputAmountNative to correctly track only the amount required for the ETH input V4 route + expect(trade.amounts.inputAmountNative).toBeDefined() + expect(trade.amounts.inputAmountNative?.greaterThan(0)).toBe(true) + // Expect outputAmount to be the sum of the output amounts of the swaps + expect(trade.amounts.outputAmount.equalTo(trade.amounts.outputAmount)).toBe(true) + expect(trade.amounts.outputAmount.equalTo(amountv2.add(amountv4))).toBe(true) + // Expect outputAmountNative to be undefined because there is no ETH output path + expect(trade.amounts.outputAmountNative).toBeUndefined() + }) + + it('can be constructed with ETHER as output for exact input swap, with V4 eth route and V2 weth route', async () => { + const routeOriginalV2 = new V2RouteSDK([pair_0_1, pair_weth_0], token1, ETHER) + const routev2 = new RouteV2(routeOriginalV2) + const amountv2 = CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(100)) + + const routeOriginalV4 = new V4RouteSDK([pool_v4_1_eth], token1, ETHER) + const routev4 = new RouteV4(routeOriginalV4) + const amountv4 = CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(1000)) + + const trade = await Trade.fromRoutes( + [{ routev2, amount: amountv2 }], + [], + TradeType.EXACT_INPUT, + [], + [{ routev4, amount: amountv4 }] + ) + + expect(trade.tradeType).toEqual(TradeType.EXACT_INPUT) + expect(trade.inputAmount.currency).toEqual(token1) + expect(trade.outputAmount.currency).toEqual(ETHER) + expect(trade.swaps.length).toEqual(2) + expect(trade.routes.length).toEqual(2) + // Expect all swap output amounts to be native + expect(trade.swaps.every((swap) => swap.outputAmount.currency.isNative)).toBe(true) + // Expect all route outputs to be ETH + expect(trade.swaps.every((swap) => swap.route.output.isNative)).toBe(true) + // However, expect the routes to be preserved (v2 using WETH and v4 using ETH) + expect(trade.swaps[0].route.pathOutput).toEqual(weth) + expect(trade.swaps[1].route.pathOutput).toEqual(ETHER) + + // Expect inputAmount to be the sum of the input amounts of the swaps + expect(trade.amounts.inputAmount.equalTo(trade.amounts.inputAmount)).toBe(true) + expect(trade.amounts.inputAmount.equalTo(amountv2.add(amountv4))).toBe(true) + // Expect inputAmountNative to be undefined because there is no ETH input path + expect(trade.amounts.inputAmountNative).toBeUndefined() + // Expect outputAmount to be the sum of the output amounts of the swaps + expect(trade.amounts.outputAmount.equalTo(trade.amounts.outputAmount)).toBe(true) + // Expect outputAmountNative to correctly track only the amount required for the ETH output V4 route + expect(trade.amounts.outputAmountNative).toBeDefined() + expect(trade.amounts.outputAmountNative?.greaterThan(0)).toBe(true) + }) + + it('can be constructed with ETHER as output for exact output swap, with V4 eth route and V2 weth route', async () => { + const routeOriginalV2 = new V2RouteSDK([pair_0_1, pair_weth_0], token1, ETHER) + const routev2 = new RouteV2(routeOriginalV2) + const amountv2 = CurrencyAmount.fromRawAmount(ETHER, JSBI.BigInt(100)) + + const routeOriginalV4 = new V4RouteSDK([pool_v4_1_eth], token1, ETHER) + const routev4 = new RouteV4(routeOriginalV4) + const amountv4 = CurrencyAmount.fromRawAmount(ETHER, JSBI.BigInt(1000)) + + const trade = await Trade.fromRoutes( + [{ routev2, amount: amountv2 }], + [], + TradeType.EXACT_OUTPUT, + [], + [{ routev4, amount: amountv4 }] + ) + + expect(trade.tradeType).toEqual(TradeType.EXACT_OUTPUT) + expect(trade.inputAmount.currency).toEqual(token1) + expect(trade.outputAmount.currency).toEqual(ETHER) + expect(trade.swaps.length).toEqual(2) + expect(trade.routes.length).toEqual(2) + // Expect all swap output amounts to be native + expect(trade.swaps.every((swap) => swap.outputAmount.currency.isNative)).toBe(true) + // Expect all route outputs to be ETH + expect(trade.swaps.every((swap) => swap.route.output.isNative)).toBe(true) + // However, expect the routes to be preserved (v2 using WETH and v4 using ETH) + expect(trade.swaps[0].route.pathOutput).toEqual(weth) + expect(trade.swaps[1].route.pathOutput).toEqual(ETHER) + + // Expect inputAmount to be the sum of the input amounts of the swaps + expect(trade.amounts.inputAmount.equalTo(trade.amounts.inputAmount)).toBe(true) + expect(trade.amounts.inputAmount.greaterThan(0)).toBe(true) + // Expect inputAmountNative to be undefined because there is no ETH input path + expect(trade.amounts.inputAmountNative).toBeUndefined() + // Expect outputAmount to be the sum of the output amounts of the swaps + expect(trade.amounts.outputAmount.equalTo(trade.amounts.outputAmount)).toBe(true) + expect(trade.amounts.outputAmount.equalTo(amountv2.add(amountv4))).toBe(true) + // Expect outputAmountNative to correctly track only the amount required for the ETH output V4 route + expect(trade.amounts.outputAmountNative).toBeDefined() + expect(trade.amounts.outputAmountNative?.equalTo(amountv4)).toBe(true) + }) + it('throws if pools are re-used between V3 routes', async () => { const routeOriginalV2 = new V2RouteSDK([pair_0_1, pair_1_2], token0, token2) const routev2 = new RouteV2(routeOriginalV2) diff --git a/sdks/router-sdk/src/entities/trade.ts b/sdks/router-sdk/src/entities/trade.ts index c0f64b946..8ceca2d4f 100644 --- a/sdks/router-sdk/src/entities/trade.ts +++ b/sdks/router-sdk/src/entities/trade.ts @@ -139,10 +139,10 @@ export class Trade inputAmount) - .reduce((total, cur) => total.add(cur), CurrencyAmount.fromRawAmount(inputCurrency, 0)) + .map(({ inputAmount: routeInputAmount }) => routeInputAmount) + .reduce((total, cur) => total.add(cur), CurrencyAmount.fromRawAmount(inputAmountCurrency, 0)) this._inputAmount = totalInputFromRoutes return this._inputAmount @@ -155,13 +155,53 @@ export class Trade outputAmount) + .map(({ outputAmount: routeOutputAmount }) => routeOutputAmount) .reduce((total, cur) => total.add(cur), CurrencyAmount.fromRawAmount(outputCurrency, 0)) this._outputAmount = totalOutputFromRoutes return this._outputAmount } + /** + * Returns the sum of all swaps within the trade + * @returns + * inputAmount: total input amount + * inputAmountNative: total amount of native currency required for ETH input paths + * - 0 if inputAmount is native but no native input paths + * - undefined if inputAmount is not native + * outputAmount: total output amount + * outputAmountNative: total amount of native currency returned from ETH output paths + * - 0 if outputAmount is native but no native output paths + * - undefined if outputAmount is not native + */ + public get amounts(): { + inputAmount: CurrencyAmount + inputAmountNative: CurrencyAmount | undefined + outputAmount: CurrencyAmount + outputAmountNative: CurrencyAmount | undefined + } { + // Find native currencies for reduce below + const inputNativeCurrency = this.swaps.find(({ inputAmount }) => inputAmount.currency.isNative)?.inputAmount + .currency + const outputNativeCurrency = this.swaps.find(({ outputAmount }) => outputAmount.currency.isNative)?.outputAmount + .currency + + return { + inputAmount: this.inputAmount, + inputAmountNative: inputNativeCurrency + ? this.swaps.reduce((total, swap) => { + return swap.route.pathInput.isNative ? total.add(swap.inputAmount) : total + }, CurrencyAmount.fromRawAmount(inputNativeCurrency, 0)) + : undefined, + outputAmount: this.outputAmount, + outputAmountNative: outputNativeCurrency + ? this.swaps.reduce((total, swap) => { + return swap.route.pathOutput.isNative ? total.add(swap.outputAmount) : total + }, CurrencyAmount.fromRawAmount(outputNativeCurrency, 0)) + : undefined, + } + } + private _executionPrice: Price | undefined /** From edf1c7b868d8196581160c9627455e21700aa15e Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:06:06 -0800 Subject: [PATCH 12/13] fix(router-sdk): fix mixed route ETH <-> WETH wrong route.path (#299) ## PR Scope Please title your PR according to the following types and scopes following [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/): - `fix(SDK name):` will trigger a patch version - `chore():` will not trigger any release and should be used for internal repo changes - `(public):` will trigger a patch version for non-code changes (e.g. README changes) - `feat(SDK name):` will trigger a minor version - `feat(breaking):` will trigger a major version for a breaking change ## Description _[Summary of the change, motivation, and context]_ ## How Has This Been Tested? _[e.g. Manually, E2E tests, unit tests, Storybook]_ ## Are there any breaking changes? _[e.g. Type definitions, API definitions]_ If there are breaking changes, please ensure you bump the major version Bump the major version (by using the title `feat(breaking): ...`), post a notice in #eng-sdks, and explicitly notify all Uniswap Labs consumers of the SDK. ## (Optional) Feedback Focus _[Specific parts of this PR you'd like feedback on, or that reviewers should pay closer attention to]_ ## (Optional) Follow Ups _[Things that weren't addressed in this PR, ways you plan to build on this work, or other ways this work could be extended]_ --- .../src/entities/mixedRoute/route.test.ts | 25 +++++++-- .../src/entities/mixedRoute/route.ts | 51 ++++++++++++++----- sdks/router-sdk/src/index.ts | 1 - .../src/utils/encodeMixedRouteToPath.test.ts | 3 +- .../src/utils/isValidTokenPath.test.ts | 19 ------- sdks/router-sdk/src/utils/isValidTokenPath.ts | 24 --------- .../router-sdk/src/utils/pathCurrency.test.ts | 17 +++++++ 7 files changed, 77 insertions(+), 63 deletions(-) delete mode 100644 sdks/router-sdk/src/utils/isValidTokenPath.test.ts delete mode 100644 sdks/router-sdk/src/utils/isValidTokenPath.ts create mode 100644 sdks/router-sdk/src/utils/pathCurrency.test.ts diff --git a/sdks/router-sdk/src/entities/mixedRoute/route.test.ts b/sdks/router-sdk/src/entities/mixedRoute/route.test.ts index 4ca7b0b67..753d1bf4d 100644 --- a/sdks/router-sdk/src/entities/mixedRoute/route.test.ts +++ b/sdks/router-sdk/src/entities/mixedRoute/route.test.ts @@ -37,7 +37,14 @@ describe('MixedRoute', () => { describe('path', () => { it('real v3 weth pool and fake v4 eth/weth pool', () => { const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_weth_eth], token0, ETHER) - expect(route.path).toEqual([token0, weth, ETHER]) + expect(route.path).toEqual([token0, weth]) + expect(route.pools).toEqual([pool_v3_0_weth]) + }) + + it('real v3 weth pool and real v4 eth pool', () => { + const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_weth_eth, pool_v4_1_eth], token0, token1) + expect(route.path).toEqual([token0, weth, token1]) + expect(route.pools).toEqual([pool_v3_0_weth, pool_v4_1_eth]) }) it('wraps pure v3 route object and successfully constructs a path from the tokens', () => { @@ -90,7 +97,7 @@ describe('MixedRoute', () => { }) it('wraps mixed route object with mixed v4 route that converts WETH -> ETH ', () => { - const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_weth_eth, pool_v4_1_eth], token0, token1) + const route = new MixedRouteSDK([pool_v3_0_weth, pool_v4_weth_eth, pool_v4_1_eth], token0, token1, true) expect(route.pools).toEqual([pool_v3_0_weth, pool_v4_weth_eth, pool_v4_1_eth]) expect(route.path).toEqual([token0, weth, ETHER, token1]) expect(route.input).toEqual(token0) @@ -110,11 +117,21 @@ describe('MixedRoute', () => { }) it('cannot wrap mixed route object with pure v4 route that converts ETH -> WETH ', () => { - expect(() => new MixedRouteSDK([pool_v4_1_eth, pool_v4_0_weth], token1, token0)).toThrow('PATH') + const route = new MixedRouteSDK([pool_v4_1_eth, pool_v4_0_weth], token1, token0) + expect(route.pools).toEqual([pool_v4_1_eth, pool_v4_0_weth]) + expect(route.path).toEqual([token1, ETHER, token0]) + expect(route.input).toEqual(token1) + expect(route.output).toEqual(token0) + expect(route.chainId).toEqual(1) }) it('cannot wrap mixed route object with pure v4 route that converts WETH -> ETH ', () => { - expect(() => new MixedRouteSDK([pool_v4_0_weth, pool_v4_1_eth], token0, token1)).toThrow('PATH') + const route = new MixedRouteSDK([pool_v4_0_weth, pool_v4_1_eth], token0, token1) + expect(route.pools).toEqual([pool_v4_0_weth, pool_v4_1_eth]) + expect(route.path).toEqual([token0, weth, token1]) + expect(route.input).toEqual(token0) + expect(route.output).toEqual(token1) + expect(route.chainId).toEqual(1) }) it('wraps complex mixed route object and successfully constructs a path from the tokens', () => { diff --git a/sdks/router-sdk/src/entities/mixedRoute/route.ts b/sdks/router-sdk/src/entities/mixedRoute/route.ts index 0ed0dff84..bd781da30 100644 --- a/sdks/router-sdk/src/entities/mixedRoute/route.ts +++ b/sdks/router-sdk/src/entities/mixedRoute/route.ts @@ -1,7 +1,6 @@ import invariant from 'tiny-invariant' import { Currency, Price, Token } from '@uniswap/sdk-core' import { Pool as V4Pool } from '@uniswap/v4-sdk' -import { isValidTokenPath } from '../../utils/isValidTokenPath' import { getPathCurrency } from '../../utils/pathCurrency' import { TPool } from '../../utils/TPool' @@ -25,9 +24,12 @@ export class MixedRouteSDK { * @param pools An array of `TPool` objects (pools or pairs), ordered by the route the swap will take * @param input The input token * @param output The output token + * @param retainsFakePool Set to true to filter out a pool that has a fake eth-weth pool */ - public constructor(pools: TPool[], input: TInput, output: TOutput) { + public constructor(pools: TPool[], input: TInput, output: TOutput, retainFakePools = false) { + pools = retainFakePools ? pools : pools.filter((pool) => !(pool instanceof V4Pool && pool.tickSpacing === 0)) invariant(pools.length > 0, 'POOLS') + // there is a pool mismatched to the path if we do not retain the fake eth-weth pools const chainId = pools[0].chainId const allOnSameChain = pools.every((pool) => pool.chainId === chainId) @@ -36,7 +38,11 @@ export class MixedRouteSDK { this.pathInput = getPathCurrency(input, pools[0]) this.pathOutput = getPathCurrency(output, pools[pools.length - 1]) - invariant(pools[0].involvesToken(this.pathInput as Token), 'INPUT') + if (!(pools[0] instanceof V4Pool)) { + invariant(pools[0].involvesToken(this.pathInput as Token), 'INPUT') + } else { + invariant((pools[0] as V4Pool).v4InvolvesToken(this.pathInput), 'INPUT') + } const lastPool = pools[pools.length - 1] if (lastPool instanceof V4Pool) { invariant(lastPool.v4InvolvesToken(output) || lastPool.v4InvolvesToken(output.wrapped), 'OUTPUT') @@ -51,20 +57,37 @@ export class MixedRouteSDK { pools[0].token0.equals(this.pathInput) ? tokenPath.push(pools[0].token1) : tokenPath.push(pools[0].token0) for (let i = 1; i < pools.length; i++) { - const prevPool = pools[i - 1] const pool = pools[i] const inputToken = tokenPath[i] - const outputToken = - pool instanceof V4Pool - ? pool.token0.equals(inputToken) - ? pool.token1 - : pool.token0 - : pool.token0.wrapped.equals(inputToken.wrapped) - ? pool.token1 - : pool.token0 - - invariant(isValidTokenPath(prevPool, pool, inputToken), `PATH`) + let outputToken + if ( + // we hit an edge case if it's a v4 pool and neither of the tokens are in the pool OR it is not a v4 pool but the input currency is eth + (pool instanceof V4Pool && !pool.involvesToken(inputToken)) || + (!(pool instanceof V4Pool) && inputToken.isNative) + ) { + // We handle the case where the inputToken =/= pool.token0 or pool.token1. There are 2 specific cases. + if (inputToken.equals(pool.token0.wrapped)) { + // 1) the inputToken is WETH and the current pool has ETH + // for example, pools: USDC-WETH, ETH-PEPE, path: USDC, WETH, PEPE + // second pool is a v4 pool, the first could be any version + outputToken = pool.token1 + } else if (inputToken.wrapped.equals(pool.token0) || inputToken.wrapped.equals(pool.token1)) { + // 2) the inputToken is ETH and the current pool has WETH + // for example, pools: USDC-ETH, WETH-PEPE, path: USDC, ETH, PEPE + // first pool is a v4 pool, the second could be any version + outputToken = inputToken.wrapped.equals(pool.token0) ? pool.token1 : pool.token0 + } else { + throw new Error(`POOL_MISMATCH pool: ${JSON.stringify(pool)} inputToken: ${JSON.stringify(inputToken)}`) + } + } else { + // then the input token must equal either token0 or token1 + invariant( + inputToken.equals(pool.token0) || inputToken.equals(pool.token1), + `PATH pool ${JSON.stringify(pool)} inputToken ${JSON.stringify(inputToken)}` + ) + outputToken = inputToken.equals(pool.token0) ? pool.token1 : pool.token0 + } tokenPath.push(outputToken) } diff --git a/sdks/router-sdk/src/index.ts b/sdks/router-sdk/src/index.ts index 1ab290cb5..47d5ccc11 100644 --- a/sdks/router-sdk/src/index.ts +++ b/sdks/router-sdk/src/index.ts @@ -12,4 +12,3 @@ export * from './utils/encodeMixedRouteToPath' export * from './utils/TPool' export * from './utils/pathCurrency' export * from './utils' -export * from './utils/isValidTokenPath' diff --git a/sdks/router-sdk/src/utils/encodeMixedRouteToPath.test.ts b/sdks/router-sdk/src/utils/encodeMixedRouteToPath.test.ts index a409ae009..3fc0d8f91 100644 --- a/sdks/router-sdk/src/utils/encodeMixedRouteToPath.test.ts +++ b/sdks/router-sdk/src/utils/encodeMixedRouteToPath.test.ts @@ -74,7 +74,8 @@ describe('#encodeMixedRouteToPath', () => { const route_1_v2_weth_v0_eth_v4_token0 = new MixedRouteSDK( [pair_1_weth, fake_v4_eth_weth_pool, pool_V4_0_eth], token1, - token0 + token0, + true ) describe('pure V3', () => { diff --git a/sdks/router-sdk/src/utils/isValidTokenPath.test.ts b/sdks/router-sdk/src/utils/isValidTokenPath.test.ts deleted file mode 100644 index c9b6b9c55..000000000 --- a/sdks/router-sdk/src/utils/isValidTokenPath.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Ether, Token, WETH9 } from '@uniswap/sdk-core' -import { FeeAmount, encodeSqrtRatioX96 } from '@uniswap/v3-sdk' -import { Pool as V4Pool } from '@uniswap/v4-sdk' -import { ADDRESS_ZERO } from '../constants' -import { isValidTokenPath } from './isValidTokenPath' - -describe('#isValidTokenPath', () => { - const SQRT_RATIO_ONE = encodeSqrtRatioX96(1, 1) - const ETHER = Ether.onChain(1) - const token1 = new Token(1, '0x0000000000000000000000000000000000000002', 18, 't1') - const weth = WETH9[1] - - const pool_v4_weth_eth = new V4Pool(weth, ETHER, 0, 0, ADDRESS_ZERO, 79228162514264337593543950336, 0, 0) - const pool_v4_1_eth = new V4Pool(token1, ETHER, FeeAmount.MEDIUM, 60, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0) - - it('v3 pool and v4 pool, with WETH and ETH unwrap', () => { - expect(isValidTokenPath(pool_v4_weth_eth, pool_v4_1_eth, ETHER)).toBe(true) - }) -}) diff --git a/sdks/router-sdk/src/utils/isValidTokenPath.ts b/sdks/router-sdk/src/utils/isValidTokenPath.ts deleted file mode 100644 index 577edb61a..000000000 --- a/sdks/router-sdk/src/utils/isValidTokenPath.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Currency, Token } from '@uniswap/sdk-core' -import { Pool as V4Pool } from '@uniswap/v4-sdk' -import { TPool } from './TPool' - -export function isValidTokenPath(prevPool: TPool, currentPool: TPool, inputToken: Currency): boolean { - if (inputToken instanceof Token && currentPool.involvesToken(inputToken)) return true - - if (currentPool instanceof V4Pool && currentPool.involvesToken(inputToken)) return true - - // throw if both v4 pools, native/wrapped tokens not interchangeable in v4 - if (prevPool instanceof V4Pool && currentPool instanceof V4Pool) return false - - // v2/v3 --> v4 valid if v2/v3 output is the wrapped version of the v4 pool native currency - if (currentPool instanceof V4Pool) { - if (currentPool.token0.wrapped.equals(inputToken) || currentPool.token1.wrapped.equals(inputToken)) return true - } - - // v4 --> v2/v3 valid if v4 output is the native version of the v2/v3 wrapped token - if (prevPool instanceof V4Pool) { - if (currentPool.involvesToken(inputToken.wrapped)) return true - } - - return false -} diff --git a/sdks/router-sdk/src/utils/pathCurrency.test.ts b/sdks/router-sdk/src/utils/pathCurrency.test.ts new file mode 100644 index 000000000..5c2bc23a9 --- /dev/null +++ b/sdks/router-sdk/src/utils/pathCurrency.test.ts @@ -0,0 +1,17 @@ +import { Ether, Token } from '@uniswap/sdk-core' +import { encodeSqrtRatioX96 } from '@uniswap/v3-sdk' +import { Pool as V4Pool } from '@uniswap/v4-sdk' +import { ADDRESS_ZERO } from '../constants' +import { getPathCurrency } from './pathCurrency' + +describe('#getPathCurrency', () => { + const SQRT_RATIO_ONE = encodeSqrtRatioX96(1, 1) + const ETHER = Ether.onChain(1) + const token1 = new Token(1, '0x0000000000000000000000000000000000000002', 18, 't1') + + const pool_v4_eth_token1 = new V4Pool(token1, ETHER, 0, 0, ADDRESS_ZERO, SQRT_RATIO_ONE, 0, 0) + + it('returns eth input', () => { + expect(getPathCurrency(ETHER, pool_v4_eth_token1)).toEqual(ETHER) + }) +}) From 02b8465192a56759bd2bf81f300d233fd93438c1 Mon Sep 17 00:00:00 2001 From: "Siyu Jiang (See-You John)" <91580504+jsy1218@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:24:30 -0800 Subject: [PATCH 13/13] feat(universalrouter-sdk): bump router-sdk to 1.22.1 (#300) --- sdks/universal-router-sdk/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdks/universal-router-sdk/package.json b/sdks/universal-router-sdk/package.json index 3866f15b0..0c6bae34f 100644 --- a/sdks/universal-router-sdk/package.json +++ b/sdks/universal-router-sdk/package.json @@ -31,7 +31,7 @@ "dependencies": { "@openzeppelin/contracts": "4.7.0", "@uniswap/permit2-sdk": "^1.3.0", - "@uniswap/router-sdk": "^1.21.4", + "@uniswap/router-sdk": "^1.22.1", "@uniswap/sdk-core": "^7.5.0", "@uniswap/universal-router": "2.0.0-beta.2", "@uniswap/v2-core": "^1.0.1", diff --git a/yarn.lock b/yarn.lock index 72847ecfa..163471c92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4509,9 +4509,9 @@ __metadata: languageName: unknown linkType: soft -"@uniswap/router-sdk@npm:^1.21.4": - version: 1.21.4 - resolution: "@uniswap/router-sdk@npm:1.21.4" +"@uniswap/router-sdk@npm:^1.22.1": + version: 1.22.1 + resolution: "@uniswap/router-sdk@npm:1.22.1" dependencies: "@ethersproject/abi": ^5.5.0 "@uniswap/sdk-core": ^7.5.0 @@ -4519,7 +4519,7 @@ __metadata: "@uniswap/v2-sdk": ^4.13.0 "@uniswap/v3-sdk": ^3.24.0 "@uniswap/v4-sdk": ^1.18.1 - checksum: 9bf06ab7f1183548a1ad217707ed36ae7fd0fec2047fb9845be12a6347af920e2669228acc3d182b276c48ba373dd5d12f80dfca89503d3473fdbc8a04abbd18 + checksum: fff26fe8607568ecfd8029fbc6ec462ccee6dddde916827a2116b4961947dee37fbae40a25d1325abe8ffbbb5280511fa9926fd034d334db681b9462ee206406 languageName: node linkType: hard @@ -4647,7 +4647,7 @@ __metadata: "@types/node": ^18.7.16 "@types/node-fetch": ^2.6.2 "@uniswap/permit2-sdk": ^1.3.0 - "@uniswap/router-sdk": ^1.21.4 + "@uniswap/router-sdk": ^1.22.1 "@uniswap/sdk-core": ^7.5.0 "@uniswap/universal-router": 2.0.0-beta.2 "@uniswap/v2-core": ^1.0.1