Developers
TypeScript SDK
Read markets, quote the band math and build instructions.
The TypeScript SDK, @taper/sdk, reads Taper's accounts, quotes the band math exactly as the program computes it, and builds every instruction from the IDL. It runs on Node 24.
Load a client
import { TaperClient, PublicKey, NATIVE_MINT, marketAddresses } from "@taper/sdk";
const client = await TaperClient.load("./idl/taper.json", process.env.SOLANA_RPC_URL!);
// Markets are derived from the collateral mint, USDC and the admin that created them.
const admin = new PublicKey(process.env.TAPER_ADMIN!);
const { market } = marketAddresses(NATIVE_MINT, admin);
// Checks mints, token programs, vault authorities and accounting in one finalized snapshot.
const state = await client.verifiedMarket(market, NATIVE_MINT, admin);
console.log(state.slot, state.market);load takes an optional fourth argument, a keypair file path. Without it the client is read-only: it can read accounts and build instructions, but not sign. It never reads a default wallet.
Addresses
import { loanAddress, lenderAddress } from "@taper/sdk";
const loan = loanAddress(market, borrower);
const lender = lenderAddress(market, owner);Quotes
The math functions use checked bigint arithmetic and round exactly as the program does.
| Function | Returns |
|---|---|
quoteSwap(bands, base, A, oracle, amount, feeBps, buyCollateral, maxCrossings) | Input used, output, fee and bands crossed for a trade across the bands. |
quoteBand(x, y, oracle, upper, A, input, feeBps, buyCollateral) | The same for one band. |
oracleJumpFee(previous, next, previousFeeBps, elapsed) | The fee a band trade pays after a price move. |
accruedDebtIndex(market, timestamp) | The debt index at a given second. |
loanHealth(bands, loan, debtIndex, oracle, discountBps) | The loan's dollars, collateral, debt and margin. |
import { quoteSwap, oracleJumpFee, type BandState } from "@taper/sdk";
// From the Market account: lastOraclePrice, oracleFeeTime and basePrice as bigints;
// feeBps, oracleFeeBps and amplification as numbers. From the Bands account: each
// band's x, y and shares as bigints.
const jump = oracleJumpFee(lastOraclePrice, oracle, oracleFeeBps, now - oracleFeeTime);
const fee = Math.max(feeBps, jump);
// Buy collateral with 1,000 USDC, crossing at most 8 bands.
const q = quoteSwap(bands as BandState[], basePrice, amplification, oracle, 1_000_000_000n, fee, true, 8);
console.log(q.output, q.crossings);Instructions
Every instruction has a builder that takes named arguments and every account explicitly. Building submits nothing.
const ix = await client.openLoan(
{ collateral: 25_000_000n, debt: 1_500_000_000n, count: 12 },
{
borrower, market, bands, loan,
borrowerStable, borrowerCollateral, stableVault, collateralVault,
collateralMint: NATIVE_MINT, collateralTokenProgram: TOKEN_PROGRAM_ID,
priceUpdate, tokenProgram: TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId,
},
);Here collateral is 25 SOL in micro-tokens and debt is 1,500 USDC in micro-dollars. The builders are openLoan, repay, closeLoan, depositLiquidity, withdrawLiquidity, swap, liquidate, health, syncCollateral, setSession, recenterMarket and initMarket. Program lists their arguments.
Prices
Instructions that need a price take a priceUpdate account. buildOracleTransactions fetches the latest update from Pyth's Hermes service, posts it with full verification, and hands you its account to build your instructions against:
import { buildOracleTransactions, sendOracleTransactions } from "@taper/sdk";
const { transactions } = await buildOracleTransactions(
client,
{ endpoint: process.env.PYTH_HERMES_URL!, accessToken: process.env.PYTH_API_KEY!, receiverId },
feedId,
async (priceUpdate) => [await client.swap(args, { ...accounts, priceUpdate })],
);
await sendOracleTransactions(client, transactions);Hermes needs a Pyth API key. The client must have been loaded with a keypair to post.
Sending
sendInstructions(client, instructions) simulates, signs, sends and waits for finalization, with a 1.4 million unit compute budget. It needs a client loaded with a keypair.
client.events(logs) decodes Taper's events from a transaction's log messages.
Governance and stakes
The SDK reads and builds everything TAPER does. Governance runs on SPL Governance and votes are weighed by Mango's voter-stake-registry; the SDK includes builders for the lock instructions and re-exports @solana/spl-governance as gov.
const g = { realm, governance };
const state = await readGovernance(connection, g);
// state.governance.admin is the address that signs as Taper's admin.
const { weight, locks } = await readVoter(connection, realm, state.realm.communityMint, wallet);
// Lock 10,000 TAPER for four years. The registrar belongs to the realm and the mint.
const registrar = registrarAddress(realm, mint);
const lock = [
vsr.createVoter({ registrar, authority: wallet, payer: wallet }),
vsr.createDepositEntry({ registrar, authority: wallet, payer: wallet, mint, index: 0, kind: LockupKind.Cliff, days: 1460 }),
vsr.deposit({ registrar, authority: wallet, mint, source: walletTaperAccount, index: 0, amount: 10_000_000_000n }),
];vsr.updateVoterWeightRecord must run in the same transaction as any vote or proposal, so the weight it reports is current. voterWeight computes the same weight locally.
Session signer stakes are Taper accounts: stakeConfigAddress(admin, programId), sessionStakeAddress(config, owner, programId) and sessionRoleAddress(market, programId) give their addresses, and client.stakeSession, client.unstakeSession and client.signSession build the instructions. stakeReleasesAt(stake) says when a stake can leave.