
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@unstake-it/sol-ag
Advanced tools
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:
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
$ npm install @unstake-it/sol-ag
$ yarn add @unstake-it/sol-ag
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(),
});
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();
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",
);
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,
}
);
}
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",
);
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,
}
);
}
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
FAQs
Staked SOL instant unstake aggregator SDK
The npm package @unstake-it/sol-ag receives a total of 3 weekly downloads. As such, @unstake-it/sol-ag popularity was classified as not popular.
We found that @unstake-it/sol-ag demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.