Design Decisions
Why the protocol is built the way it is: trade-offs, alternatives considered, and the reasoning behind each major design choice.
Why Offchain Order Signing (EIP-712)?
Users sign primary market orders offchain; operators submit them onchain to USDxMarket for settlement. This provides:
Operator-submitted execution -- the mint/redeem operator (
MINT_OPERATOR_ROLE/REDEEM_OPERATOR_ROLE) pays for the settlement submission; users may still need an onchain token approvalOperator control -- the market controls execution timing and batching, and re-runs every pre-trade check before state changes
Replay protection -- per-account nonce bitmaps and order expiry (
deadline) prevent reuse
The bitmap-based nonce system (_nonceBitmaps) packs 256 nonces into a single storage slot per accountId, reducing gas cost compared to sequential nonce tracking. Nonces are namespaced per account and consumed only on successful settlement; a cancelled order is invalidated through an order tombstone rather than by consuming its nonce.
Why Route-Based Custody?
During minting, a route specifies the allowed asset pair and settlement mode, and the signed order binds a concrete channelId naming the approved custody path (custodian, adapter, or cross-chain rail). This lets collateral settle across approved custody channels instead of a single hard-coded destination. The market validates the route and channel against policy, but route support alone does not prove diversification or remove custody risk.
Why Configurable Linear Reward Vesting?
Realized trading results are delivered to the rewards vault as USDx rewards through fundRewards() (gated by REWARD_MANAGER_ROLE), which folds the top-up into a linear vesting schedule rather than making it available instantly. Where does that reward come from? It is the realized output of strategies whose structural edge is cross-venue and cross-asset fragmentation, a durable, market-neutral yield source that does not rely on any single mechanism. Funding is one component (and can be the larger share in a given period); see the Transparency Dashboard for current attribution, and How Axis Earns Yield for the model.
Vesting rather than an instant credit prevents:
Front-running -- depositing just before a reward is delivered, then exiting immediately to capture it
Price manipulation -- sudden jumps in the share exchange rate
Rewards vest linearly over a configured window; the live value is a configured parameter. A top-up recomputes rewardRate and extends periodFinish, folding into the active window rather than resetting the clock, and is bounded by an optional maxRewardRate cap. Only vested rewards count toward accountedAssets, so the exchange rate rises smoothly as rewards vest rather than jumping on each funding.
Why Asynchronous (ERC-7540) Redemption?
StakedUSDx uses an asynchronous redemption model (modeled on ERC-7540) rather than an instant withdrawal. The lifecycle:
A holder calls
requestRedeem(), which burns the shares immediately and moves the reserved USDx out ofaccountedAssetsintopendingRedeemAssets. The request snapshots its terms, includingeligibleAt = requestedAt + policy.cooldown.After the cooldown elapses. An account holding
REDEMPTION_SERVICER_ROLEcallsserviceRedemptions(), which moves eligible requests from pending to claimable (pendingRedeemAssets→claimableRedeemAssets).The controller then claims the underlying USDx.
Because a request's assets are carved out of accountedAssets and tracked as in-vault liabilities (pendingRedeemAssets + claimableRedeemAssets = redeemLiabilities), the redeeming USDx earns no further rewards and cannot dilute active holders, the same USDx is never counted as both active assets and a liability. There is no separate escrow (silo) contract; the pending and claimable buckets live inside the vault.
A still-pending request can be cancelled with cancelRedeemRequest(), which releases the reserved assets and re-mints the burned shares to the owner. The cooldown is configurable up to a MAX_COOLDOWN_DURATION of 90 days; the live value is a configured parameter, and later cooldown changes affect only future requests because each request snapshots its own eligibleAt.
Why Two-Level Restrictions?
Restrictions are a coarse graduated control rather than a single on/off switch, but the granularity stops at two levels. There is no per-action bitmap. StakedUSDx tracks two booleans per account, _restricted (soft) and _fullRestriction (full); there is no RestrictionState struct and no per-subject scope, validUntil, or evidenceRef fields. The derived status is one of three values, CLEAR, RESTRICTED, or FULL_RESTRICTION:
Soft (
RESTRICTED) blocks non-exit actions, itsblockedActionscode isNON_EXIT. The account cannot deposit or receive transfers, but can still redeem/exit its own position.Full (
FULL_RESTRICTION) blocks everything, itsblockedActionscode isALL, including claims and exits.
blockedActions is a single bytes32 code taking only BLOCKED_NONE, BLOCKED_NON_EXIT, or BLOCKED_ALL. It is not a bitmap of individual actions such as TRANSFER_IN, TRANSFER_OUT, DEPOSIT, REQUEST_REDEEM, CLAIM, or CANCEL_REDEEM_REQUEST, none of those symbols exist in the code, so a subject cannot be blocked from one action while allowed another beyond the soft/full split. The only basis code emitted is AUTHORITY (BASIS_AUTHORITY); there is no SANCTIONS/LEGAL_ORDER/JURISDICTION/TERMS/ELIGIBILITY enum onchain. Raw KYC/KYB, sanctions hits, and legal notes stay offchain.
A restriction is applied through addToBlacklist(target, isFullRestriction) and cleared with removeFromBlacklist, both under RESTRICTION_ROLE, which spans both the token (USDx) and the vault (StakedUSDx). The role is distinct from the mint, burn, settlement, upgrade, and redemption-servicing roles. At the token level, USDx implements a coarse Circle-compatible binary blacklist (also under RESTRICTION_ROLE). There is no IRestrictionManager interface in the V2 source, the restriction logic lives inline in the contracts.
Why Donation-Attack Resistance?
StakedUSDx computes share conversions against accountedAssets (active principal plus already-vested rewards), using ERC-4626 floor rounding and virtual shares/assets offsets, rather than against the vault's raw USDx balance. This mitigates the "donation attack", an attacker manipulating the exchange rate by transferring assets directly to the vault.
Because a direct USDx donation raises the raw assetBalance but not accountedAssets, it does not move the exchange rate: reserved redemption liabilities and still-unvested rewards are deliberately excluded from the figure that prices shares. The virtual-offset convention further blunts first-depositor rounding manipulation.
Why a Bounded Reward-Manager Role?
Reward delivery is separated from the vault's administrative control plane. REWARD_MANAGER_ROLE gates only fundRewards, folding a top-up into the vesting schedule. It does not set reward configuration (setRewardConfig is onlyRole(DEFAULT_ADMIN_ROLE)) and it does not pause reward funding (pauseRewardFunding is onlyRole(PAUSER_ROLE)). Even the funding it can do is bounded: each top-up is checked against the configured maxRewardRate cap and reverts while _rewardPaused is set. The role is distinct from DEFAULT_ADMIN_ROLE, from the redemption servicer, and from the pause/restriction roles.
Following the Axis _OPERATOR / _MANAGER split, this keeps frequent reward top-ups on a narrowly-scoped role rather than requiring the admin key for routine yield delivery. The separation limits the reward manager to the contract's defined capabilities; it does not remove operator, admin, or key-management risk.
Last updated

