UNPKG

@uniswap/v4-sdk

Version:

⚒️ An SDK for building applications on top of Uniswap V4

242 lines 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.V4PositionManager = void 0; const tslib_1 = require("tslib"); const sdk_core_1 = require("@uniswap/sdk-core"); const jsbi_1 = tslib_1.__importDefault(require("jsbi")); const position_1 = require("./entities/position"); const calldata_1 = require("./utils/calldata"); const actionConstants_1 = require("./actionConstants"); const abi_1 = require("@ethersproject/abi"); const multicall_1 = require("./multicall"); const tiny_invariant_1 = tslib_1.__importDefault(require("tiny-invariant")); const internalConstants_1 = require("./internalConstants"); const utils_1 = require("./utils"); const positionManagerAbi_1 = require("./utils/positionManagerAbi"); const NFT_PERMIT_TYPES = { Permit: [ { name: 'spender', type: 'address' }, { name: 'tokenId', type: 'uint256' }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], }; // type guard function isMint(options) { return Object.keys(options).some((k) => k === 'recipient'); } function shouldCreatePool(options) { if (options.createPool) { (0, tiny_invariant_1.default)(options.sqrtPriceX96 !== undefined, internalConstants_1.NO_SQRT_PRICE); return true; } return false; } class V4PositionManager { /** * Cannot be constructed. */ constructor() { } /** * Public methods to encode method parameters for different actions on the PositionManager contract */ static createCallParameters(poolKey, sqrtPriceX96) { return { calldata: this.encodeInitializePool(poolKey, sqrtPriceX96), value: (0, calldata_1.toHex)(0), }; } static addCallParameters(position, options) { /** * Cases: * - if pool does not exist yet, encode initializePool * then, * - if is mint, encode MINT_POSITION. If migrating, encode a SETTLE and SWEEP for both currencies. Else, encode a SETTLE_PAIR. If on a NATIVE pool, encode a SWEEP. * - else, encode INCREASE_LIQUIDITY and CLOSE_CURRENCY for each token (handles accrued fees). If on a NATIVE pool, encode a SWEEP. */ (0, tiny_invariant_1.default)(jsbi_1.default.greaterThan(position.liquidity, internalConstants_1.ZERO), internalConstants_1.ZERO_LIQUIDITY); const calldataList = []; const planner = new utils_1.V4PositionPlanner(); // Encode initialize pool. if (isMint(options) && shouldCreatePool(options)) { // No planner used here because initializePool is not supported as an Action calldataList.push(V4PositionManager.encodeInitializePool(position.pool.poolKey, options.sqrtPriceX96)); } // position.pool.currency0 is native if and only if options.useNative is set (0, tiny_invariant_1.default)(position.pool.currency0 === options.useNative || (!position.pool.currency0.isNative && options.useNative === undefined), internalConstants_1.NATIVE_NOT_SET); // adjust for slippage const maximumAmounts = position.mintAmountsWithSlippage(options.slippageTolerance); const amount0Max = (0, calldata_1.toHex)(maximumAmounts.amount0); const amount1Max = (0, calldata_1.toHex)(maximumAmounts.amount1); // We use permit2 to approve tokens to the position manager if (options.batchPermit) { calldataList.push(V4PositionManager.encodePermitBatch(options.batchPermit.owner, options.batchPermit.permitBatch, options.batchPermit.signature)); } // mint if (isMint(options)) { const recipient = (0, sdk_core_1.validateAndParseAddress)(options.recipient); planner.addMint(position.pool, position.tickLower, position.tickUpper, position.liquidity, amount0Max, amount1Max, recipient, options.hookData); } else { // increase planner.addIncrease(options.tokenId, position.liquidity, amount0Max, amount1Max, options.hookData); } let value = (0, calldata_1.toHex)(0); // If migrating, we need to settle and sweep both currencies individually if (isMint(options) && options.migrate) { if (options.useNative) { // unwrap the exact amount needed to send to the pool manager planner.addUnwrap(internalConstants_1.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 // recipient will be same as the v4 lp token recipient planner.addSweep(options.useNative ? position.pool.currency0.wrapped : position.pool.currency0, options.recipient); planner.addSweep(position.pool.currency1, options.recipient); } else { if (isMint(options)) { // Mint: the user can never be owed a token when minting (delta is always >= 0), so SETTLE_PAIR is safe planner.addSettlePair(position.pool.currency0, position.pool.currency1); } else { // Increase: use CLOSE_CURRENCY instead of SETTLE_PAIR because accrued fees on // existing positions can flip the delta positive on one side (e.g. single-sided // positions with fees), causing SETTLE_PAIR to revert. CLOSE_CURRENCY handles // both directions — settles if the user owes, takes if the user is owed. planner.addCloseCurrency(position.pool.currency0); planner.addCloseCurrency(position.pool.currency1); } if (options.useNative) { // Any sweeping must happen after the settling. // native currency will always be currency0 in v4 value = (0, calldata_1.toHex)(amount0Max); planner.addSweep(position.pool.currency0, actionConstants_1.MSG_SENDER); } } calldataList.push(V4PositionManager.encodeModifyLiquidities(planner.finalize(), options.deadline)); return { calldata: multicall_1.Multicall.encodeMulticall(calldataList), value, }; } /** * Produces the calldata for completely or partially exiting a position * @param position The position to exit * @param options Additional information necessary for generating the calldata * @returns The call parameters */ static removeCallParameters(position, options) { var _a; /** * cases: * - if liquidityPercentage is 100%, encode BURN_POSITION and then TAKE_PAIR * - else, encode DECREASE_LIQUIDITY and then TAKE_PAIR */ const calldataList = []; const planner = new utils_1.V4PositionPlanner(); const tokenId = (0, calldata_1.toHex)(options.tokenId); if (options.burnToken) { // if burnToken is true, the specified liquidity percentage must be 100% (0, tiny_invariant_1.default)(options.liquidityPercentage.equalTo(internalConstants_1.ONE), internalConstants_1.CANNOT_BURN); // if there is a permit, encode the ERC721Permit permit call if (options.permit) { calldataList.push(V4PositionManager.encodeERC721Permit(options.permit.spender, options.permit.tokenId, options.permit.deadline, options.permit.nonce, options.permit.signature)); } // slippage-adjusted amounts derived from current position liquidity const { amount0: amount0Min, amount1: amount1Min } = position.burnAmountsWithSlippage(options.slippageTolerance); planner.addBurn(tokenId, amount0Min, amount1Min, options.hookData); } else { // construct a partial position with a percentage of liquidity const partialPosition = new position_1.Position({ pool: position.pool, liquidity: options.liquidityPercentage.multiply(position.liquidity).quotient, tickLower: position.tickLower, tickUpper: position.tickUpper, }); // If the partial position has liquidity=0, this is a collect call and collectCallParameters should be used (0, tiny_invariant_1.default)(jsbi_1.default.greaterThan(partialPosition.liquidity, internalConstants_1.ZERO), internalConstants_1.ZERO_LIQUIDITY); // slippage-adjusted underlying amounts const { amount0: amount0Min, amount1: amount1Min } = partialPosition.burnAmountsWithSlippage(options.slippageTolerance); planner.addDecrease(tokenId, partialPosition.liquidity.toString(), amount0Min.toString(), amount1Min.toString(), (_a = options.hookData) !== null && _a !== void 0 ? _a : internalConstants_1.EMPTY_BYTES); } planner.addTakePair(position.pool.currency0, position.pool.currency1, actionConstants_1.MSG_SENDER); calldataList.push(V4PositionManager.encodeModifyLiquidities(planner.finalize(), options.deadline)); return { calldata: multicall_1.Multicall.encodeMulticall(calldataList), value: (0, calldata_1.toHex)(0), }; } /** * Produces the calldata for collecting fees from a position * @param position The position to collect fees from * @param options Additional information necessary for generating the calldata * @returns The call parameters */ static collectCallParameters(position, options) { const calldataList = []; const planner = new utils_1.V4PositionPlanner(); const tokenId = (0, calldata_1.toHex)(options.tokenId); const recipient = (0, sdk_core_1.validateAndParseAddress)(options.recipient); /** * To collect fees in V4, we need to: * - encode a decrease liquidity by 0 * - and encode a TAKE_PAIR */ planner.addDecrease(tokenId, '0', '0', '0', options.hookData); planner.addTakePair(position.pool.currency0, position.pool.currency1, recipient); calldataList.push(V4PositionManager.encodeModifyLiquidities(planner.finalize(), options.deadline)); return { calldata: multicall_1.Multicall.encodeMulticall(calldataList), value: (0, calldata_1.toHex)(0), }; } // Initialize a pool static encodeInitializePool(poolKey, sqrtPriceX96) { return V4PositionManager.INTERFACE.encodeFunctionData(internalConstants_1.PositionFunctions.INITIALIZE_POOL, [ poolKey, sqrtPriceX96.toString(), ]); } // Encode a modify liquidities call static encodeModifyLiquidities(unlockData, deadline) { return V4PositionManager.INTERFACE.encodeFunctionData(internalConstants_1.PositionFunctions.MODIFY_LIQUIDITIES, [unlockData, deadline]); } // Encode a permit batch call static encodePermitBatch(owner, permitBatch, signature) { return V4PositionManager.INTERFACE.encodeFunctionData(internalConstants_1.PositionFunctions.PERMIT_BATCH, [ owner, permitBatch, signature, ]); } // Encode a ERC721Permit permit call static encodeERC721Permit(spender, tokenId, deadline, nonce, signature) { return V4PositionManager.INTERFACE.encodeFunctionData(internalConstants_1.PositionFunctions.ERC721PERMIT_PERMIT, [ spender, tokenId, deadline, nonce, signature, ]); } // Prepare the params for an EIP712 signTypedData request static getPermitData(permit, positionManagerAddress, chainId) { return { domain: { name: 'Uniswap V4 Positions NFT', chainId, verifyingContract: positionManagerAddress, }, types: NFT_PERMIT_TYPES, values: permit, }; } } exports.V4PositionManager = V4PositionManager; V4PositionManager.INTERFACE = new abi_1.Interface(positionManagerAbi_1.positionManagerAbi); //# sourceMappingURL=PositionManager.js.map