StakeXDCDocs
Liquid staking

Smart contract reference

Technical reference for PrimeStakedXDCV32, the V3 liquid staking vault. The contract is an ERC-4626-style native-XDC vault with self-service withdrawals, a buffer-aware queue, o…

Technical reference for PrimeStakedXDC_V3_2, the V3 liquid staking vault. The contract is an ERC-4626-style native-XDC vault with self-service withdrawals, a buffer-aware queue, on-chain masternode integration, and time-locked governance.

Address: 0xDc74c0DaED82ae94486DeeF22991d2F54173c734. Non-upgradeable: deployed with a regular constructor, no proxy.


User functions

stake() payable

Deposits native XDC and mints psXDC shares to the sender at the current exchange rate.

ParameterDetail
msg.valueAmount of native XDC to deposit
ReturnspsXDC shares minted

depositNative(uint256 assets, address receiver) payable

Same as stake but lets you specify a separate receiver. msg.value must equal assets.

withdraw(uint256 assets, address receiver, address owner) / redeem(uint256 shares, address receiver, address owner)

Standard ERC-4626 instant redemption. Reverts if the vault's liquid buffer cannot cover the request.

withdrawWithQueue(uint256 assets, address receiver, address owner) / redeemWithQueue(uint256 shares, address receiver, address owner)

The "smart" redemption used by the app. Settles instantly when the buffer is sufficient, otherwise escrows the shares and adds a request to the FIFO queue.

claimQueuedAssets(address receiver)

Pays out any XDC parked in pendingQueuedAssets for the caller (e.g. from previously failed receiver payouts or processed queue requests where the receiver could not receive directly).

cancelQueuedWithdrawal(uint256 requestId)

Cancels a pending queued withdrawal you previously created and returns the escrowed shares to your wallet.

Read-only helpers

FunctionReturns
totalAssets()Total XDC tracked by the vault (includes principal staked with masternodes)
convertToShares(uint256 assets) / convertToAssets(uint256 shares)Exchange-rate conversions
previewDeposit / previewWithdraw / previewRedeemUI-friendly previews using the current rate
maxWithdraw(address) / maxRedeem(address)Liquidity-aware caps that reflect the buffer
outstandingValidatorPrincipalByOperator(address operator)Principal currently staked with a specific masternode operator

Operator functions (validator management)

Anyone with the appropriate role can call these; users do not call them directly.

FunctionRolePurpose
proposeMasternode(...)PROPOSER_ROLEManually propose a new masternode
triggerAutoPropose(uint256 maxNodes)anyoneTrigger the bounded auto-propose loop. Always blocked while there is a withdrawal queue backlog so user redemptions are prioritized over new proposals
processWithdrawalQueue(uint256 maxRequests)anyoneProcess up to maxRequests FIFO entries
syncTrackedAssets()anyoneReconcile trackedTotalAssets with the contract's native balance after unexpected inflows

Operations & risk

These are gated by the V3 role split. They never let an admin move user funds; they only tune parameters and report validator outcomes.

FunctionRoleNotes
distributeRewards() payableOPERATIONS_MANAGER_ROLEReward lane: credits the sent XDC (minus the optional rewardFeeBps skim, default 0, hard-capped at 20%) to trackedTotalAssets, i.e. raises the psXDC share price for every holder. This is how the ~5.5% APY reaches holders, roughly monthly.
setRewardFeeConfig(uint16 bps, address recipient)DEFAULT_ADMIN_ROLEOptional protocol fee on distributions (currently 0).
fundBacking() payableMIGRATION_MANAGER_ROLEBacking lane: adds un-earmarked XDC intended for proposeMasternode. Never mints shares or moves NAV.
setOperatorScanLimit(uint256) / setQueueScanLimit(uint256)OPERATIONS_MANAGER_ROLEBounded scan limits used by auto-propose and queue processing
setMinStake(uint256)OPERATIONS_MANAGER_ROLEMinimum XDC required to attempt a masternode propose
setValidator(address)OPERATIONS_MANAGER_ROLEConfigures the XDC validator contract address
reportValidatorLoss(address operator, uint256 assets)RISK_MANAGER_ROLEReports a validator loss; capped per-report (maxLossBpsPerReport) and per-day (maxDailyLossBps)
reportMasternodeResignPrincipal(address operator)PROPOSER_ROLEAccounts for masternode principal that has been returned to the vault after resignation cooldown

grantRole, revokeRole, and renounceRole are intentionally disabled. Sensitive role rotations must go through the delayed governance path below to prevent bypassing the time-lock.


Delayed governance

Every sensitive change is a schedule → wait → execute flow gated by governanceDelay:

ScheduleExecuteCancel
setGovernanceDelay(delay_)executeGovernanceDelay()cancelGovernanceDelayChange()
setOperationsManager(account)executeOperationsManager()cancelOperationsManagerChange()
setRiskManager(account)executeRiskManager()cancelRiskManagerChange()
setMaxLossBpsPerReport(bps)executeMaxLossBpsPerReport()cancelMaxLossBpsPerReportChange()
setMaxDailyLossBps(bps)executeMaxDailyLossBps()cancelMaxDailyLossBpsChange()

The owner handoff itself uses the same pattern:

  • scheduleOwnerTransfer(newOwner) → wait governanceDelayexecuteOwnerTransfer(). Cancel any time with cancelOwnerTransfer().

transferOwnership and renounceOwnership are disabled so ownership can never change without the delay.


V3.2: the permanent ledger model

V3.1 introduced a temporary underBackedMode that suspended reward distribution until the vault was fully backed. V3.2 (the live vault) makes the partially-backed state the permanent, first-class operating model and removes the mode flag, the percentage buffer (bufferBps) and fundMigrationLiquidity entirely:

SurfaceDetail
trackedTotalAssetsA liability ledger: what the vault owes holders. Real backing (native balance + outstandingValidatorPrincipal) may sit below it; the gap is disclosed off-chain from the public getters as (trackedTotalAssets + totalFailedPayouts) − (balance + outstandingValidatorPrincipal).
Reward lane — distributeRewards()Always open. The operations manager forwards the period's validator yield; it raises trackedTotalAssets (the share price) regardless of the backing gap, so holders earn on schedule. Optional hard-capped rewardFeeBps (default 0).
Backing lanes — fundBacking() and migration-manager sendsAlways open, never NAV. fundBacking() adds un-earmarked liquidity for proposeMasternode. A raw send from the MIGRATION_MANAGER_ROLE wallet is earmarked to queueBackingBudget, from which the FIFO withdrawal queue is settled exclusively. Neither mints shares nor touches trackedTotalAssets; third-party sends are accepted as silent backing donations, so outsiders cannot pump the share price.
queueBackingBudget (public uint)The ring-fenced queue budget. Also receives returned masternode principal (via withdrawResignedMasternode) up to the queue's unfunded amount.
Immediate withdrawalsDraw only from unencumbered surplus liquidity (balance − queue earmark − failed-payout reserve). Fresh stakers can always exit their own liquidity, and the earmarked queue funding cannot be front-run.
Queue processingprocessWithdrawalQueue(maxRequests) walks the FIFO paying previewRedeem(shares) at the current rate from queueBackingBudget; a request the budget cannot cover is skipped (and retried later), never cancelled.
NAV protectionsyncTrackedAssets() only ratchets tracked assets upwards (donations, vault-node rewards). Real validator losses go through reportValidatorLoss, bounded by maxLossBpsPerReport / maxDailyLossBps under the risk manager.

Events worth indexing

EventWhen
Deposit(sender, owner, assets, shares)Stake / deposit
Withdraw(sender, receiver, owner, assets, shares)Instant or queued payout
WithdrawalQueued(requestId, owner, shares)A request entered the FIFO queue
WithdrawalProcessed(requestId, receiver, assets, shares)The queue processed a request
WithdrawalCanceled(requestId, owner, shares)User cancelled their own queued request
AutoProposeFailed(reason)Auto-propose was attempted but didn't execute; never reverts the stake flow
ValidatorLossReported(operator, assets, reporter)Risk manager recorded a loss
MigrationLiquidityFunded(amount)Migration manager topped up bridge liquidity

Security properties

  • Non-blocking auto-propose path: stake never reverts because of a failed propose.
  • Deferred payout fallback for rejecting receivers via pendingQueuedAssets + claimQueuedAssets.
  • Liquidity-aware maxWithdraw / maxRedeem.
  • Bounded scans (operatorScanLimit, queueScanLimit) to prevent gas-griefing.
  • AccessControl role split (proposer / operations / risk / migration).
  • Delayed execution for every sensitive role / risk parameter change.
  • Per-report and cumulative per-day loss caps.
  • Direct role mutation (grantRole, revokeRole) disabled to prevent delayed-governance bypass.

V3 ArchitectureDeployed Contracts & Addresses

On this page