diff --git a/sdks/v4-sdk/src/PositionManager.test.ts b/sdks/v4-sdk/src/PositionManager.test.ts index 8e3cb336e..4293cc02f 100644 --- a/sdks/v4-sdk/src/PositionManager.test.ts +++ b/sdks/v4-sdk/src/PositionManager.test.ts @@ -300,7 +300,8 @@ describe('PositionManager', () => { recipient, slippageTolerance, deadline, - migrate: true, + additionalAmount0: 0, + additionalAmount1: 0, }) // Rebuild the data with the planner for the expected mint. MUST sweep since we are using the native currency. @@ -338,7 +339,8 @@ describe('PositionManager', () => { recipient, slippageTolerance, deadline, - migrate: true, + additionalAmount0: 0, + additionalAmount1: 0, useNative: currency_native, }) @@ -367,6 +369,85 @@ describe('PositionManager', () => { expect(value).toEqual('0x00') }) + it('succeeds when migrating out of range and need to transfer eth', () => { + const position: Position = new Position({ + pool: pool_1_eth, + tickLower: TICK_SPACINGS[FeeAmount.MEDIUM], + tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM] * 2, + liquidity: 1, + }) + const { calldata, value } = V4PositionManager.addCallParameters(position, { + recipient, + slippageTolerance, + deadline, + additionalAmount0: 1, + additionalAmount1: 0, + 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] * 2, + 1, + toHex(amount0Max), + toHex(amount1Max), + recipient, + EMPTY_BYTES, + ]) + + 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), 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 when migrating out of range and need to transfer extra currency', () => { + const position: Position = new Position({ + pool: pool_0_1, + tickLower: TICK_SPACINGS[FeeAmount.MEDIUM], + tickUpper: TICK_SPACINGS[FeeAmount.MEDIUM] * 2, + liquidity: 1, + }) + const { calldata, value } = V4PositionManager.addCallParameters(position, { + recipient, + slippageTolerance, + deadline, + additionalAmount0: 1, + additionalAmount1: 0, + }) + + // 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_0_1.poolKey, + TICK_SPACINGS[FeeAmount.MEDIUM], + TICK_SPACINGS[FeeAmount.MEDIUM] * 2, + 1, + toHex(amount0Max), + toHex(amount1Max), + recipient, + EMPTY_BYTES, + ]) + + 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)) + + 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 869fe3057..0fb2ab213 100644 --- a/sdks/v4-sdk/src/PositionManager.ts +++ b/sdks/v4-sdk/src/PositionManager.ts @@ -60,11 +60,6 @@ export interface MintSpecificOptions { * Initial price to set on the pool if creating */ sqrtPriceX96?: BigintIsh - - /** - * Whether the mint is part of a migration from V3 to V4. - */ - migrate?: boolean } /** @@ -114,21 +109,15 @@ export interface CollectSpecificOptions { recipient: string } -export interface TransferOptions { +export interface MigrateSpecificOptions { /** - * The account sending the NFT. + * The additional amount of currency0 that needs to be transferred if needed */ - sender: string - + additionalAmount0: BigintIsh /** - * The account that should receive the NFT. + * The additional amount of currency1 that needs to be transferred if needed */ - recipient: string - - /** - * The id of the token being sent. - */ - tokenId: BigintIsh + additionalAmount1: BigintIsh } export interface PermitDetails { @@ -183,9 +172,10 @@ export interface NFTPermitData { } export type MintOptions = CommonOptions & CommonAddLiquidityOptions & MintSpecificOptions +export type MigrateOptions = CommonOptions & CommonAddLiquidityOptions & MintSpecificOptions & MigrateSpecificOptions export type IncreaseLiquidityOptions = CommonOptions & CommonAddLiquidityOptions & ModifyPositionSpecificOptions -export type AddLiquidityOptions = MintOptions | IncreaseLiquidityOptions +export type AddLiquidityOptions = MintOptions | IncreaseLiquidityOptions | MigrateOptions export type RemoveLiquidityOptions = CommonOptions & RemoveLiquiditySpecificOptions & ModifyPositionSpecificOptions @@ -196,6 +186,10 @@ function isMint(options: AddLiquidityOptions): options is MintOptions { return Object.keys(options).some((k) => k === 'recipient') } +function isMigrate(options: AddLiquidityOptions): options is MigrateOptions { + return Object.keys(options).some((k) => k === 'additionalAmount0') +} + function shouldCreatePool(options: MintOptions): boolean { if (options.createPool) { invariant(options.sqrtPriceX96 !== undefined, NO_SQRT_PRICE) @@ -284,9 +278,11 @@ export abstract class V4PositionManager { let value: string = toHex(0) + let needToSendEth = isMigrate(options) && options.useNative && options.additionalAmount0 > '0' + // If migrating, we need to settle and sweep both currencies individually - if (isMint(options) && options.migrate) { - if (options.useNative) { + if (isMigrate(options)) { + if (options.useNative && !needToSendEth) { // unwrap the exact amount needed to send to the pool manager planner.addUnwrap(OPEN_DELTA) // payer is v4 position manager