Skip to content

TypeScript SDK

The @jpool/sdk package lets you interact with the JPool stake pool programmatically. You can deposit and withdraw SOL, direct stake to specific validators, query pool state and exchange rates, and look up direct stake records.

CLI alternative

For one-off operations from the command line, see the CLI Reference. The SDK is designed for building applications and services on top of JPool.

Installation

bash
npm install @jpool/sdk
bash
pnpm add @jpool/sdk
bash
yarn add @jpool/sdk

Peer dependency: @solana/web3.js v1.x.

Quick start

typescript
import { Connection, clusterApiUrl, LAMPORTS_PER_SOL } from '@solana/web3.js'
import { JPoolClient } from '@jpool/sdk'

const connection = new Connection(clusterApiUrl('mainnet-beta'))
const client = new JPoolClient(connection)

const poolInfo = await client.poolInfo()
console.log('Total staked:', Number(poolInfo.totalLamports) / LAMPORTS_PER_SOL, 'SOL')

const rate = client.poolTokenRate(poolInfo)
console.log('Exchange rate:', rate, 'lamports per pool token')

Configuration

The JPoolClient constructor accepts an optional configuration object:

typescript
import { STAKE_POOL_PROGRAM_ID } from '@solana/spl-stake-pool'

const client = new JPoolClient(connection, {
  poolAddress: new PublicKey('CUSTOM_POOL_ADDRESS'),
  programId: STAKE_POOL_PROGRAM_ID,
  refCode: 'YOUR_REFERRAL_CODE',
})
OptionTypeDefaultDescription
poolAddressPublicKeyJPool pool addressStake pool account to interact with
programIdPublicKeySPL Stake Pool programStake pool program ID
refCodestringReferral code included in transaction memos for attribution
debugbooleanfalseEnable debug logging

You can update individual options after construction. The configure method returns the client instance, so calls can be chained:

typescript
client
  .configure('refCode', 'NEW_CODE')
  .configure('debug', true)

Depositing SOL

Use depositSol to stake SOL and receive liquid pool tokens (JSOL). The method accepts a DepositSolProps object:

typescript
import { Transaction, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'

const userPublicKey = new PublicKey('YOUR_WALLET_ADDRESS')

const { instructions, signers } = await client.depositSol({
  from: userPublicKey,
  lamports: 1 * LAMPORTS_PER_SOL,
})

const transaction = new Transaction().add(...instructions)
const { blockhash } = await connection.getLatestBlockhash()
transaction.recentBlockhash = blockhash
transaction.feePayer = userPublicKey

// Sign with ephemeral signers, then with your wallet
transaction.sign(...signers)
// ... sign with wallet and send

Parameters

ParameterTypeRequiredDescription
fromPublicKeyYesWallet depositing SOL and receiving pool tokens
lamportsnumberYesAmount of SOL to deposit, in lamports
directVotePublicKeyNoValidator to direct your stake to
ephemeralAddressPublicKeyNoCustom ephemeral keypair address

Direct staking on deposit

Pass a directVote to direct your stake to a specific validator while still receiving liquid pool tokens:

typescript
const { instructions, signers } = await client.depositSol({
  from: userPublicKey,
  lamports: 5 * LAMPORTS_PER_SOL,
  directVote: new PublicKey('VALIDATOR_VOTE_ACCOUNT'),
})

For more on how direct staking works, see Direct Staking and Direct Stake Integration.

Depositing a stake account

Use depositStake to convert an existing delegated stake account into pool tokens. The method accepts a DepositStakeProps object:

typescript
const { instructions, signers } = await client.depositStake({
  authority: userPublicKey,
  vote: new PublicKey('VALIDATOR_VOTE_ACCOUNT'),
  stake: new PublicKey('STAKE_ACCOUNT_ADDRESS'),
})

The stake account must already be delegated and in an active state.

Parameters

ParameterTypeRequiredDescription
authorityPublicKeyYesOwner (withdraw authority) of the stake account
votePublicKeyYesValidator vote account the stake is delegated to
stakePublicKeyYesStake account to deposit
directVotePublicKeyNoValidator to direct your stake to after deposit

Withdrawing SOL

Use withdrawSol to burn pool tokens and receive SOL from the pool reserve. The method accepts a WithdrawSolProps object:

typescript
const { instructions, signers } = await client.withdrawSol({
  from: userPublicKey,
  amount: poolTokensToBurn,
})

Parameters

ParameterTypeRequiredDescription
fromPublicKeyYesWallet that owns the pool tokens and will receive SOL
amountnumberYesNumber of pool tokens to burn (not SOL)
withdrawAuthorityPublicKeyNoCustom withdraw authority
ephemeralTransferAuthorityPublicKeyNoCustom ephemeral transfer authority
directIdPublicKeyNoDirect stake identifier for unstaking from a specific validator

WARNING

The amount parameter is in pool tokens, not SOL. To estimate the SOL you will receive, multiply by the pool token rate:

typescript
const poolInfo = await client.poolInfo()
const rate = client.poolTokenRate(poolInfo)
const expectedSOL = poolTokenAmount * rate

Withdrawing stake

Use withdrawStake to burn pool tokens and receive a delegated stake account instead of liquid SOL. The method accepts a WithdrawStakeProps object:

typescript
const { instructions, signers } = await client.withdrawStake({
  from: userPublicKey,
  amount: poolTokensToBurn,
})

The resulting stake account will be delegated to a validator from the pool. You will need to deactivate it and wait for the cooldown (roughly one epoch) before the SOL becomes available.

Parameters

ParameterTypeRequiredDescription
fromPublicKeyYesWallet that owns the pool tokens
amountnumberYesNumber of pool tokens to burn (not SOL)
ephemeralTransferAuthorityPublicKeyNoCustom ephemeral transfer authority
directIdPublicKeyNoDirect stake identifier for unstaking from a specific validator

Transaction signing

All transaction-building methods (depositSol, depositStake, withdrawSol, withdrawStake) return { instructions, signers }:

  • instructions: an array of TransactionInstruction objects to add to your transaction.
  • signers: ephemeral keypairs created by the SPL library for temporary operations. These must sign the transaction along with your wallet.

The full signing flow:

typescript
// 1. Get instructions and signers from an SDK method
const { instructions, signers } = await client.depositSol({ from, lamports })

// 2. Build the transaction
const transaction = new Transaction().add(...instructions)
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
transaction.recentBlockhash = blockhash
transaction.lastValidBlockHeight = lastValidBlockHeight
transaction.feePayer = from

// 3. Sign with ephemeral signers
transaction.sign(...signers)

// 4. Sign with your wallet (Phantom, Solflare, etc.)
// 5. Send the transaction
const signature = await connection.sendRawTransaction(transaction.serialize())
await connection.confirmTransaction(signature)

Querying pool data

Pool info

Fetch the current state of the stake pool:

typescript
const poolInfo = await client.poolInfo()

Key fields on the returned StakePoolInfo object:

FieldTypeDescription
totalLamportsbigintTotal SOL under pool management
poolTokenSupplybigintTotal pool tokens in circulation
poolMintPublicKeyPool token mint address
reserveStakePublicKeyReserve stake account address
solDepositFeenumberSOL deposit fee as a decimal (0.03 = 3%)
solWithdrawalFeenumberSOL withdrawal fee as a decimal
stakeDepositFeenumberStake account deposit fee as a decimal
stakeWithdrawalFeenumberStake account withdrawal fee as a decimal
epochFeenumberPer-epoch management fee as a decimal
lastUpdateEpochbigintEpoch when the pool was last updated
All StakePoolInfo fields
FieldTypeDescription
managerPublicKeyPool manager who can modify pool settings
stakerPublicKeyStaker who can manage stake accounts
stakeDepositAuthorityPublicKeyAuthority for stake deposits
stakeWithdrawBumpSeednumberBump seed for the stake withdrawal authority PDA
validatorListPublicKeyValidator list account
reserveStakePublicKeyReserve stake account
poolMintPublicKeyPool token mint
managerFeeAccountPublicKeyAccount that receives manager fees
tokenProgramIdPublicKeyToken program ID
totalLamportsbigintTotal SOL under pool management
poolTokenSupplybigintTotal pool tokens in circulation
lastUpdateEpochbigintEpoch when the pool was last updated
lastEpochPoolTokenSupplybigintPool token supply in the previous epoch
lastEpochTotalLamportsbigintTotal lamports in the previous epoch
lockupUnixTimestampbigintUnix timestamp when the lockup expires
lockupEpochbigintEpoch when the lockup expires
lockupCustodianPublicKeyCustodian who can unlock before lockup expires
epochFeenumberPer-epoch management fee as a decimal
nextEpochFeenumber | undefinedNext epoch's fee if scheduled for change
preferredDepositValidatorVoteAddressPublicKey | undefinedPreferred validator for deposits
preferredWithdrawValidatorVoteAddressPublicKey | undefinedPreferred validator for withdrawals
stakeDepositFeenumberFee on stake deposits as a decimal
stakeWithdrawalFeenumberFee on stake withdrawals as a decimal
nextStakeWithdrawalFeenumber | undefinedNext epoch's stake withdrawal fee if scheduled
stakeReferralFeenumberReferral fee percentage (0-100) for stake operations
solDepositAuthorityPublicKey | undefinedAuthority that can perform SOL deposits
solDepositFeenumberFee on SOL deposits as a decimal
solReferralFeenumberReferral fee percentage (0-100) for SOL deposits
solWithdrawAuthorityPublicKey | undefinedAuthority that can perform SOL withdrawals
solWithdrawalFeenumberFee on SOL withdrawals as a decimal
nextSolWithdrawalFeenumber | undefinedNext epoch's SOL withdrawal fee if scheduled

Exchange rate

Calculate the current rate between pool tokens and lamports:

typescript
const rate = client.poolTokenRate(poolInfo)
// rate > 1 means the pool has earned staking rewards

Returns 1.0 if the pool token supply is zero.

Reserve balance

Check the pool's liquid SOL reserve. Returns undefined if the reserve account does not exist.

typescript
const reserveLamports = await client.reserveBalance(poolInfo)

Fees

Deposit and withdrawal fees are automatically deducted from operations. To check current fees:

typescript
const poolInfo = await client.poolInfo()
console.log('SOL deposit fee:', poolInfo.solDepositFee * 100, '%')
console.log('SOL withdrawal fee:', poolInfo.solWithdrawalFee * 100, '%')
console.log('Stake deposit fee:', poolInfo.stakeDepositFee * 100, '%')
console.log('Stake withdrawal fee:', poolInfo.stakeWithdrawalFee * 100, '%')

Direct stakes and wallet bindings

Querying direct stakes

Look up direct stake records by wallet or validator. At least one filter is required. The method accepts a DirectStakesQuery object.

typescript
// By wallet
const stakes = await client.getDirectStakes({
  wallet: userPublicKey,
})

// By validator
const validatorStakes = await client.getDirectStakes({
  vote: validatorVoteAccount,
})

Querying wallet bindings

Look up wallet binding records. Exactly one of wallet or vote must be provided. The method accepts a WalletBindingsQuery object.

typescript
// By wallet
const bindings = await client.getWalletBindings({
  wallet: userPublicKey,
})

// By validator
const validatorBindings = await client.getWalletBindings({
  vote: validatorVoteAccount,
})

For creating and managing direct stakes and wallet bindings programmatically, see Direct Stake Integration.

Constants

The SDK exports the JPool pool address and token mint:

typescript
import { POOL_ADDRESS, POOL_MINT_ADDRESS } from '@jpool/sdk'

console.log('Pool address:', POOL_ADDRESS.toBase58())
// CtMyWsrUtAwXWiGr9WjHT5fC3p3fgV8cyGpLTo2LJzG1

console.log('Pool token mint:', POOL_MINT_ADDRESS.toBase58())
// 7Q2afV64in6N6SeZsAAB81TJzwDoD6zpqmHkzi9Dcavn

You can also access the SDK version:

typescript
console.log('SDK version:', JPoolClient.version)