Banks & Prices
Find banks, understand asset tags, read bank metrics and rate limiters, and work with oracle prices.
A bank is a lending pool for one mint with its own interest curve, caps, weights and oracle. This page is the home for everything bank- and price-related in the SDK. It assumes you have a client from Getting Started.
Finding banks
Project0Client.initialize loads every bank in the group once. You then read them from memory:
import { PublicKey } from "@solana/web3.js";
import { AssetTag } from "@0dotxyz/p0-ts-sdk";
// All banks as an array, or as a Map keyed by base58 address
const all = client.banks;
const byAddress = client.bankMap; // Map<string, Bank>
// One bank by address (returns `Bank | undefined`)
const bank = client.getBank(new PublicKey("BANK_ADDRESS"));
if (!bank) throw new Error("Bank not found");
// All banks for a mint, optionally filtered by asset tag
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const usdcBanks = client.getBanksByMint(USDC_MINT); // may include Kamino/Drift/JupLend wrappers
const [usdcBank] = client.getBanksByMint(USDC_MINT, AssetTag.DEFAULT);Useful fields on a Bank: address, mint, mintDecimals, tokenSymbol, config (asset tag, weights, caps, operationalState, interestRateConfig, oracleKeys), rateLimiter, lastUpdate, and the share-conversion helpers getAssetQuantity(shares) / getLiabilityQuantity(shares).
Asset tags and comingling rules
The SDK uses AssetTag to determine what kinds of assets can be held together in a single account. This prevents incompatible positions from being comingled:
| Tag | Value | Meaning |
|---|---|---|
AssetTag.DEFAULT | 0 | Regular assets (USDC, USDT, …) that can be comingled with other DEFAULT assets or with SOL |
AssetTag.SOL | 1 | Only the native SOL bank. Accounts with SOL can hold either DEFAULT assets or STAKED assets, not both |
AssetTag.STAKED | 2 | Native-stake LSTs. Accounts with STAKED positions can only deposit other STAKED assets or SOL, and can only borrow SOL |
AssetTag.KAMINO | 3 | Kamino integration banks |
AssetTag.DRIFT | 4 | Drift integration banks |
AssetTag.SOLEND | 5 | Solend integration banks (not yet active) |
AssetTag.JUPLEND | 6 | Jupiter Lend integration banks |
Comingling rules:
- DEFAULT assets can mix with other DEFAULT assets and SOL
- SOL can mix with DEFAULT assets or STAKED assets (but not both in the same account)
- STAKED assets can only mix with other STAKED assets or SOL
- You cannot hold both DEFAULT and STAKED assets in the same account (unless only SOL is present)
const usdcBank = client.getBanksByMint(USDC_MINT, AssetTag.DEFAULT)[0];
const solBank = client.getBanksByMint(SOL_MINT, AssetTag.SOL)[0];
const lstBanks = client.banks.filter((b) => b.config.assetTag === AssetTag.STAKED);
const kaminoBanks = client.banks.filter((b) => b.config.assetTag === AssetTag.KAMINO);
const driftBanks = client.banks.filter((b) => b.config.assetTag === AssetTag.DRIFT);
const juplendBanks = client.banks.filter((b) => b.config.assetTag === AssetTag.JUPLEND);Integration banks (Kamino, Drift, Jupiter Lend) reuse the same mint as a standard bank but are deposit-only wrappers with their own instructions — see Integrations.
Depositable and borrowable predicates
Two helpers tell you whether a bank accepts the standard deposit/borrow instructions, which is what you want when choosing a bank for a loop, swap or bridge hop:
import { isStandardDepositable, isStandardBorrowable } from "@0dotxyz/p0-ts-sdk";
// DEFAULT/SOL tag and Operational state
isStandardDepositable(bank);
// DEFAULT/SOL tag, Operational state, and borrowLimit > 0
isStandardBorrowable(bank);Integration wrappers set borrowLimit = 0 and reject a standard borrow with program error 6200 WrongAssetTagForStandardInstructions; ReduceOnly/Paused banks reject new deposits with 6017 BankReduceOnly.
Bank metrics
computeBankMetrics aggregates every number a bank UI usually shows. It is pure: pass whatever (bank, oraclePrice) snapshot you want reflected.
import { computeBankMetrics } from "@0dotxyz/p0-ts-sdk";
const key = usdcBank.address.toBase58();
const metrics = computeBankMetrics({
bank: usdcBank,
oraclePrice: client.oraclePriceByBank.get(key)!,
assetShareValueMultiplier: client.assetShareValueMultiplierByBank.get(key), // Kamino/Drift/JupLend only
symbol: usdcBank.tokenSymbol,
});
console.log(`${metrics.symbol}: supply ${(metrics.supplyApy * 100).toFixed(2)}% / borrow ${(metrics.borrowApy * 100).toFixed(2)}%`);
console.log(`utilization ${(metrics.utilizationRate * 100).toFixed(1)}%, TVL $${metrics.totalDepositsUsd.toFixed(0)}`);All amounts are UI-scaled numbers; USD values use the realtime oracle price with no margin weight.
Prop
Type
When you only need one or two numbers, call the per-metric helpers directly (no oracle math is run for rate or capacity helpers):
| Helper | Returns |
|---|---|
computeBankSupplyApy(bank) / computeBankBorrowApy(bank) | APY as a number fraction |
computeInterestRates(bank) | { lendingRate, borrowingRate } APRs as BigNumber |
computeUtilizationRate(bank) | BigNumber in [0, 1] |
computeRemainingCapacity(bank) | { depositCapacity, borrowCapacity } in native units, interest-buffered |
computeBankDepositCapRemaining(bank) / computeBankBorrowCapRemaining(bank) | UI number, Infinity when the cap is inactive |
computeBankAvailableLiquidity(bank, multiplier?) | BigNumber UI units: max(0, deposits − borrows) — what the vault can pay out now |
computeBankProjectedAvailableLiquidity(bank, multiplier?) | Same, minus the liquidity that interest accrual will consume before your transaction lands |
computeBankTotalDeposits(bank, multiplier?) / computeBankTotalBorrows(bank) | UI number |
computeBankProjectedAvailableLiquidity is the one the wrapper's max-withdraw/max-borrow helpers use, because the program accrues interest before its liquidity check. The interest curve and caps are described on Lending & Borrowing and Caps & Rate Limits.
Rate limiters
Banks and the group can carry net-outflow rate limiters (hourly and daily sliding windows) that cap withdrawals plus borrows. The SDK mirrors the on-chain window math read-only:
import {
computeBankRateLimitRemaining,
computeGroupRateLimitRemainingUsd,
} from "@0dotxyz/p0-ts-sdk";
// Remaining bank-level outflow in UI units of the bank's mint, or null if no limiter is enabled
const bankRemaining = computeBankRateLimitRemaining(usdcBank);
// Remaining group-level outflow in USD, or null if the group has no limiter
const groupRemainingUsd = computeGroupRateLimitRemainingUsd(client.group.rateLimiter);
console.log(bankRemaining?.toFixed(2) ?? "no bank limiter", groupRemainingUsd?.toFixed(2) ?? "no group limiter");Both accept an optional nowSeconds second argument (defaults to Date.now() / 1000). computeMaxBorrowForBank and computeMaxWithdrawForBank already take these into account — see Health & Max Amounts. Which flows are exempt and how windows roll over is explained on Caps & Rate Limits.
Oracle prices
The client fetches a price for every bank at initialization and keeps it in client.oraclePriceByBank (keyed by base58 bank address):
const oraclePrice = client.oraclePriceByBank.get(usdcBank.address.toBase58());
if (!oraclePrice) throw new Error("No price for bank");
console.log(`Realtime: $${oraclePrice.priceRealtime.price.toFixed(4)} ±${oraclePrice.priceRealtime.confidence.toFixed(4)}`);
console.log(`Weighted (EMA): $${oraclePrice.priceWeighted.price.toFixed(4)}`);
console.log(`Updated: ${new Date(oraclePrice.timestamp.toNumber() * 1000).toISOString()}`);An OraclePrice has two PriceWithConfidence legs plus a timestamp:
Prop
Type
PriceWithConfidence is { price, confidence, lowestPrice, highestPrice } (all BigNumber), where lowestPrice/highestPrice are price ∓ confidence after the SDK applies the same confidence-interval scaling and cap as the program. The program values collateral at the low bound and liabilities at the high bound; use getPrice to pick the same way:
import { getPrice, getPriceWithConfidence, PriceBias } from "@0dotxyz/p0-ts-sdk";
const collateralPrice = getPrice(oraclePrice, PriceBias.Lowest); // realtime, low bound
const debtPrice = getPrice(oraclePrice, PriceBias.Highest, true); // weighted, high bound
const mid = getPrice(oraclePrice); // PriceBias.None, realtime
const realtimeLeg = getPriceWithConfidence(oraclePrice, false); // === oraclePrice.priceRealtimeRefreshing prices
The cached prices go stale in a long-running process. Refresh them with fetchOracleData and use the returned maps (the client's map is read-only, so either rebuild the client or keep your own map for computations):
import { fetchOracleData } from "@0dotxyz/p0-ts-sdk";
const { bankOraclePriceMap, mintOraclePriceMap } = await fetchOracleData(client.banks, {
pythOpts: { mode: "on-chain", connection }, // or { mode: "api", pythOnchainData: {...}, stakedCollatData: {...} }
swbOpts: { mode: "on-chain", connection },
isolatedBanksOpts: { fetchPrices: true },
});
const freshUsdc = bankOraclePriceMap.get(usdcBank.address.toBase58());fetchOracleData routes each bank by its oracle setup: zero-oracle banks get a zero price, fixed-price banks their constant, and Pyth/Switchboard banks are fetched from chain (mode: "on-chain") or from your own price API (mode: "api"). Oracle cranking — writing fresh Switchboard/Pyth updates on-chain before a borrow or withdraw — is handled by the action builders and documented on Transactions. How the protocol picks and validates oracles is on Oracles.
Bank state
bank.config.operationalState is an OperationalState enum value:
| State | Effect |
|---|---|
Operational | Normal operation |
ReduceOnly | No new deposits or borrows; withdraws and repays allowed |
ReduceOnlyWithBorrowingPower | Reduce-only, but existing deposits still count as collateral |
Paused | All operations blocked |
Uninitialized | Awaiting a Jupiter Lend seed deposit; all operations blocked |
KilledByBankruptcy | Bank was wound down after a bankruptcy |
CircuitBroken | Halted by the per-bank oracle circuit breaker (0.1.10+); risk-increasing operations are rejected until the price settles |
CircuitBroken only decodes with SDK >= 2.7.0; see Program Upgrade 0.1.10. The protocol-level description of each state is on Bank States.
import { OperationalState } from "@0dotxyz/p0-ts-sdk";
const active = client.banks.filter((b) => b.config.operationalState === OperationalState.Operational);Related
- Lending & Borrowing — using a bank once you have picked one
- Health & Max Amounts — bank-aware max borrow/withdraw/deposit
- Integrations — Kamino, Drift and Jupiter Lend banks
- Oracles and Caps & Rate Limits — protocol background
- Example:
05-oracle-prices.ts