Borrow

Borrow against a coin

Lock a coin's tokens in its pool and borrow SOL from the same pool. One fee up front, no interest, no deadline. quoteBorrow mirrors every check the program makes, so a loan you quote is a loan that lands.

How loans work

  • Lending opens on a coin once its pool holds 0.1 SOL (MIN_POOL_SOL_FOR_LENDING) and 150 slots after the pool was created.
  • You can borrow up to 40% of your tokens' value (MAX_LTV_BPS). The value is what the tokens would sell for against a pool of the same depth, at the lower of the spot and the safe price, so a pumped price can't be borrowed against.
  • A pool lends out at most 15% of its SOL (BORROW_CAP_BPS); the minimum loan is 0.01 SOL (MIN_DEBT_LAMPORTS).
  • The only cost is a one-time fee taken from the loan: 1% when the pool's cap is empty, rising to 3% at 80% use and 10% when full.
  • The pool sets aside the matching share of its tokens with the SOL it lends, so taking or repaying a loan doesn't move the price.
  • One position per wallet per coin. It stays open until you repay it, or until it is liquidated.

Quote a loan

ts
import { quoteBorrow } from "@proglet/sdk";

const pool = await client.fetchPool(mint);
const slot = BigInt(await connection.getSlot());

const max = quoteBorrow(pool!, collateral, slot);              // the most you can borrow
const q = quoteBorrow(pool!, collateral, slot, 20_000_000n);   // or a chosen amount (capped at the max)

if (q.blocked) throw new Error(q.blocked); // "lending-closed" | "cooldown" | "no-price" | "too-small"
valuebigintCollateral value in lamports at min(spot, safe price), the price borrow uses.
maxDebtbigintThe largest loan the program accepts right now: 40% LTV, the pool's cap, the pool's SOL.
cappedbooleanTrue when the pool's cap, not the 40% LTV, sets maxDebt.
debtbigintThe loan this quote is for.
feeBps, feebigintThe one-time fee rate and amount.
receivebigintWhat lands in the wallet: debt minus fee.
blockedstring?Why borrow would fail now, if it would.

Borrow

ts
import { borrowInstruction } from "@proglet/sdk";

const ix = borrowInstruction({
  mint,
  owner: wallet.publicKey,
  collateral,
  debt: (max.maxDebt * 9_950n) / 10_000n, // a hair under the max, in case the price ticks down
  maxFeeBps: q.feeBps + 25n,              // the fee can rise if someone borrows first
});

maxFeeBps protects you: if the fee has risen above it by the time the transaction lands, it fails with FeeAboveMax. Opening a loan also pays rent for the position account (about 0.0016 SOL), refunded when you repay in full.

Borrow more or add collateral

borrow also adds to an open position. The 40% check runs on the position's totals, and the fee is charged on the new debt only. Pass the open position to quoteBorrow to quote it:

ts
const position = await client.fetchPosition(mint, wallet.publicKey);

// Borrow more against what is already locked:
const more = quoteBorrow(pool!, 0n, slot, undefined, position!);
borrowInstruction({ mint, owner, collateral: 0n, debt: more.maxDebt, maxFeeBps: more.feeBps + 25n });

// Only add collateral (no fee, lowers the risk; works even while new loans are paused):
borrowInstruction({ mint, owner, collateral: extraTokens, debt: 0n, maxFeeBps: 0n });

Repay

repayInstruction pays the whole debt, returns all the collateral and closes the position. repayPartialInstruction pays part of it and returns the same share of the collateral, so the loan's LTV doesn't change.

ts
import { quoteRepayPartial, repayInstruction, repayPartialInstruction } from "@proglet/sdk";

const part = quoteRepayPartial(position!, 5_000_000n);
// part.ok, part.collateralOut, part.remainingDebt, part.maxPartial

const ix = part.ok ? repayPartialInstruction(mint, wallet.publicKey, 5_000_000n) : repayInstruction(mint, wallet.publicKey);
What stays after a partial repay must be at least 0.01 SOL, and a loan can't be repaid in the same couple of slots it was opened or increased (MIN_LOAN_SLOTS), which rules out flash loans. Repaying is never paused.

Health and liquidation

ts
import { positionHealth, liquidateInstruction, absorbInstruction } from "@proglet/sdk";

const h = positionHealth(pool!, position!, slot); // valued at the safe price, like the program
h?.ltvBps;        // debt / value
h?.liquidatable;  // true past 1/1.5 = 66.7%
  • A loan can be liquidated by anyone once its collateral, at the safe price, is worth less than 1.5 times the debt. The liquidator pays the debt and receives tokens worth the debt plus a bonus of 2.5% to 15% (deeper underwater, bigger bonus); the rest of the collateral stays in the pool.
  • If the collateral is worth less than the debt itself, anyone can call absorb instead: the position closes, its tokens go into the pool, and the pool takes the SOL loss.
  • Both are valued at the safe price, never the live spot, so a one-slot dump can't trigger them.