5/5
## Understanding the PositionManager Subscription Mechanism The `PositionManager.sol` smart contract includes a powerful feature that allows external contracts to subscribe to specific NFT positions. This subscription mechanism enables developers to build sophisticated applications that react in real-time to on-chain events, such as a position's liquidity being modified. This lesson breaks down how these notifications work, how to manage subscriptions, and a critical consideration for developers building subscriber contracts. ### How Notifications are Triggered on Liquidity Modification The entry point for the notification process is the internal `_modifyLiquidity` function within `PositionManager.sol`. This function is executed whenever a user adds or removes liquidity from a position. The core of the mechanism is a simple conditional check. Before the function completes, it inspects the position's data to see if a subscriber is attached. If one exists, it triggers the notification logic. ```solidity // PositionManager.sol function _modifyLiquidity( PositionInfo info, PoolKey memory poolKey, int256 liquidityChange, bytes32 salt, bytes calldata hookData ) internal returns (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) { // ... logic to modify liquidity ... // Check if a subscriber is attached to this position if (info.hasSubscriber) { // Call the internal notification function _notifyModifyLiquidity(uint256(salt), liquidityChange, feesAccrued); } } ``` The boolean `info.hasSubscriber` acts as the switch. If `true`, the `_notifyModifyLiquidity` function is called, initiating the external notification. ### The Notifier Contract: The Engine of Subscriptions While the trigger is in `PositionManager.sol`, the implementation of `_notifyModifyLiquidity` resides in an abstract contract it inherits from: `Notifier.sol`. This contract contains all the core logic for managing subscribers and dispatching notifications. The `_notifyModifyLiquidity` function is responsible for two main tasks: 1. Looking up the subscriber's address associated with the `tokenId`. 2. Making an external, low-level call to the subscriber contract, passing along the event details. ```solidity // Notifier.sol // This function is called from PositionManager after a liquidity change function _notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) internal { // Retrieve the subscriber's address from the mapping address _subscriber = address(subscriber[tokenId]); // Encode and send the external call to the subscriber contract bool success = _call( _subscriber, abi.encodeCall(ISubscriber.notifyModifyLiquidity, (tokenId, liquidityChange, feesAccrued)) ); // Revert if the notification call fails if (!success) { _subscriber.bubbleUpAndRevertWith( ISubscriber.notifyModifyLiquidity.selector, ModifyLiquidityNotificationFailed.selector ); } } ``` Here, `Notifier.sol` acts as a reliable messenger, encoding the function call and its parameters and forwarding them to the destination contract. If the subscriber contract reverts or fails to handle the notification, the entire `_modifyLiquidity` transaction will revert, ensuring atomicity. ### Managing Subscriptions with `subscribe` and `unsubscribe` Since `PositionManager` inherits from `Notifier`, users can interact with its public functions to manage subscriptions directly. The primary functions for this are `subscribe` and `unsubscribe`. These functions allow an NFT position's owner (or an approved address) to assign or remove a subscriber contract for their specific `tokenId`. ```solidity // Notifier.sol // User-facing function to add a subscription function subscribe(uint256 tokenId, address newSubscriber, bytes calldata data) external payable { // ... requires checks for ownership/approval ... // Logic to set the new subscriber in the mapping // and notify the newSubscriber contract } // User-facing function to remove a subscription function unsubscribe(uint256 tokenId) external payable { // ... requires checks for ownership/approval ... _unsubscribe(tokenId); // Calls the internal unsubscribe logic } ``` ### A Critical Developer Note: The `unsubscribe` Gas Limit A crucial design choice in the `Notifier` contract is the fixed gas limit applied to unsubscribe notifications. To prevent potential griefing attacks—where a malicious subscriber contract could implement a costly `notifyUnsubscribe` function to make unsubscribing prohibitively expensive—the notification call is sent with a predetermined, limited amount of gas. This limit is defined by an immutable state variable, `unsubscribeGasLimit`, which is set when the `PositionManager` contract is deployed. ```solidity // Notifier.sol // State variable for the gas limit uint256 public immutable unsubscribeGasLimit; constructor(uint256 _unsubscribeGasLimit) { unsubscribeGasLimit = _unsubscribeGasLimit; } // Inside the internal _unsubscribe function function _unsubscribe(uint256 tokenId) internal { // ... logic to get the current subscriber ... // If there is a subscriber contract if (address(_subscriber).code.length > 0) { // ... // Try to call notifyUnsubscribe with the fixed gas limit try _subscriber.notifyUnsubscribe{gas: unsubscribeGasLimit}(tokenId) catch {} } // Delete the subscriber from the mapping delete subscriber[tokenId]; } ``` **Key Takeaways for Developers:** * **Gas Efficiency is Mandatory:** If you are building a subscriber contract, your `notifyUnsubscribe` function **must** be highly gas-efficient. All of its logic must execute within the `unsubscribeGasLimit`. If it consumes more gas, the call will fail. * **Unsubscribing Will Not Fail:** Due to the `try...catch` block, even if the notification call runs out of gas, the parent `unsubscribe` transaction will still succeed. The subscription will be successfully removed, but your contract will simply not be notified. * **Query the Limit:** To ensure your contract is compatible, you should query the public `unsubscribeGasLimit` state variable on the deployed `PositionManager` contract you intend to integrate with. This will give you the precise gas budget your `notifyUnsubscribe` function must adhere to.
The PositionManager.sol smart contract includes a powerful feature that allows external contracts to subscribe to specific NFT positions. This subscription mechanism enables developers to build sophisticated applications that react in real-time to on-chain events, such as a position's liquidity being modified. This lesson breaks down how these notifications work, how to manage subscriptions, and a critical consideration for developers building subscriber contracts.
The entry point for the notification process is the internal _modifyLiquidity function within PositionManager.sol. This function is executed whenever a user adds or removes liquidity from a position.
The core of the mechanism is a simple conditional check. Before the function completes, it inspects the position's data to see if a subscriber is attached. If one exists, it triggers the notification logic.
The boolean info.hasSubscriber acts as the switch. If true, the _notifyModifyLiquidity function is called, initiating the external notification.
While the trigger is in PositionManager.sol, the implementation of _notifyModifyLiquidity resides in an abstract contract it inherits from: Notifier.sol. This contract contains all the core logic for managing subscribers and dispatching notifications.
The _notifyModifyLiquidity function is responsible for two main tasks:
Looking up the subscriber's address associated with the tokenId.
Making an external, low-level call to the subscriber contract, passing along the event details.
Here, Notifier.sol acts as a reliable messenger, encoding the function call and its parameters and forwarding them to the destination contract. If the subscriber contract reverts or fails to handle the notification, the entire _modifyLiquidity transaction will revert, ensuring atomicity.
subscribe and unsubscribeSince PositionManager inherits from Notifier, users can interact with its public functions to manage subscriptions directly. The primary functions for this are subscribe and unsubscribe.
These functions allow an NFT position's owner (or an approved address) to assign or remove a subscriber contract for their specific tokenId.
unsubscribe Gas LimitA crucial design choice in the Notifier contract is the fixed gas limit applied to unsubscribe notifications. To prevent potential griefing attacks—where a malicious subscriber contract could implement a costly notifyUnsubscribe function to make unsubscribing prohibitively expensive—the notification call is sent with a predetermined, limited amount of gas.
This limit is defined by an immutable state variable, unsubscribeGasLimit, which is set when the PositionManager contract is deployed.
Key Takeaways for Developers:
Gas Efficiency is Mandatory: If you are building a subscriber contract, your notifyUnsubscribe function must be highly gas-efficient. All of its logic must execute within the unsubscribeGasLimit. If it consumes more gas, the call will fail.
Unsubscribing Will Not Fail: Due to the try...catch block, even if the notification call runs out of gas, the parent unsubscribe transaction will still succeed. The subscription will be successfully removed, but your contract will simply not be notified.
Query the Limit: To ensure your contract is compatible, you should query the public unsubscribeGasLimit state variable on the deployed PositionManager contract you intend to integrate with. This will give you the precise gas budget your notifyUnsubscribe function must adhere to.
An architectural breakdown to The PositionManager Subscription Mechanism - Uncover the mechanics of real-time position event notifications triggered by liquidity modifications, and delve into the critical `unsubscribe` gas limit.
Previous lesson
Previous
Next lesson
Next
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 PositionManager
Uniswap v4 Universal Router
Uniswap v4 Singleton architecture and flash accounting
Uniswap v4 operations and lifecycle
Uniswap v4 multihopping and quoting
How to build a Uniswap v4 swap router
How to build a smart contract a liquidation bot executes
Last updated on November 6, 2025
Duration: 6min
Duration: 8min
Duration: 39min
Duration: 35min
Duration: 34min
Duration: 31min
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 PositionManager
Uniswap v4 Universal Router
Uniswap v4 Singleton architecture and flash accounting
Uniswap v4 operations and lifecycle
Uniswap v4 multihopping and quoting
How to build a Uniswap v4 swap router
How to build a smart contract a liquidation bot executes
Last updated on November 6, 2025