TypeScript SDK

Integration Banks & Native Stake

Deposit into and withdraw from Kamino and Jupiter Lend banks, and turn native stake accounts into LSTs you can deposit.

Some Project 0 banks do not hold the token themselves: they route deposits into an external venue — Kamino or Jupiter Lend — and account for your position in that venue's receipt token. Native stake banks work the other way round: you convert a stake account into a single-validator LST first, then deposit the LST like any other asset. This page assumes you have a client and wrappedAccount from Getting Started.

What integration banks are

A bank's config.assetTag tells you which venue backs it: AssetTag.KAMINO (3) or AssetTag.JUPLEND (6). They are separate banks from the AssetTag.DEFAULT bank of the same mint, so use the tag when you look them up:

import { AssetTag, USDC_MINT } from "@0dotxyz/p0-ts-sdk";

const [kaminoUsdc] = client.getBanksByMint(USDC_MINT, AssetTag.KAMINO);
const [jupUsdc] = client.getBanksByMint(USDC_MINT, AssetTag.JUPLEND);

Each integration bank exposes the venue accounts it needs on the bank itself — bank.kaminoIntegrationAccounts (kaminoReserve, kaminoObligation) or bank.jupLendIntegrationAccounts (jupLendingState, jupFTokenVault, jupFTokenAta). Positions in these banks are denominated in the venue's share token (cTokens, fTokens), and client.assetShareValueMultiplierByBank converts them back to the underlying; see Banks & Prices for tags and multipliers, and Cross-venue Integrations for the protocol mechanics and comingling rules.

Reading integration state

Project0Client.initialize() fetches the live venue state for every integration bank into client.bankIntegrationMap, a plain object keyed by bank address (BankIntegrationMetadataMap):

const kaminoMeta = client.bankIntegrationMap[kaminoUsdc.address.toBase58()];
const jupMeta = client.bankIntegrationMap[jupUsdc.address.toBase58()];

kaminoMeta?.kaminoStates; // { reserveState: KaminoReserve, obligationState: KaminoObligation, farmState? }
jupMeta?.jupLendStates; // { jupLendingState, jupTokenReserveState, jupRewardsRateModel, fTokenTotalSupply }

The deposit and withdraw methods below take these objects as arguments, so you read them from the map and pass them through. The map is a snapshot from when the client was initialised; to refresh it without rebuilding the client, call fetchBankIntegrationMetadata({ connection, banks: client.banks, integrations?: ["kamino" | "juplend"] }) and merge the result with Object.assign(client.bankIntegrationMap, fresh).

Deposit and withdraw

const bankKey = kaminoUsdc.address.toBase58();
const reserve = client.bankIntegrationMap[bankKey]!.kaminoStates!.reserveState;

// makeKaminoDepositTx(bankAddress, amount, reserve, opts?) -> ExtendedV0Transaction
const depositTx = await wrappedAccount.makeKaminoDepositTx(kaminoUsdc.address, "100", reserve);

// makeKaminoWithdrawTx(bankAddress, amount, reserve, withdrawAll?, opts?) -> TransactionBuilderResult
const withdrawResult = await wrappedAccount.makeKaminoWithdrawTx(
  kaminoUsdc.address,
  "50",
  reserve,
  false
);

Amounts are UI units of the underlying token; the SDK divides by the bank's share multiplier to get the cToken amount the program expects. The Layer 3 makeKaminoWithdrawTx additionally accepts a TypedAmount ({ value, type: "cToken" }) when you already hold a cToken quantity. opts are the usual MakeDepositIxOpts / MakeWithdrawIxOpts (wrapAndUnwrapSol, createAtas, ...).

Deposits return a single ExtendedV0Transaction; withdrawals return { transactions, actionTxIndex } because, like any withdraw, they may need an oracle crank first. Send them exactly as you would a normal borrow or withdraw.

You never add venue refresh instructions yourself. The builders prepend a Kamino reserve refresh or Jupiter Lend rate update to the action transaction whenever the account touches an integration bank — including normal borrows and withdraws on other banks while you hold an integration position. Because that state goes stale within a slot, flash-loan flows that include one set mustBeAtomicBundle: true. See Transactions.

Other operations — borrowing against an integration position, looping into a Kamino bank, swapping collateral — use the regular methods; the only integration-specific entry points are the deposit and withdraw pairs above.

Native stake

AssetTag.STAKED banks accept a single-validator LST minted by the SPL single-pool program. Moving a native stake account into Project 0 is therefore two steps: mint the LST from the stake account, then deposit the LST. The native-stake helpers are standalone functions (not wrapper methods) exported from the package root, in src/services/native-stake/.

Find the bank and validator

The SDK ships a hardcoded map of staked banks so you can go from a bank to its validator vote account and LST mint without extra RPC calls:

import { getStakedBankMetadataMap, getValidatorVoteAccountByBank } from "@0dotxyz/p0-ts-sdk";
import { PublicKey } from "@solana/web3.js";

const stakedMeta = getStakedBankMetadataMap(); // Map<bankAddress, StakedBankMetadata>
const entry = stakedMeta.get(stakedBankAddress.toBase58())!;
// entry: { bankAddress, validatorVoteAccount, tokenAddress (LST mint), tokenName, tokenSymbol }

const validator = new PublicKey(getValidatorVoteAccountByBank()[stakedBankAddress.toBase58()]!);

Your stake account must be delegated to that validator; stake on a different validator cannot be minted into this bank's LST.

Mint the LST from a stake account

makeMintStakedLstTx creates the LST ATA if needed, splits the stake account when amount is less than its delegated stake, re-authorises staker and withdrawer to the pool, and deposits the stake into the single-validator pool. You receive LST in your wallet.

import { makeMintStakedLstTx } from "@0dotxyz/p0-ts-sdk";

const mintTx = await makeMintStakedLstTx({
  amount: "1.5", // SOL (UI units); pass a value >= the delegated stake to convert all of it
  authority: wrappedAccount.authority,
  stakeAccountPk: new PublicKey("<your stake account>"),
  validator,
  connection,
  luts: client.addressLookupTables,
});
// ExtendedV0Transaction, type DEPOSIT_STAKE; mintTx.signers holds the split keypair for partial amounts

Deposit the LST

The LST is an ordinary SPL token, so the deposit is a normal makeDepositTx into the staked bank:

const depositTx = await wrappedAccount.makeDepositTx(stakedBankAddress, "1.5");

Redeem and merge

makeRedeemStakedLstTx does the reverse — creates a fresh stake account, burns LST and withdraws the stake from the pool into it (the new keypair is in tx.signers) — and makeMergeStakeAccountsTx merges two of your stake accounts (same authority and validator) so you can mint from one:

import { makeRedeemStakedLstTx, makeMergeStakeAccountsTx } from "@0dotxyz/p0-ts-sdk";

const redeemTx = await makeRedeemStakedLstTx({
  amount: "1.5", // LST amount (UI units)
  authority: wrappedAccount.authority,
  validator,
  connection,
  luts: client.addressLookupTables,
});

const mergeTx = await makeMergeStakeAccountsTx({
  authority: wrappedAccount.authority,
  sourceStakeAccount: new PublicKey("<stake account to drain>"),
  destinationStakeAccount: new PublicKey("<stake account to keep>"),
  connection,
  luts: client.addressLookupTables,
});

All three return an ExtendedV0Transaction (types DEPOSIT_STAKE, WITHDRAW_STAKE, MERGE_STAKE_ACCOUNTS) and accept an optional blockhash. Withdrawing from the staked bank is a normal makeWithdrawTx; redeem afterwards if you want the stake account back. Instruction-level variants makeMintStakedLstIx / makeRedeemStakedLstIx are exported too. For the user-facing flow and the list of supported validators see the Staking guide.

The examples/12–14 scripts in the SDK repository call these as wrapper methods (wrappedAccount.makeMintStakedLstTx(...)); in 2.7.x the exported API is the standalone functions shown above.

Gamma LP vaults

The root export also contains Layer 3 builders for Gamma LP vaults (src/services/vaults/): makeVaultDepositTx({ user, lpVault, connection, amount, tokenProgram?, luts?, blockhash? }) (amount in raw base units of the vault asset), makeVaultDepositWithSwapTx (swap inputMint into the vault asset, then deposit), makeVaultWithdrawTx({ ..., sharesAmount }), makeVaultCompleteWithdrawalTx, their *Ix counterparts, and the readers fetchGammaLpVault, fetchGammaWithdrawReceipt, resolveVaultTokenProgram. These act on the vault program directly rather than on a marginfi account.

On this page