Getting Started
Install the Project 0 TypeScript SDK, pick an environment, and run your first deposit and borrow.
The p0-ts-sdk (@0dotxyz/p0-ts-sdk) is the official SDK for the marginfi protocol on Solana. This page gets you from an empty project to a funded account with a deposit and a borrow. Everything after that lives in the topic pages linked at the end.
Installation
The minimum supported version is 2.7.0 (required for the 0.1.10 program upgrade):
npm install @0dotxyz/p0-ts-sdk@^2.7.0The SDK is built against these peer dependencies — match them in your project to avoid duplicate PublicKey/Transaction classes:
| Package | Version |
|---|---|
@solana/web3.js | 1.98.4 |
@coral-xyz/anchor | 0.30.1 |
The package exposes four entry points:
| Import path | What it contains |
|---|---|
@0dotxyz/p0-ts-sdk | Everything you normally need: Project0Client, MarginfiAccountWrapper, bank/price/health helpers, types |
@0dotxyz/p0-ts-sdk/vendor | Heavy oracle and venue integrations (Pyth, Switchboard, Kamino, Drift) kept out of the main bundle |
@0dotxyz/p0-ts-sdk/jupiter | Jupiter swap helpers |
@0dotxyz/p0-ts-sdk/instructions | Raw Anchor instruction builders — see Architecture |
Environments
getConfig(environment, overrides?) returns a Project0Config for one of the deployed groups:
import { getConfig } from "@0dotxyz/p0-ts-sdk";
import type { Environment, Project0Config } from "@0dotxyz/p0-ts-sdk";
// Environment = "production" | "staging" | "staging-mainnet-clone" | "staging-alt"
const config: Project0Config = getConfig("production");
// { environment: "production", programId: PublicKey, groupPk: PublicKey }
console.log(config.programId.toBase58(), config.groupPk.toBase58());
// Override the program or group (e.g. a local fork) while keeping the rest
const custom = getConfig("staging", { groupPk: myGroupPk });| Environment | Cluster | Use it for |
|---|---|---|
production | Mainnet | Real funds |
staging | Mainnet | Integration testing against the staging group (already runs program 0.1.10) |
staging-mainnet-clone | Mainnet | Staging group mirroring production bank configs |
staging-alt | Mainnet | Secondary staging group |
getConfig() with no argument defaults to "production". The program and group addresses are listed on Program Addresses.
The client never holds a signer. Project0Client is constructed with an
empty Anchor wallet and only reads chain state and builds
transactions. Signing and sending is always your job — with a wallet adapter
in the browser or a Keypair on the server.
Quickstart
This walks through the class-based API (Project0Client + MarginfiAccountWrapper). The lower, functional layers are covered in Architecture.
Initialize the client
The Project0Client is the entry point. It loads the group, every bank, oracle prices, mint data, lookup tables and integration metadata at initialization.
import { Connection } from "@solana/web3.js";
import { Project0Client, getConfig } from "@0dotxyz/p0-ts-sdk";
const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
const config = getConfig("production");
const client = await Project0Client.initialize(connection, config);
console.log(`Loaded ${client.banks.length} banks`);Initialization fetches all protocol data upfront. Reuse one client instance across your application instead of re-initializing per request.
Create or load an account
A marginfi account is a PDA derived from your wallet, an account index and an optional third-party id. Create one if you don't have it, otherwise load it:
// First time: create an account at index 0
const createTx = await client.createMarginfiAccountTx(wallet.publicKey, 0);
const sig = await wallet.sendTransaction(createTx, connection);
await connection.confirmTransaction(sig);
// Afterwards: discover and load it
const [accountAddress] = await client.getAccountAddresses(wallet.publicKey);
const wrappedAccount = await client.fetchAccount(accountAddress);fetchAccount also simulates the account's health cache so health-based numbers are fresh. Multiple accounts per wallet, PDA derivation, projection and account lifecycle are covered on Accounts.
Find a bank
Banks are lending pools for a single mint. A mint can have several banks (a standard bank plus Kamino/Drift/Jupiter Lend wrappers), so filter by AssetTag to get the standard one:
import { PublicKey } from "@solana/web3.js";
import { AssetTag } from "@0dotxyz/p0-ts-sdk";
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const [usdcBank] = client.getBanksByMint(USDC_MINT, AssetTag.DEFAULT);
if (!usdcBank) throw new Error("USDC bank not found");See Banks & Prices for asset tags, bank metrics and oracle prices.
Deposit
makeDepositTx(bankAddress, amount, opts?) returns a single legacy Transaction (an ExtendedTransaction). Amounts are in UI units:
const depositTx = await wrappedAccount.makeDepositTx(usdcBank.address, "100");
const sig = await wallet.sendTransaction(depositTx, connection);
await connection.confirmTransaction(sig);Borrow
Borrows (and withdrawals) are health-checked on-chain, which can require oracle cranks in separate transactions. makeBorrowTx therefore returns a TransactionBuilderResult — { transactions, actionTxIndex } — and you send every transaction in order. Check the limit first with computeMaxBorrowForBank:
import { simulateBundle } from "@0dotxyz/p0-ts-sdk";
// Largest borrow the account can take from this bank right now (UI units)
const maxBorrow = wrappedAccount.computeMaxBorrowForBank(usdcBank.address);
console.log(`Max borrow: ${maxBorrow.toString()} USDC`);
const borrowResult = await wrappedAccount.makeBorrowTx(usdcBank.address, "50");
console.log(`Transactions: ${borrowResult.transactions.length}`);
console.log(`Borrow is at index: ${borrowResult.actionTxIndex}`);
// Optional: simulate the whole set at once (needs an RPC that supports simulateBundle)
const results = await simulateBundle(connection.rpcEndpoint, borrowResult.transactions);
results.forEach((r, i) => {
if (r.err) throw new Error(`tx ${i} failed: ${JSON.stringify(r.err)}`);
});
// Sign and send in order
for (const tx of borrowResult.transactions) {
const sig = await wallet.sendTransaction(tx, connection);
await connection.confirmTransaction(sig);
}Always send all transactions in borrowResult.transactions, in order. The
extra ones crank stale oracles; skipping them makes the borrow fail its
health check.
Repay, withdraw, the per-operation options and the canonical signing snippets are on Lending & Borrowing. Multi-transaction handling, bundles and priority fees are on Transactions.
Next steps
Accounts
Create, derive, load and read accounts; close and transfer them.
Banks & Prices
Find banks, asset tags, bank metrics, rate limiters and oracle prices.
Lending & Borrowing
Deposit, withdraw, borrow and repay with all options.
Health & Max Amounts
Health cache, free collateral, max borrow/withdraw/deposit, e-mode.
Looping
Leverage loops with swap-provider config and bridged routes.
TypeScript support
The SDK is written in TypeScript and exports all of its types from the root entry point:
import type {
MarginfiAccountType,
BankType,
BalanceType,
OraclePrice,
Project0Config,
MarginRequirementType,
} from "@0dotxyz/p0-ts-sdk";Enable strict mode in your tsconfig.json for the best experience:
{
"compilerOptions": {
"strict": true,
"moduleResolution": "bundler",
"target": "ES2022"
}
}Related
- Overview — package entry points, running the examples, getting help
- Accounts — everything about marginfi accounts
- Lending & Borrowing — the four core operations in depth
- Program Upgrade 0.1.10 — why
>= 2.7.0is required - Examples:
01-deposit.ts,02-borrow.ts