Shield Pool

A Shield Pool is where shielded users deposit yield-bearing assets to get protection, and protectors deposit collateral to back that protection and earn commission. This page describes how deposits, withdrawals, and fee flows work: who can do what, when, and what they receive. Whether you want to shield your assets or provide protection and earn, the sections below cover the mechanics.

Overview

A Shield Pool creates a protection relationship between two types of participants:

  • Shielded Users: Deposit yield-bearing tokens to receive protection against value loss
  • Protectors: Deposit collateral tokens to earn commission from the yield generated by shielded assets

The pool manages deposits, withdrawals, yield distribution, and ensures that protector collateral is always sufficient to cover shielded deposits. Collateralization is based on original deposit values (valueAtDeposit), not current token amounts, ensuring symmetry between shielded users and protectors.

Core Data Structures

ShieldedDeposit (ERC721 NFT Position)

Each shielded deposit is represented as an ERC721 NFT token. The position data is stored in the ShieldReceiptNFT contract:

  • tokenId: Unique identifier for the NFT position
  • amount: Current shielded token balance in the pool (reduces as fees are claimed)
  • depositTime: Timestamp when the deposit was made (used for yield calculations and transfer locks)
  • valueAtDeposit: USD value of the deposit at time of deposit (for cross-asset withdrawals)
  • collateralAmount: Original collateral in backing token terms (for cross-asset withdrawal cap)
  • lastFeeClaimTime: Last time fees were calculated
  • isWithdrawn: Flag indicating if the deposit has been withdrawn

ProtectorPosition (ProtectorReceiptNFT)

Each protector position is an ERC721 NFT with:

  • amount: Total backing tokens deposited
  • depositTime: Timestamp when the deposit was made
  • unlockRequestTime: When the unlock will complete (0 = not started). Set by startUnlockProcess(tokenId) to block.timestamp + unlockDuration. Withdrawal is allowed when unlockRequestTime != 0 and unlockRequestTime <= block.timestamp

Commission is tracked in the pool using a share-based (rewards-per-share) pattern. Protectors claim via the pool's claimCommission(tokenId); getClaimableCommission(tokenId) returns the claimable amount.

PoolConfig

Governance-controlled pool parameters:

  • minDepositAmount: Minimum deposit allowed
  • maxDepositAmount: Maximum single deposit
  • maxTotalValueLocked: Maximum total value locked in the pool
  • minimumPoolTime: Minimum time assets must stay before cross-asset (protector-token) withdrawal (default 1 day; max 90 days per MAX_MINIMUM_POOL_TIME)
  • unlockDuration: Duration of the unlock period for protector withdrawals (default 28 days; bounds 1-365 days per ConstantsLib)
  • protocolFee: Protocol fee rate (basis points)
  • protocolFeeRecipient: Address receiving protocol fees
  • priceOracle: Oracle contract for token valuations

PoolState

Current pool balances:

  • insuredTokenBalance: Total shielded tokens held in the pool
  • totalUnderwriteTokenBalance: Sum of all backing token balances

Main Operations

Deposits

depositUnderwriteAsset(asset, depositAmount, minReceivedAmount): Protectors deposit collateral tokens

  • asset must be UNDERWRITER_TOKEN
  • minReceivedAmount: Slippage protection for fee-on-transfer tokens (via SlippageLib.enforceMinReceived)
  • Validates deposit amount and TVL limits
  • Mints protector receipt NFT (ERC721) representing the collateral position
  • Returns a unique tokenId for the NFT

depositInsuredAsset(asset, depositAmount, minReceivedAmount): Shielded users deposit assets to be protected

  • asset must be INSURED_TOKEN
  • minReceivedAmount: Slippage protection for fee-on-transfer tokens
  • Requires sufficient protector collateral (capacity check via oracle)
  • Calculates and stores valueAtDeposit (USD) and collateralAmount (for cross-asset cap)
  • Mints shielded receipt NFT (ERC721) representing the shielded position
  • Returns a unique tokenId for the NFT
  • Collateralization is based on original deposit values, not current token amounts

Withdrawals

insuredWithdraw(tokenId, preferredAsset, minAmountOut): Shielded users withdraw their deposits

  • preferredAsset: INSURED_TOKEN (yield minus fees) or UNDERWRITER_TOKEN (principal only, i.e. cross-asset / "activate shield")
  • minAmountOut: Slippage protection
  • Cross-asset (protector) requires minimumPoolTime since depositTime
  • For cross-asset: uses valueAtDeposit and getPriceWithCircuitBreaker(UNDERWRITER_TOKEN); payout is capped by pos.collateralAmount
  • Calculates and accumulates fees (commission, pool fee, protocol fee) then burns the receipt NFT

partialWithdrawInsured(tokenId, withdrawAmount, preferredAsset, minAmountOut) -> newTokenId: Partial withdrawal from a shielded position

  • preferredAsset must be INSURED_TOKEN (partial does not support cross-asset)
  • Burns the old NFT and mints a new one with the remainder; preserves depositTime
  • Runs _calculateAndAccumulateFees on the full position; enforces remaining >= minDepositAmount after fees

underwriterWithdraw(tokenId, amount, preferredAsset, minAmountOut): Protectors withdraw their collateral

  • preferredAsset must be UNDERWRITER_TOKEN
  • Can withdraw only the available amount (subject to utilization lock). Use getAvailableForWithdrawal(tokenId)
  • If unlock was requested and unlockRequestTime <= block.timestamp, the full position can be withdrawn (subject to utilization)
  • On partial withdrawal, the pool auto-claims pending commission before updating the position
  • Burns the receipt NFT on full withdrawal

startUnlockProcess(tokenId): Initiates the unlock period for backing tokens

  • Sets unlockRequestTime = block.timestamp + unlockDuration (time when unlock completes)
  • Withdrawal is allowed when unlockRequestTime != 0 and unlockRequestTime <= block.timestamp

cancelUnlockProcess(tokenId): Cancels an active unlock

  • Resets unlockRequestTime to 0. Only the NFT owner can call.

Yield and Fees

claimRewards(tokenId): Calculates and accumulates fees for a position (does not transfer to recipients; those are paid via claimCommission, payPoolFee, payProtocolFee)

  • 24-hour cooldown per tokenId; earlier calls revert with ClaimRewardsCooldownNotMet
  • Reduces position amount by fees and updates valueAtDeposit and lastFeeClaimTime
  • Can be called by anyone for any position

payPoolFee(): Pool creator or governance claims accumulated pool fees. See Fee Structure.

payProtocolFee(): Protocol fee recipient or governance claims accumulated protocol fees.

claimCommission(tokenId): Protector claims accumulated commission (in shielded tokens). Commission is tracked in the pool via the rewards-per-share pattern; use getClaimableCommission(tokenId) for the claimable amount.

For a complete breakdown of fee calculations, basis points, and distribution mechanics, see Fee Structure.

Access Control

Pools can optionally implement access control through the IPoolAccessControl interface:

  • Pool creator can set an access control contract
  • If set, all deposit/withdraw operations check permissions
  • If address(0), no restrictions (public pool)
  • Allows for private pools with custom access logic

Receipt NFTs (ERC721 NFTs)

Each pool uses two ERC721 NFT contracts to represent positions:

  • ShieldReceiptNFT: Represents shielded deposits as non-fungible tokens

    • Each deposit mints a unique NFT with a tokenId
    • NFT contains position data (amount, deposit time, value at deposit, etc.)
    • NFTs can be transferred (subject to transfer lock period)
    • Supports standard ERC721 operations (balanceOf, tokenOfOwnerByIndex, ownerOf, etc.)
  • ProtectorReceiptNFT: Represents protector collateral as non-fungible tokens

    • Each deposit mints a unique NFT with a tokenId
    • NFT contains position data (amount, depositTime, unlockRequestTime). Commission is tracked in the pool, not in the NFT.
    • NFTs can be transferred (subject to transfer lock period)
    • Supports standard ERC721 operations

Receipt NFTs are ERC721 tokens, allowing for:

  • Unique identification of each position via tokenId
  • Transferability of positions (with optional transfer locks)
  • Integration with NFT marketplaces and DeFi protocols
  • On-chain position data storage within the NFT contract

For detailed information about NFT mechanics, transfer locks, and integration opportunities, see Receipt NFTs.

View Functions

  • getAvailableForWithdrawal(tokenId): Maximum protector amount withdrawable (accounts for utilization lock)
  • getLockedAmount(tokenId): Amount locked for a protector position
  • getUtilizationRatio() / getUtilizationRatioUsd(): Ratio of required collateral to protector value (token-based or USD-based)
  • getReservedFees(): Total fees reserved (pool fee, protocol fee, commissions); not withdrawable by users
  • getWithdrawableBalance(): Shielded token balance available for user withdrawals (excludes reserved fees)
  • getClaimableCommission(tokenId): Claimable commission for a protector position
  • getShieldDepositInfo(tokenId) / getProtectorDepositInfo(tokenId): Position data and metadata
  • getOracleInfo(): Oracle address and dual-feed status (if CompositeOracle)
  • getUserNFTCounts(user): Shielded and protector NFT counts per user
  • getPoolBalances(): insuredTokenBalance and totalUnderwriteTokenBalance

Advanced / Governance

migrateExistingPosition(tokenId): Governance-only. Migrates a protector position to the rewards-per-share system (MasterChef); sets rewardDebt to 0 so historical rewards can be claimed. One-time per position.

Supporting Components

Libraries

  • SlippageLib: Enforces minimum received amounts for slippage protection
  • ErrorsLib: Custom errors for gas-efficient error handling
  • EventsLib: Standardized events for off-chain indexing
  • ConstantsLib: Protocol-wide constants (basis point scale, limits, etc.)

Base Contract

  • ProtocolAccessControlUpgradeable: Provides governance controls, pausable functionality, and reentrancy protection (see Security Model)

Interfaces

  • ISplitRiskPool: Defines the public interface for pool interactions
  • IShieldReceiptNFT: Interface for shielded receipt NFT operations (mint, burn, getPosition, updatePosition)
  • IProtectorReceiptNFT: Interface for protector receipt NFT operations
  • IPriceOracle: Interface for token price and valuation queries

Upgradeability

Pools are deployed as UUPS (Universal Upgradeable Proxy Standard) proxies, allowing for:

  • Future improvements without losing state
  • Governance-controlled upgrades
  • Implementation contract updates

The pool implementation contract is immutable, while the proxy can be upgraded by governance.

Collateralization Model

The pool uses a symmetric collateralization model based on original deposit values:

  • Original Deposit Values: Each shielded deposit stores its USD value at deposit time (valueAtDeposit)
  • Protector Requirements: Collateral requirements are calculated from the sum of all original deposit values (totalValueAtDeposit), not current token amounts
  • Symmetric Protection:
    • Shielded users can only claim their original valueAtDeposit when activating their shield
    • Protectors only need to collateralize original deposit values, not yield appreciation
  • Benefits:
    • Shielded token price appreciation does not lock additional protector collateral
    • Predictable collateral requirements for protectors
    • Fair risk distribution between shielded users and protectors

This design ensures that both parties work with the same baseline: the original principal deposit value, creating a balanced protection relationship.