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

@unstake-it/sol-ag

v0.6.3

Published

Staked SOL instant unstake aggregator SDK

Downloads

29

Readme

npm-version npm-license Twitter

unstake.it Staked SOL Instant Unstake Aggregator Typescript SDK

unstake.it allows users to instantly unstake their Solana stake accounts to liquid SOL.

This SDK provides the core UnstakeAg class that aggregates the various unstake routes to compute the best route for a given stake account and unstake amount.

The SDK is heavily inspired by, and uses, @jup-ag/core. The usage patterns are very similar.

Contents:

API

For easy dapp integration without having to install this SDK, we provide a ready-to-use API at https://api.unstake.it

API documentation is available at https://api.unstake.it

Installation

npm

$ npm install @unstake-it/sol-ag

yarn

$ yarn add @unstake-it/sol-ag

Example

Initialize

import { Connection } from "@solana/web3.js";
import { UnstakeAg, legacyTxAmmsToExclude } from "@unstake-it/sol-ag";

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

// This loads the required accounts for all stake pools
// and jup-ag from on-chain.
// The arg type is `JupiterLoadParams` from jup-ag
const unstake = await UnstakeAg.load({
  cluster: "mainnet-beta",
  connection,
  // if you're using only legacy transactions (no lookup tables),
  // you should set ammsToExclude to legacyTxAmmsToExclude() to
  // avoid running into transaction size limits
  ammsToExclude: legacyTxAmmsToExclude(),
});

Initialize with Reference to Shared Jupiter object

If you're already using the @jup-ag/core SDK elsewhere in your code, you can construct an UnstakeAg object that uses the same existing Jupiter object to avoid fetching and caching duplicate accounts.

import { Jupiter, JupiterLoadParams } from "@jup-ag/core";
import { UnstakeAg } from "@unstake-it/sol-ag";

const myJupParams: JupiterLoadParams = { ... };

const jupiter = await Jupiter.load(myJupParams);

const stakePools = UnstakeAg.createStakePools(myJupParams.cluster);
const withdrawStakePools = UnstakeAg.createWithdrawStakePools(myJupParams.cluster);
const hybridPools = UnstakeAg.createHybridPools(myJupParams.cluster);

const unstake = new UnstakeAg(myJupParams, stakePools, withdrawStakePools, hybridPools, jupiter);

// call unstake.updatePools()
// to perform an initial fetch of all stake pools' accounts
await unstake.updatePools();

Compute Routes

import { PublicKey } from "@solana/web3.js";
import { getStakeAccount } from "@soceanfi/solana-stake-sdk";
import { outLamports, minOutLamports, totalRentLamports } from "@unstake-it/sol-ag";

const stakeAccountPubkey = new PublicKey(...);
const stakeAccount = await getStakeAccount(connection, stakeAccountPubkey);
const routes = await unstake.computeRoutes({
  stakeAccount,
  amountLamports: BigInt(stakeAccount.lamports),
  slippageBps: 10,
  // you can optionally collect a fee on top
  // of any jup swaps, just as you can in jup sdk
  jupFeeBps: 3,
});
const bestRoute = routes[0];
const {
  stakeAccInput: {
    stakePool,
    inAmount,
    outAmount,
  },
  // optional jup-ag `RouteInfo` for any additional swaps
  // via jup required to convert stake pool tokens into SOL
  jup,
} = bestRoute;

console.log(
  "Route will give me",
  outLamports(bestRoute),
  "lamports, and at least",
  minOutLamports(bestRoute),
  "lamports at max slippage.",
  "I need to spend an additional",
  totalRentLamports(bestRoute),
  "lamports to pay for rent",
);

Create Transaction(s) From Route

import { prepareSetupTx, prepareUnstakeTx, prepareCleanupTx } from "@unstake-it/sol-ag";

// returned transactions do not have `recentBlockhash` or `feePayer` set
// and are not signed
const exchangeReturn =
  await unstake.exchange({
    route: bestRoute,
    stakeAccount,
    stakeAccountPubkey,
    user: MY_WALLET_KEYPAIR.publicKey,
    // You can optionally provide a mapping of StakePool output tokens / wrapped SOL
    // to your token account of the same type to collect stake pool referral fees / jup swap fees
    feeAccounts: {
      "So11111111111111111111111111111111111111112": MY_WRAPPED_SOL_ACCOUNT,
      "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm": MY_SCNSOL_ACCOUNT,
    },
  });

const {
  setupTransaction,
  unstakeTransaction: { tx, signers },
  cleanupTransaction,
} = exchangeReturn;

const { blockhash, lastValidBlockHeight } = await unstake.connection.getLatestBlockhash();
const feePayer = MY_WALLET_KEYPAIR.publicKey;

const setupTx = prepareSetupTx(exchangeReturn, blockhash, feePayer);
if (setupTx) {
  setupTx.partialSign(MY_WALLET_KEYPAIR);
  const signature = await unstake.connection.sendRawTransaction(
    setupTx.serialize(),
  );
  await unstake.connection.confirmTransaction(
    {
      signature,
      blockhash,
      lastValidBlockHeight,
    }
  );
}

const unstakeTx = prepareUnstakeTx(exchangeReturn, blockhash, feePayer);
unstakeTx.partialSign(MY_WALLET_KEYPAIR);
const signature = await unstake.connection.sendRawTransaction(
  unstakeTx.serialize(),
);
await unstake.connection.confirmTransaction(
  {
    signature,
    blockhash,
    lastValidBlockHeight,
  }
);

const cleanupTx = prepareCleanupTx(exchangeReturn, blockhash, feePayer);
if (cleanupTx) {
  cleanupTx.partialSign(MY_WALLET_KEYPAIR);
  const signature = await unstake.connection.sendRawTransaction(
    cleanupTx.serialize(),
  );
  await unstake.connection.confirmTransaction(
    {
      signature,
      blockhash,
      lastValidBlockHeight,
    }
  );
}

Compute Routes for xSOL

The aggregator also handles the unstaking of xSOL (supported liquid staking derivatives).

import { PublicKey } from "@solana/web3.js";
import { getStakeAccount } from "@soceanfi/solana-stake-sdk";
import JSBI from "jsbi";
import { isXSolRouteJupDirect, outLamportsXSol, minOutLamportsXSol, totalRentLamportsXSol } from "@unstake-it/sol-ag"

const scnSOL = new PublicKey("5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm");
const routesScnSol = await unstake.computeRoutesXSol({
  inputMint: scnSOL,
  amount: JSBI.BigInt(1_000_000_000)
  slippageBps: 10,
  // args are the same as jups' computeRoutes(), except
  // - feeBps -> jupFeeBps
  // - +shouldIgnoreRouteErrors: boolean
  // - +stakePoolsToExclude: StakePoolsToExclude
});
const bestRouteScnSol = routesScnSol[0];
if (isXSolRouteJupDirect(bestRouteScnSol)) {
  const {
    jup, // jup RouteInfo type
  } = bestRouteScnSol;
} else {
  const {
    withdrawStake: {
      withdrawStakePool,
      inAmount,
      outAmount,
      stakeSplitFrom,
    },
    intermediateDummyStakeAccountInfo,
    unstake, // UnstakeRoute type
  } = bestRouteScnSol;
}

console.log(
  "Route will give me",
  outLamportsXSol(bestRouteScnSol),
  "lamports, and at least",
  minOutLamportsXSol(bestRouteScnSol),
  "lamports at max slippage",
  "I need to spend an additional",
  totalRentLamportsXSol(bestRouteScnSol),
  "lamports to pay for rent",
);

Create Transaction(s) From Route for xSOL

If required, stake pool stake withdraw instructions are placed in setupTransaction. This means that if the main unstakeTransaction fails, the user will be left with a stake account.

import { prepareSetupTx, prepareUnstakeTx, prepareCleanupTx } from "@unstake-it/sol-ag";

// returned transactions do not have `recentBlockhash` or `feePayer` set
// and are not signed
const exchangeReturn =
  await unstake.exchangeXSol({
    route: bestRouteScnSol,
    user: MY_WALLET_KEYPAIR.publicKey,
    srcTokenAccount: MY_SCNSOL_ACCOUNT,
    // You can optionally provide a mapping of StakePool output tokens / wrapped SOL
    // to your token account of the same type to collect stake pool referral fees / jup swap fees
    feeAccounts: {
      "So11111111111111111111111111111111111111112": MY_WRAPPED_SOL_ACCOUNT,
    },
  });

const {
  setupTransaction,
  unstakeTransaction: { tx, signers },
  cleanupTransaction,
} = exchangeReturn;

const { blockhash, lastValidBlockHeight } = await unstake.connection.getLatestBlockhash();
const feePayer = MY_WALLET_KEYPAIR.publicKey;

const setupTx = prepareSetupTx(exchangeReturn, blockhash, feePayer);
if (setupTx) {
  setupTx.partialSign(MY_WALLET_KEYPAIR);
  const signature = await unstake.connection.sendRawTransaction(
    setupTx.serialize(),
  );
  await unstake.connection.confirmTransaction(
    {
      signature,
      blockhash,
      lastValidBlockHeight,
    }
  );
}

const unstakeTx = prepareUnstakeTx(exchangeReturn, blockhash, feePayer);
unstakeTx.partialSign(MY_WALLET_KEYPAIR);
const signature = await unstake.connection.sendRawTransaction(
  unstakeTx.serialize(),
);
await unstake.connection.confirmTransaction(
  {
    signature,
    blockhash,
    lastValidBlockHeight,
  }
);

const cleanupTx = prepareCleanupTx(exchangeReturn, blockhash, feePayer);
if (cleanupTx) {
  cleanupTx.partialSign(MY_WALLET_KEYPAIR);
  const signature = await unstake.connection.sendRawTransaction(
    cleanupTx.serialize(),
  );
  await unstake.connection.confirmTransaction(
    {
      signature,
      blockhash,
      lastValidBlockHeight,
    }
  );
}

Lookup Table

We provide a utility script for creating a lookup table that contains most of the included stake pools' relevant addresses and some commonly used programs and sysvars.

# verify that your solana cli config is correct
solana config get

yarn lut

A lookup table maintained by the team is available on mainnet-beta at EhWxBHdmQ3yDmPzhJbKtGMM9oaZD42emt71kSieghy5

Learn More