@uniswap/v4-sdk
Version:
⚒️ An SDK for building applications on top of Uniswap V4
242 lines • 12.2 kB
JavaScript
"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