Deposit, Withdraw, Borrow & Repay
Build and send the four core account operations with the MarginfiAccountWrapper.
The four core operations — deposit, withdraw, borrow and repay — are one-line calls on MarginfiAccountWrapper. The wrapper looks up the bank, the mint's token program, oracle prices and lookup tables from the client, builds the instructions, and hands you back transactions ready to sign.
This page assumes you have a client and wrappedAccount from Getting Started. Amounts are always in token UI units (Amount = BigNumber | number | string, e.g. "100" for 100 USDC), never native lamports.
Return shapes
Deposits and repays never need an oracle crank, so they come back as a single legacy transaction. Borrows and withdrawals run a health check on-chain, which may require cranking Switchboard oracles first, so they come back as a list.
| Method | Returns | Transaction type |
|---|---|---|
makeDepositTx | ExtendedTransaction | legacy Transaction (feePayer set, no blockhash) |
makeRepayTx | ExtendedTransaction | legacy Transaction (feePayer set, no blockhash) |
makeBorrowTx | TransactionBuilderResult = { transactions, actionTxIndex } | ExtendedV0Transaction[] (blockhash already set) |
makeWithdrawTx | TransactionBuilderResult = { transactions, actionTxIndex } | ExtendedV0Transaction[] (blockhash already set) |
ExtendedTransaction / ExtendedV0Transaction are plain web3.js transactions with extra metadata: type (TransactionType.DEPOSIT, BORROW, CRANK, ...), optional signers (extra keypairs the SDK generated, e.g. temporary wSOL accounts), addressLookupTables and unitsConsumed. actionTxIndex points at the transaction that holds your actual action; anything before it is an oracle crank. See Transactions for the full result-type reference.
Borrow and withdraw may return more than one transaction. Always iterate
over result.transactions and send them in order — never just the last one.
Deposit
makeDepositTx(bankAddress: PublicKey, amount: Amount, opts?: MakeDepositIxOpts): Promise<ExtendedTransaction>MakeDepositIxOpts:
Prop
Type
import { AssetTag } from "@0dotxyz/p0-ts-sdk";
import { PublicKey } from "@solana/web3.js";
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const usdcBank = client.getBanksByMint(USDC_MINT, AssetTag.DEFAULT)[0];
// Deposit 100 USDC
const depositTx = await wrappedAccount.makeDepositTx(usdcBank.address, "100");
// Legacy transaction: set a blockhash before signing
depositTx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
depositTx.feePayer = wallet.publicKey;
// Optional: simulate first
const sim = await connection.simulateTransaction(depositTx);
console.log(`Compute units: ${sim.value.unitsConsumed}`);computeMaxDepositForBank tells you how much the bank still accepts (its deposit cap, optionally min'd with your wallet balance) — see Max amounts.
Withdraw
makeWithdrawTx(bankAddress: PublicKey, amount: Amount, withdrawAll?: boolean, opts?: MakeWithdrawIxOpts): Promise<TransactionBuilderResult>Pass withdrawAll = true to close the position entirely; the SDK then excludes the bank from the health check (its balance goes to zero) and amount is ignored by the program.
MakeWithdrawIxOpts:
Prop
Type
// Largest withdrawal that keeps the account healthy (token UI units)
const maxWithdraw = wrappedAccount.computeMaxWithdrawForBank(solBank.address);
const withdrawResult = await wrappedAccount.makeWithdrawTx(
solBank.address,
"0.5", // Withdraw 0.5 SOL
false // withdrawAll
);
console.log(`Transactions: ${withdrawResult.transactions.length}`);If the account has no liabilities the SDK skips the oracle crank entirely and you get exactly one transaction.
Borrow
makeBorrowTx(bankAddress: PublicKey, amount: Amount, opts?: MakeBorrowIxOpts): Promise<TransactionBuilderResult>MakeBorrowIxOpts:
Prop
Type
// Max borrow in token UI units (health, caps, liquidity and rate limiters)
const maxBorrow = wrappedAccount.computeMaxBorrowForBank(usdcBank.address);
console.log(`Max borrow: ${maxBorrow.toString()} USDC`);
const borrowResult = await wrappedAccount.makeBorrowTx(
usdcBank.address,
"100" // Borrow 100 USDC
);
console.log(`Transactions in bundle: ${borrowResult.transactions.length}`);
console.log(`Main action at index: ${borrowResult.actionTxIndex}`);Check computeMaxBorrowForBank before building — the program rejects a borrow that breaches initial health, a bank cap or a rate limiter, and the health-based number already accounts for the bank's origination fee. Details on Health, Max Amounts & E-mode.
Repay
makeRepayTx(bankAddress: PublicKey, amount: Amount, repayAll?: boolean, opts?: MakeRepayIxOpts): Promise<ExtendedTransaction>repayAll = true repays the whole liability including interest accrued up to execution. Still pass a realistic amount: the program settles the full debt regardless, but for the SOL bank the SDK uses amount to decide how much native SOL to wrap, and your token balance must cover the final figure. MakeRepayIxOpts is identical to MakeDepositIxOpts (wrapAndUnwrapSol, wSolBalanceUi, overrideInferAccounts).
// Repay 50 USDC
const repayTx = await wrappedAccount.makeRepayTx(usdcBank.address, "50");
// ...or close the debt entirely (amount is an estimate; repayAll settles the exact figure)
const repayAllTx = await wrappedAccount.makeRepayTx(usdcBank.address, "100.5", true);
repayTx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
repayTx.feePayer = wallet.publicKey;To repay debt with collateral you already hold in the account (no wallet funds), use makeRepayWithCollatTx — see Position Swaps.
Sending
Whatever the operation, the same loop works: normalise to an array, sign each transaction (plus any signers the SDK attached), send in order, and wait for confirmation before sending the next one.
wallet.sendTransaction from @solana/wallet-adapter-react accepts both legacy and versioned transactions. Legacy transactions (deposit/repay) need a blockhash; versioned ones (borrow/withdraw) already have one.
import { VersionedTransaction } from "@solana/web3.js";
import type { SolanaTransaction } from "@0dotxyz/p0-ts-sdk";
async function sendAll(txs: SolanaTransaction[]) {
const signatures: string[] = [];
for (const tx of txs) {
if (!(tx instanceof VersionedTransaction)) {
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
tx.feePayer = wallet.publicKey;
}
const sig = await wallet.sendTransaction(tx, connection, {
signers: tx.signers, // extra keypairs generated by the SDK, if any
});
await connection.confirmTransaction(sig, "confirmed");
signatures.push(sig);
}
return signatures;
}
await sendAll([depositTx]);
await sendAll(borrowResult.transactions);Before sending a multi-transaction result you can simulate the whole list in one call with simulateBundle(connection.rpcEndpoint, result.transactions). Ordering guarantees, Jito bundles, priority fees and lookup tables are covered on Transactions.
FAQ
Related
- Health, Max Amounts & E-mode —
computeMax*ForBankand what limits them - Transactions — result types, oracle cranking, bundles, priority fees
- Position Swaps — repay with collateral, collateral and debt swaps
- Lending and Borrowing — protocol mechanics
- Examples:
01-deposit.ts,02-borrow.ts,03-withdraw.ts,04-repay.ts