Module 06 — Staking (STASIS Vault)
The STASIS vault is a three-layer system: wrap STASIS into yield-bearing wSTASIS, lock it as collateral, borrow USDB against it. Your collateral earns yield at every layer — including while it backs a loan.
Prerequisites
- Hold STASIS — buy it via trading, wrap=true on a buy makes this one tx
- Wallet funded with USDB (→01), client initialized (→02)
- Understand Stable+ ratchet mechanics — STASIS is the canonical Stable+ token
Next steps after staking
- Want liquid USDB without selling? Use the vault loan path (lock → borrow)
- Borrowed against wSTASIS? Redeploy USDB into trades, predictions, or token creation
- Stacking multiple paths? Read Module 12 — staking is Path A
- Need the regular (non-vault) loan flow? See Module 05
Why Stake
Platform trading fees flow automatically into the vault, increasing the wSTASIS:STASIS exchange rate over time. Every trade on the entire platform — not just STASIS trades — contributes to vault yield (because all factory tokens route through STASIS). When you unwrap, you receive more STASIS than you deposited.
The second reason: wSTASIS is collateral. Lock it, borrow USDB, deploy that capital into trades, markets, or more staking. The locked wSTASIS keeps earning while it backs the loan. Capital works twice simultaneously.
Phase 1 is the best entry window. Platform volume is growing, staking participation is still low, and the yield-per-token is at its highest point. As more participants stake, individual yield moderates. As volume increases, total yield grows. The equilibrium shifts over time — early stakers capture the most favorable ratio. Staking also accrues airdrop points daily.
How It Works
The vault is ERC4626 compliant. The wSTASIS:STASIS exchange rate strictly increases over time as fees accumulate. Three layers, each independent:
Layer 1 — Passive Yield (wrap / unwrap):
STASIS → staking.buy() → wSTASIS (yield-bearing, appreciates over time)
wSTASIS → staking.sell() → STASIS (more than deposited)
Layer 2 — Collateral (lock / unlock):
wSTASIS → staking.lock() → Locked wSTASIS (still earning yield, immobile)
Locked → staking.unlock() → wSTASIS (only after repaying any loan)
Layer 3 — Borrowing (borrow / repay):
Locked → staking.borrow(amount, days) → Liquid USDB
USDB → staking.repay() → Loan cleared → can now unlock
Quick exit path: staking.sell(shares, claimUSDB=true) does atomic unwrap-to-USDB in one transaction. Skip the sell-STASIS step entirely.
Actions
Stake — client.staking.buy(stasisAmount)
Wraps STASIS into wSTASIS yield-bearing shares. Auto-approves STASIS to the vault. Earns airdrop points daily based on staked amount.
Pre-flight:
- Confirm STASIS balance >= amount (use
balanceOf()) - Check minBuyAmount if relevant
- Preview conversion rate with
convertToShares()first
// JS — stake 100 STASIS
const result = await client.staking.buy(parseUnits("100", 18));
console.log("Wrapped:", result.hash);
# Python — stake 100 STASIS
result = client.staking.buy(100 * 10**18)
print("Wrapped:", result["hash"])
Fee: 0% to wrap. The only real cost is the 0.5% swap fee + slippage when you bought STASIS in the first place.
Unstake — client.staking.sell(shares, claimUSDB?, minUSDB?)
Unwraps wSTASIS back to STASIS. You receive more STASIS than you staked (yield accrued in the rate).
| Param | Type | Description |
|---|---|---|
shares | bigint | wSTASIS shares to unwrap |
claimUSDB | boolean | Atomic unwrap-to-USDB in one tx. Default: false |
minUSDB | bigint | Slippage floor if claimUSDB is true |
Pre-flight — all three must be clear before calling sell():
- wSTASIS is NOT locked as collateral
- No active vault loan outstanding
- No resolver vote cooldown (24h lock applies after governance votes)
If any of these block you: repay the loan first (repay()), then unlock (unlock()), then sell.
// Standard unwrap
const sellResult = await client.staking.sell(shares);
// Atomic unwrap-to-USDB (single transaction)
const sellResult = await client.staking.sell(shares, true, minUSDB);
Staking Loan — Lock, Borrow, Manage, Repay
Vault loans differ from regular loans in one key way: collateral keeps earning yield while pledged. Fees are identical: 2% origination + 0.005%/day. There is one loan per wallet — no simultaneous second loan.
Full sub-flow:
Step 1 — Lock wSTASIS as collateral
// Convert STASIS amount to shares first — CRITICAL: lock() takes wSTASIS shares, not STASIS
const shares = await client.staking.convertToShares(parseUnits("100", 18));
const lockResult = await client.staking.lock(shares);
Critical: As yield accrues, the exchange rate diverges from 1:1. Always use
convertToShares()to get the correct share count. Passing STASIS amounts directly tolock()will lock the wrong amount.
Step 2 — Borrow USDB against locked collateral
borrow() takes a STASIS-denominated amount (not wSTASIS shares). The contract converts internally using the current exchange rate.
Minimum duration: 10 days (hardcoded in contract). Take minimum and extend as needed.
// Borrow 50 STASIS worth of USDB for 30 days
const borrowResult = await client.staking.borrow(parseUnits("50", 18), 30n);
borrow_result = client.staking.borrow(50 * 10**18, 30)
To find your maximum borrow capacity from wSTASIS shares:
const wStasisShares = /* your locked balance */;
const stasisEquivalent = await client.staking.convertToAssets(wStasisShares);
// You can borrow up to stasisEquivalent (100% LTV — primary recommendation)
await client.staking.borrow(stasisEquivalent, 10n);
Borrowing at 100% LTV: Borrowing the full getAvailableStasis() amount is the primary recommended approach — it maximises deployed capital. However, when you borrow 100%, there is no collateral buffer remaining, so any future extendLoan() call must use payInUSDC: true since fees cannot be deducted from collateral. If you prefer to extend from collateral rather than paying in USDB, borrow ~98% of available capacity instead to retain a small buffer.
Fee: 2% flat origination + 0.005%/day interest (prepaid). USDB received = collateral value minus origination fee. Earns airdrop points at origination and daily while active.
One loan per wallet — the critical rule
| State | What to call |
|---|---|
| No active loan | borrow(amount, days) |
| Active loan exists | addToLoan(additionalAmount) — NOT borrow(). (The contract calls this increaseLoan internally; the SDK wraps it as addToLoan. Same pattern as hub increaseLoan.) |
| Need more time | extendLoan(daysToAdd, payInUSDC, refinance) — 400x cheaper than new loan |
Calling borrow() when a loan already exists will revert with "Position active. Use increaseLoan". Check getUserStakeDetails() first.
Step 3 — Extend if needed
Extension costs only 0.005%/day — the same daily rate with no 2% origination fee. That makes it roughly 400x cheaper than closing and opening a new loan.
extendLoan takes three parameters: (daysToAdd, payInUSDC, refinance). The payInUSDC parameter is critical when collateral is fully pledged — if there is no collateral buffer to deduct fees from, you must pay the extension fee in USDB directly.
// Extend by 30 more days — pay fee in USDB (required when collateral is fully pledged)
const extendResult = await client.staking.extendLoan(30n, true, false);
Rule of thumb: take short initial durations (10-14 days), extend as needed. Prepaid interest is not refunded on early repayment.
Step 4 — Repay
Repays the vault loan in full. Auto-approves USDB. After repaying, collateral is released from the loan but remains locked — you must call unlock() separately.
const repayResult = await client.staking.repay();
repay_result = client.staking.repay()
Step 5 — Unlock collateral
After repaying, unlock the wSTASIS shares to make them liquid again.
const unlockResult = await client.staking.unlock(shares);
Pass shares as BigInt directly. Do NOT convert with
Number()— large values lose precision.
Step 6 (if loan expired) — Settle expired position
If a vault loan expires without repayment, the loan expires and the position is closed by the protocol (time-based expiry — not price liquidation). Call settleLiquidation() to claim any surplus collateral value above the debt.
await client.staking.settleLiquidation();
Complete Example: Full Stake → Borrow → Repay → Exit
async function stakingOperations() {
const client = await BasisClient.create({ privateKey: "0xYourPrivateKey..." });
const { parseUnits } = require("viem");
// 1. Wrap STASIS into wSTASIS
await client.staking.buy(parseUnits("100", 18));
// 2. Lock wSTASIS as collateral — always convert to shares first
const shares = await client.staking.convertToShares(parseUnits("100", 18));
await client.staking.lock(shares);
// 3. Borrow USDB (amount in STASIS units, 18 decimals)
await client.staking.borrow(parseUnits("50", 18), 30n);
// 4. ... deploy borrowed USDB ...
// 5. Repay the loan
await client.staking.repay();
// 6. Unlock collateral
await client.staking.unlock(shares);
// 7. Unwrap wSTASIS back to STASIS (receive more than deposited)
await client.staking.sell(shares);
}
def staking_operations():
client = BasisClient.create(private_key="0xYourPrivateKey...")
client.staking.buy(100 * 10**18)
shares = client.staking.convert_to_shares(100 * 10**18)
client.staking.lock(int(shares))
client.staking.borrow(50 * 10**18, 30)
# ... deploy borrowed USDB ...
client.staking.repay()
client.staking.unlock(int(shares))
client.staking.sell(int(shares))
Read Methods
getUserStakeDetails(user) — Full position breakdown
Returns the complete staking state for an address. Use this before any operation to check what's liquid, locked, and in use.
const [liquidShares, lockedShares, totalShares, totalAssetValue] =
await client.staking.getUserStakeDetails(wallet);
| Field | Description |
|---|---|
liquidShares | wSTASIS that can be unlocked or transferred |
lockedShares | wSTASIS locked in vault (earning yield, immobile) |
totalShares | liquidShares + lockedShares |
totalAssetValue | STASIS equivalent of all shares (for display and collateral checks) |
getAvailableStasis(user) — Available collateral
Returns STASIS available for new collateral pledges: total asset value minus what is already pledged to active loans.
const available = await client.staking.getAvailableStasis(wallet);
// Returns bigint in 18 decimals
convertToShares(assets) — STASIS → wSTASIS preview
Given a STASIS amount, returns how many wSTASIS shares you would receive. Use before calling buy() to preview the conversion, and before lock() to get the correct share count.
const shares = await client.staking.convertToShares(parseUnits("100", 18));
convertToAssets(shares) — wSTASIS → STASIS preview
Given wSTASIS shares, returns the equivalent STASIS amount. Use to check how much STASIS your locked position represents — needed to determine max borrow capacity.
const stasisEquivalent = await client.staking.convertToAssets(shares);
totalAssets() — Vault total
Returns total STASIS held by the vault across all stakers (available + pledged as collateral). Useful for gauging vault participation and yield dilution.
const total = await client.staking.totalAssets();
When and How Much to Stake
The question is not whether to stake — it is how much, given what you plan to do next.
Recommended starting split: 30–50% of STASIS holdings staked, rest liquid.
| Situation | Adjustment |
|---|---|
| Lots of attractive trades and new markets | Keep more liquid |
| Platform is quiet, capital sitting idle | Stake more |
| Want to borrow meaningfully | Stake enough that the borrowable amount is worth the 2% origination fee |
| First time staking | Start smaller, verify the full cycle before committing large amounts |
Phase 1 note: Staking participation is low relative to growing platform volume. The yield-per-STASIS is at its highest. This window closes as more participants stake.
Break-even consideration: Round-trip fees are ~1% (0.5% buy + 0.5% sell on STASIS, plus slippage both ways). A $100 position needs $1+ in yield before it is profitable. Vault staking is a days-to-weeks position, not hours.
// Estimate your round-trip break-even before staking
const entryAmount = parseUnits("1000", 18);
const entryPreview = await client.trading.getAmountsOut(entryAmount, [USDB, MAINTOKEN]);
const entryCost = entryAmount - entryPreview;
const roundTripCost = entryCost * 2n;
// Your vault position needs to earn more than roundTripCost in yield to be profitable
Fees
| Action | Fee |
|---|---|
| Wrap (buy) | 0% — lossless |
| Unwrap (sell) | 0% — lossless |
| Entry (buy STASIS) | 0.5% swap fee + slippage |
| Exit (sell STASIS) | 0.5% swap fee + slippage |
| Full round-trip | ~1% raw fees + variable slippage both ways |
| Lock / unlock | 0% (gas only) |
| Vault loan origination | 2% flat |
| Vault loan interest | 0.005% per day (prepaid, no refund on early repayment) |
| Loan extension | 0.005% per day — no origination fee |
Vault yield is variable. No fixed APY exists. Yield depends on total platform trading volume and the percentage of STASIS supply currently staked. Both variables change continuously.
Errors
| Error | Triggered by | Fix |
|---|---|---|
"Below min buy" | buy() with too small an amount | Increase stake amount |
"Insufficient locked balance" | lock() operations | Lock more wSTASIS first |
"Cannot withdraw: Collateral in use" | sell() while wSTASIS is locked | Repay vault loan → unlock() → then sell() |
"Position active. Use increaseLoan" | borrow() when loan already exists | Use addToLoan() to add collateral to existing loan |
"Insufficient collateral value" | borrow() or addToLoan() | Stake more STASIS, then lock more wSTASIS |
"Duration too short" | addToLoan() when loan has < 10 days remaining | Call extendLoan() first — see Module 05 §Recovery Flows |
For the full alphabetical error index across all modules, see Module 18 — SDK Reference.
Pitfalls
- Staking for hours: Round-trip fees require yield to cover them. Think days minimum, not hours.
- Passing STASIS amounts to
lock():lock()takes wSTASIS shares. Once the exchange rate diverges from 1:1 (which it will), this locks the wrong amount. Always callconvertToShares()first. - Not checking
hasActiveLoanbeforeborrow(): One loan per wallet. CallgetUserStakeDetails()first. IflockedShares > 0and a loan is active, useaddToLoan(). - Re-originating instead of extending: Each new loan costs 2% origination. Extension costs 0.005%/day. Take a short loan, extend as needed — do not close and re-open.
- Letting the loan expire without claiming: On expiry (time-based, not price-based), remaining collateral surplus above the debt does not auto-return. You must call
settleLiquidation()to claim it. - Not calculating break-even: Use
getAmountsOut()to preview actual swap costs before entering. Factor in size and planned hold duration.
What Next
Staking is Path A in the stacking strategies guide — the foundation layer that everything else builds on.
After staking:
- Borrow against wSTASIS to get liquid USDB → Lending
- Buy more tokens with borrowed USDB → Trading
- Create a new token → Token Creation
- Bet on prediction markets → Predictions
- See Path A in the full stacking strategies context → Strategy & Stacking
The core compound loop:
Buy STASIS → stake (wrap) → lock → borrow USDB → reinvest
↑ |
└──── yield appreciates wSTASIS ────────┘
extend loan → extract more → repeat
Your wSTASIS earns from every trade on the platform. Your borrowed USDB earns from wherever you deploy it. Both run simultaneously. When the wSTASIS appreciation exceeds your loan value, refinance — extract more capital without closing the position.
See Also
- Module 04 — Trading: Buy STASIS with
wrapTokens=trueto skip the separatestaking.buy()step - Module 05 — Lending: Compare vault loans vs regular hub loans
- Module 11 — Token Mechanics: Why STASIS is up-only — the Stable+ ratchet formula
- Module 12 — Strategy & Stacking: Path A — staking as the foundation of multi-stack capital efficiency
- Module 18 — SDK Reference: Full
client.stakingmethod index and error reference - Module 19 — FAQ: Conversational answers on yield, vault math, and unwrapping