For the complete documentation index, see llms.txt. This page is also available as Markdown.

Staking (SODA asset)

Error handling conventions: This module returns Result<T, SodaxError<NarrowCode>> from every async public method. Discriminate on error.code (a closed reason-only union) and error.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 }walletProvider is forbidden; returns an unsigned transaction payload.

  • { raw: false, walletProvider }walletProvider is required and chain-narrowed from srcChainKey.

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:

  1. isAllowanceValid → check allowance

  2. approve → approve if needed

  3. createStakeIntent → spoke tx only

  4. stake → full relay + hub confirmation (or relay manually using relayData)

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: maxPenalty applies in full.

  • Between minUnstakingPeriod and unstakingPeriod: 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

Method
Codes

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

Every staking error carries an error.context payload. Fields vary by code:

Field
Set on
Notes

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:

v1 code
v2 code
Notes

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_MAINNET

  • Non-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 minUnstakingPeriod and unstakingPeriod.

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