@wireio/stake
v3.0.0
Published
LIQ Staking Module for Wire Network
Readme
@wireio/stake — Outpost & LIQ Staking SDK
TypeScript / JavaScript SDK for the Outpost and Liquidity Staking (LIQ) protocol on the Wire Network.
It gives dApps a single façade (Staker) over Solana and EVM implementations, while still exposing
chain‑specific power features (Outpost, pretokens, SquadsX, validator tools, etc.).
What this SDK does
- One
Stakerthat selects the right chain client bychainId. - Deposit / withdraw native → LIQ tokens (SOL→liqSOL, ETH→liqETH today; LiqMON/LiqPOL as those deployments arrive).
- Stake / unstake LIQ into yield pools (Solana Outpost; EVM staking manager).
- Pre-launch WIRE pretokens purchase helpers.
- Rich
getPortfolioview (native, liq, staked, $WIRE, yield, extras). - Gas / fee helpers (
getDepositFee,getDepositBuffer,getSystemAPY). - Solana extras: balance correction + registration, Outpost state, multisig (SquadsX) flows.
- EVM extras: withdrawal receipts, validator deposit/lock, tranche snapshots.
- Public instaswap execution clients for EVM and Solana outpost swaps, bridge deposits, faucet helpers, and artifact-backed chain reads used by Hub.
SquadsX (Solana multisig) at a glance
- Pass
extras.squadsXwhen constructing a SolanaStakerConfig:const cfg: StakerConfig = { provider: solAdapter, network: solNetwork, pubKey: new WirePubKey(KeyType.ED, solAdapter.publicKey!.toBytes()), extras: { squadsX: { multisigPDA: '<base58>', vaultIndex: 0 } }, }; - When present, every write call (
deposit,withdraw,stake,unstake,buy) is wrapped as a Squads vault transaction viasendSquadsIxs, producing a proposal that you approve/execute in Squads. - The Hub staking service persists the PDAs in
localStorage(hub_squadsx_multisigpda,hub_squadsx_vaultindex) and reinitializes theStakerwith Squads enabled; copy that pattern for multisig-first apps.
Mental model
- Facade first: Use
Stakereverywhere; it routes to the right chain client and keeps the API identical. - Base units only: All amounts are lamports/wei (no floats internally).
- Read-only vs write: If you omit
provider/pubKey, clients spin up in read-only mode—perfect for tranche snapshots, APY, and portfolio lookups against public RPCs. Add a signer (provider+pubKey) to unlock writes (deposit/withdraw/stake/unstake/buy/register). The Hub service uses read-only configs to prefill dashboards before wallets connect, then re-initializes with signer configs once a wallet is present. - State views:
getPortfolio()is the fastest way to know “native, liq, staked, $WIRE, yield” plus handy PDAs/addresses. - Snapshots:
getTrancheSnapshot(chainId)powers price ladders, liquidity/utilization charts, and native USD hints.
Supported networks (today)
- Solana full clients:
SolChainID.Mainnet,SolChainID.Devnetfor public devnet, andSOL_WIRE_DEVNET_CHAIN_ID(devnet-wire) for the Wire Solana devnet. - EVM contract service chains:
EvmChainID.Ethereum,EvmChainID.Hoodi, andEVM_WIRE_DEVNET_CHAIN_ID(31337). Hoodi currently aliases the mainnet ABI artifact/address bundle inEvmContractService; switch that alias tosrc/assets/evm/hoodiand a Hoodi address book when Hoodi-specific artifacts or deployments diverge. - Registered connect/read-only chains: Monad Testnet (
MONAD_TESTNET_CHAIN_ID, LiqMON) and Polygon Amoy (POLYGON_AMOY_CHAIN_ID). They are in the chain registry now but intentionally do not instantiate staking clients until the full contract suite/artifacts are deployed.
The unified interface is chain-id driven: new Staker(configs, selectedChainId) builds per-chain clients behind the scenes, and staker.setChain(chainId) swaps the active client when one exists while still recording read-only selections. Adding future chains requires extending SDK_CHAIN_REGISTRY first, then wiring a full IStakingClient only when staking contracts/programs are deployed.
Installation
npm install @wireio/stakePeer requirement: @wireio/core >=0.1.0 <0.6.0.
Quick start: pick a chain, then call through client
import { Staker, StakerConfig, SolChainID, EvmChainID } from '@wireio/stake';
import { PublicKey as WirePubKey, KeyType } from '@wireio/core';
import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
import { ethers } from 'ethers';
// ---- Solana example (wallet adapter) ----
const solAdapter: BaseSignerWalletAdapter = /* connected wallet */;
const solCfg: StakerConfig = {
provider: solAdapter,
network: {
chainId: SolChainID.Devnet,
name: 'Solana Devnet',
rpcUrls: ['https://...', 'wss://...'],
nativeCurrency: { symbol: 'SOL', decimals: 9 },
},
pubKey: new WirePubKey(KeyType.ED, solAdapter.publicKey!.toBytes()),
extras: { squadsX: { multisigPDA: '<base58>', vaultIndex: 0 } }, // optional
};
// ---- EVM example (browser provider) ----
const ethProvider = new ethers.providers.Web3Provider(window.ethereum);
const ethCfg: StakerConfig = {
provider: ethProvider,
network: {
chainId: EvmChainID.Ethereum,
name: 'Ethereum',
rpcUrls: ['https://...'],
nativeCurrency: { symbol: 'ETH', decimals: 18 },
},
};
const staker = new Staker([solCfg, ethCfg], SolChainID.Devnet); // default active chain
const client = staker.client; // typed staking client for the selected chainCommon calls
Amounts are always base units (lamports / wei).
await client?.deposit(1_000_000_000n); // stake 1 SOL -> liqSOL (or 1e9 wei -> liqETH)
await client?.withdraw(500_000_000n); // liq -> native withdraw request
await client?.stake(200_000_000n); // stake liq into yield pool (Sol Outpost / ETH manager)
await client?.unstake(100_000_000n); // reverse stake (where supported)
await client?.buy(50_000_000n); // purchase WIRE pretokens with liq
const portfolio = await client?.getPortfolio(); // unified balance snapshotSolana flow (what happens)
deposit(lamports): builds ix viaDepositClient, optionally wraps in SquadsX vault tx, sends and confirms.withdraw(lamports): burns liqSOL and mints a withdrawal NFT receipt (payout later via operators).stake/unstake: interacts with Outpost (synd/desynd) to move liqSOL into/out of yield pool.buy(lamports): purchase $WIRE pretokens using liqSOL (Token-2022).- Multisig: if
extras.squadsXis set, write calls become Squads proposals (sendSquadsIxs). - Portfolio extras include key PDAs (vault, reserve pool, user ATA) plus Outpost index/shares math.
EVM flow (what happens)
deposit(amountWei): viaConvertClient.performDeposit; returns tx hash.withdraw(amountWei): burns liqETH and enqueues withdrawal; receipts are NFTs.getPendingWithdraws()/claimWithdraw(tokenId): manage withdrawal queue.stake(amountWei): stakes liqETH;unstakePrelaunch(tokenId, recipient)handles prelaunch path.buy(amountWei): pretokens viaPretokenClient.purchasePretokensWithLiqETH.validatorDeposit(): validator bond/lock helper.- Portfolio includes native/liq/staked/$WIRE and yield (index/shares) when contracts expose them.
- Depositor-backed ETH writes keep a
callStaticpreflight and fall back to an explicitgasLimitwhenestimateGasbreaks on ETH Wire Devnet, so valid stake / instaswap / pretoken submits can still reach the wallet prompt.
Solana specifics
- Clients composed under the hood:
DepositClient,DistributionClient,OutpostClient,TokenClient,ValidatorLeaderboardClient,SolanaProgramService. - SquadsX multisig: provide
extras.squadsXto have deposits / stakes wrapped as vault transactions (sendSquadsIxs). - Outpost / Yield:
getPortfolio()returnsyield(index-scale math) andstakedamounts;wirereports pretokens (1e8 scale). - Fresh-wallet reads: missing liqSOL Token-2022 ATAs are treated as zero-balance on read paths, so
first-time wallets can connect before their first liqSOL receive. Write flows that need to mint to the
user ATA should still rely on on-chain
init_if_neededaccount constraints or an explicit ATA-create instruction in the submitted transaction. The Solana staking client now auto-prepends that ATA create step for receive-sideclaimLiqsolRewards,unstake, andunstakeAndWithdrawwrites when needed. - Helpers:
getDepositBuffer,getDepositFee,getSystemAPY, tranche snapshots viagetTrancheSnapshot(chainId).
EVM specifics
- Uses
ethersprovider/signer; falls back to JSON RPC for read-only. - Modules used:
ConvertClient(deposit/withdraw),StakeClient,PretokenClient,OPPClient,ReceiptClient(withdrawal NFTs),ValidatorClient. - The checked-in
31337devnet addresses insrc/networks/evm/contract.tsare a Wire EVM devnet deployment snapshot and must be refreshed whenever that devnet is redeployed. - Additional endpoints:
getPendingWithdraws()→ pending withdrawal receipts.claimWithdraw(tokenId)→ claim queued withdrawal by NFT id.unstakePrelaunch(tokenId, recipient)→ prelaunch unstake path.validatorDeposit()→ validator bond/lock flow.
getTrancheSnapshot(chainId)→ pretoken ladder view with price/supply growth.getOPPMessages(address?)andgetEthStats()for operator/pay-rate data.
Cross-chain instaswap execution
EvmStakingClient.instaswapClient.submitCrossChain(...)handles LIQETH approval, depositor preflight, recoverable OPP epoch reconciliation, andinstaswapCrossChainsubmission.SolanaStakingClient.instaswapClient.submitCrossChain(...)handles LIQSOL outpost account assembly using the bridge/OPP account set required by the current Solana IDL,instaswapCrossChainsubmission, timeout recovery, and generic OPP epoch finalization retries.EvmStakingClient.instaswapClient.depositToWire(...)andSolanaStakingClient.instaswapClient.depositToWire(...)own bridge deposit account wiring, submission, and OPP recovery.SolanaStakingClient.stake(...)/OutpostClient.buildStakeIx(...)use the same bridge/OPP account set forsyndicateLiqsolToWire;desyndremains pool-based on the current devnet IDL.- Native ETH/SOL faucet requests stay in Hub. The SDK owns the artifact-backed deposit/mint and bridge/outpost execution paths that consume those balances.
- Solana native deposits retry through
registerUserwhen the program reportsLegacyUserRecordMigrationRequired, so legacy distribution user records do not block LIQSOL minting flows. getOutpostAddresses()andgetConnectedBalances(...)expose lightweight artifact-backed reads so consuming apps do not instantiate contracts/program PDAs directly.- Keep product concerns such as route selection, quote math, wallet network UX, and native-token top-up policy outside the SDK. The Hub swap service is the reference consumer for that split.
Portfolio shape (all chains)
type Portfolio = {
native: { amount: bigint; symbol: string; decimals: number };
liq: { amount: bigint; symbol: string; decimals: number; ata?: PublicKey };
staked: { amount: bigint; symbol: string; decimals: number };
wire: { amount: bigint; symbol: string; decimals: number };
yield?: { currentIndex: bigint; indexScale: bigint; totalShares: bigint;
userShares: bigint; estimatedClaim?: bigint; estimatedYield?: bigint };
extras?: Record<string, any>;
chainID: number;
};Error handling
- All write methods throw on failure; wrap with
try/catch. - Solana transaction failures include logs; EVM methods surface tx hashes for inspection.
- Passing
provideris required for writes; read-only is supported for many getters.
Using from a service (pattern)
Your staking.service.ts can simply forward to the selected client. The Wire Hub webapp
(wire-hub-webapp/src/app/_services/staking.service.ts) does this and adds read-only Outpost
clients, tranche/APY polling, SquadsX, and retryable portfolio refresh. Typical shape:
export class StakingService {
constructor(private staker: Staker) {}
deposit(amount: bigint) { return this.staker.client?.deposit(amount); }
withdraw(amount: bigint) { return this.staker.client?.withdraw(amount); }
stake(amount: bigint) { return this.staker.client?.stake(amount); }
unstake(amount: bigint) { return this.staker.client?.unstake(amount); }
buy(amount: bigint) { return this.staker.client?.buy(amount); }
portfolio() { return this.staker.client?.getPortfolio(); }
stats(chainId: number) { return this.staker.client?.getTrancheSnapshot(chainId); }
}Switch chains at runtime with staker.setChain(chainId).
Hub-specific extras you can reuse:
- Read-only bootstrap: configs with only
networkto fetch tranche snapshots/APY before a wallet connects. - APY helpers:
getSystemAPY()per chain; normalize number/string/bigint shapes. - Deposit buffer:
getDepositBuffer({ balanceOverrideLamports })to compute max-spendable. - SquadsX multisig: store
multisigPDA+vaultIndex(localStorage keyshub_squadsx_multisigpda,hub_squadsx_vaultindex) and pass viaextras.squadsXso write calls become Squads proposals. - ETH prelaunch/receipts:
getPendingWithdraws,claimWithdraw(tokenId),unstakePrelaunch(tokenId, recipient),fetchPrelaunchReceipts. - Tranche ladders: cache
getTrancheSnapshot(chainId)per chain for dashboards (liquidity/utilization/arb spread).
Portfolio fields (returned by getPortfolio)
native: wallet balance in base units; symbol/decimals from network.liq: liquid staking token balance (Token-2022 ATA on Sol; ERC-20 on EVM).staked: liq staked into Outpost (Sol) or staking manager (ETH).wire: pretoken balance (1e8 scale).yield:currentIndex,indexScale,totalShares,userShares,estimatedClaim,estimatedYield.extras: PDAs, price/index hints useful for UIs and debugging.chainID: the chain the snapshot came from.
Tranche snapshot fields (via getTrancheSnapshot)
currentTranche,currentPriceUsd(1e8),currentTrancheSupply,initialTrancheSupply.ladder: nearby rows{ id, capacity, sold, remaining, priceUsd }(1e8 scale).currentIndex,totalShares,totalPretokensSold,supplyGrowthBps,priceGrowthCents.nativePriceUsdif available (SDK backfills from market data when the chain doesn’t provide it).
Building, testing, docs
npm ci # install
npm run prepare # build to lib/ via rollup
npm test # full test suite (make targets exist for sol/evm split)
npm run docs # generate typedoc to docs/ (gh-pages ready)Project structure (src/)
staker.ts– chain router / façade.types.ts– shared interfaces (IStakingClient, Portfolio, tranche snapshots).networks/solana/*– clients, IDL wiring, constants, utils.networks/evm/*– contract service, clients, tranche math.assets/– IDLs, ABIs, artifacts.
Versioning & license
- License: FSL-1.1-Apache-2.0 (see
LICENSE.md).
Links
- API reference: https://Wire-Network.github.io/sdk-stake/
- Source: https://github.com/Wire-Network/sdk-stake
