Skip to main content

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

PropertyValue
LicenseBUSL-1.1
Solidity^0.8.25
InheritanceERC721, Ownable
Token NamePrivacyPool Positions
Token SymbolPPP

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:

NameTypeDescription
initialOwneraddressContract owner

Functions

Configuration

setPoolManager

Set the authorized pool manager (the PrivacyPool contract).

function setPoolManager(address _poolManager) external onlyOwner

Parameters:

NameTypeDescription
_poolManageraddressPool 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:

NameTypeDescription
toaddressNFT recipient
token0addressFirst token address
token1addressSecond token address
tickLowerint24Lower tick (min int24 for full range)
tickUpperint24Upper tick (max int24 for full range)
liquidityeuint64Encrypted liquidity amount
token0Amounteuint64Encrypted token0 amount
token1Amounteuint64Encrypted token1 amount
isConfidentialboolTrue for encrypted positions

Returns:

NameTypeDescription
tokenIduint256Minted NFT ID

updatePosition

Update an existing position's amounts.

function updatePosition(
uint256 tokenId,
euint64 newLiquidity,
euint64 newToken0Amount,
euint64 newToken1Amount
) external onlyPoolManager

Parameters:

NameTypeDescription
tokenIduint256Position NFT ID
newLiquidityeuint64New encrypted liquidity
newToken0Amounteuint64New encrypted token0 amount
newToken1Amounteuint64New encrypted token1 amount

burn

Destroy a position NFT when liquidity is removed.

function burn(uint256 tokenId) external onlyPoolManager

Parameters:

NameTypeDescription
tokenIduint256Position 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

  1. Access Control: Only the pool manager can mint/burn/update positions
  2. FHE Permissions: Encrypted values are only accessible to position owners
  3. Transfer Safety: Position tracking is updated atomically with transfers
  4. Ownership Verification: Pool verifies NFT ownership before liquidity operations