π±Staking
The StakingService class reachable through sodax.staking instance provides functionality for staking SODA tokens, unstaking, claiming rewards, and retrieving staking information. It supports operations across different blockchain chains with automatic hub chain integration.
Methods
isAllowanceValid
Checks if the current allowance is sufficient for the staking operations.
Parameters:
params: Staking parameters including action type and amountspokeProvider: The spoke chain provider instance
Returns: Promise<Result<boolean, StakingError<'ALLOWANCE_CHECK_FAILED'>>>
Note: For Stellar-based operations, the allowance system works differently:
Source Chain (Stellar): The standard
isAllowanceValidmethod works as expected for EVM chains, but for Stellar as the source chain, this method checks and establishes trustlines automatically.Staking Flow: Staking operations always flow from spoke chains (including Stellar) to the hub chain (Sonic), so Stellar is only used as a source chain for staking operations.
Example:
const result = await sodax.staking.isAllowanceValid({
params: {
amount: 1000000000000000000n, // 1 SODA
minReceive: 950000000000000000n, // 0.95 xSODA minimum
account: '0x1234567890abcdef...',
action: 'stake'
},
spokeProvider: baseSpokeProvider
});
if (result.ok && result.value) {
console.log('Allowance is sufficient');
} else {
console.log('Need to approve tokens first');
}approve
Approves token spending for the staking operations. This method is only supported for EVM-based spoke chains.
Parameters:
params: Staking parametersspokeProvider: The spoke provider instanceraw: Whether to return raw transaction data (optional, default: false)
Returns: Promise<Result<TxReturnType<S, R>, StakingError<'APPROVAL_FAILED'>>>
Note: For Stellar-based operations, the approval system works differently:
Source Chain (Stellar): The standard
approvemethod works as expected for EVM chains, but for Stellar as the source chain, this method establishes trustlines automatically.Staking Flow: Staking operations always flow from spoke chains (including Stellar) to the hub chain (Sonic), so Stellar is only used as a source chain for staking operations.
Example:
const result = await sodax.staking.approve({
params: {
amount: 1000000000000000000n,
minReceive: 950000000000000000n,
account: '0x1234567890abcdef...',
action: 'stake'
},
spokeProvider: baseSpokeProvider,
raw: false
});
if (result.ok) {
console.log('Approval transaction hash:', result.value);
} else {
console.error('Approval failed:', result.error);
}Stellar Trustline Requirements
For Stellar-based staking operations, you need to handle trustlines when Stellar is used as the source chain. See Stellar Trustline Requirements for detailed information and code examples.
Note: Staking operations always flow from spoke chains (including Stellar) to the hub chain (Sonic), so Stellar is only used as a source chain for staking operations.
stake
Executes a complete stake transaction, including creating the stake intent and relaying it to the hub chain.
Parameters:
params: Stake parameters including amount, minimum receive amount, and accountspokeProvider: The spoke chain provider instancetimeout: Optional timeout in milliseconds (default: 60 seconds)
Returns: Promise<Result<[SpokeTxHash, HubTxHash], StakingError<'STAKE_FAILED'> | RelayError>>
Example:
const result = await sodax.staking.stake(
{
amount: 1000000000000000000n, // 1 SODA
minReceive: 950000000000000000n, // 0.95 xSODA minimum
account: '0x1234567890abcdef...',
action: 'stake'
},
baseSpokeProvider,
30000
);
if (result.ok) {
const [spokeTxHash, hubTxHash] = result.value;
console.log('Stake successful:', { spokeTxHash, hubTxHash });
} else {
console.error('Stake failed:', result.error);
}createStakeIntent
Creates a stake intent on the spoke chain without relaying it to the hub. This is useful for advanced users who want to handle the relaying process manually.
Parameters:
params: Stake parameters including amount, minimum receive amount, and accountspokeProvider: The spoke chain provider instanceraw: Whether to return raw transaction data (optional, default: false)
Returns: Promise<Result<TxReturnType<S, R>, StakingError<'STAKE_FAILED'>> & { data?: { address: string; payload: Hex } }>
Example:
const result = await sodax.staking.createStakeIntent({
params: {
amount: 1000000000000000000n,
minReceive: 950000000000000000n,
account: '0x1234567890abcdef...',
action: 'stake'
},
spokeProvider: baseSpokeProvider,
raw: false
});
if (result.ok) {
console.log('Stake intent created:', result.value);
console.log('Extra data:', result.data);
} else {
console.error('Stake intent creation failed:', result.error);
}Note: This method only executes the transaction on the spoke chain and creates the stake intent. To successfully stake tokens, you need to:
Check if the allowance is sufficient using
isAllowanceValidApprove the appropriate contract to spend the tokens using
approveCreate the stake intent using this method
Relay the transaction to the hub and await completion using the
stakemethod
unstake
Executes a complete unstake transaction for unstaking xSoda shares.
Parameters:
params: Unstake parameters including amount and accountspokeProvider: The spoke chain provider instancetimeout: Optional timeout in milliseconds (default: 60 seconds)
Returns: Promise<Result<[SpokeTxHash, HubTxHash], StakingError<'UNSTAKE_FAILED'> | RelayError>>
Example:
const result = await sodax.staking.unstake(
{
amount: 1000000000000000000n, // 1 xSODA
account: '0x1234567890abcdef...',
action: 'unstake'
},
baseSpokeProvider,
30000
);
if (result.ok) {
const [spokeTxHash, hubTxHash] = result.value;
console.log('Unstake successful:', { spokeTxHash, hubTxHash });
} else {
console.error('Unstake failed:', result.error);
}createUnstakeIntent
Creates an unstake intent on the spoke chain without relaying it to the hub.
Parameters:
params: Unstake parameters including amount and accountspokeProvider: The spoke chain provider instanceraw: Whether to return raw transaction data (optional, default: false)
Returns: Promise<Result<TxReturnType<S, R>, StakingError<'UNSTAKE_FAILED'>> & { data?: { address: string; payload: Hex } }>
Example:
const result = await sodax.staking.createUnstakeIntent({
params: {
amount: 1000000000000000000n,
account: '0x1234567890abcdef...',
action: 'unstake'
},
spokeProvider: baseSpokeProvider,
raw: false
});
if (result.ok) {
console.log('Unstake intent created:', result.value);
console.log('Extra data:', result.data);
} else {
console.error('Unstake intent creation failed:', result.error);
}Note: This method only executes the transaction on the spoke chain and creates the unstake intent. To successfully unstake tokens, you need to:
Check if the allowance is sufficient using
isAllowanceValidApprove the appropriate contract to spend the tokens using
approveCreate the unstake intent using this method
Relay the transaction to the hub and await completion using the
unstakemethod
instantUnstake
Executes a complete instant unstake transaction for instantly unstaking xSoda shares.
Parameters:
params: Instant unstake parameters including amount, minimum amount, and accountspokeProvider: The spoke chain provider instancetimeout: Optional timeout in milliseconds (default: 60 seconds)
Returns: Promise<Result<[SpokeTxHash, HubTxHash], StakingError<'INSTANT_UNSTAKE_FAILED'> | RelayError>>
Example:
const result = await sodax.staking.instantUnstake(
{
amount: 1000000000000000000n, // 1 xSODA
minAmount: 950000000000000000n, // 0.95 SODA minimum
account: '0x1234567890abcdef...',
action: 'instantUnstake'
},
baseSpokeProvider,
30000
);
if (result.ok) {
const [spokeTxHash, hubTxHash] = result.value;
console.log('Instant unstake successful:', { spokeTxHash, hubTxHash });
} else {
console.error('Instant unstake failed:', result.error);
}createInstantUnstakeIntent
Creates an instant unstake intent on the spoke chain without relaying it to the hub.
Parameters:
params: Instant unstake parameters including amount, minimum amount, and accountspokeProvider: The spoke chain provider instanceraw: Whether to return raw transaction data (optional, default: false)
Returns: Promise<Result<TxReturnType<S, R>, StakingError<'INSTANT_UNSTAKE_FAILED'>> & { data?: { address: string; payload: Hex } }>
Example:
const result = await sodax.staking.createInstantUnstakeIntent({
params: {
amount: 1000000000000000000n,
minAmount: 950000000000000000n,
account: '0x1234567890abcdef...',
action: 'instantUnstake'
},
spokeProvider: baseSpokeProvider,
raw: false
});
if (result.ok) {
console.log('Instant unstake intent created:', result.value);
console.log('Extra data:', result.data);
} else {
console.error('Instant unstake intent creation failed:', result.error);
}Note: This method only executes the transaction on the spoke chain and creates the instant unstake intent. To successfully instant unstake tokens, you need to:
Create the instant unstake intent using this method
Relay the transaction to the hub and await completion using the
instantUnstakemethod
claim
Executes a complete claim transaction for claiming unstaked tokens after the unstaking period.
Parameters:
params: Claim parameters including requestId and claimable amountspokeProvider: The spoke chain provider instancetimeout: Optional timeout in milliseconds (default: 60 seconds)
Returns: Promise<Result<[SpokeTxHash, HubTxHash], StakingError<'CLAIM_FAILED'> | RelayError>>
Example:
const result = await sodax.staking.claim(
{
requestId: 1n,
amount: 950000000000000000n, // claimable amount after penalty
action: 'claim'
},
baseSpokeProvider,
30000
);
if (result.ok) {
const [spokeTxHash, hubTxHash] = result.value;
console.log('Claim successful:', { spokeTxHash, hubTxHash });
} else {
console.error('Claim failed:', result.error);
}createClaimIntent
Creates a claim intent on the spoke chain without relaying it to the hub.
Parameters:
params: Claim parameters including requestId and claimable amountspokeProvider: The spoke chain provider instanceraw: Whether to return raw transaction data (optional, default: false)
Returns: Promise<Result<TxReturnType<S, R>, StakingError<'CLAIM_FAILED'>> & { data?: { address: string; payload: Hex } }>
Example:
const result = await sodax.staking.createClaimIntent({
params: {
requestId: 1n,
amount: 950000000000000000n,
action: 'claim'
},
spokeProvider: baseSpokeProvider,
raw: false
});
if (result.ok) {
console.log('Claim intent created:', result.value);
console.log('Extra data:', result.data);
} else {
console.error('Claim intent creation failed:', result.error);
}Note: This method only executes the transaction on the spoke chain and creates the claim intent. To successfully claim tokens, you need to:
Create the claim intent using this method
Relay the transaction to the hub and await completion using the
claimmethod
cancelUnstake
Executes a complete cancel unstake transaction for cancelling an unstake request.
Parameters:
params: Cancel unstake parameters including requestIdspokeProvider: The spoke chain provider instancetimeout: Optional timeout in milliseconds (default: 60 seconds)
Returns: Promise<Result<[SpokeTxHash, HubTxHash], StakingError<'CANCEL_UNSTAKE_FAILED'> | RelayError>>
Example:
const result = await sodax.staking.cancelUnstake(
{
requestId: 1n,
action: 'cancelUnstake'
},
baseSpokeProvider,
30000
);
if (result.ok) {
const [spokeTxHash, hubTxHash] = result.value;
console.log('Cancel unstake successful:', { spokeTxHash, hubTxHash });
} else {
console.error('Cancel unstake failed:', result.error);
}createCancelUnstakeIntent
Creates a cancel unstake intent on the spoke chain without relaying it to the hub.
Parameters:
params: Cancel unstake parameters including requestIdspokeProvider: The spoke chain provider instanceraw: Whether to return raw transaction data (optional, default: false)
Returns: Promise<Result<TxReturnType<S, R>, StakingError<'CANCEL_UNSTAKE_FAILED'>> & { data?: { address: string; payload: Hex } }>
Example:
const result = await sodax.staking.createCancelUnstakeIntent({
params: {
requestId: 1n,
action: 'cancelUnstake'
},
spokeProvider: baseSpokeProvider,
raw: false
});
if (result.ok) {
console.log('Cancel unstake intent created:', result.value);
console.log('Extra data:', result.data);
} else {
console.error('Cancel unstake intent creation failed:', result.error);
}Note: This method only executes the transaction on the spoke chain and creates the cancel unstake intent. To successfully cancel an unstake request, you need to:
Create the cancel unstake intent using this method
Relay the transaction to the hub and await completion using the
cancelUnstakemethod
getStakingInfoFromSpoke
Retrieves comprehensive staking information for a user using spoke provider.
Parameters:
spokeProvider: The spoke chain provider instance
Returns: Promise<Result<StakingInfo, StakingError<'INFO_FETCH_FAILED'>>>
Example:
const result = await sodax.staking.getStakingInfoFromSpoke(baseSpokeProvider);
if (result.ok) {
const stakingInfo = result.value;
console.log('Total staked:', stakingInfo.totalStaked.toString());
console.log('User xSODA balance:', stakingInfo.userXSodaBalance.toString());
console.log('User xSODA value:', stakingInfo.userXSodaValue.toString());
} else {
console.error('Failed to get staking info:', result.error);
}getStakingInfo
Retrieves comprehensive staking information for a user by address.
Parameters:
userAddress: The user's address
Returns: Promise<Result<StakingInfo, StakingError<'INFO_FETCH_FAILED'>>>
Example:
const result = await sodax.staking.getStakingInfo('0x1234567890abcdef...');
if (result.ok) {
const stakingInfo = result.value;
console.log('Total staked:', stakingInfo.totalStaked.toString());
console.log('User xSODA balance:', stakingInfo.userXSodaBalance.toString());
console.log('User xSODA value:', stakingInfo.userXSodaValue.toString());
} else {
console.error('Failed to get staking info:', result.error);
}getUnstakingInfo
Retrieves unstaking information for a user.
Parameters:
param: The user's address or spoke provider
Returns: Promise<Result<UnstakingInfo, StakingError<'INFO_FETCH_FAILED'>>>
Example:
const result = await sodax.staking.getUnstakingInfo(baseSpokeProvider);
if (result.ok) {
const unstakingInfo = result.value;
console.log('Total unstaking:', unstakingInfo.totalUnstaking.toString());
console.log('Unstake requests:', unstakingInfo.userUnstakeSodaRequests.length);
} else {
console.error('Failed to get unstaking info:', result.error);
}getUnstakingInfoWithPenalty
Retrieves unstaking information with penalty calculations for a user.
Parameters:
param: The user's address or spoke provider
Returns: Promise<Result<UnstakingInfo & { requestsWithPenalty: UnstakeRequestWithPenalty[] }, StakingError<'INFO_FETCH_FAILED'>>>
Example:
const result = await sodax.staking.getUnstakingInfoWithPenalty(baseSpokeProvider);
if (result.ok) {
const unstakingInfo = result.value;
console.log('Total unstaking:', unstakingInfo.totalUnstaking.toString());
unstakingInfo.requestsWithPenalty.forEach((request, index) => {
console.log(`Request ${index}:`);
console.log(' Amount:', request.request.amount.toString());
console.log(' Penalty:', request.penalty.toString());
console.log(' Penalty %:', request.penaltyPercentage);
console.log(' Claimable:', request.claimableAmount.toString());
});
} else {
console.error('Failed to get unstaking info with penalty:', result.error);
}getStakingConfig
Retrieves staking configuration from the stakedSoda contract.
Returns: Promise<Result<StakingConfig, StakingError<'INFO_FETCH_FAILED'>>>
Example:
const result = await sodax.staking.getStakingConfig();
if (result.ok) {
const config = result.value;
console.log('Unstaking period:', config.unstakingPeriod.toString(), 'seconds');
console.log('Min unstaking period:', config.minUnstakingPeriod.toString(), 'seconds');
console.log('Max penalty:', config.maxPenalty.toString(), '%');
} else {
console.error('Failed to get staking config:', result.error);
}getInstantUnstakeRatio
Retrieves the instant unstake ratio for a given amount.
Parameters:
amount: The amount of xSoda to estimate instant unstake for
Returns: Promise<Result<bigint, StakingError<'INFO_FETCH_FAILED'>>>
Example:
const result = await sodax.staking.getInstantUnstakeRatio(1000000000000000000n);
if (result.ok) {
console.log('Instant unstake ratio:', result.value.toString());
} else {
console.error('Failed to get instant unstake ratio:', result.error);
}getConvertedAssets
Retrieves converted assets amount for xSODA shares.
Parameters:
amount: The amount of xSoda shares to convert
Returns: Promise<Result<bigint, StakingError<'INFO_FETCH_FAILED'>>>
Example:
const result = await sodax.staking.getConvertedAssets(1000000000000000000n);
if (result.ok) {
console.log('Converted assets:', result.value.toString());
} else {
console.error('Failed to get converted assets:', result.error);
}getStakeRatio
Retrieves stake ratio for a given amount (xSoda amount and preview deposit).
Parameters:
amount: The amount of SODA to estimate stake for
Returns: Promise<Result<[bigint, bigint], StakingError<'INFO_FETCH_FAILED'>>>
Example:
const result = await sodax.staking.getStakeRatio(1000000000000000000n);
if (result.ok) {
const [xSodaAmount, previewDepositAmount] = result.value;
console.log('xSODA amount:', xSodaAmount.toString());
console.log('Preview deposit amount:', previewDepositAmount.toString());
} else {
console.error('Failed to get stake ratio:', result.error);
}Types
StakeParams
export type StakeParams = {
amount: bigint; // amount to stake
minReceive: bigint; // minimum amount to receive
account: Address; // account to stake from
action: 'stake';
};UnstakeParams
export type UnstakeParams = {
amount: bigint;
account: Address;
action: 'unstake';
};ClaimParams
export type ClaimParams = {
requestId: bigint;
amount: bigint; // claimable amount after penalty calculation
action: 'claim';
};CancelUnstakeParams
export type CancelUnstakeParams = {
requestId: bigint;
action: 'cancelUnstake';
};InstantUnstakeParams
export type InstantUnstakeParams = {
amount: bigint;
minAmount: bigint;
account: Address;
action: 'instantUnstake';
};StakingInfo
export type StakingInfo = {
totalStaked: bigint; // Total SODA staked (totalAssets from xSODA vault)
totalUnderlying: bigint; // Total underlying SODA assets in the vault
userXSodaBalance: bigint; // User's xSODA shares (raw balance)
userXSodaValue: bigint; // User's xSODA value in SODA (converted)
userUnderlying: bigint; // User's underlying SODA amount
};UnstakingInfo
export type UnstakingInfo = {
userUnstakeSodaRequests: readonly UserUnstakeInfo[];
totalUnstaking: bigint;
};UnstakeRequestWithPenalty
export type UnstakeRequestWithPenalty = UserUnstakeInfo & {
penalty: bigint;
penaltyPercentage: number;
claimableAmount: bigint;
};StakingConfig
export type StakingConfig = {
unstakingPeriod: bigint; // in seconds
minUnstakingPeriod: bigint; // in seconds
maxPenalty: bigint; // percentage (1-100)
};StakingAction
export type StakingAction = 'stake' | 'unstake' | 'claim' | 'cancelUnstake' | 'instantUnstake';Error Handling
All methods return a Result type that indicates success or failure:
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };Common error codes include:
STAKE_FAILED: Stake transaction failedUNSTAKE_FAILED: Unstake transaction failedINSTANT_UNSTAKE_FAILED: Instant unstake transaction failedCLAIM_FAILED: Claim transaction failedCANCEL_UNSTAKE_FAILED: Cancel unstake transaction failedINFO_FETCH_FAILED: Failed to fetch staking informationALLOWANCE_CHECK_FAILED: Insufficient allowance for the transactionAPPROVAL_FAILED: Token approval transaction failed
Usage Flow
The typical staking operation follows this sequence:
Check allowance using
isAllowanceValid()Approve tokens using
approve()if neededFor Stellar source chains: Check and establish trustlines (see Stellar Trustline Requirements)
Execute staking operation using
stake(),unstake(),instantUnstake(),claim(), orcancelUnstake()Monitor progress using the returned transaction hashes
Supported Chains
The service supports various blockchain networks as source chains for staking operations:
EVM chains (Ethereum, Polygon, Base, etc.)
Sonic (hub chain - can be both source and destination)
Non-EVM chains (Icon, Sui, Stellar, etc.)
Note: All staking operations flow from spoke chains (including Stellar) to the hub chain (Sonic). Stellar and other non-EVM chains can only be used as source chains for staking operations.
Penalty System
The staking system includes a penalty mechanism for early unstaking:
Minimum Unstaking Period: No penalty if unstaking after this period
Maximum Penalty: Applied if unstaking before the minimum period
Reduction Period: Penalty gradually reduces between minimum and full unstaking periods
The penalty is calculated based on the time elapsed since the unstake request was initiated.
Instant Unstaking
Instant unstaking allows users to immediately receive SODA tokens in exchange for xSODA shares, but with a reduced amount due to the instant liquidity mechanism. The actual amount received depends on the current liquidity pool and is calculated using the getInstantUnstakeRatio method.
Last updated