Smart Contract Reference (V3)
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.
msg.value
Amount of native XDC to deposit
Returns
psXDC 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.
migrate(address receiver, uint256 assets, uint256 minSharesOut) (bridge-only)
Mints V3 shares to receiver against assets of XDC contributed by the migration bridge. Only callable by the configured PrimeStakedXDC_V3MigrationBridge while the migration window is open. minSharesOut protects callers from unfavorable exchange-rate movement.
Read-only helpers
totalAssets()
Total XDC tracked by the vault (includes principal staked with masternodes)
convertToShares(uint256 assets) / convertToAssets(uint256 shares)
Exchange-rate conversions
previewDeposit / previewWithdraw / previewRedeem
UI-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.
proposeMasternode(...)
PROPOSER_ROLE
Manually propose a new masternode
triggerAutoPropose(uint256 maxNodes)
anyone
Trigger 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)
anyone
Process up to maxRequests FIFO entries
syncTrackedAssets()
anyone
Reconcile 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.
setBufferBps(uint256)
OPERATIONS_MANAGER_ROLE
Sets the percentage of total assets kept liquid for instant redeem
setOperatorScanLimit(uint256) / setQueueScanLimit(uint256)
OPERATIONS_MANAGER_ROLE
Bounded scan limits used by auto-propose and queue processing
setMinStake(uint256)
OPERATIONS_MANAGER_ROLE
Minimum XDC required to attempt a masternode propose
setValidator(address)
OPERATIONS_MANAGER_ROLE
Configures the XDC validator contract address
reportValidatorLoss(address operator, uint256 assets)
RISK_MANAGER_ROLE
Reports a validator loss; capped per-report (maxLossBpsPerReport) and per-day (maxDailyLossBps)
reportMasternodeResignPrincipal(address operator)
PROPOSER_ROLE
Accounts 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:
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)→ waitgovernanceDelay→executeOwnerTransfer(). Cancel any time withcancelOwnerTransfer().
transferOwnership and renounceOwnership are disabled so ownership can never change without the delay.
Migration controls
setMigrationBridge(address)
admin
Configures the address allowed to call migrate(...)
setMigrationInProgress(bool)
MIGRATION_MANAGER_ROLE
Opens or closes the migration window
fundMigrationLiquidity() payable
MIGRATION_MANAGER_ROLE
Tops up backing liquidity for migrated shares. Only callable while the migration window is open
Direct native sends to the vault while migration is in progress are accepted only from the migration manager and are treated as migration liquidity (they do not inflate totalAssets or convertToShares).
V3.1 additions: under-backed mode
V3.1 adds a single mechanism on top of the audited V3 design so the vault could launch while the legacy masternode collateral is still being transferred in:
underBackedMode (public bool)
Set once at go-live if the vault's liquid + tracked backing was below its share liability. Auto-clears inside syncTrackedAssets() when real backing catches up; no admin can force it back on.
queueBackingBudget (public uint)
A ring-fenced budget, active only while under-backed. Migration-manager funding and returned masternode principal accrue here and are reserved for the FIFO withdrawal queue.
Withdrawal behaviour
Immediate withdrawals draw only from unencumbered surplus liquidity (new stakes stay withdrawable); queued requests are paid from queueBackingBudget as funding tranches arrive.
NAV protection
While under-backed, syncTrackedAssets() cannot write NAV down, so the transition mechanics can never reduce the value of existing shares.
fundMigrationLiquidity()
Callable by the MIGRATION_MANAGER_ROLE during under-backed mode to inject collateral tranches (roughly one masternode's worth per week) without minting shares or inflating NAV.
Once the full collateral has been transferred, underBackedMode clears itself and the contract behaves exactly like the audited V3.
Events worth indexing
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.
Last updated