Staking SDK
The Staking SDK simplifies common interactions with the Polkadot staking pallet:
- Fetches validator performance data for each era.
- Computes nominator rewards and active validator sets.
- Integrates the account balance with nomination status and locks.
- Lists and inspects nomination pools, including commission details.
- Builds batched staking transactions for bonding, nominating, payee updates, and unwinding positions.
Getting Started
Install the Staking SDK using your package manager:
npm i @polkadot-api/sdk-stakingThen, create a client and pass it to createStakingSdk.
import { createStakingSdk } from "@polkadot-api/sdk-staking"
import { createClient } from "polkadot-api"
import { getSmProvider } from "polkadot-api/sm-provider"
import { polkadot, polkadot_asset_hub } from "polkadot-api/chains"
import { start } from "polkadot-api/smoldot"
import { dot } from "@polkadot-api/descriptors"
const smoldot = start()
const chain = smoldot.addChain({ chainSpec: polkadot }).then((relay) =>
smoldot.addChain({
chainSpec: polkadot_asset_hub,
potentialRelayChains: [relay],
}),
)
const client = createClient(getSmProvider(chain))
const typedApi = client.getTypedApi(dot)
const stakingSdk = createStakingSdk(client)Validators
The SDK queries all the information that represents the state of a validator for a specific era:
interface ValidatorRewards {
address: SS58String
// In [0-1]
commission: number
blocked: boolean
points: number
reward: bigint
commissionShare: bigint
nominatorsShare: bigint
activeBond: bigint
selfStake: bigint
nominatorCount: number
}It provides two methods:
getEraValidators(era): Gets all the validators for a specific era.getValidatorRewards(addr, era): Gets the information for one specific validator.
getEraValidators
const activeEra = await typedApi.query.Staking.ActiveEra.getValue()
const prevEra = activeEra!.index - 1
const { validators, totalRewards, totalBond } =
await stakingSdk.getEraValidators(prevEra)
console.log("Validators in era", prevEra)
validators.forEach((validator) => {
console.log(validator.address, validator.reward.toString())
})
console.log("Era reward pool:", totalRewards.toString())
console.log("Total active bond:", totalBond.toString())getValidatorRewards
Use getValidatorRewards when you only need a single validator.
const validatorRewards = await stakingSdk.getValidatorRewards(
"13UVJyLnbVp8c4FQeiGRMVBP7xph2wHCuf2RzvyxJomXJ7RL",
prevEra,
)
if (validatorRewards) {
const commissionPct = validatorRewards.commission * 100
console.log(
`Validator earned ${validatorRewards.reward} (commission ${commissionPct.toFixed(
2,
)}%)`,
)
}Nominators
Due to the storage structure of the pallet, getting the nomination information for one specific address, like the active bond, active validators, etc. is very expensive: To query one specific address, it needs to fetch every other active nominator for all validators.
The SDK adds a caching mechanism per-era, so that the same work can be reused across different requests.
getNominatorActiveValidators
getNominatorActiveValidators fetches the validators where a nominatorโs stake was active during the era, including its effective bond per validator.
const active = await stakingSdk.getNominatorActiveValidators(
"1zugcUbZDfeG...",
prevEra,
)
console.log(active)getNominatorRewards
getNominatorRewards summarizes the rewards and commission attributed to the nominator in the chosen era. It contains a super-set of information from getNominatorActiveValidators, since it combines it with the rewards for each validator.
const rewards = await stakingSdk.getNominatorRewards("1zugcUbZDfeG...", prevEra)
console.log("Total era reward:", rewards.total.toString())
console.log("Per validator:", rewards.byValidator)getActiveNominators
You can also query for the list of all nominators with getActiveNominators.
const nominators = await stakingSdk.getActiveNominators(prevEra)
console.log("Unique active nominators:", nominators.length)Manage Nominations
The SDK provides transaction builders that batch the required staking calls based on the current state of the account.
upsertNomination
Use upsertNomination to adjust bond size, validator targets, or reward destination in a single callable batch. The helper will look at the current status of that nominator and only apply the appropriate changes. It will also first try to rebond unbonding funds before bonding unlocked ones.
const tx = await stakingSdk.upsertNomination("1zugcUbZDfeG...", {
bond: 50_000_000_000n,
validators: [
"1463EpGxchpSL1PeDnBEHUdQk2Mty8jXYEAJwQAVaHR6oWG6",
"13UVJyLnbVp8c4FQeiGRMVBP7xph2wHCuf2RzvyxJomXJ7RL",
],
})
await tx.signAndSubmit(signer)stopNomination
stopNomination performs three actions into a single batch: chill (remove all nominated validators), unbond and sets the payee to Stash if it was compounding. But it's also smart about it: only performs the transactions that are needed based on the current account's state.
const unwind = await stakingSdk.stopNomination("1zugcUbZDfeG...")
await unwind.signAndSubmit(signer)Account Status
getAccountStatus$ surfaces an observable that combines balance data, staking ledger info, and nomination pool membership.
First of all, it exposes a normalized account balance that is easier to consume:
interface Balance {
// Original on-chain info
raw: {
free: bigint
reserved: bigint
frozen: bigint
// Added for convenience; set to 0 when the account does not exist.
existentialDeposit: bigint
}
// Total tokens in the account
total: bigint
// Portion of `total` balance that is somehow locked (overlapping reserved, frozen, and existential deposit)
locked: bigint
// Portion of `free` balance that can't be transferred.
untouchable: bigint
// Portion of `free` balance that can be transferred.
spendable: bigint
}Then for nomination status it has an interface which shows whether it's currently nominating, unlocks, and other various information:
interface Nomination {
// Nomination parameters
nominating: {
validators: SS58String[]
} | null
controller: SS58String | null
payee: StakingRewardDestination | null
// Nomination status
currentBond: bigint
totalLocked: bigint
maxBond: bigint
canNominate: boolean
unlocks: Array<{
value: bigint
era: number
}>
// Contextual information
minNominationBond: bigint
lastMinRewardingBond: bigint
}Finally, nominationPool summarises pool membership, including the pool ID (if any), bonded amount, pending rewards, and unlock schedule:
interface NominationPool {
pool: number | null
currentBond: bigint
points: bigint
pendingRewards: bigint
unlocks: Array<{
value: bigint
era: number
}>
}As a small example:
import { filter, map } from "rxjs"
const subscription = stakingSdk
.getAccountStatus$("1zugcUbZDfeG...")
.pipe(
filter(({ balance }) => balance.locked > 0n),
map(({ balance, nomination }) => ({
spendable: balance.spendable,
currentBond: nomination.currentBond,
})),
)
.subscribe((status) => console.log("Updated status", status))
// Later, when done
subscription.unsubscribe()Nomination Pools
getNominationPools
List the current nomination pools with getNominationPools. Each entry contains addresses, commission policies, active nominations, and state.
const pools = await stakingSdk.getNominationPools()
const openPools = pools.filter((pool) => pool.state === "Open")
console.table(
openPools.map(({ id, name, bond, memberCount, commission }) => ({
id,
name,
members: memberCount,
totalBond: bond.toString(),
commission: commission.current * 100,
})),
)getNominationPool$
You can also subscribe to a single pool using getNominationPool$, which keeps track of ledger changes and parameter updates.
const pool$ = stakingSdk.getNominationPool$(123)
const poolSub = pool$.subscribe((pool) => console.log("Pool 123 update", pool))
// Later, when done
poolSub.unsubscribe()unbondNominationPool
Unbonding from a nomination pool is not straight-forward, since it deals with points instead of tokens.
For this reason, the SDK exports the function unbondNominationPool. The helper computes the correct unbonding points and creates the appropriate transaction.
const tx = await stakingSdk.unbondNominationPool(
"1zugcUbZDfeG...",
10_000_000_000n,
)
await tx.signAndSubmit(signer)