PositionManager
0x58daec3116aae6d93017baaea7749052e8a04fa7
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
23,877 bytes
ABI entries
74
22 read · 17 write
License
none
Contract information
- Address
- 0x58daec3116aae6d93017baaea7749052e8a04fa7
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x4e59b44847…26c0B4956C
- Creation tx
- 0x228c18ada6…5a2db037c8
Token
- Name
- Uniswap v4 Positions NFT
- Symbol
- UNI-V4-POSM
- Decimals
- —
- Holders
- 7,536
- Market
- View token page →
Read contract (22)
DOMAIN_SEPARATOR() → bytes32
WETH9() → address
balanceOf(address) → uint256
getApproved(uint256) → address
getPoolAndPositionInfo(uint256) → tuple, uint256
getPositionLiquidity(uint256) → uint128
isApprovedForAll(address, address) → bool
msgSender() → address
name() → string
nextTokenId() → uint256
nonces(address, uint256) → uint256
ownerOf(uint256) → address
permit2() → address
poolKeys(bytes25) → address, address, uint24, int24, address
poolManager() → address
positionInfo(uint256) → uint256
subscriber(uint256) → address
supportsInterface(bytes4) → bool
symbol() → string
tokenDescriptor() → address
tokenURI(uint256) → string
unsubscribeGasLimit() → uint256
Events (5)
ApprovalApprovalForAllSubscriptionTransferUnsubscription
ABI
[
{
"inputs": [
{
"internalType": "contract IPoolManager",
"name": "_poolManager",
"type": "address"
},
{
"internalType": "contract IAllowanceTransfer",
"name": "_permit2",
"type": "address"
},
{
"internalType": "uint256",
"name": "_unsubscribeGasLimit",
"type": "uint256"
},
{
"internalType": "contract IPositionDescriptor",
"name": "_tokenDescriptor",
"type": "address"
},
{
"internalType": "contract IWETH9",
"name": "_weth9",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "subscriber",
"type": "address"
}
],
"name": "AlreadySubscribed",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "subscriber",
"type": "address"
},
{
"internalType": "bytes",
"name": "reason",
"type": "bytes"
}
],
"name": "BurnNotificationReverted",
"type": "error"
},
{
"inputs": [],
"name": "ContractLocked",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "DeadlinePassed",
"type": "error"
},
{
"inputs": [
{
"internalType": "Currency",
"name": "currency",
"type": "address"
}
],
"name": "DeltaNotNegative",
"type": "error"
},
{
"inputs": [
{
"internalType": "Currency",
"name": "currency",
"type": "address"
}
],
"name": "DeltaNotPositive",
"type": "error"
},
{
"inputs": [],
"name": "GasLimitTooLow",
"type": "error"
},
{
"inputs": [],
"name": "InputLengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientBalance",
"type": "error"
},
{
"inputs": [],
"name": "InvalidContractSignature",
"type": "error"
},
{
"inputs": [],
"name": "InvalidEthSender",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSignature",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSignatureLength",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSigner",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint128",
"name": "maximumAmount",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amountRequested",
"type": "uint128"
}
],
"name": "MaximumAmountExceeded",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint128",
"name": "minimumAmount",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amountReceived",
"type": "uint128"
}
],
"name": "MinimumAmountInsufficient",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "subscriber",
"type": "address"
},
{
"internalType": "bytes",
"name": "reason",
"type": "bytes"
}
],
"name": "ModifyLiquidityNotificationReverted",
"type": "error"
},
{
"inputs": [],
"name": "NoCodeSubscriber",
"type": "error"
},
{
"inputs": [],
"name": "NoSelfPermit",
"type": "error"
},
{
"inputs": [],
"name": "NonceAlreadyUsed",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "caller",
"type": "address"
}
],
"name": "NotApproved",
"type": "error"
},
{
"inputs": [],
"name": "NotPoolManager",
"type": "error"
},
{
"inputs": [],
"name": "NotSubscribed",
"type": "error"
},
{
"inputs": [],
"name": "PoolManagerMustBeLocked",
"type": "error"
},
{
"inputs": [],
"name": "SignatureDeadlineExpired",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "subscriber",
"type": "address"
},
{
"internalType": "bytes",
"name": "reason",
"type": "bytes"
}
],
"name": "SubscriptionReverted",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "action",
"type": "uint256"
}
],
"name": "UnsupportedAction",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "subscriber",
"type": "address"
}
],
"name": "Subscription",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "subscriber",
"type": "address"
}
],
"name": "Unsubscription",
"type": "event"
},
{
"inputs": [],
"name": "DOMAIN_SEPARATOR",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "WETH9",
"outputs": [
{
"internalType": "contract IWETH9",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getPoolAndPositionInfo",
"outputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "poolKey",
"type": "tuple"
},
{
"internalType": "PositionInfo",
"name": "info",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getPositionLiquidity",
"outputs": [
{
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
}
],
"name": "initializePool",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "unlockData",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "modifyLiquidities",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "actions",
"type": "bytes"
},
{
"internalType": "bytes[]",
"name": "params",
"type": "bytes[]"
}
],
"name": "modifyLiquiditiesWithoutUnlock",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "msgSender",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes[]",
"name": "data",
"type": "bytes[]"
}
],
"name": "multicall",
"outputs": [
{
"internalType": "bytes[]",
"name": "results",
"type": "bytes[]"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "nextTokenId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "word",
"type": "uint256"
}
],
"name": "nonces",
"outputs": [
{
"internalType": "uint256",
"name": "bitmap",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "nonce",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "permit",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"components": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint160",
"name": "amount",
"type": "uint160"
},
{
"internalType": "uint48",
"name": "expiration",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "nonce",
"type": "uint48"
}
],
"internalType": "struct IAllowanceTransfer.PermitDetails",
"name": "details",
"type": "tuple"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "sigDeadline",
"type": "uint256"
}
],
"internalType": "struct IAllowanceTransfer.PermitSingle",
"name": "permitSingle",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "permit",
"outputs": [
{
"internalType": "bytes",
"name": "err",
"type": "bytes"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "permit2",
"outputs": [
{
"internalType": "contract IAllowanceTransfer",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"components": [
{
"components": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint160",
"name": "amount",
"type": "uint160"
},
{
"internalType": "uint48",
"name": "expiration",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "nonce",
"type": "uint48"
}
],
"internalType": "struct IAllowanceTransfer.PermitDetails[]",
"name": "details",
"type": "tuple[]"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "sigDeadline",
"type": "uint256"
}
],
"internalType": "struct IAllowanceTransfer.PermitBatch",
"name": "_permitBatch",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "permitBatch",
"outputs": [
{
"internalType": "bytes",
"name": "err",
"type": "bytes"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "nonce",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "permitForAll",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes25",
"name": "poolId",
"type": "bytes25"
}
],
"name": "poolKeys",
"outputs": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "positionInfo",
"outputs": [
{
"internalType": "PositionInfo",
"name": "info",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "nonce",
"type": "uint256"
}
],
"name": "revokeNonce",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "newSubscriber",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "subscribe",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "subscriber",
"outputs": [
{
"internalType": "contract ISubscriber",
"name": "subscriber",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenDescriptor",
"outputs": [
{
"internalType": "contract IPositionDescriptor",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "unsubscribe",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "unsubscribeGasLimit",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {Position} from "@uniswap/v4-core/src/libraries/Position.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {IPositionDescriptor} from "./interfaces/IPositionDescriptor.sol";
import {ERC721Permit_v4} from "./base/ERC721Permit_v4.sol";
import {ReentrancyLock} from "./base/ReentrancyLock.sol";
import {IPositionManager} from "./interfaces/IPositionManager.sol";
import {Multicall_v4} from "./base/Multicall_v4.sol";
import {PoolInitializer_v4} from "./base/PoolInitializer_v4.sol";
import {DeltaResolver} from "./base/DeltaResolver.sol";
import {BaseActionsRouter} from "./base/BaseActionsRouter.sol";
import {Actions} from "./libraries/Actions.sol";
import {Notifier} from "./base/Notifier.sol";
import {CalldataDecoder} from "./libraries/CalldataDecoder.sol";
import {Permit2Forwarder} from "./base/Permit2Forwarder.sol";
import {SlippageCheck} from "./libraries/SlippageCheck.sol";
import {PositionInfo, PositionInfoLibrary} from "./libraries/PositionInfoLibrary.sol";
import {LiquidityAmounts} from "./libraries/LiquidityAmounts.sol";
import {NativeWrapper} from "./base/NativeWrapper.sol";
import {IWETH9} from "./interfaces/external/IWETH9.sol";
// 444444444
// 444444444444 444444
// 444 44 4444
// 44 4 44 444
// 44 44 44 44
// 44 44 44 44
// 44 444444 44 44
// 444 4444 4444 44
// 44 4444 444444444444444444
// 44 44 4 44444 444444
// 444444444444 44 4 444 44
// 44 44444444 4 444 44
// 444 44 44 444
// 44 4 4444444444444444444444444 4444444444 4444
// 44 44444444444444444444444444 444 44444
// 444 44 44444444444444444444444444
// 4444 444444444444444444444444
// 4444 444444 444444444444444
// 44444 444444 44444444444444444444444
// 444444444444444 4 44444 44444444444444444444
// 444 444444444444444444444444444
// 444 44444 44444444444 44444444
// 444 4 44444444444444 444444444
// 4444 444 44 4444444444444 44444444
// 44 44444 44444444 44444444444444444444 44444
// 444 444444 4444 4444 444444444444444444 44 4444
// 4444 44 44444 44444444444 444444444444444444444 44444444
// 44444 4444 4444444444 444444444444444444444444 44444
// 44444 44444 444 444444 4444444444444444444444444 44444
// 4444 44 44 4 44444444444444444444444444 444 44444
// 44444444 444 44 4 4 444444 4 44444444444444444444444444444 4444444
// 444444 44 44444444444 44444444444444 444444444444444 444444
// 444444 44 4444 44444 44 44444444444444444444444 4444444 44444
// 44 444444 44 444444444 444 4444444444444444444444444444444444 4444444
// 44 4444444444444 44 44 44 4444444444444444444444444444444 444444
// 44 44444444444444444444444444 4 44 4444444444444444444444444444444 4 444444
// 4 4444 4 4 4444444444444444444444444 44 4444444
// 4444 4444444444444444444444444 4 4444 44444444
// 4444 444444444444444444444444 44444 44444 4444444444
// 44444 44 444444444444444444444444444444444444444444444444444444
// 44444444444 4444444444444444444444444444444444444444444444444444444
// 4444444444444 44444444444444444444444444444444444444444444444444444444
// 444444444444444 444444444444444444444444444444444444444444444444444444444
// 44444444444444444 4444444444444444444444444444444444444444444444444444444444
// 44444444444444444 44444444444444444444444444444444444444444444444444444444
// 44444444444444444444 444444444444444444444444444444444444444444444444444444444
// 444444444444444444444 444444444444444444444444444444444444444444444444444444444
// 444444444444444444444 4444444444444444444444444444444444444444444444444444444
// 44444444444444444444444444444444444444444444444444444444444444444444444444444
// 444444444444444444444444444444444444444444444444444444444444444444444444444
// 44444444444444444444444444444444444444444444444444444444444444444444444444
// 44444444444444444444444444444444444444444444444444 444444444444444444
// 444444444444444444444444444444444444444444444444 44444444444444444444
// 444 444 444 44 444444444444444444444 4444 444444444444444444444
// 444 444 44 44 44444444 4444444444444 44444444444444444444444
// 444 444 4444 4444 4444444444444444 44444444444444444444444444
// 4444444444444444444444444444444444444444 44444444444444444444444444444
// 444 4444444444444444444444444 44444444444444444444444444444444
// 4444444 444444444444 4444444444444444444444444444444444
// 4444444444 44444444444444444444444444444444444
// 444444444444444444444444444444444444444444444444444444
// 44444444444444444444444444444444444444444
// 4444444444444444444
/// @notice The PositionManager (PosM) contract is responsible for creating liquidity positions on v4.
/// PosM mints and manages ERC721 tokens associated with each position.
contract PositionManager is
IPositionManager,
ERC721Permit_v4,
PoolInitializer_v4,
Multicall_v4,
DeltaResolver,
ReentrancyLock,
BaseActionsRouter,
Notifier,
Permit2Forwarder,
NativeWrapper
{
using StateLibrary for IPoolManager;
using TransientStateLibrary for IPoolManager;
using SafeCast for uint256;
using SafeCast for int256;
using CalldataDecoder for bytes;
using SlippageCheck for BalanceDelta;
/// @inheritdoc IPositionManager
/// @dev The ID of the next token that will be minted. Skips 0
uint256 public nextTokenId = 1;
IPositionDescriptor public immutable tokenDescriptor;
mapping(uint256 tokenId => PositionInfo info) public positionInfo;
mapping(bytes25 poolId => PoolKey poolKey) public poolKeys;
constructor(
IPoolManager _poolManager,
IAllowanceTransfer _permit2,
uint256 _unsubscribeGasLimit,
IPositionDescriptor _tokenDescriptor,
IWETH9 _weth9
)
BaseActionsRouter(_poolManager)
Permit2Forwarder(_permit2)
ERC721Permit_v4("Uniswap v4 Positions NFT", "UNI-V4-POSM")
Notifier(_unsubscribeGasLimit)
NativeWrapper(_weth9)
{
tokenDescriptor = _tokenDescriptor;
}
/// @notice Reverts if the deadline has passed
/// @param deadline The timestamp at which the call is no longer valid, passed in by the caller
modifier checkDeadline(uint256 deadline) {
if (block.timestamp > deadline) revert DeadlinePassed(deadline);
_;
}
/// @notice Reverts if the caller is not the owner or approved for the ERC721 token
/// @param caller The address of the caller
/// @param tokenId the unique identifier of the ERC721 token
/// @dev either msg.sender or msgSender() is passed in as the caller
/// msgSender() should ONLY be used if this is called from within the unlockCallback, unless the codepath has reentrancy protection
modifier onlyIfApproved(address caller, uint256 tokenId) override {
if (!_isApprovedOrOwner(caller, tokenId)) revert NotApproved(caller);
_;
}
/// @notice Enforces that the PoolManager is locked.
modifier onlyIfPoolManagerLocked() override {
if (poolManager.isUnlocked()) revert PoolManagerMustBeLocked();
_;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
return IPositionDescriptor(tokenDescriptor).tokenURI(this, tokenId);
}
/// @inheritdoc IPositionManager
function modifyLiquidities(bytes calldata unlockData, uint256 deadline)
external
payable
isNotLocked
checkDeadline(deadline)
{
_executeActions(unlockData);
}
/// @inheritdoc IPositionManager
function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params)
external
payable
isNotLocked
{
_executeActionsWithoutUnlock(actions, params);
}
/// @inheritdoc BaseActionsRouter
function msgSender() public view override returns (address) {
return _getLocker();
}
function _handleAction(uint256 action, bytes calldata params) internal virtual override {
if (action < Actions.SETTLE) {
if (action == Actions.INCREASE_LIQUIDITY) {
(uint256 tokenId, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) =
params.decodeModifyLiquidityParams();
_increase(tokenId, liquidity, amount0Max, amount1Max, hookData);
return;
} else if (action == Actions.INCREASE_LIQUIDITY_FROM_DELTAS) {
// DEPRECATED - vulnerable to sandwich attacks, do not use. See _increaseFromDeltas().
(uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) =
params.decodeIncreaseLiquidityFromDeltasParams();
_increaseFromDeltas(tokenId, amount0Max, amount1Max, hookData);
return;
} else if (action == Actions.DECREASE_LIQUIDITY) {
(uint256 tokenId, uint256 liquidity, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) =
params.decodeModifyLiquidityParams();
_decrease(tokenId, liquidity, amount0Min, amount1Min, hookData);
return;
} else if (action == Actions.MINT_POSITION) {
(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) = params.decodeMintParams();
_mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData);
return;
} else if (action == Actions.MINT_POSITION_FROM_DELTAS) {
// DEPRECATED - vulnerable to sandwich attacks, do not use. See _mintFromDeltas().
(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) = params.decodeMintFromDeltasParams();
_mintFromDeltas(poolKey, tickLower, tickUpper, amount0Max, amount1Max, _mapRecipient(owner), hookData);
return;
} else if (action == Actions.BURN_POSITION) {
// Will automatically decrease liquidity to 0 if the position is not already empty.
(uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) =
params.decodeBurnParams();
_burn(tokenId, amount0Min, amount1Min, hookData);
return;
}
} else {
if (action == Actions.SETTLE_PAIR) {
(Currency currency0, Currency currency1) = params.decodeCurrencyPair();
_settlePair(currency0, currency1);
return;
} else if (action == Actions.TAKE_PAIR) {
(Currency currency0, Currency currency1, address recipient) = params.decodeCurrencyPairAndAddress();
_takePair(currency0, currency1, _mapRecipient(recipient));
return;
} else if (action == Actions.SETTLE) {
(Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool();
_settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency));
return;
} else if (action == Actions.TAKE) {
(Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256();
_take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency));
return;
} else if (action == Actions.CLOSE_CURRENCY) {
Currency currency = params.decodeCurrency();
_close(currency);
return;
} else if (action == Actions.CLEAR_OR_TAKE) {
(Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256();
_clearOrTake(currency, amountMax);
return;
} else if (action == Actions.SWEEP) {
(Currency currency, address to) = params.decodeCurrencyAndAddress();
_sweep(currency, _mapRecipient(to));
return;
} else if (action == Actions.WRAP) {
uint256 amount = params.decodeUint256();
_wrap(_mapWrapUnwrapAmount(CurrencyLibrary.ADDRESS_ZERO, amount, Currency.wrap(address(WETH9))));
return;
} else if (action == Actions.UNWRAP) {
uint256 amount = params.decodeUint256();
_unwrap(_mapWrapUnwrapAmount(Currency.wrap(address(WETH9)), amount, CurrencyLibrary.ADDRESS_ZERO));
return;
}
}
revert UnsupportedAction(action);
}
/// @dev Calling increase with 0 liquidity will credit the caller with any underlying fees of the position
function _increase(
uint256 tokenId,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
bytes calldata hookData
) internal onlyIfApproved(msgSender(), tokenId) {
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);
// Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager.
(BalanceDelta liquidityDelta, BalanceDelta feesAccrued) =
_modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData);
// Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued
(liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max);
}
/// @notice DEPRECATED: Vulnerable to sandwich attacks - do not use this function.
/// @dev Same issue as _mintFromDeltas -- the delta-based approach provides no minimum
/// liquidity protection. Use _increase() instead.
function _increaseFromDeltas(uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData)
internal
onlyIfApproved(msgSender(), tokenId)
{
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);
uint256 liquidity;
{
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());
// Use the credit on the pool manager as the amounts for the mint.
liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtPriceAtTick(info.tickLower()),
TickMath.getSqrtPriceAtTick(info.tickUpper()),
_getFullCredit(poolKey.currency0),
_getFullCredit(poolKey.currency1)
);
}
// Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager.
(BalanceDelta liquidityDelta, BalanceDelta feesAccrued) =
_modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData);
// Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued
(liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max);
}
/// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position
function _decrease(
uint256 tokenId,
uint256 liquidity,
uint128 amount0Min,
uint128 amount1Min,
bytes calldata hookData
) internal onlyIfApproved(msgSender(), tokenId) {
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);
// Note: the tokenId is used as the salt.
(BalanceDelta liquidityDelta, BalanceDelta feesAccrued) =
_modifyLiquidity(info, poolKey, -(liquidity.toInt256()), bytes32(tokenId), hookData);
// Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued
(liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min);
}
function _mint(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint256 liquidity,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) internal {
// mint receipt token
uint256 tokenId;
// tokenId is assigned to current nextTokenId before incrementing it
unchecked {
tokenId = nextTokenId++;
}
_mint(owner, tokenId);
// Initialize the position info
PositionInfo info = PositionInfoLibrary.initialize(poolKey, tickLower, tickUpper);
positionInfo[tokenId] = info;
// Store the poolKey if it is not already stored.
// On UniswapV4, the minimum tick spacing is 1, which means that if the tick spacing is 0, the pool key has not been set.
bytes25 poolId = info.poolId();
if (poolKeys[poolId].tickSpacing == 0) {
poolKeys[poolId] = poolKey;
}
// fee delta can be ignored as this is a new position
(BalanceDelta liquidityDelta,) =
_modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData);
liquidityDelta.validateMaxIn(amount0Max, amount1Max);
}
/// @notice DEPRECATED: Vulnerable to sandwich attacks - do not use this function.
/// @dev The amount0Max/amount1Max parameters are redundant because settled amounts already
/// act as an upper bound. There is no lower bound on liquidity received.
/// If the price is manipulated, fewer tokens are used (the rest are swept back),
/// less liquidity is minted, and the max check never triggers.
/// Use _mint() instead, which takes an explicit liquidity amount with proper slippage protection.
function _mintFromDeltas(
PoolKey calldata poolKey,
int24 tickLower,
int24 tickUpper,
uint128 amount0Max,
uint128 amount1Max,
address owner,
bytes calldata hookData
) internal {
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());
// Use the credit on the pool manager as the amounts for the mint.
uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtPriceAtTick(tickLower),
TickMath.getSqrtPriceAtTick(tickUpper),
_getFullCredit(poolKey.currency0),
_getFullCredit(poolKey.currency1)
);
_mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, owner, hookData);
}
/// @dev this is overloaded with ERC721Permit_v4._burn
function _burn(uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData)
internal
onlyIfApproved(msgSender(), tokenId)
{
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);
uint256 liquidity = uint256(_getLiquidity(tokenId, poolKey, info.tickLower(), info.tickUpper()));
address owner = ownerOf(tokenId);
// Clear the position info.
positionInfo[tokenId] = PositionInfoLibrary.EMPTY_POSITION_INFO;
// Burn the token.
_burn(tokenId);
// Can only call modify if there is non zero liquidity.
BalanceDelta feesAccrued;
if (liquidity > 0) {
BalanceDelta liquidityDelta;
// do not use _modifyLiquidity as we do not need to notify on modification for burns.
ModifyLiquidityParams memory params = ModifyLiquidityParams({
tickLower: info.tickLower(),
tickUpper: info.tickUpper(),
liquidityDelta: -(liquidity.toInt256()),
salt: bytes32(tokenId)
});
(liquidityDelta, feesAccrued) = poolManager.modifyLiquidity(poolKey, params, hookData);
// Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued
(liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min);
}
// deletes then notifies the subscriber
if (info.hasSubscriber()) _removeSubscriberAndNotifyBurn(tokenId, owner, info, liquidity, feesAccrued);
}
function _settlePair(Currency currency0, Currency currency1) internal {
// the locker is the payer when settling
address caller = msgSender();
_settle(currency0, caller, _getFullDebt(currency0));
_settle(currency1, caller, _getFullDebt(currency1));
}
function _takePair(Currency currency0, Currency currency1, address recipient) internal {
_take(currency0, recipient, _getFullCredit(currency0));
_take(currency1, recipient, _getFullCredit(currency1));
}
function _close(Currency currency) internal {
// this address has applied all deltas on behalf of the user/owner
// it is safe to close this entire delta because of slippage checks throughout the batched calls.
int256 currencyDelta = poolManager.currencyDelta(address(this), currency);
// the locker is the payer or receiver
address caller = msgSender();
if (currencyDelta < 0) {
// Casting is safe due to limits on the total supply of a pool
_settle(currency, caller, uint256(-currencyDelta));
} else {
_take(currency, caller, uint256(currencyDelta));
}
}
/// @dev integrators may elect to forfeit positive deltas with clear
/// if the forfeit amount exceeds the user-specified max, the amount is taken instead
/// if there is no credit, no call is made.
function _clearOrTake(Currency currency, uint256 amountMax) internal {
uint256 delta = _getFullCredit(currency);
if (delta == 0) return;
// forfeit the delta if its less than or equal to the user-specified limit
if (delta <= amountMax) {
poolManager.clear(currency, delta);
} else {
_take(currency, msgSender(), delta);
}
}
/// @notice Sweeps the entire contract balance of specified currency to the recipient
function _sweep(Currency currency, address to) internal {
uint256 balance = currency.balanceOfSelf();
if (balance > 0) currency.transfer(to, balance);
}
/// @dev if there is a subscriber attached to the position, this function will notify the subscriber
function _modifyLiquidity(
PositionInfo info,
PoolKey memory poolKey,
int256 liquidityChange,
bytes32 salt,
bytes calldata hookData
) internal returns (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) {
(liquidityDelta, feesAccrued) = poolManager.modifyLiquidity(
poolKey,
ModifyLiquidityParams({
tickLower: info.tickLower(), tickUpper: info.tickUpper(), liquidityDelta: liquidityChange, salt: salt
}),
hookData
);
if (info.hasSubscriber()) {
_notifyModifyLiquidity(uint256(salt), liquidityChange, feesAccrued);
}
}
// implementation of abstract function DeltaResolver._pay
function _pay(Currency currency, address payer, uint256 amount) internal override {
if (payer == address(this)) {
currency.transfer(address(poolManager), amount);
} else {
// Casting from uint256 to uint160 is safe due to limits on the total supply of a pool
permit2.transferFrom(payer, address(poolManager), uint160(amount), Currency.unwrap(currency));
}
}
/// @notice an internal helper used by Notifier
function _setSubscribed(uint256 tokenId) internal override {
positionInfo[tokenId] = positionInfo[tokenId].setSubscribe();
}
/// @notice an internal helper used by Notifier
function _setUnsubscribed(uint256 tokenId) internal override {
positionInfo[tokenId] = positionInfo[tokenId].setUnsubscribe();
}
/// @dev overrides solmate transferFrom in case a notification to subscribers is needed
/// @dev will revert if pool manager is locked
function transferFrom(address from, address to, uint256 id) public virtual override onlyIfPoolManagerLocked {
super.transferFrom(from, to, id);
if (positionInfo[id].hasSubscriber()) _unsubscribe(id);
}
/// @inheritdoc IPositionManager
function getPoolAndPositionInfo(uint256 tokenId) public view returns (PoolKey memory poolKey, PositionInfo info) {
info = positionInfo[tokenId];
poolKey = poolKeys[info.poolId()];
}
/// @inheritdoc IPositionManager
function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity) {
(PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId);
liquidity = _getLiquidity(tokenId, poolKey, info.tickLower(), info.tickUpper());
}
function _getLiquidity(uint256 tokenId, PoolKey memory poolKey, int24 tickLower, int24 tickUpper)
internal
view
returns (uint128 liquidity)
{
bytes32 positionId = Position.calculatePositionKey(address(this), tickLower, tickUpper, bytes32(tokenId));
liquidity = poolManager.getPositionLiquidity(poolKey.toId(), positionId);
}
}
Chain explorer5847msChain node121ms