Staking (SODA asset)
Error handling conventions: This module returns
Result<T, SodaxError<NarrowCode>>from every async public method. Discriminate onerror.code(a closed reason-only union) anderror.feature === 'staking'. See Error Handling below.
The 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
import { Sodax, ChainKeys } from '@sodax/sdk';
const sodax = new Sodax();
const initResult = await sodax.config.initialize();
if (!initResult.ok) {
console.error('SDK initialization failed:', initResult.error.message);
}Calling Convention
All mutating methods accept a single SpokeExecActionParams object with the following shape:
{
params: <StakingParams>, // action-specific params (includes srcChainKey)
walletProvider: ..., // required when raw: false; chain-narrowed by srcChainKey
raw?: boolean, // false (default) → sign & broadcast; true → return raw tx payload
skipSimulation?: boolean, // optional: skip preflight simulation
timeout?: number, // optional: relay timeout in milliseconds (default: 120 000)
}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 a stake, 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.
Signature:
Example:
approve
Submits a token-spending approval on the source chain for a stake, unstake, or instantUnstake action.
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.
Must be called before executing the corresponding action whenever isAllowanceValid returns false.
Signature:
Example (signed):
Example (raw tx):
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 calls createStakeIntent 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:
Example:
createStakeIntent
Submits the stake transaction on the spoke chain without relaying to the hub.
Returns IntentTxResult 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:
Example:
Full manual flow:
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 calling claim 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:
Example:
createUnstakeIntent
Submits the unstake transaction on the spoke chain without relaying to the hub.
Signature:
Example:
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. Use getInstantUnstakeRatio to preview the SODA output before calling this method.
Prerequisite: call isAllowanceValid + approve before instant unstaking on EVM chains.
Signature:
Example:
createInstantUnstakeIntent
Submits the instant-unstake transaction on the spoke chain without relaying to the hub.
Signature:
Example:
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. Use getUnstakingInfoWithPenalty first to preview the exact claimable amount given the current penalty.
Signature:
Example:
createClaimIntent
Submits the claim transaction on the spoke chain without relaying to the hub.
Signature:
Example:
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:
Example:
createCancelUnstakeIntent
Submits the cancel-unstake transaction on the spoke chain without relaying to the hub.
Signature:
Example:
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 to getStakingInfo.
Signature:
Example:
getStakingInfo
Fetches comprehensive staking information for a hub wallet address directly.
Signature:
Example:
getUnstakingInfo
Fetches all pending unstake requests and the total SODA amount currently unstaking for a user.
Signature:
Example:
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.
Signature:
Example:
getStakingConfig
Reads the current staking configuration from the StakedSoda contract.
Signature:
Example:
getInstantUnstakeRatio
Estimates the SODA amount receivable from instantly unstaking a given quantity of xSoda shares.
Calls StakingRouter.estimateInstantUnstake on-chain. Use this before calling instantUnstake to set an appropriate minAmount slippage guard.
Signature:
Example:
getConvertedAssets
Converts a quantity of xSoda shares to its current underlying SODA value using the vault's convertToAssets view function. The result increases over time as staking rewards accrue.
Signature:
Example:
getStakeRatio
Estimates the xSoda shares and preview-deposit amount for a given SODA input. Calls StakingRouter.estimateXSodaAmount on-chain. Use this to display expected output before a stake transaction.
Signature:
Example:
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 on StakingService 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
stake
VALIDATION_FAILED, INTENT_CREATION_FAILED, TX_VERIFICATION_FAILED, TX_SUBMIT_FAILED, RELAY_TIMEOUT, RELAY_FAILED, EXECUTION_FAILED, UNKNOWN
unstake
VALIDATION_FAILED, INTENT_CREATION_FAILED, TX_SUBMIT_FAILED, RELAY_TIMEOUT, RELAY_FAILED, EXECUTION_FAILED, UNKNOWN
instantUnstake
VALIDATION_FAILED, INTENT_CREATION_FAILED, TX_SUBMIT_FAILED, RELAY_TIMEOUT, RELAY_FAILED, EXECUTION_FAILED, UNKNOWN
claim
VALIDATION_FAILED, INTENT_CREATION_FAILED, TX_SUBMIT_FAILED, RELAY_TIMEOUT, RELAY_FAILED, EXECUTION_FAILED, UNKNOWN
cancelUnstake
VALIDATION_FAILED, INTENT_CREATION_FAILED, TX_SUBMIT_FAILED, RELAY_TIMEOUT, RELAY_FAILED, EXECUTION_FAILED, UNKNOWN
create<Op>Intent
VALIDATION_FAILED, INTENT_CREATION_FAILED, UNKNOWN
approve
VALIDATION_FAILED, APPROVE_FAILED, UNKNOWN
isAllowanceValid
VALIDATION_FAILED, ALLOWANCE_CHECK_FAILED, UNKNOWN
getStakingInfo* / getUnstakingInfo* / getStakingConfig / getInstantUnstakeRatio / getConvertedAssets / getStakeRatio
VALIDATION_FAILED, LOOKUP_FAILED, UNKNOWN
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
contextEvery staking error carries an error.context payload. Fields vary by code:
srcChainKey
all orchestrator + intent codes
low-cardinality — suitable as a logger / Sentry tag
action
all orchestrator + intent codes
one of 'stake' | 'unstake' | 'instantUnstake' | 'claim' | 'cancelUnstake'
phase
most codes
'validate' | 'intentCreation' | 'verify' | 'submit' | 'relay' | 'approve' | 'allowanceCheck' | 'infoFetch'
relayCode
RELAY_TIMEOUT / TX_SUBMIT_FAILED / RELAY_FAILED
mirrors the relay-layer RELAY_ERROR_CODES contract; carries 'RELAY_POLLING_FAILED' so polling outage is distinguishable from generic failure
field / reason
VALIDATION_FAILED
which precondition tripped
method
LOOKUP_FAILED
the read-only method name ('getStakingInfo', 'getUnstakingInfo', 'getStakingConfig', …) — partitions the 8 readers without per-method codes
Type guards
Per-method type guards are runtime-checked and compile-checked in lockstep with the union types. Use them in catch blocks to short-circuit when a foreign code escapes:
Available guards: 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 typed VALIDATION_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 v1 StakingError<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:
STAKE_FAILED
EXECUTION_FAILED
Generic stake catch-all. Underlying cause on error.cause.
UNSTAKE_FAILED
EXECUTION_FAILED
Generic unstake catch-all.
INSTANT_UNSTAKE_FAILED
EXECUTION_FAILED
Generic instant-unstake catch-all.
CLAIM_FAILED
EXECUTION_FAILED
Generic claim catch-all.
CANCEL_UNSTAKE_FAILED
EXECUTION_FAILED
Generic cancel-unstake catch-all.
INFO_FETCH_FAILED
LOOKUP_FAILED
Shared by all 8 read-only methods; partition via context.method.
ALLOWANCE_CHECK_FAILED
ALLOWANCE_CHECK_FAILED
Allowance check failed at the spoke layer.
APPROVAL_FAILED
APPROVE_FAILED
Approve operation failed.
(none)
VALIDATION_FAILED
New: typed precondition failures (replaces prose Error throws from invariant).
(none)
INTENT_CREATION_FAILED
New: per-op intent-creation phase tag (e.g. spoke deposit revert).
(none)
TX_VERIFICATION_FAILED
New: spoke tx verification phase tag (only set by stake).
(none)
TX_SUBMIT_FAILED / RELAY_TIMEOUT / RELAY_FAILED
New: typed relay-phase codes mapped from the shared RELAY_ERROR_CODES contract.
(none)
UNKNOWN
Reserved fallback for never-classified errors.
Usage Flow
Full stake flow (EVM spoke chain)
Full unstake + claim flow
Supported Chains
All staking operations accept any SpokeChainKey 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_MAINNETNon-EVM chains:
ChainKeys.ICON_MAINNET,ChainKeys.SUI_MAINNET,ChainKeys.STELLAR_MAINNET, etc.
Approval support: EVM spoke chains, hub chain, and Stellar only. All other non-EVM chains do not require on-chain approval.
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.
Use 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 by getInstantUnstakeRatio.
Always call getInstantUnstakeRatio before instantUnstake to set an appropriate minAmount slippage guard.
Last updated