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 },
scopeOpts: { mode: "on-chain", connection }, // or { mode: "api", scopeOnchainData: {...} }
oracleMultiplierOpts: { mode: "on-chain", connection }, // or { mode: "api", multiplierData: {...} }
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 and Scope banks are fetched from chain (mode: "on-chain") or from your own price API (mode: "api"). Oracle cranking — writing fresh Switchboard 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.
Scope and exchange-rate oracles (program 0.1.11)
Program 0.1.11 adds two families of oracle setup, and fetchOracleData takes one optional option block for each:
scopeOpts— banks withOracleSetup.Scope. The SDK reads the bank's entry (config.scopeEntryIndex) out of the ScopeOraclePricesaccount inoracleKeys[0]and applies the bank'soracleMaxAgeto the entry timestamp. Scope prices carry no confidence interval, solowestPrice === price === highestPrice.oracleMultiplierOpts— banks priced as a Pyth base feed times an on-chain exchange rate:PythMSOL/KaminoMSOL/JuplendMSOL(Marinade),PythLST/KaminoLST/JuplendLST(SPL and Sanctum stake pools) andPTPyth/PTFixed(Exponent vaults). The SDK fetches the rate account, mirrors the program's validation (rate bounds, stake-pool epoch staleness, PT maturity schedule) and bakes the rate into the price: an mSOL bank'sOraclePriceis mSOL/USD, a jitoSOL bank's is jitoSOL/USD, a PT bank's is the PT's own USD price. This matches the price the program caches on the bank. A venue's receipt-token rate (Kamino cToken, JupLend fToken) is not in the price — it stays inclient.assetShareValueMultiplierByBank, as for every other venue bank.
Both options are optional so existing callers keep compiling. If you omit one while such banks exist, those banks are priced at zero and the SDK logs a warning — they are never silently priced from the bare base feed. Project0Client.initialize passes both in on-chain mode for you.
A bank whose setup this SDK version does not know parses as OracleSetup.Unknown (source "unknown", zero price) instead of failing to decode, so a newer program release cannot break bank loading.
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