PositionNFT
ERC721 token representing liquidity positions in a PrivacyPool.
Overview
PositionNFT tracks liquidity positions with encrypted amounts. Each NFT represents:
- A liquidity deposit in a specific pool
- Encrypted token amounts
- Fee growth snapshots for reward calculation
Contract Details
| Property | Value |
|---|---|
| License | BUSL-1.1 |
| Solidity | ^0.8.25 |
| Inheritance | ERC721, Ownable |
| Token Name | PrivacyPool Positions |
| Token Symbol | PPP |
Structs
Position
struct Position {
address token0; // First token address
address token1; // Second token address
int24 tickLower; // Lower tick boundary
int24 tickUpper; // Upper tick boundary
euint64 liquidity; // Encrypted liquidity amount
euint64 token0Amount; // Encrypted token0 amount
euint64 token1Amount; // Encrypted token1 amount
bool isConfidential; // Always true for encrypted positions
uint256 createdAt; // Creation timestamp
uint256 lastUpdated; // Last update timestamp
}
State Variables
mapping(uint256 => Position) private _positions; // Token ID to position
mapping(address => uint256[]) private _userPositions; // User's position IDs
uint256 private _tokenIdCounter; // Next token ID
address public poolManager; // Authorized pool
Constructor
constructor(address initialOwner)
Parameters:
| Name | Type | Description |
|---|---|---|
| initialOwner | address | Contract owner |
Functions
Configuration
setPoolManager
Set the authorized pool manager (the PrivacyPool contract).
function setPoolManager(address _poolManager) external onlyOwner
Parameters:
| Name | Type | Description |
|---|---|---|
| _poolManager | address | Pool contract address |
Note: Only the pool manager can mint, update, and burn positions.
Position Management
mint
Create a new position NFT.
function mint(
address to,
address token0,
address token1,
int24 tickLower,
int24 tickUpper,
euint64 liquidity,
euint64 token0Amount,
euint64 token1Amount,
bool isConfidential
) external onlyPoolManager returns (uint256 tokenId)
Parameters:
| Name | Type | Description |
|---|---|---|
| to | address | NFT recipient |
| token0 | address | First token address |
| token1 | address | Second token address |
| tickLower | int24 | Lower tick (min int24 for full range) |
| tickUpper | int24 | Upper tick (max int24 for full range) |
| liquidity | euint64 | Encrypted liquidity amount |
| token0Amount | euint64 | Encrypted token0 amount |
| token1Amount | euint64 | Encrypted token1 amount |
| isConfidential | bool | True for encrypted positions |
Returns:
| Name | Type | Description |
|---|---|---|
| tokenId | uint256 | Minted NFT ID |
updatePosition
Update an existing position's amounts.
function updatePosition(
uint256 tokenId,
euint64 newLiquidity,
euint64 newToken0Amount,
euint64 newToken1Amount
) external onlyPoolManager
Parameters:
| Name | Type | Description |
|---|---|---|
| tokenId | uint256 | Position NFT ID |
| newLiquidity | euint64 | New encrypted liquidity |
| newToken0Amount | euint64 | New encrypted token0 amount |
| newToken1Amount | euint64 | New encrypted token1 amount |
burn
Destroy a position NFT when liquidity is removed.
function burn(uint256 tokenId) external onlyPoolManager
Parameters:
| Name | Type | Description |
|---|---|---|
| tokenId | uint256 | Position NFT ID |
Effects:
- Removes from user's position list
- Deletes position data
- Burns the NFT
View Functions
getPosition
Get position data.
function getPosition(uint256 tokenId) external view returns (Position memory)
Returns: Complete Position struct including encrypted values.
getUserPositions
Get all position IDs owned by an address.
function getUserPositions(address user) external view returns (uint256[] memory)
getUserPositionCount
Get number of positions owned by an address.
function getUserPositionCount(address user) external view returns (uint256)
tokenOfOwnerByIndex
Get position ID at a specific index in user's list.
function tokenOfOwnerByIndex(
address owner,
uint256 index
) external view returns (uint256)
positionExists
Check if a position exists.
function positionExists(uint256 tokenId) external view returns (bool)
Events
event PositionCreated(
uint256 indexed tokenId,
address indexed owner,
address token0,
address token1,
int24 tickLower,
int24 tickUpper,
bool isConfidential
);
event PositionUpdated(uint256 indexed tokenId, uint256 liquidityHandle);
Transfer Behavior
When a position NFT is transferred, FHE permissions are updated:
function _update(
address to,
uint256 tokenId,
address auth
) internal override returns (address) {
// Update user position tracking
// Remove from previous owner's list
// Add to new owner's list
// Grant FHE permissions to new owner
FHE.allow(position.liquidity, to);
FHE.allow(position.token0Amount, to);
FHE.allow(position.token1Amount, to);
return super._update(to, tokenId, auth);
}
This ensures:
- New owners can decrypt their position values
- Position tracking stays accurate
- Encrypted data remains accessible
Tick Range
Lunarys currently uses full-range liquidity:
int24 constant TICK_LOWER_FULL_RANGE = type(int24).min;
int24 constant TICK_UPPER_FULL_RANGE = type(int24).max;
The tick fields are included for future concentrated liquidity support.
Token URI
Returns a base64-encoded JSON metadata URI:
function tokenURI(uint256 tokenId) public view override returns (string memory)
Usage Example
// PositionNFT is deployed by PoolFactory
// Pool manager is set automatically
// Users interact through PrivacyPool
pool.contributeLiquidity(amountA, amountB);
// -> mints PositionNFT to user
// Query positions
uint256[] memory positions = positionNFT.getUserPositions(user);
PositionNFT.Position memory pos = positionNFT.getPosition(positions[0]);
// Remove liquidity (burns NFT)
pool.removeLiquidity(positions[0]);
Security Considerations
- Access Control: Only the pool manager can mint/burn/update positions
- FHE Permissions: Encrypted values are only accessible to position owners
- Transfer Safety: Position tracking is updated atomically with transfers
- Ownership Verification: Pool verifies NFT ownership before liquidity operations