Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions sdks/v4-sdk/src/PositionManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,85 @@ describe('PositionManager', () => {
expect(value).toEqual('0x00')
})

it('succeeds when migrating out of range and need to transfer eth', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would love a test for non-eth too, and for ETH-WETH wrapping combos if they are to be supported too

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,
migrate: true,
currencyAmount: { inputCurrency: currency_native, inputAmount: 1 },
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,
migrate: true,
currencyAmount: { inputCurrency: currency0, inputAmount: 1 },
})

// 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,
Expand Down
27 changes: 15 additions & 12 deletions sdks/v4-sdk/src/PositionManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigintIsh, Percent, validateAndParseAddress, NativeCurrency } from '@uniswap/sdk-core'
import { BigintIsh, Percent, validateAndParseAddress, NativeCurrency, Currency } from '@uniswap/sdk-core'
import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer'
import JSBI from 'jsbi'
import { Position } from './entities/position'
Expand Down Expand Up @@ -62,7 +62,7 @@ export interface MintSpecificOptions {
sqrtPriceX96?: BigintIsh

/**
* Whether the mint is part of a migration from V3 to V4.
* Whether the mint is part of a migration from V3 to V4
*/
migrate?: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like we are pulling out migrate into its own object; might need to change trading api impl

}
Expand Down Expand Up @@ -114,21 +114,22 @@ export interface CollectSpecificOptions {
recipient: string
}

export interface TransferOptions {
export interface MigrateSpecificOptions {
/**
* The account sending the NFT.
* The additional currency and amount that needs to be transferred if migrating (a) from out-of-range to in-range, or (b) from out-of-range to out-of-range on the opposite side
*/
sender: string
currencyAmount?: CurrencyAmount
}

export interface CurrencyAmount {
/**
* The account that should receive the NFT.
* The additional currency that needs to be transferred
*/
recipient: string

inputCurrency: Currency
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why there is a need for a 3rd currency specification? If we're migrating from v3 we can only ever migrate to a pool that contains the same currencies no?

And if we detect the pool is weth, we can just auto-unwrap to eth?

/**
* The id of the token being sent.
* The amount of additional currency that needs to be transferred
*/
tokenId: BigintIsh
inputAmount: BigintIsh
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not too bad it seems on the Trading API side to send in; curious how to derive the amount, seems it wouldnt bee to tedious to compute from earlier convos


export interface PermitDetails {
Expand Down Expand Up @@ -182,7 +183,7 @@ export interface NFTPermitData {
values: NFTPermitValues
}

export type MintOptions = CommonOptions & CommonAddLiquidityOptions & MintSpecificOptions
export type MintOptions = CommonOptions & CommonAddLiquidityOptions & MintSpecificOptions & MigrateSpecificOptions
export type IncreaseLiquidityOptions = CommonOptions & CommonAddLiquidityOptions & ModifyPositionSpecificOptions

export type AddLiquidityOptions = MintOptions | IncreaseLiquidityOptions
Expand Down Expand Up @@ -284,9 +285,11 @@ export abstract class V4PositionManager {

let value: string = toHex(0)

let needToSendEth = isMint(options) && options.migrate && options.currencyAmount?.inputCurrency.isNative

// If migrating, we need to settle and sweep both currencies individually
if (isMint(options) && options.migrate) {
if (options.useNative) {
if (options.useNative && !needToSendEth) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the case where neededCurrency is ETH and the position is in WETH this path should still be taken i think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the opposite right? it would need to wrap not unwrap

// unwrap the exact amount needed to send to the pool manager
planner.addUnwrap(OPEN_DELTA)
// payer is v4 position manager
Expand Down
Loading