Skip to main content
These flows use many instructions. If you hit compute limits, add ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }) (or higher) as the first instruction in your transaction.
Deposit your stake account into a Single Pool, receive pool tokens, then deposit those tokens into the corresponding Native Staked Vault (nsHELIUS, nsJUPITER, etc.) as collateral. Flow:
  1. Split stake into a new account
  2. Deposit the new stake into Single Pool → receive pool tokens
  3. Deposit pool tokens into vault via getOperateIx

Deposit

1

Import Dependencies

npm install @solana/web3.js @solana/spl-token @solana/spl-single-pool-classic bn.js @jup-ag/lend @jup-ag/lend-read
import {
  SinglePoolProgram,
  findPoolAddress,
  findPoolMintAddress,
} from "@solana/spl-single-pool-classic";
import {
  Connection, Keypair, PublicKey, StakeProgram,
  TransactionMessage, VersionedTransaction,
} from "@solana/web3.js";
import BN from "bn.js";
import {
  createAssociatedTokenAccountInstruction,
  getAccount, getAssociatedTokenAddressSync,
} from "@solana/spl-token";
import { getOperateIx } from "@jup-ag/lend/borrow";
import { Client } from "@jup-ag/lend-read";
2

Split Stake and Deposit into Single Pool

Use StakeProgram.split and SinglePoolProgram.deposit from @solana/spl-single-pool-classic.
const SINGLE_POOL_PROGRAM_ID = new PublicKey("SVSPxpvHdN29nkVg9rPapPNDddN5DipNLRUFhyjFThE");
const pool = await findPoolAddress(SINGLE_POOL_PROGRAM_ID, voteAccount);
const newStakeAccount = Keypair.generate();
const stakeRent = await connection.getMinimumBalanceForRentExemption(StakeProgram.space);

const { instructions: splitInstructions } = StakeProgram.split(
  { stakePubkey: userStakeAccount, authorizedPubkey: signer, splitStakePubkey: newStakeAccount.publicKey, lamports: lamportAmount.toNumber() },
  stakeRent
);
instructions.push(...splitInstructions);

const mint = await findPoolMintAddress(SINGLE_POOL_PROGRAM_ID, pool);
const userAta = getAssociatedTokenAddressSync(new PublicKey(mint), signer);
try { await getAccount(connection, userAta); } catch {
  instructions.push(createAssociatedTokenAccountInstruction(signer, userAta, signer, new PublicKey(mint)));
}

const depositIxs = await SinglePoolProgram.deposit({
  connection, userWallet: signer, pool, userStakeAccount: newStakeAccount.publicKey,
});
instructions.push(...depositIxs.instructions);
3

Deposit Pool Tokens into Vault

Find the vault by supplyToken = pool mint, then use getOperateIx.
const client = new Client(connection);
const allVaults = await client.vault.getAllVaults();
const vault = allVaults.find((v) => v.constantViews.supplyToken.equals(poolMint));
if (!vault) throw new Error("Vault not found");

const { ixs: operateIxs, addressLookupTableAccounts } = await getOperateIx({
  vaultId: vault.constantViews.vaultId,
  positionId: 0,
  colAmount: poolTokenAmount,
  debtAmount: new BN(0),
  connection,
  signer,
});
instructions.push(...operateIxs);
4

Assemble and Execute the Transaction

Build a v0 message with all instructions and address lookup tables, sign with the user keypair and new stake account keypair.
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
const message = new TransactionMessage({
  payerKey: signer,
  recentBlockhash: blockhash,
  instructions,
}).compileToV0Message(addressLookupTableAccounts ?? []);

const tx = new VersionedTransaction(message);
tx.sign([userKeypair, newStakeAccount]);

const signature = await connection.sendTransaction(tx, {
  skipPreflight: false, maxRetries: 3, preflightCommitment: "confirmed",
});
console.log("Deposit successful:", signature);
If the user already holds pool tokens, skip the Single Pool steps and only call getOperateIx to deposit into the vault.