> For the complete documentation index, see [llms.txt](https://docs.axis.to/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.axis.to/reference/staking-contracts.md).

# Staking Contracts Reference

Technical reference for `StakedUSDx`, the V2 rewards vault. For design rationale, see [Architecture](/technical-and-architecture/architecture.md). For usage instructions, see [Stake & Unstake](/susdx-the-rewards-vault/stake-and-unstake.md). Terminology follows the Protocol Taxonomy (the V2 single source of truth) rather than being redefined here.

V2 collapses the V1 staking system into a **single contract**. The V1 layout, a base vault (`StakedAxisUSD`), a cooldown subclass (`StakedAxisUSDV2`), a separate escrow (`AxisUSDSilo`), and an external distributor (`StakingRewardsDistributor`), no longer exists. In V2 there is **no silo**: redemption liabilities are held in-vault as pending/claimable buckets. There is **no separate distributor**: rewards are funded directly into the vault by a `REWARD_MANAGER_ROLE` holder. Withdrawals are **async (ERC-7540)** rather than a synchronous cooldown-then-unstake flow.

***

## StakedUSDx

**Verified source:** [read on Etherscan](https://etherscan.io/address/0xEB892628D1E58BC475A6dCB7F5dBC4F591632AA4#code) **Inherits:** `Initializable`, `ERC4626Upgradeable`, `AccessControlUpgradeable`, `IStakedUSDx`

An upgradeable ERC-4626 rewards vault. Users deposit **USDx** (the asset) and receive **sUSDx** (the share, ERC-20 name/symbol `"Staked USDx"` / `"sUSDx"`). Shares appreciate as funded rewards vest linearly into vault accounting. Exits are asynchronous: a holder calls `requestRedeem`, waits out a cooldown, a servicer makes the request claimable, and the holder then claims. The vault is its own ERC-7575 share token, `share()` returns `address(this)`, and implements the ERC-7540 async-redeem interfaces (`IERC7540Redeem`, `IERC7540Operator`).

Deployed behind a `TransparentUpgradeableProxy` with a governance-owned `ProxyAdmin`; the implementation disables initializers in its constructor and is set up once via `initialize`.

### Roles

| Role                       | Constant                                | Authority                                                                                     |
| -------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------- |
| `DEFAULT_ADMIN_ROLE`       | `bytes32(0)`                            | Admin over every other role; configures rewards, redemption controls, and policies            |
| `REWARD_MANAGER_ROLE`      | `keccak256("REWARD_MANAGER_ROLE")`      | Funds rewards via `fundRewards`                                                               |
| `REDEMPTION_SERVICER_ROLE` | `keccak256("REDEMPTION_SERVICER_ROLE")` | Services the redemption queue (pending → claimable)                                           |
| `PAUSER_ROLE`              | `keccak256("PAUSER_ROLE")`              | Pauses deposits, reward funding, and each redemption sub-flow                                 |
| `RESTRICTION_ROLE`         | `keccak256("RESTRICTION_ROLE")`         | Sets/clears soft and full restrictions; redistributes shares and requests off frozen accounts |

At `initialize(asset_, admin, rewardDuration_)`, the `admin` address is granted all five roles. See the taxonomy for the intended production custody of each role at launch.

### Constants

| Constant                       | Value                     | Description                                            |
| ------------------------------ | ------------------------- | ------------------------------------------------------ |
| `MAX_COOLDOWN_DURATION`        | `90 days`                 | Upper bound for the redemption cooldown                |
| `DEFAULT_REDEMPTION_POLICY_ID` | `bytes32("DEFAULT")`      | Id of the always-enabled default redemption policy     |
| `REQUEST_ID`                   | `uint256` (starts at `1`) | Public monotonic counter; next request id to be minted |
| `WAD`                          | `1e18`                    | Fixed-point scale for `exchangeRate` (internal)        |
| `MAX_SERVICE_SCAN_REQUESTS`    | `256`                     | Per-call scan bound in `serviceRedemptions` (internal) |

### Accounting model

sUSDx is priced against `accountedAssets`, active principal plus already-vested rewards, **not** the raw USDx balance. Unvested rewards and reserved redemption liabilities are deliberately excluded from the share price so servicing, claims, and vesting can never spend the same USDx twice.

| Read                      | Meaning                                                                                                                                             |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `totalAssets()`           | `accountedAssets` including rewards vested up to now; drives ERC-4626 conversions                                                                   |
| `accountedAssets()`       | Active principal + vested rewards backing sUSDx                                                                                                     |
| `pendingRedeemAssets()`   | USDx reserved for `PENDING` requests (shares already burned, value moved out of `accountedAssets`)                                                  |
| `claimableRedeemAssets()` | USDx reserved for serviced `CLAIMABLE` requests, awaiting claim                                                                                     |
| `exchangeRate()`          | 18-decimal USDx-per-sUSDx (`totalAssets · 1e18 / totalSupply`, floor); returns `1e18` when supply is 0                                              |
| `getVaultState()`         | Full snapshot: supply, assets, both liability buckets, `redeemLiabilities`, `requiredAssets`, `assetBalance`, `exchangeRate`, `cooldown`, `solvent` |

Solvency invariant: `assetBalance >= requiredAssets`, where `requiredAssets = accountedAssets + pendingRedeemAssets + claimableRedeemAssets + unvestedRewards`. `redeemLiabilities = pendingRedeemAssets + claimableRedeemAssets`.

`decimals()` is `18`. `convertToShares`/`convertToAssets` use floor rounding against `accountedAssets`. `previewWithdraw()` and `previewRedeem()` **always revert** (`OperationNotAllowed`) because claims are async and share-exact rather than previewable.

### Deposit (stake)

Synchronous ERC-4626 entry, USDx in, sUSDx out immediately.

| Function                                    | Access   | Description                                                                                 |
| ------------------------------------------- | -------- | ------------------------------------------------------------------------------------------- |
| `deposit(uint256 assets, address receiver)` | external | Pull USDx, checkpoint rewards, credit `accountedAssets`, mint shares                        |
| `mint(uint256 shares, address receiver)`    | external | Same, denominated in shares                                                                 |
| `maxDeposit` / `maxMint`                    | view     | Return `0` when deposits are paused or the receiver is restricted, else `type(uint256).max` |

Deposits revert while `depositPaused` is set or when the caller or receiver is restricted.

### Reward funding & vesting

Rewards are funded **directly into the vault**, there is no external distributor. A `REWARD_MANAGER_ROLE` holder transfers USDx in and folds it into a linear vesting schedule.

| Function                                                          | Access                | Description                                                                                                       |
| ----------------------------------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `fundRewards(uint256 assets, bytes32 sourceRef)`                  | `REWARD_MANAGER_ROLE` | Checkpoint vested rewards, pull `assets`, increment `unvestedRewards`, recompute `rewardRate`, set `periodFinish` |
| `setRewardConfig(uint256 duration, uint256 maxRate, bool paused)` | `DEFAULT_ADMIN_ROLE`  | Set vesting duration, optional `maxRewardRate` cap, and funding-pause; reschedules active rewards                 |
| `pauseRewardFunding()`                                            | `PAUSER_ROLE`         | Pause funding and freeze the active vesting schedule                                                              |
| `rewardConfig()` / `rewardState()`                                | view                  | Report authority/duration/maxRate/paused, and live `unvestedRewards`/`rewardRate`/`periodFinish`                  |

Behavior: `fundRewards` reverts if reward funding is paused, `assets` or `rewardDuration` is zero, or `totalSupply()` is zero. A top-up **folds into the active vesting window** (it does not reset the clock, see `_activeRewardDuration`) and is rejected if the resulting `rewardRate` would exceed a non-zero `maxRewardRate`. Vested rewards are materialized into `accountedAssets` on each checkpoint (deposit, request, or fund); unvested rewards stay outside the share price until they drip in. After `periodFinish`, `rewardRate` drops to zero and any remainder is treated as fully vested.

Emits `RewardsFunded(funder, assets, sourceRef, rewardRate, periodFinish)` and `RewardConfigUpdated(authority, duration, maxRate, paused)`.

### Async redemption lifecycle (ERC-7540)

Exits are a three-step async flow. Shares are burned up front at request time; USDx moves through the in-vault pending and claimable liability buckets and is transferred out only at claim.

```
requestRedeem ──▶ PENDING ──serviceRedemptions──▶ CLAIMABLE ──withdraw/redeem──▶ CLAIMED
   (burn shares,     (cooldown,        (REDEMPTION_        (release reserved
    reserve assets)   awaiting service)  SERVICER_ROLE)       USDx to receiver)
```

**1, Request.** `requestRedeem(uint256 shares, address controller, address owner)` burns the owner's shares, moves the corresponding assets from `accountedAssets` to `pendingRedeemAssets`, and records a `RedemptionState` (shares, `assetsReserved`, `requestedAt`, `eligibleAt = requestedAt + policy.cooldown`, `policyId`, `status = PENDING`). Returns a monotonic `requestId`. Fully restricted owners/controllers cannot open requests; a soft-restricted owner may only exit to their own controller. Reverts if requests are paused, on zero inputs, if the caller is not the owner/controller or their approved operator, if the owner's balance is short (`MinSharesViolation`), or if `shares` exceed the policy's `maxRequestShares`. Emits `RedeemRequest`.

**2, Service.** A `REDEMPTION_SERVICER_ROLE` holder moves eligible pending requests to `CLAIMABLE`, shifting reserved USDx from `pendingRedeemAssets` to `claimableRedeemAssets`. This is the in-vault replacement for V1's silo transfer.

| Function                                                      | Access                     | Description                                                                                                                                      |
| ------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `serviceRedemptions(uint256 maxShares)`                       | `REDEMPTION_SERVICER_ROLE` | Batched scan in request-id order; services eligible requests up to `maxShares` (soft cap) and `MAX_SERVICE_SCAN_REQUESTS` (256) scanned per call |
| `serviceRedeemRequest(uint256 requestId, address controller)` | `REDEMPTION_SERVICER_ROLE` | Service one specific eligible request by id and controller                                                                                       |

`serviceRedemptions` skips requests still in cooldown but carries a **forward-progress guarantee**: the first eligible request in a batch is always serviced, so a single oversized request can never permanently stall the queue (`maxShares` is a soft batch cap, not a per-request maximum, per-request size is bounded by `policy.maxRequestShares`). Both forms revert if servicing is paused and emit `RedemptionsServiced(serviceId, requestsServiced, sharesMadeClaimable, assetsReserved)`.

**3, Claim.** In this async vault `withdraw` and `redeem` are **claim-only**, they release already-serviced (`CLAIMABLE`) assets rather than exiting live shares (which were burned at request time).

| Function                                                         | Access                          | Description                                                                                                           |
| ---------------------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `redeem(uint256 shares, address receiver, address controller)`   | controller or approved operator | Share-exact claim (floor rounding across serviced lots); returns assets                                               |
| `withdraw(uint256 assets, address receiver, address controller)` | controller or approved operator | Asset-denominated claim; partial lots round shares up and may revert, use `redeem` for share-exact partials           |
| `maxRedeem(controller)` / `maxWithdraw(controller)`              | view                            | Controller's total claimable shares / assets; return `0` when claims are paused or the controller is fully restricted |

`withdraw(maxWithdraw(controller))` (every lot consumed in full) always succeeds; some intermediate asset amounts below the max may revert on per-lot share rounding. Claims revert while claims are paused, on zero inputs, or on restriction violations (fully restricted controller/receiver/owner; soft-restricted parties may only settle to themselves). Both emit `Withdraw(sender, receiver, controller, assets, shares)`.

**Cancel.** `cancelRedeemRequest(uint256 requestId, address controller)` cancels a still-`PENDING` request: it sets status `CANCELLED`, releases reserved assets back to `accountedAssets`, and re-mints the burned shares to the owner. Blocked when cancellations are paused or when the controller/owner is fully restricted (shares would return to a frozen holder). Emits `RedeemRequestCancelled`.

**Operators.** `setOperator(address operator, bool approved)` lets a controller authorize an operator to act on its redemption requests and claims; `isOperator(controller, operator)` reads the flag. Emits `OperatorSet`.

**Request reads:** `pendingRedeemRequest(requestId, controller)`, `claimableRedeemRequest(requestId, controller)`, `getRedeemRequest(controller)` (aggregate `RedeemRequestState`), `redemptionState(requestId, controller)` (full `RedemptionState`).

### Redemption controls & policies

Cooldown, servicing bounds, and pause flags are global controls; per-account overrides ride on named policies.

| Function                                                                                                                            | Access               | Description                                                                                                 |
| ----------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------- |
| `setRedemptionControls(cooldown, maxServiceBatchShares, requestPaused, claimPaused, servicePaused, cancelPaused, serviceAuthority)` | `DEFAULT_ADMIN_ROLE` | Set global cooldown, batch cap, all pause flags, and service-authority metadata                             |
| `setCooldownDuration(uint256 duration)`                                                                                             | `DEFAULT_ADMIN_ROLE` | Update the default cooldown (≤ `MAX_COOLDOWN_DURATION`) while preserving other controls                     |
| `setRedemptionPolicy(bytes32 policyId, RedemptionPolicy policy)`                                                                    | `DEFAULT_ADMIN_ROLE` | Create/update a named policy (`cooldown`, `maxRequestShares`, `enabled`); default policy cannot be disabled |
| `setAccountRedemptionPolicy(address account, bytes32 policyId)`                                                                     | `DEFAULT_ADMIN_ROLE` | Assign an enabled policy to an account, or clear to the default                                             |
| `pauseRedeemRequests` / `pauseRedeemClaims` / `pauseRedemptionServicing` / `pauseRedeemCancellations`                               | `PAUSER_ROLE`        | Pause each redemption sub-flow independently                                                                |
| `redemptionControls()` / `redemptionPolicy(policyId)` / `accountRedemptionPolicy(account)`                                          | view                 | Read current controls, a policy, or an account's assigned policy                                            |

A request snapshots its `eligibleAt` from the policy in force at request time, so later cooldown changes affect only future requests. The cooldown is configurable up to `MAX_COOLDOWN_DURATION` (90 days); the current value is **7 days**. Emits `RedemptionControlsUpdated`, `RedemptionPolicyUpdated`, `AccountRedemptionPolicyUpdated`, `CooldownDurationUpdated`, and `RedemptionPauseUpdated`.

### Restrictions

Two tiers, **soft** (blocks entry and non-self exit) and **full** (freezes the account), gated by `RESTRICTION_ROLE`. Enforcement is centralized in the `_update` share-transfer hook plus per-action checks.

| Function                                                                       | Access             | Description                                                                        |
| ------------------------------------------------------------------------------ | ------------------ | ---------------------------------------------------------------------------------- |
| `addToBlacklist(address target, bool isFullRestriction)`                       | `RESTRICTION_ROLE` | Apply soft or full restriction; emits `RestrictionUpdated`                         |
| `removeFromBlacklist(address target, bool)`                                    | `RESTRICTION_ROLE` | Clear all restriction flags                                                        |
| `redistributeLockedAmount(address from, address to)`                           | `RESTRICTION_ROLE` | Move all liquid shares off a fully restricted account to an unrestricted recipient |
| `redistributeRedeemRequest(uint256 requestId, address controller, address to)` | `RESTRICTION_ROLE` | Relocate an in-flight pending/claimable request away from a frozen account         |

`getAccountState(account)` reports `shareBalance`, `assetsValue`, both liability buckets, `restrictionStatus` (`CLEAR` / `RESTRICTED` / `FULL_RESTRICTION`), `blockedActions` (`NONE` / `NON_EXIT` / `ALL`), and `restrictionBasis`.

### Admin & introspection

| Function                                                  | Access               | Description                                                                                                                                     |
| --------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `setDepositPause(bool paused)`                            | `DEFAULT_ADMIN_ROLE` | Set the deposit pause flag; emits `DepositPauseUpdated`                                                                                         |
| `pauseDeposits()`                                         | `PAUSER_ROLE`        | Emergency deposit pause                                                                                                                         |
| `rescueTokens(address token, uint256 amount, address to)` | `DEFAULT_ADMIN_ROLE` | Rescue unrelated tokens or surplus USDx; reverts on the share token (`InvalidToken`) and cannot dip into `requiredAssets`                       |
| `share()`                                                 | view                 | Returns `address(this)` (ERC-7575 vault-is-share)                                                                                               |
| `supportsInterface(bytes4)`                               | view                 | Advertises ERC-7540 (`IERC7540Operator`, `IERC7540Redeem`), ERC-7575, `IRedemptionManager`, `IRewardManager`, `IVaultShareToken`, `IStakedUSDx` |

### Types

```solidity
enum RedemptionStatus { NONE, PENDING, CLAIMABLE, CLAIMED, CANCELLED }

struct RedemptionState {
    uint256 requestId;
    address controller;
    address owner;
    uint256 shares;
    uint256 assetsReserved;
    uint256 requestedAt;
    uint256 eligibleAt;
    bytes32 policyId;
    RedemptionStatus status;
}

struct RedeemRequestState {          // per-controller aggregate
    uint256 pendingShares;
    uint256 pendingAssets;
    uint256 claimableShares;
    uint256 claimableAssets;
}

struct RedemptionPolicy { uint256 cooldown; uint256 maxRequestShares; bool enabled; }

struct RewardState {
    uint256 accountedAssets;
    uint256 unvestedRewards;
    uint256 rewardRate;
    uint256 periodFinish;
    uint256 lastUpdated;
}
```

### Events

| Event                            | Source               | Parameters                                                               |
| -------------------------------- | -------------------- | ------------------------------------------------------------------------ |
| `Deposit` / `Withdraw`           | ERC-4626             | Standard deposit and claim events                                        |
| `RedeemRequest`                  | ERC-7540             | `controller`, `owner`, `requestId`, `sender`, `shares`                   |
| `OperatorSet`                    | ERC-7540             | `controller`, `operator`, `approved`                                     |
| `RewardsFunded`                  | `IRewardManager`     | `funder`, `assets`, `sourceRef`, `rewardRate`, `periodFinish`            |
| `RewardConfigUpdated`            | `IRewardManager`     | `authority`, `duration`, `maxRate`, `paused`                             |
| `RedemptionsServiced`            | `IRedemptionManager` | `serviceId`, `requestsServiced`, `sharesMadeClaimable`, `assetsReserved` |
| `RedeemRequestCancelled`         | `IRedemptionManager` | `requestId`, `controller`, `owner`, `sharesReturned`, `assetsReleased`   |
| `RedemptionControlsUpdated`      | `IRedemptionManager` | cooldown, batch cap, four pause flags, service authority                 |
| `RedemptionPolicyUpdated`        | `IRedemptionManager` | `policyId`, `cooldown`, `maxRequestShares`, `enabled`                    |
| `AccountRedemptionPolicyUpdated` | `IRedemptionManager` | `account`, `policyId`                                                    |
| `RedemptionPauseUpdated`         | `IRedemptionManager` | `pause` (which flag), `paused`                                           |
| `CooldownDurationUpdated`        | `StakedUSDx`         | `previousDuration`, `newDuration`                                        |
| `RestrictionUpdated`             | `StakedUSDx`         | `target`, `status`, `basis`, `authorityRef`                              |
| `LockedAmountRedistributed`      | `StakedUSDx`         | `from`, `to`, `shares`                                                   |
| `RedeemRequestRedistributed`     | `StakedUSDx`         | `requestId`, `from`, `to`, `shares`, `assets`, `status`                  |
| `DepositPauseUpdated`            | `StakedUSDx`         | `paused`                                                                 |
| `Rescue`                         | `StakedUSDx`         | `token`, `to`, `amount`                                                  |

### Errors

| Error                     | Trigger                                                                        |
| ------------------------- | ------------------------------------------------------------------------------ |
| `InvalidZeroAddress()`    | Zero address parameter                                                         |
| `InvalidAmount()`         | Zero amount, zero reward duration, over-policy request, or over-surplus rescue |
| `OperationNotAllowed()`   | Paused flow, restriction violation, disabled preview, or unauthorized caller   |
| `MinSharesViolation()`    | Redeem request exceeds the owner's share balance                               |
| `InsufficientClaimable()` | Claim exceeds the controller's serviced/claimable balance                      |
| `InvalidToken()`          | `rescueTokens` targeting the share token                                       |
| `InvalidCooldown()`       | Cooldown greater than `MAX_COOLDOWN_DURATION`                                  |

***

## Interfaces

`StakedUSDx` composes several V2 interfaces, each a frozen review surface:

| Interface                             | Role                                                                           |
| ------------------------------------- | ------------------------------------------------------------------------------ |
| `IStakedUSDx`                         | Top-level vault ABI (composes the below)                                       |
| `IRewardManager`                      | `fundRewards`, `rewardConfig`, `rewardState`, reward events                    |
| `IRedemptionManager`                  | Async redemption lifecycle, controls, policies, and events                     |
| `IAsyncRedeemVault`                   | Marker `is IERC7540Redeem`, the async-redeem surface                           |
| `IVaultShareToken`                    | Accounting reads (`accountedAssets`, `requiredAssets`, `redeemLiabilities`, …) |
| `IERC7540Redeem` / `IERC7540Operator` | ERC-7540 async-redeem and operator standard surfaces                           |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.axis.to/reference/staking-contracts.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
