UPD — Universal Private DollarConcepts

StabilizerNFT System

How the StabilizerNFT coordinates collateral allocation, mint/burn priority ordering, and liquidations.

StabilizerNFT System

User1.00 stETHStabilizer0.25 stETHPositionEscrow1.25 stETHat 125% ratiobacking 2,300 UPDUPD2,300parbuffermintdelivered

The user contributes par (1 stETH); the Stabilizer adds the 25% buffer (0.25 stETH). Both lock into the PositionEscrow at the configured 125% ratio. UPD is minted to the user at par (oracle price × user contribution).

What is a Stabilizer?

A Stabilizer is an NFT representing an overcollateralization position in the UPD system. Each Stabilizer:

  • Holds stETH as the overcollateral portion that backs minted UPD (the user's stETH covers par; the stabilizer covers the buffer above par)
  • Has a priority rank (lower NFT ID = higher priority for both filling and being protected from redemptions)
  • Sets its own minimum collateralization ratio (range: 125% to 1000%, default 125%)
  • Can be liquidated if its position drops below an ID-dependent threshold

Think of Stabilizers as the overcollateral providers — they put up the buffer above par so each UPD has more than $1 of stETH backing it.

Becoming a Stabilizer

Minting a StabilizerNFT is a permissionless action — anyone can become a Stabilizer by depositing stETH collateral and minting an NFT. There are no whitelists, no approval process, and no gatekeeper. The system is designed to be open from day one.

At launch, a founding Stabilizer will seed the system with initial liquidity, giving the ecosystem an anchor to bootstrap from. But the system doesn't depend on any single participant — additional Stabilizers can join at any time, independently and without permission.

The economics of being a Stabilizer:

RoleBenefitRisk
Low-ID StabilizerFilled first when users mint UPD, last to face redemptions, eligible for the highest liquidation thresholds (see Liquidation)Capital sits idle until allocated; depeg/liquidation risk on stETH
High-ID StabilizerCapital still earns the same per-position yield economicsFirst to face redemptions (LIFO burn order); lower liquidation threshold

Because the burn order is LIFO (last in, first out), later Stabilizers bear more redemption pressure.

Off-chain Hedging Is the Whole Point

When a user mints UPD against your buffer, you take on a long-ETH position — the stETH you put up as overcollateral is exposed to ETH price decline, while the UPD liability above your collateral is dollar-denominated. The yield you earn comes from running a short hedge off-chain (typically a delta-neutral perpetual futures short) that nets out the price exposure and captures the funding rate. The protocol creates the long position by accepting your buffer; you create the matching short position to make the trade pay.

The protocol enforces a hard invariant on every Stabilizer, regardless of strategy:

  • You can free stETH that has accrued above your minimum collateralization ratio (rebalanceToMinRatio) — never below it.
  • You can shrink your position by burning UPD against it (unallocateMyself) — the freed stETH is exactly your collateral slice at the position's current ratio.
  • You can deposit and withdraw funds in your own StabilizerEscrow (the unallocated buffer) at will — the par-backing collateral inside PositionEscrow is never touchable directly.

What no Stabilizer can do, regardless of role or signing power: release collateral below the minimum ratio, reach into another Stabilizer's position, change the collateral asset (it's stETH, full stop), or alter another Stabilizer's hedge. The protocol enforces solvency at the position level. Your strategy above that floor is yours; the floor itself isn't yours to move.

Mint Priority

When UPD is minted, AllocationLib iterates Stabilizers from lowest ID to highest (the unallocated linked list, sorted by NFT ID). For each Stabilizer the loop:

  1. Reads the current unallocatedStETH balance from that Stabilizer's StabilizerEscrow
  2. Computes how much of that balance is needed to back the user's remaining stETH at the Stabilizer's configured ratio
  3. Moves both sides into the Stabilizer's PositionEscrow
  4. Mints UPD to the user proportional to the user's contributed stETH × oracle price
  5. If the Stabilizer's escrow ran dry mid-allocation, removes it from the unallocated list and moves to the next-lowest ID

If the loop runs out of gas before fully consuming the user's deposit (gasleft() < MIN_GAS = 250,000), the unconsumed stETH is refunded to the caller. If no Stabilizer has unallocated funds, the mint reverts with NoUnallocatedFunds.

Worked Example: Spill Across Two Stabilizers

User2.00 stETHStabilizer #1 @ 125%0.20 stETH (used in full)Position #11.00 stETHbacks 1,840 UPDPosition #21.80 stETHbacks 2,760 UPDStabilizer #2 @ 150%5.00 stETH (0.60 used)0.801.200.200.60

The user's 2.00 stETH is allocated in priority order. Stabilizer #1 fills first (its full 0.20 stETH paired with 0.80 user stETH → Position #1, 1,840 UPD). The remaining 1.20 stETH spills into Stabilizer #2 (only 0.60 of its 5.00 stETH consumed → Position #2, 2,760 UPD). Total: 4,600 UPD.

Setup:

  • ETH/USD = $2,300
  • User deposits 2 stETH (intends to mint ~4,600 UPD)
  • Stabilizer #1 has 0.20 stETH unallocated, configured at 125% ratio
  • Stabilizer #2 has 5.00 stETH unallocated, configured at 150% ratio

The loop visits #1 first.

Slice on Stabilizer #1:

remaining_user_stETH       = 2.00
stabilizer_needed_at_125%  = 2.00 × (12500 - 10000) / 10000 = 0.50 stETH
escrow has only 0.20 — adjust user share down:
max_user_stETH supportable = 0.20 × 10000 / (12500 - 10000) = 0.80 stETH
user_share                 = min(0.80, 2.00) = 0.80 stETH
toAllocate (stabilizer)    = 0.20 stETH

Position #1 receives 0.80 + 0.20 = 1.00 stETH
UPD minted (slice)         = 0.80 × 2,300 = 1,840 UPD
Position #1 liability +=     1,840 UPD
remaining_user_stETH       = 2.00 - 0.80 = 1.20 stETH

Stabilizer #1's escrow is now empty; it is removed from the unallocated list. Loop continues to #2.

Slice on Stabilizer #2:

remaining_user_stETH       = 1.20
stabilizer_needed_at_150%  = 1.20 × (15000 - 10000) / 10000 = 0.60 stETH
escrow has 5.00 — fully covered.
user_share                 = 1.20 stETH
toAllocate (stabilizer)    = 0.60 stETH

Position #2 receives 1.20 + 0.60 = 1.80 stETH
UPD minted (slice)         = 1.20 × 2,300 = 2,760 UPD
Position #2 liability +=     2,760 UPD
remaining_user_stETH       = 0

Result:

UPD mintedstETH locked in PositionEscrowPosition ratio
Stabilizer #11,8401.00125%
Stabilizer #22,7601.80150%
User receives4,600 UPD(no stETH back — fully consumed)

The reporter snapshot increases by 2.80 stETH total (the user's 2.00 plus 0.80 from Stabilizers). The system-wide collateralization ratio is recalculated against the new combined liability of 4,600 UPD.

Burn Priority (LIFO)

When UPD is burned, the system iterates from highest ID to lowest:

Burn 1,000 UPD:
  Stabilizer #N has collateral → burn from #N first (last in, first out)
  Continue from N-1, N-2...

The highest-ID Stabilizer (lowest priority) bears the first redemption risk.

This LIFO ordering creates a natural risk/reward structure:

  • Low-ID Stabilizers: Get filled first (more UPD minted against them), first to benefit
  • High-ID Stabilizers: Get burned first (more redemption risk), should demand higher yield

Position Escrow

Each Stabilizer's allocated stETH is held in its own PositionEscrow contract:

Stabilizer #1 → PositionEscrow #1 (holds stETH backing #1's UPD)
Stabilizer #2 → PositionEscrow #2
...

This isolation ensures that one Stabilizer's insolvency doesn't immediately affect others.

Liquidation

A position is liquidatable when its ratio drops below an ID-dependent threshold (not a flat 100%) and below the system-wide ratio. The threshold ranges from 110% for anonymous liquidators to 125% for Stabilizer #1, decreasing 0.5% per ID step. The liquidator receives the position's collateral plus a 5% bonus (default liquidationLiquidatorPayoutPercent), with InsuranceEscrow covering any shortfall on a best-effort basis.

The full mechanics — the two-condition gate, the payout split between liquidator/insurance/position, three worked examples, and how to find liquidatable positions off-chain — live on the dedicated Liquidation page.

LinkedListLib priority order

LinkedListLib maintains Stabilizers sorted by NFT ID (not by collateralization ratio). This is the priority order used for allocation (lowest → highest) and burn (highest → lowest). Liquidation discovery is not maintained by the linked list — it is the liquidator's responsibility to identify undercollateralized positions off-chain.

Unallocated Collateral

Stabilizers also have an StabilizerEscrow for unallocated stETH — collateral deposited but not yet backing any UPD. This allows Stabilizers to:

  • Pre-fund before minting demand
  • Hold a buffer for emergency use
// Deposit ETH (auto-staked into stETH via Lido)
stabilizerNFT.addUnallocatedFundsEth{value: ethAmount}(tokenId);

// Deposit stETH directly (no Lido step)
stabilizerNFT.addUnallocatedFundsStETH(tokenId, stEthAmount);

// Withdraw unallocated stETH (sent to NFT owner)
stabilizerNFT.removeUnallocatedFunds(tokenId, stEthAmount);

// Adjust the per-position minimum collateralization ratio
// Range: 12500 (125%) to 100000 (1000%)
stabilizerNFT.setMinCollateralizationRatio(tokenId, 15000); // 150%

Owner-Only Position Operations

Beyond the deposit/withdraw of unallocated funds shown above, the NFT owner has two operations that touch the allocated (UPD-backing) side of their position:

// Burn UPD to free your own collateral. Targets your specific position
// (vs burnUPD which targets the highest-ID position globally). All freed stETH
// — both the user-side par and your overcollateral buffer — returns to your
// StabilizerEscrow as unallocated funds.
stabilizerNFT.unallocateMyself(tokenId, updAmount, priceQuery);

// Move excess stETH from PositionEscrow → StabilizerEscrow.
// As stETH rebases (Lido yield), the position's ratio drifts above your
// configured minimum. This call frees the excess back to your unallocated
// funds without touching the UPD liability.
uint256 stEthFreed = stabilizerNFT.rebalanceToMinRatio(tokenId, priceQuery);

rebalanceToMinRatio is how Stabilizers reclaim the stETH yield that has accrued to their allocated collateral over time. unallocateMyself is how a Stabilizer voluntarily exits or reduces their position without waiting for the natural LIFO burn flow.

Direct Collateral Additions

Stabilizers can add collateral directly to their escrow outside of the normal mint flow — via addCollateralEth(), addCollateralStETH(), or by sending ETH directly to the escrow's receive() function.

When this happens, the escrow calls StabilizerNFT.onCollateralAdded(tokenId, stEthAmount), which in turn notifies the OvercollateralizationReporter via updateSnapshot(). This keeps the system-wide collateral snapshot accurate without waiting for a full syncFromChain() reconciliation.

Escrow Upgradeability

Both PositionEscrow and StabilizerEscrow are deployed as BeaconProxy instances behind an UpgradeableBeacon. This means:

  • Each Stabilizer gets its own proxy contract (isolated storage, isolated collateral)
  • All proxies of the same type share a single beacon that points to the current implementation
  • Calling beacon.upgradeTo(newImpl) atomically upgrades every existing escrow of that type in a single transaction
  • No migration, no redeployment, no per-escrow action required
UpgradeableBeacon (PositionEscrow)
  └─ implementation: PositionEscrowV1
  └─ proxies: [Escrow#1, Escrow#2, Escrow#3, ...]

beacon.upgradeTo(PositionEscrowV2)
  └─ All proxies now delegate to V2

The BeaconProxyFactory is the contract that StabilizerNFT calls to deploy new escrow proxies when a Stabilizer is created. It holds references to both beacons and deploys the correct proxy type.

Upgrade authority

The beacon owner can upgrade all escrows atomically. This is a privileged operation — the owner address should be a multisig or governance contract. Review the beacon owner before integrating.

The StabilizerNFT is the entry point for the UPP pool's compliance layer. When the pool mints/burns UPD, it interacts with the StabilizerNFT, not UPDToken directly.

On this page