Integration & Usage guide
USM (User Savings Module) offers two plug‑and‑play savings primitives that integrators and farmers can compose with: s0xAsset and ORBT StakingRewards. This page explains how to integrate.
High level architecture
User/Integrator Wallet
├─ deposits 0xAsset ──► s0xAsset (ERC4626 vault)
│ • exchangeRateRay grows per second (when enabled)
│ • optional rewardToken emissions via rewardIndexRay
│ • withdrawals can be time‑gated (minUnstakeDelay) and capped (exitBufferBps)
│
└─ stakes ORBT ──► ORBT StakingRewards
• linear emissions over rewardsDuration
• claim via getReward(); combined exit()Quick start
A) Integrate s0xAsset (ERC‑4626 vault)
Approve & deposit
User approves
underlyingAssetto the s0xAsset contractCall
deposit(assets, receiver)ormint(shares, receiver)
(Optional) Read APY / conversion previews
previewDeposit,previewMint,previewWithdraw,previewRedeempreviewExchangeRateRay()to read up‑to‑date exchange rate without state changes
Withdraw or redeem
Call
withdraw(assets, receiver, owner)orredeem(shares, receiver, owner)Enforced:
minUnstakeDelay(if set) - withdrawals/redeems require the recipient to be past the lockexitBufferBps- caps max assets withdrawable per call vs vault liquidity
Rewards (if configured)
Read
claimable(account)Claim via
claim(to, amount)(use0to claim all)
Note:
drip()can be called permissionlessly to settle interest & rewards. Integrators generally do not need to call it in UI flows; usepreview*reads.
B) Integrate ORBT StakingRewards
Approve & stake
User approves
ORBTto the staking contract.Call
stake(amount)(orstake(amount, referral)if you track referrals)
Claim rewards
Call
getReward(); or useexit()to withdraw entire stake and claim in one call
Withdraw
Call
withdraw(amount)to unstake ORBT (no lock built into this contract)
Reward epochs are funded by
rewardsDistributionvianotifyRewardAmount(reward)and stream linearly forrewardsDuration.
Behavior & math
s0xAsset: interest & shares
Exchange rate accumulator
exchangeRateRay(assets per share, scaled 1e27) grows per second byrateRayviadrip().If
rewardsOnlyMode = true(default), the exchange rate is frozen (no interest minting). Governance can later enable interest by turning this off and (optionally) settingrateRay.
Interest minting
On growth, the vault mints additional underlying to itself:
interestAssets = totalShares * (newRate - oldRate) / RAY.Assumption:
underlyingAssetmust exposemint(address,uint256)and grant minter rights to s0xAsset.
Rewards index
rewardIndexRayaccumulates rewards per share (RAYscale). On user balance changes, per‑account debt is advanced to keep rewards accurate.
Withdrawal guardrails
minUnstakeDelay- users receiving/minting shares have theirearliestUnstakeTimepushed forward; withdrawals/redeems requireblock.timestamp >= earliestUnstakeTime[owner].exitBufferBps- capswithdraw(assets, ...)to a fraction oftotalAssets()(e.g., 10_000 = 100%).
APR estimator (interest):
APR estimator (rewards):
ℹ️ (Price/TVL come from off‑chain sources.)
ORBT StakingRewards - emissions
notifyRewardAmount(reward)setsrewardRatefor the next epoch:If previous epoch ended:
rewardRate = reward / rewardsDurationIf mid‑epoch: carry over leftover and smooth:
rewardRate = (reward + remaining * oldRate) / rewardsDuration
View helpers:
rewardPerToken()andearned(account)(standard Synthetix math)getRewardForDuration() = rewardRate * rewardsDuration
APR estimator:
Frontend
Read user balances & claimables
Approve & deposit / stake (ethers.js)
Withdraw / redeem (ethers.js)
Claim rewards (ethers.js)
Preview UX (no state changes)
Governance & admin surfaces
s0xAsset
AccessControl
ADMINpause()/unpause()_authorizeUpgrade()(UUPS)setGovernance(address)(one‑time)setMinUnstakeDelay(uint256)
Governance (external
IORBTGovernance) viaexecuteGovernanceAction(type,payload)ACT_SET_RATE→ payload:uint256 newRateRay(must be ≥ 1e27)ACT_SET_REWARD_CONFIG→ payload:(address rewardToken, address rewardVault, uint256 rewardRatePerSecond)ACT_SET_REWARDS_ONLY_MODE→ payload:bool enabled
The reward vault must pre‑fund and
approvethe s0xAsset totransferFromrewards when users claim.
ORBT StakingRewards
Owner
setRewardsDuration(uint256)(can also mid‑epoch: leftover is rescaled)setRewardsDistribution(address)recoverERC20(token, amount)(cannot recover the staking token)
RewardsDistribution
notifyRewardAmount(uint256 reward)(must ensure contract balance covers the stream)
Events to index / watch
s0xAsset
Deposit(address caller, address receiver, uint256 assets, uint256 shares)Withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)Drip(uint256 newExchangeRateRay, uint256 mintedInterest)RewardAccrued(uint256 newRewardIndexRay, uint256 rewards, uint256 dt)RewardClaimed(address user, address to, uint256 amount)RewardConfigSet(...),RewardsOnlyModeSet(bool)MinUnstakeDelaySet(uint256 old, uint256 new)
ORBT StakingRewards
Staked(address user, uint256 amount)/Withdrawn(address user, uint256 amount)RewardAdded(uint256 reward)/RewardPaid(address user, uint256 reward)RewardsDurationUpdated(uint256 newDuration)/RewardsDistributionUpdated(address)Referral(uint16 referral, address user, uint256 amount)
Edge cases & gotchas
First depositor: s0xAsset mints
MIN_LIQUIDITYshares toaddress(1)to avoid rounding issues. The first real depositor gets 1:1 assets → shares at init.Rewards‑only default:
rewardsOnlyMode = trueafter initialization; the interest accumulator is frozen until governance disables this mode. If governance re‑enables rewards‑only later,rateRayis snapped to1e27internally.Transfer extends lock: Any inbound share transfer/mint pushes
earliestUnstakeTime[to]forward byminUnstakeDelay.Exit buffer:
withdraw(assets, ...)reverts ifassets > totalAssets() * exitBufferBps / 10_000.Pausable: User actions and
drip()respect pause state.Minting requirement: Underlying must implement
mint(address,uint256)and grant minter rights to s0xAsset.
Minimal ABI (for light integrators)
Security notes (for integrators)
Always check
preview*functions to estimate user outcomes before sending state‑changing txs.For s0xAsset:
Treat the vault as custodial over
underlyingAsset; withdrawals can be rate‑limited byexitBufferBpsand time‑gated byminUnstakeDelay.Ensure the reward vault is funded and has approved the s0xAsset contract, otherwise
claimwill revert.Upgrades are gated by
ADMIN; monitor implementation changes.
For ORBT StakingRewards:
notifyRewardAmountrequires the contract balance to cover the stream; the call reverts if under‑funded.No built‑in slashing/lock - strategies layering locks must be external.
FAQs
Q: Do I need to call drip() in my strategy?
A: Not required. Reads use preview logic. Anyone may call drip(); it’s opportunistic.
Q: Can I compound rewards into more shares?
A: Yes, claim rewards and swap→underlying, then deposit again. This compounding can be automated off‑chain.
Q: How is the first deposit priced?
A: 1:1 assets:shares (minus a tiny MIN_LIQUIDITY minted to an unreachable address to stabilize rounding).
Q: Can governance switch to rewards‑only later? A: Yes. When enabled, interest stops (rate snaps to 1e27 effectively), but reward emissions can continue.
Last updated