> For the complete documentation index, see [llms.txt](https://docs.primestaking.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.primestaking.xyz/products/xdc-liquid-staking/smart-contract-functions.md).

# Smart Contract Reference (V3)

Technical reference for [`PrimeStakedXDC_V3_2`](/products/contract-addresses.md), 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.

{% hint style="info" %}
Address: [`0xDc74c0DaED82ae94486DeeF22991d2F54173c734`](https://xdcscan.com/address/0xDc74c0DaED82ae94486DeeF22991d2F54173c734). **Non-upgradeable**: deployed with a regular constructor, no proxy.
{% endhint %}

***

## User functions

### `stake() payable`

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

| Parameter   | Detail                          |
| ----------- | ------------------------------- |
| `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

| Function                                                              | Returns                                                                     |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `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.

| Function                                      | Role            | Purpose                                                                                                                                                |
| --------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `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.

| Function                                                       | Role                      | Notes                                                                                               |
| -------------------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------- |
| `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`:

| Schedule                        | Execute                        | Cancel                              |
| ------------------------------- | ------------------------------ | ----------------------------------- |
| `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 `governanceDelay` → `executeOwnerTransfer()`. Cancel any time with `cancelOwnerTransfer()`.

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

***

## Migration controls

| Function                           | Role                     | Purpose                                                                                         |
| ---------------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------- |
| `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:

| Surface                            | Detail                                                                                                                                                                                               |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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

| Event                                                      | When                                                                        |
| ---------------------------------------------------------- | --------------------------------------------------------------------------- |
| `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 Architecture](/products/xdc-liquid-staking/v3-architecture.md) → [Deployed Contracts & Addresses](/products/contract-addresses.md)
