Staking Contracts Reference
Technical reference for StakedUSDx, the V2 rewards vault. For design rationale, see Architecture. For usage instructions, see Stake & Unstake. 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 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
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
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.
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.
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.
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.
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.
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).
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.
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.
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
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
Events
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
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:
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
Last updated

