Error handling conventions: This module returnsTheResult<T, SodaxError<NarrowCode>>from every async public method. Discriminate onerror.code(a closed reason-only union) anderror.feature === 'staking'. See Error Handling below.
StakingService class, reachable through sodax.staking, provides functionality for staking SODA tokens,
unstaking, claiming rewards, and retrieving staking information. It supports operations across all spoke chains
with automatic hub chain integration.
Setup
Calling Convention
All mutating methods accept a singleSpokeExecActionParams object with the following shape:
raw: true / raw: false rules (enforced at compile time):
{ raw: true }—walletProvideris forbidden; returns an unsigned transaction payload.{ raw: false, walletProvider }—walletProvideris required and chain-narrowed fromsrcChainKey.
Methods
isAllowanceValid
Checks whether the current token allowance is sufficient for astake, unstake, or instantUnstake action.
- EVM spoke chains: checks the asset-manager allowance.
- Hub chain (Sonic): checks the user’s hub wallet allowance.
- Stellar: delegates to the Stellar spoke allowance check (trustlines).
- Other non-EVM chains: no on-chain allowance required; always resolves
true.
approve
Submits a token-spending approval on the source chain for astake, unstake, or instantUnstake action.
Some tokens take two transactions. A few ERC-20s of the 2017 TetherToken lineage — Ethereum USDT
is the only one in the SODAX token list today — reject an allowance change from one non-zero value to
another, so approve sends approve(0) first and waits for it to be mined before the real approval.
The user signs twice; the returned value is still a single transaction hash, the last one’s.
Detection simulates the approval rather than consulting a token list, so a token listed later behaves
the same way.
Supported chains: EVM spoke chains, hub chain (Sonic), and Stellar. All other chains return an error.
The spender address is resolved automatically:
- Hub chain: the user’s hub wallet (derived from spoke address).
- EVM spoke chain: the chain’s asset-manager contract.
isAllowanceValid returns false.
Signature:
Stellar Trustline Requirements
For Stellar-based staking operations,isAllowanceValid and approve handle trustlines automatically
when Stellar is the source chain. See
Stellar Trustline Requirements
for details.
Staking operations always flow from spoke chains (including Stellar) to the hub chain (Sonic), so Stellar
is only used as a source chain.
stake
Stakes SODA tokens from a spoke chain, relays the intent to the hub, and waits for hub confirmation. Internally callscreateStakeIntent to submit on the spoke, then relays the cross-chain packet and waits
for the hub transaction to land. For hub-chain callers (srcChainKey: ChainKeys.SONIC_MAINNET) the spoke
and hub hashes are identical.
Prerequisite: call isAllowanceValid + approve before staking on EVM chains.
Signature:
createStakeIntent
Submits the stake transaction on the spoke chain without relaying to the hub. ReturnsIntentTxResult containing the spoke tx result and the relayData (hub wallet address + encoded
payload) needed for a subsequent manual relay step. Use stake for the full end-to-end flow.
Signature:
isAllowanceValid→ check allowanceapprove→ approve if neededcreateStakeIntent→ spoke tx onlystake→ full relay + hub confirmation (or relay manually usingrelayData)
unstake
Initiates an unstake request for xSoda shares, relays the intent to the hub, and waits for confirmation. Unstaking begins a waiting period. The user receives SODA only after callingclaim once the period
elapses. Early claims incur a linear penalty (see getStakingConfig). For immediate redemption without a
waiting period, use instantUnstake instead.
Prerequisite: call isAllowanceValid + approve before unstaking on EVM chains.
Signature:
createUnstakeIntent
Submits the unstake transaction on the spoke chain without relaying to the hub. Signature:instantUnstake
Instantly redeems xSoda shares for SODA without a waiting period, relays the intent to the hub, and waits for confirmation. Routes through the StakingRouter, which provides immediate liquidity at the cost of slippage. UsegetInstantUnstakeRatio to preview the SODA output before calling this method.
Prerequisite: call isAllowanceValid + approve before instant unstaking on EVM chains.
Signature:
createInstantUnstakeIntent
Submits the instant-unstake transaction on the spoke chain without relaying to the hub. Signature:claim
Claims SODA from an unstake request, relays the intent to the hub, and waits for confirmation. Can be invoked before the unstaking period has fully elapsed, in which case the claim incurs a linear penalty against the claimable amount. UsegetUnstakingInfoWithPenalty first to preview the exact
claimable amount given the current penalty.
Signature:
createClaimIntent
Submits the claim transaction on the spoke chain without relaying to the hub. Signature:cancelUnstake
Cancels a pending unstake request and re-stakes the underlying SODA as xSoda shares, relays to the hub, and waits for confirmation. Aborts the waiting period and redeposits SODA back into the xSoda vault so the user continues earning staking rewards. Signature:createCancelUnstakeIntent
Submits the cancel-unstake transaction on the spoke chain without relaying to the hub. Signature:getStakingInfoFromSpoke
Fetches comprehensive staking information for a user identified by their spoke-chain address and chain key. Resolves the hub wallet address from the spoke address, then delegates togetStakingInfo.
Signature:
getStakingInfo
Fetches comprehensive staking information for a hub wallet address directly. Signature:getUnstakingInfo
Fetches all pending unstake requests and the total SODA amount currently unstaking for a user. Signature:getUnstakingInfoWithPenalty
Fetches all pending unstake requests enriched with current penalty calculations. Applies the linear penalty model to each request based on elapsed time:- Before
minUnstakingPeriod:maxPenaltyapplies in full. - Between
minUnstakingPeriodandunstakingPeriod: penalty decreases linearly to zero. - After
unstakingPeriod: no penalty.
getStakingConfig
Reads the current staking configuration from the StakedSoda contract. Signature:getInstantUnstakeRatio
Estimates the SODA amount receivable from instantly unstaking a given quantity of xSoda shares. CallsStakingRouter.estimateInstantUnstake on-chain. Use this before calling instantUnstake to set
an appropriate minAmount slippage guard.
Signature:
getConvertedAssets
Converts a quantity of xSoda shares to its current underlying SODA value using the vault’sconvertToAssets view function. The result increases over time as staking rewards accrue.
Signature:
getStakeRatio
Estimates the xSoda shares and preview-deposit amount for a given SODA input. CallsStakingRouter.estimateXSodaAmount on-chain. Use this to display expected output before a stake
transaction.
Signature:
Types
StakeParams
UnstakeParams
InstantUnstakeParams
ClaimParams
CancelUnstakeParams
StakingInfo
UnstakingInfo
UnstakeRequestWithPenalty
StakingConfig
StakingActionType
TxHashPair
Returned by the full relay methods (stake, unstake, instantUnstake, claim, cancelUnstake):
Error Handling
All async public methods onStakingService return Promise<Result<T, SodaxError<NarrowCode>>> where
NarrowCode is a narrow per-method union of StakingErrorCode. Discriminate on error.code, never
on error.message. The original lower-level failure (a viem revert, a fetch error, a relay timeout)
is preserved on error.cause; structured metadata (chain, action, phase, relayCode) is on
error.context.
Per-method error code unions
Note:
TX_VERIFICATION_FAILED only appears in StakeOrchestrationErrorCode because stake is the
only orchestrator that calls spoke.verifyTxHash. The 4 non-stake orchestrators
(unstake/instantUnstake/claim/cancelUnstake) share StakingOrchestrationErrorCode.
Structured context
Every staking error carries an error.context payload. Fields vary by code:
Type guards
Per-method type guards are runtime-checked and compile-checked in lockstep with the union types. Use them incatch blocks to short-circuit when a foreign code escapes:
isStakingError (broad — any staking error), isStakeOrchestrationError (the
stake orchestrator only, the one path with TX_VERIFICATION_FAILED), isStakingOrchestrationError
(the 4 non-stake orchestrators), isStakingCreateIntentError (shared by all 5 create*Intent
methods), isStakingApproveError, isStakingAllowanceCheckError, isStakingInfoFetchError.
Per-operation discrimination across the orchestrators is via error.context.action (one of
'stake' | 'unstake' | 'instantUnstake' | 'claim' | 'cancelUnstake').
Validation invariant
Precondition failures throw a typedVALIDATION_FAILED from inside the public method’s
try/catch, surfacing as a typed Result.error rather than a generic prose Error. This means
consumers can discriminate validation failures the same way as any other code.
Migration from the pre-v2 taxonomy
The published v1StakingError<Code> shape (8 codes: STAKE_FAILED, UNSTAKE_FAILED,
INSTANT_UNSTAKE_FAILED, CLAIM_FAILED, CANCEL_UNSTAKE_FAILED, INFO_FETCH_FAILED,
ALLOWANCE_CHECK_FAILED, APPROVAL_FAILED) is restored here with module-prefixed names and
cause-preservation:
Usage Flow
Full stake flow (EVM spoke chain)
Full unstake + claim flow
Supported Chains
All staking operations accept anySpokeChainKey as the source chain. The hub chain (Sonic,
ChainKeys.SONIC_MAINNET) may also be used as the source — in that case, spoke and hub tx hashes are
identical. Example chains:
- EVM spoke chains:
ChainKeys.BASE_MAINNET,ChainKeys.ETHEREUM_MAINNET,ChainKeys.ARBITRUM_MAINNET, etc. - Hub chain:
ChainKeys.SONIC_MAINNET - Non-EVM chains:
ChainKeys.ICON_MAINNET,ChainKeys.SUI_MAINNET,ChainKeys.STELLAR_MAINNET, etc.
Penalty System
The staking system includes a penalty mechanism for early unstaking:minUnstakingPeriod— maximum penalty applies if claiming before this time elapses.unstakingPeriod— no penalty once this full period elapses.- Linear reduction — penalty decreases linearly between
minUnstakingPeriodandunstakingPeriod.
getStakingConfig to read the current parameters, and getUnstakingInfoWithPenalty to see the
exact penalty for each pending request.
Instant Unstaking
Instant unstaking allows users to immediately receive SODA tokens in exchange for xSoda shares, bypassing the waiting period, but receiving a reduced amount due to the immediate-liquidity mechanism. The actual amount received depends on current pool conditions and is estimated bygetInstantUnstakeRatio.
Always call getInstantUnstakeRatio before instantUnstake to set an appropriate minAmount slippage
guard.