Integration Banks & Native Stake
Deposit into and withdraw from Kamino, Drift 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, Drift 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), AssetTag.DRIFT (4) 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 [driftUsdc] = client.getBanksByMint(USDC_MINT, AssetTag.DRIFT);
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), bank.driftIntegrationAccounts (driftSpotMarket, driftUser, driftUserStats), 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 meta = client.bankIntegrationMap[kaminoUsdc.address.toBase58()];
meta?.kaminoStates; // { reserveState: KaminoReserve, obligationState: KaminoObligation, farmState? }
meta?.driftStates; // { spotMarketState: DriftSpotMarket, userState: DriftUser, userRewards: DriftRewards[], userStatsState? }
meta?.jupLendStates; // { jupLendingState, jupTokenReserveState, jupRewardsRateModel, jupRateModel, 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" | "drift" | "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, Drift market update 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 amountsDeposit 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.
Related
- Banks & Prices — asset tags, comingling rules, share multipliers
- Transactions — integration refresh instructions and
mustBeAtomicBundle - Cross-venue Integrations — how integration banks work on-chain
- Staking guide — native stake in the app
- Examples:
12-mint-staked-lst.ts,13-redeem-staked-lst.ts,14-merge-stake-accounts.ts