npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zebec-network/zebec-stake-sdk

v1.3.1

Published

An SDK for zebec network stake solana program

Downloads

203

Readme

Zebec Stake Sdk

An SDK for interacting with the Zebec Network staking program on Solana. Supports creating and managing staking lockup pools, staking/unstaking tokens, and querying on-chain staking data.

Table of Contents


Installation

npm install @zebec-network/zebec-stake-sdk
yarn add @zebec-network/zebec-stake-sdk
pnpm add @zebec-network/zebec-stake-sdk

Quick Setup

Read-only (no wallet required)

Use this setup for querying on-chain data without signing transactions.

import { Connection } from "@solana/web3.js";
import { createReadonlyProvider, StakeServiceBuilder } from "@zebec-network/zebec-stake-sdk";

const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
const provider = createReadonlyProvider(connection);

const service = new StakeServiceBuilder()
  .setNetwork("mainnet-beta")
  .setProvider(provider)
  .setProgram()
  .build();

With wallet (for signing transactions)

Use this setup when you need to send transactions (stake, unstake, create/update lockups).

import { Connection, Keypair } from "@solana/web3.js";
import { Wallet } from "@coral-xyz/anchor";
import {
  createAnchorProvider,
  StakeServiceBuilder,
} from "@zebec-network/zebec-stake-sdk";

const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
const keypair = Keypair.fromSecretKey(/* your secret key bytes */);
const wallet = new Wallet(keypair);

const provider = createAnchorProvider(connection, wallet, { commitment: "confirmed" });

const service = new StakeServiceBuilder()
  .setNetwork("mainnet-beta")
  .setProvider(provider)
  .setProgram()
  .build();

Default setup (mainnet, no wallet)

All builder methods are optional — calling them without arguments uses sensible defaults.

import { StakeServiceBuilder } from "@zebec-network/zebec-stake-sdk";

// Defaults: mainnet-beta network, ReadonlyProvider with public RPC
const service = new StakeServiceBuilder()
  .setNetwork()
  .setProvider()
  .setProgram()
  .build();

API Documentation

StakeServiceBuilder

A fluent builder for constructing a StakeService. Methods must be called in order: setNetworksetProvidersetProgrambuild.

class StakeServiceBuilder {
  setNetwork(network?: "mainnet-beta" | "devnet"): StakeServiceBuilder
  setProvider(provider?: ReadonlyProvider | AnchorProvider): StakeServiceBuilder
  setProgram(createProgram?: (provider) => Program<ZebecStakeIdlV1>): StakeServiceBuilder
  build(): StakeService
}

| Method | Description | | ------ | ----------- | | setNetwork(network?) | Set the target network. Defaults to "mainnet-beta". Must be called before setProvider. | | setProvider(provider?) | Set the provider. Accepts ReadonlyProvider or AnchorProvider. Defaults to ReadonlyProvider using the public cluster RPC. Must be called before setProgram. | | setProgram(createProgram?) | Set the Anchor program. Optionally accepts a factory (provider) => Program. Defaults to the built-in IDL. | | build() | Validates all settings and returns a StakeService instance. Throws if any step was skipped. |

Errors thrown by the builder:

  • "InvalidOperation: Network is set twice."setNetwork called more than once.
  • "InvalidOperation: Provider is set twice."setProvider called more than once.
  • "InvalidOperation: Program is set twice."setProgram called more than once.
  • "InvalidOperation: Network is not set."setProvider/setProgram called before setNetwork.
  • "InvalidOperation: Provider is not set."setProgram called before setProvider.
  • Network mismatch error if the provider's RPC endpoint does not match the set network.

StakeService

The main service class. Exposes methods for managing lockup pools, staking, and reading on-chain data. All transaction methods return a TransactionPayload that must be executed separately.

initLockup(params)

Creates a new staking lockup pool. Only the creator can later update it.

service.initLockup(params: {
  stakeToken: Address;       // Mint address of the token to be staked
  rewardToken: Address;      // Mint address of the reward token
  name: string;              // Unique name for the lockup (used to derive its address)
  fee: Numeric;              // Fee amount per stake (in token units, e.g. 5 = 5 tokens)
  feeVault: Address;         // Public key of the account that collects fees
  rewardSchemes: RewardScheme[]; // Array of lock durations and annual reward rates
  minimumStake: Numeric;     // Minimum stake amount (in token units)
  creator?: Address;         // Defaults to provider.publicKey
}): Promise<TransactionPayload>

updateLockup(params)

Updates an existing lockup pool. Only callable by the original creator.

service.updateLockup(params: {
  lockupName: string;        // Name of the lockup to update
  fee: Numeric;              // New fee amount
  feeVault: Address;         // New fee vault address
  rewardSchemes: RewardScheme[]; // Updated reward schemes
  minimumStake: Numeric;     // Updated minimum stake amount
  updater?: Address;         // Defaults to provider.publicKey
}): Promise<TransactionPayload>

stake(params)

Stakes tokens into a lockup pool for a specified lock period.

service.stake(params: {
  lockupName: string;        // Name of the lockup to stake into
  amount: Numeric;           // Amount to stake (in token units, e.g. 100 = 100 tokens)
  lockPeriod: number;        // Lock duration in seconds — must match an existing reward scheme
  nonce: bigint;             // Current user nonce (use getUserNonceInfo to retrieve)
  feePayer?: Address;        // Defaults to staker
  staker?: Address;          // Defaults to provider.publicKey
}): Promise<TransactionPayload>

unstake(params)

Unstakes tokens and claims the accrued reward after the lock period has elapsed.

service.unstake(params: {
  lockupName: string;        // Name of the lockup
  nonce: bigint;             // Nonce of the stake to unstake
  feePayer?: Address;        // Defaults to staker
  staker?: Address;          // Defaults to provider.publicKey
}): Promise<TransactionPayload>

getLockupInfo(lockupAddress)

Fetches and returns human-readable information about a lockup pool.

service.getLockupInfo(lockupAddress: Address): Promise<LockupInfo | null>

Returns null if the lockup does not exist.

getStakeInfo(stakeAddress, lockupAddress)

Fetches information about a specific stake position.

service.getStakeInfo(stakeAddress: Address, lockupAddress: Address): Promise<StakeInfo | null>

Returns null if the stake account does not exist. Throws if the lockup does not exist.

getUserNonceInfo(userNonceAddress)

Returns the user's current nonce for a given lockup. The nonce is used to derive stake addresses and must be passed when calling stake().

service.getUserNonceInfo(userNonceAddress: Address): Promise<UserNonceInfo | null>

Returns null if the user has never staked in this lockup (nonce is implicitly 0n).

getAllStakesInfoOfUser(userAddress, lockupAddress, options?)

Fetches all historical stake positions for a user in a given lockup pool, including the transaction signature for each stake.

service.getAllStakesInfoOfUser(
  userAddress: Address,
  lockupAddress: Address,
  options?: {
    minDelayMs?: number;    // Minimum ms between RPC calls (default: 400)
    maxConcurrent?: number; // Max concurrent RPC calls (default: 3)
  }
): Promise<StakeInfoWithHash[]>

getAllStakesInfo(lockupAddress)

Fetches all stake positions across all users in a lockup pool.

service.getAllStakesInfo(lockupAddress: Address): Promise<StakeInfo[]>

getAllStakesCount(lockupAddress)

Returns the total number of stake accounts associated with a lockup pool.

service.getAllStakesCount(lockupAddress: Address): Promise<number>

getStakeSignatureForStake(stakeInfo)

Retrieves the on-chain transaction signature for a given stake position.

service.getStakeSignatureForStake(stakeInfo: StakeInfo): Promise<string | null>

Properties

| Property | Type | Description | | -------- | ---- | ----------- | | programId | PublicKey | The deployed staking program's public key | | connection | Connection | The active Solana RPC connection | | provider | Provider | The underlying Anchor/Readonly provider | | program | Program<ZebecStakeIdlV1> | The Anchor program instance |


Providers

Two provider types are available depending on your use case.

createReadonlyProvider(connection, walletAddress?)

Creates a lightweight provider for read-only operations. Does not require a wallet.

import { createReadonlyProvider } from "@zebec-network/zebec-stake-sdk";

const provider = createReadonlyProvider(connection);
// or with an optional wallet address for context
const provider = createReadonlyProvider(connection, "YourWalletPublicKey...");

createAnchorProvider(connection, wallet, options?)

Creates a full Anchor provider capable of signing and sending transactions.

import { createAnchorProvider } from "@zebec-network/zebec-stake-sdk";

const provider = createAnchorProvider(connection, wallet, {
  commitment: "confirmed",
});

The wallet must implement the AnchorWallet interface:

interface AnchorWallet {
  publicKey: PublicKey;
  signTransaction<T extends Transaction | VersionedTransaction>(tx: T): Promise<T>;
  signAllTransactions<T extends Transaction | VersionedTransaction>(txs: T[]): Promise<T[]>;
}

PDA Utilities

Helper functions for deriving program-derived addresses. All programId parameters default to the mainnet program ID.

import {
  deriveLockupAddress,
  deriveStakeAddress,
  deriveUserNonceAddress,
  deriveStakeVaultAddress,
  deriveRewardVaultAddress,
} from "@zebec-network/zebec-stake-sdk";

| Function | Description | | -------- | ----------- | | deriveLockupAddress(name, programId?) | Derives the lockup PDA from its unique name | | deriveStakeAddress(staker, lockup, nonce, programId?) | Derives a stake PDA for a given staker, lockup, and nonce | | deriveUserNonceAddress(user, lockup, programId?) | Derives the user nonce PDA tracking a user's total stake count | | deriveStakeVaultAddress(lockup, programId?) | Derives the vault PDA that holds staked tokens | | deriveRewardVaultAddress(lockup, programId?) | Derives the vault PDA that holds reward tokens |


Types

// Human-readable reward scheme: duration in seconds and annual rate as a percentage
type RewardScheme = {
  duration: number;       // Lock period in seconds (e.g., 2592000 = 30 days)
  rewardRate: Numeric;    // Annual reward rate as a percentage (e.g., "5.00" = 5%)
};

// Returned by getLockupInfo()
type LockupInfo = {
  address: string;
  feeInfo: {
    fee: string;           // Fee amount in token units
    feeVault: string;      // Fee collector address
  };
  rewardToken: {
    tokenAddress: string;
  };
  stakeToken: {
    tokenAdress: string;   // Note: single 'd' in 'adress' (matches on-chain field)
    totalStaked: string;   // Total tokens currently staked in this lockup
  };
  stakeInfo: {
    name: string;
    creator: string;
    rewardSchemes: RewardScheme[];
    minimumStake: string;
  };
};

// Returned by getStakeInfo() and getAllStakesInfo()
type StakeInfo = {
  address: string;         // Stake PDA address
  nonce: bigint;           // Stake nonce (index within this user's stakes)
  createdTime: number;     // Unix timestamp of when the stake was created
  stakedAmount: string;    // Amount staked in token units
  rewardAmount: string;    // Accrued reward in reward token units
  stakeClaimed: boolean;   // Whether the stake has been unstaked
  lockPeriod: number;      // Lock duration in seconds
  staker: string;          // Staker's public key
  lockup: string;          // Lockup PDA address
};

// Returned by getAllStakesInfoOfUser()
type StakeInfoWithHash = StakeInfo & {
  hash: string;            // Transaction signature of the original stake
};

// Returned by getUserNonceInfo()
type UserNonceInfo = {
  address: string;         // User nonce PDA address
  nonce: bigint;           // Current nonce (equals total number of stakes made)
};

// Accepted wherever amounts are passed
type Numeric = string | number;

Constants

import { ZEBEC_STAKE_PROGRAM, STAKE_LOOKUP_TABLE_ADDRESS } from "@zebec-network/zebec-stake-sdk";

// Program IDs
ZEBEC_STAKE_PROGRAM.mainnet  // "zSTKzGLiN6T6EVzhBiL6sjULXMahDavAS2p4R62afGv"
ZEBEC_STAKE_PROGRAM.devnet   // "zSTKzGLiN6T6EVzhBiL6sjULXMahDavAS2p4R62afGv"

// Address Lookup Table accounts for versioned transactions
STAKE_LOOKUP_TABLE_ADDRESS["mainnet-beta"]  // "EoKjJejKr4XsBdtUuYwzZcYd6tpGNijxCGgQocxtxQ8t"
STAKE_LOOKUP_TABLE_ADDRESS["devnet"]        // "C4R2sL6yj7bzKfbdfwCfH68DZZ3QnzdmedE9wQqTfAAA"

Usage Examples

Read-only queries

import { Connection } from "@solana/web3.js";
import {
  createReadonlyProvider,
  deriveLockupAddress,
  deriveUserNonceAddress,
  StakeServiceBuilder,
} from "@zebec-network/zebec-stake-sdk";

const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
const provider = createReadonlyProvider(connection);

const service = new StakeServiceBuilder()
  .setNetwork("mainnet-beta")
  .setProvider(provider)
  .setProgram()
  .build();

// Derive the lockup address from its name
const lockupName = "ZBCN_Lockup_003";
const lockupAddress = deriveLockupAddress(lockupName, service.programId);

// Fetch lockup pool info
const lockupInfo = await service.getLockupInfo(lockupAddress);
console.log(lockupInfo);
// {
//   address: "...",
//   feeInfo: { fee: "5", feeVault: "..." },
//   rewardToken: { tokenAddress: "..." },
//   stakeToken: { tokenAdress: "...", totalStaked: "1234567" },
//   stakeInfo: {
//     name: "ZBCN_Lockup_003",
//     creator: "...",
//     rewardSchemes: [
//       { duration: 2592000, rewardRate: "3.00" },
//       { duration: 7776000, rewardRate: "5.00" },
//     ],
//     minimumStake: "1"
//   }
// }

// Fetch all stakes in a lockup
const allStakes = await service.getAllStakesInfo(lockupAddress);
console.log(`Total active positions: ${allStakes.length}`);

// Fetch total stake count
const count = await service.getAllStakesCount(lockupAddress);
console.log(`Total stake accounts: ${count}`);

Initialize a lockup pool

Requires an AnchorProvider (wallet with signing capability).

import { Connection, Keypair } from "@solana/web3.js";
import { Wallet } from "@coral-xyz/anchor";
import {
  createAnchorProvider,
  deriveLockupAddress,
  StakeServiceBuilder,
} from "@zebec-network/zebec-stake-sdk";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const keypair = Keypair.fromSecretKey(/* your secret key bytes */);
const wallet = new Wallet(keypair);
const provider = createAnchorProvider(connection, wallet, { commitment: "confirmed" });

const service = new StakeServiceBuilder()
  .setNetwork("devnet")
  .setProvider(provider)
  .setProgram()
  .build();

const ZBCN_MINT = "ZBCNpuD7YMXzTHB2fhGkGi78MNsHGLRXUhRewNRm9RU";

const payload = await service.initLockup({
  stakeToken: ZBCN_MINT,
  rewardToken: ZBCN_MINT,
  name: "My_Lockup_001",
  fee: 0,                        // 0 token fee per stake
  feeVault: "FeeVaultPublicKey...",
  minimumStake: 1,               // Minimum 1 token to stake
  rewardSchemes: [
    { duration: 2592000, rewardRate: "3.00" },  // 30 days @ 3% APR
    { duration: 7776000, rewardRate: "5.00" },  // 90 days @ 5% APR
    { duration: 15552000, rewardRate: "7.00" }, // 180 days @ 7% APR
  ],
});

const signature = await payload.execute({ commitment: "confirmed" });
console.log("Lockup created:", signature);

const lockupAddress = deriveLockupAddress("My_Lockup_001", service.programId);
const lockupInfo = await service.getLockupInfo(lockupAddress);
console.log(lockupInfo);

Update a lockup pool

Only the original creator can update a lockup.

const payload = await service.updateLockup({
  lockupName: "My_Lockup_001",
  fee: 5,
  feeVault: "NewFeeVaultPublicKey...",
  minimumStake: 10,
  rewardSchemes: [
    { duration: 2592000, rewardRate: "5.00" },  // 30 days @ 5% APR
    { duration: 7776000, rewardRate: "8.00" },  // 90 days @ 8% APR
    { duration: 15552000, rewardRate: "12.00" }, // 180 days @ 12% APR
  ],
});

const signature = await payload.execute({ commitment: "confirmed" });
console.log("Lockup updated:", signature);

Stake tokens

import {
  deriveUserNonceAddress,
  deriveLockupAddress,
} from "@zebec-network/zebec-stake-sdk";

const lockupName = "My_Lockup_001";
const lockupAddress = deriveLockupAddress(lockupName, service.programId);

// Get the user's current nonce (determines the next stake account address)
const userNonceAddress = deriveUserNonceAddress(
  wallet.publicKey,
  lockupAddress,
  service.programId,
);
const nonceInfo = await service.getUserNonceInfo(userNonceAddress);
const nonce = nonceInfo ? nonceInfo.nonce : 0n;

// Stake 1000 tokens for 30 days (2592000 seconds)
const payload = await service.stake({
  lockupName,
  amount: 1000,
  lockPeriod: 2592000, // must match a duration in the lockup's rewardSchemes
  nonce,               // current nonce; incremented on-chain after staking
});

const signature = await payload.execute({ commitment: "confirmed" });
console.log("Stake signature:", signature);

Unstake tokens

Unstaking returns the original staked tokens plus any accrued reward. Can only be called after the lock period has elapsed.

import { deriveStakeAddress } from "@zebec-network/zebec-stake-sdk";

// The nonce used when staking identifies which stake position to unstake
const stakeNonce = 0n;

const payload = await service.unstake({
  lockupName: "My_Lockup_001",
  nonce: stakeNonce,
});

const signature = await payload.execute({ commitment: "confirmed" });
console.log("Unstake signature:", signature);

// Verify the stake was claimed
const lockupAddress = deriveLockupAddress("My_Lockup_001", service.programId);
const stakeAddress = deriveStakeAddress(
  wallet.publicKey,
  lockupAddress,
  stakeNonce,
  service.programId,
);
const stakeInfo = await service.getStakeInfo(stakeAddress, lockupAddress);
console.log("Stake claimed:", stakeInfo?.stakeClaimed); // true
console.log("Reward received:", stakeInfo?.rewardAmount);

Fetch stake data

// All stakes for a specific user in a lockup
const userStakes = await service.getAllStakesInfoOfUser(
  wallet.publicKey,
  lockupAddress,
  {
    maxConcurrent: 3,  // max parallel RPC calls (default: 3)
    minDelayMs: 400,   // ms between requests to avoid rate limits (default: 400)
  }
);

for (const stake of userStakes) {
  console.log({
    nonce: stake.nonce.toString(),
    amount: stake.stakedAmount,
    reward: stake.rewardAmount,
    claimed: stake.stakeClaimed,
    lockPeriod: stake.lockPeriod,
    txHash: stake.hash,
  });
}

// Single stake by address
const stakeAddress = deriveStakeAddress(
  wallet.publicKey,
  lockupAddress,
  0n,
  service.programId,
);
const stakeInfo = await service.getStakeInfo(stakeAddress, lockupAddress);

Development

Environment setup

Create a .env file at the project root for running tests:

RPC_URL=https://your-mainnet-rpc-url
DEVNET_RPC_URL=https://your-devnet-rpc-url

# JSON arrays of base58-encoded secret keys
MAINNET_SECRET_KEYS=["base58SecretKey1","base58SecretKey2"]
DEVNET_SECRET_KEYS=["base58SecretKey1","base58SecretKey2"]

Commands

# Build the package
npm run build

# Run all tests
npm test

# Run a single test file
npm run test:single ./test/e2e/getLockupInfo.test.ts

# Run a specific test by name pattern
npm run test:single ./test/e2e/stakeAndUnstake.test.ts -- -f "stake()"

# Format code
npm run format

Publish

Build and bump the version in package.json, then:

npm publish --access public