Skip to content

API Reference

Client-side API reference for interacting with the Rate Swap Protocol.

TypeScript Client

The protocol can be accessed using Anchor's TypeScript client.

Basic Setup

typescript
import { Program, AnchorProvider } from '@coral-xyz/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { RateSwap } from '../target/types/rate_swap';
import idl from '../target/idl/rate_swap.json';

// Setup connection
const connection = new Connection('https://api.devnet.solana.com');
const wallet = /* your wallet */;
const provider = new AnchorProvider(connection, wallet, {});

// Initialize program
const programId = new PublicKey('CPrPi4AnqM2RhQXZ9n7gyWb4i7BcTJDDWruhonuRU1jc');
const program = new Program(idl, programId, provider);

Core API Methods

Pool Operations

Initialize Pool

typescript
async function initPool(
  sharesMint: PublicKey,
  quoteMint: PublicKey,
  args: {
    maxRateMoveBps: number;      // 1-5000
    utilKinkWad: BN;             // WAD-scaled
    utilMaxWad: BN;              // WAD-scaled
    minRiskScalarWad: BN;        // WAD-scaled
  }
): Promise<string> {
  const [poolPda] = PublicKey.findProgramAddressSync(
    [Buffer.from('pool'), sharesMint.toBuffer()],
    program.programId
  );

  const tx = await program.methods
    .initPool({
      maxRateMoveBps: args.maxRateMoveBps,
      utilKinkWad: args.utilKinkWad,
      utilMaxWad: args.utilMaxWad,
      minRiskScalarWad: args.minRiskScalarWad,
    })
    .accounts({
      pool: poolPda,
      sharesMint,
      quoteVault: /* derive quote vault ATA */,
      quoteMint,
      authority: wallet.publicKey,
      systemProgram: SystemProgram.programId,
      tokenProgram: TOKEN_PROGRAM_ID,
      rent: SYSVAR_RENT_PUBKEY,
    })
    .rpc();

  return tx;
}

Deposit to Pool (LP)

typescript
async function depositPool(
  pool: PublicKey,
  amount: number
): Promise<string> {
  const poolData = await program.account.pool.fetch(pool);

  const tx = await program.methods
    .depositPool(new BN(amount))
    .accounts({
      pool,
      quoteVault: poolData.quoteVault,
      sharesMint: poolData.sharesMint,
      depositor: wallet.publicKey,
      depositorQuote: /* depositor's quote token ATA */,
      depositorShares: /* depositor's shares ATA */,
      tokenProgram: TOKEN_PROGRAM_ID,
    })
    .rpc();

  return tx;
}

Withdraw from Pool (LP)

typescript
async function withdrawPool(
  pool: PublicKey,
  shares: number
): Promise<string> {
  const poolData = await program.account.pool.fetch(pool);

  const tx = await program.methods
    .withdrawPool(new BN(shares))
    .accounts({
      pool,
      quoteVault: poolData.quoteVault,
      sharesMint: poolData.sharesMint,
      withdrawer: wallet.publicKey,
      withdrawerQuote: /* withdrawer's quote token ATA */,
      withdrawerShares: /* withdrawer's shares ATA */,
      tokenProgram: TOKEN_PROGRAM_ID,
    })
    .rpc();

  return tx;
}

Market Operations

Initialize Market

typescript
async function initMarket(
  pool: PublicKey,
  oracle: PublicKey,
  params: {
    ammSeed: number;
    maturity: number;
    initialRateWad: BN;
    minAbsRateWad: BN;
    maxAbsRateWad: BN;
    rateOffsetWad: BN;
    swapFeeBps: number;
    maintenanceMarginBps: number;
    initialMarginBps: number;
    liquidationPenaltyBps: number;
    protocolFeeShareBps: number;
    oiCapNotionalWad: BN;
    dv01CapWad: BN;
    riskWeightWad: BN;
    minTimeFloorSecs: number;
    liquidityWeightBps: number;
    minRateFloorWad: BN;
    imMultWad: BN;
    mmMultWad: BN;
  }
): Promise<string> {
  const ammSeedBuffer = Buffer.alloc(8);
  ammSeedBuffer.writeBigUInt64LE(BigInt(params.ammSeed));

  const [marketPda] = PublicKey.findProgramAddressSync(
    [Buffer.from('market'), pool.toBuffer(), ammSeedBuffer],
    program.programId
  );

  const tx = await program.methods
    .initMarket({
      ammSeed: new BN(params.ammSeed),
      maturity: new BN(params.maturity),
      initialRateWad: params.initialRateWad,
      minAbsRateWad: params.minAbsRateWad,
      maxAbsRateWad: params.maxAbsRateWad,
      rateOffsetWad: params.rateOffsetWad,
      swapFeeBps: params.swapFeeBps,
      maintenanceMarginBps: params.maintenanceMarginBps,
      initialMarginBps: params.initialMarginBps,
      liquidationPenaltyBps: params.liquidationPenaltyBps,
      protocolFeeShareBps: params.protocolFeeShareBps,
      oiCapNotionalWad: params.oiCapNotionalWad,
      dv01CapWad: params.dv01CapWad,
      riskWeightWad: params.riskWeightWad,
      minTimeFloorSecs: new BN(params.minTimeFloorSecs),
      liquidityWeightBps: params.liquidityWeightBps,
      minRateFloorWad: params.minRateFloorWad,
      imMultWad: params.imMultWad,
      mmMultWad: params.mmMultWad,
    })
    .accounts({
      market: marketPda,
      pool,
      oracle,
      authority: wallet.publicKey,
      systemProgram: SystemProgram.programId,
    })
    .rpc();

  return tx;
}

Refresh Market Liquidity

typescript
async function refreshMarketLiquidity(
  market: PublicKey
): Promise<string> {
  const marketData = await program.account.market.fetch(market);
  const poolData = await program.account.pool.fetch(marketData.pool);

  const tx = await program.methods
    .refreshMarketLiquidity()
    .accounts({
      market,
      pool: marketData.pool,
      quoteVault: poolData.quoteVault,
    })
    .rpc();

  return tx;
}

Margin Operations

Initialize Margin

typescript
async function initMargin(pool: PublicKey): Promise<string> {
  const [marginPda] = PublicKey.findProgramAddressSync(
    [Buffer.from('margin'), pool.toBuffer(), wallet.publicKey.toBuffer()],
    program.programId
  );

  const tx = await program.methods
    .initMargin()
    .accounts({
      margin: marginPda,
      pool,
      authority: wallet.publicKey,
      systemProgram: SystemProgram.programId,
    })
    .rpc();

  return tx;
}

Deposit Margin

typescript
async function depositMargin(
  margin: PublicKey,
  amount: number
): Promise<string> {
  const marginData = await program.account.margin.fetch(margin);
  const poolData = await program.account.pool.fetch(marginData.pool);

  const tx = await program.methods
    .depositMargin({ amount: new BN(amount) })
    .accounts({
      margin,
      pool: marginData.pool,
      quoteVault: poolData.quoteVault,
      depositor: wallet.publicKey,
      depositorQuote: /* depositor's quote token ATA */,
      tokenProgram: TOKEN_PROGRAM_ID,
    })
    .rpc();

  return tx;
}

Withdraw Margin

typescript
async function withdrawMargin(
  margin: PublicKey,
  amount: number,
  marketsAndOracles: { market: PublicKey; oracle: PublicKey }[]
): Promise<string> {
  const marginData = await program.account.margin.fetch(margin);
  const poolData = await program.account.pool.fetch(marginData.pool);

  // Build remaining accounts for settlement
  const remainingAccounts = marketsAndOracles.flatMap(({ market, oracle }) => [
    { pubkey: market, isWritable: true, isSigner: false },
    { pubkey: oracle, isWritable: false, isSigner: false },
  ]);

  const tx = await program.methods
    .withdrawMargin({ amount: new BN(amount) })
    .accounts({
      margin,
      pool: marginData.pool,
      quoteVault: poolData.quoteVault,
      withdrawer: wallet.publicKey,
      withdrawerQuote: /* withdrawer's quote token ATA */,
      tokenProgram: TOKEN_PROGRAM_ID,
    })
    .remainingAccounts(remainingAccounts)
    .rpc();

  return tx;
}

Position Operations

Execute Swap

typescript
async function swap(
  margin: PublicKey,
  market: PublicKey,
  notionalDeltaWad: BN  // WAD-scaled, signed
): Promise<string> {
  const marginData = await program.account.margin.fetch(margin);
  const marketData = await program.account.market.fetch(market);

  const tx = await program.methods
    .swap({ notionalDeltaWad })
    .accounts({
      margin,
      market,
      oracle: marketData.oracle,
      pool: marginData.pool,
      authority: wallet.publicKey,
    })
    .rpc();

  return tx;
}

Example: Open 100,000 notional pay-fixed position

typescript
const WAD = new BN(10).pow(new BN(18));
const notional = new BN(100_000).mul(WAD);  // positive = pay fixed
await swap(marginPda, marketPda, notional);

Example: Close position (opposite swap)

typescript
const notional = new BN(-100_000).mul(WAD);  // negative reverses
await swap(marginPda, marketPda, notional);

Liquidate Position (Crank)

typescript
async function liquidate(
  margin: PublicKey,  // Victim's margin account
  market: PublicKey
): Promise<string> {
  const marginData = await program.account.margin.fetch(margin);
  const marketData = await program.account.market.fetch(market);

  // Note: No liquidator token accounts needed - penalty goes to pool
  const tx = await program.methods
    .liquidate()
    .accounts({
      margin,
      market,
      oracle: marketData.oracle,
      pool: marginData.pool,
      liquidator: wallet.publicKey,  // Permissionless crank
    })
    .rpc();

  return tx;
}

Crank-Only Model: The liquidator doesn't receive tokens directly. The liquidation penalty is credited to the pool's accounting, benefiting LPs.

Oracle Operations

Initialize Oracle

typescript
async function initOracle(
  oracleKeypair: Keypair,
  pool: PublicKey,
  maxStalenessSecs: number,
  maxAbsDfPerSecWad: BN,      // Max delta per second (WAD/sec)
  maxAbsDfPerUpdateWad: BN    // Max delta per update (WAD)
): Promise<string> {
  const tx = await program.methods
    .initOracle({
      maxStalenessSecs: new BN(maxStalenessSecs),
      maxAbsDfPerSecWad,
      maxAbsDfPerUpdateWad,
    })
    .accounts({
      oracle: oracleKeypair.publicKey,
      pool,
      authority: wallet.publicKey,
      payer: wallet.publicKey,
      systemProgram: SystemProgram.programId,
    })
    .signers([oracleKeypair])
    .rpc();

  return tx;
}

// Example: Initialize with reasonable bounds for funding rate oracle
const WAD = new BN(10).pow(new BN(18));
const maxPerSec = WAD.div(new BN(360));       // ~1% per hour
const maxPerUpdate = WAD.div(new BN(10));     // 10% per update
await initOracle(oracleKeypair, poolPda, 3600, maxPerSec, maxPerUpdate);

// Example: Initialize with effectively unlimited bounds
const MAX_I128 = new BN(2).pow(new BN(127)).sub(new BN(1));
await initOracle(oracleKeypair, poolPda, 3600, MAX_I128, MAX_I128);

Update Oracle

typescript
async function updateOracle(
  oracle: PublicKey,
  market: PublicKey,
  pool: PublicKey,
  newRateIndexWad: BN
): Promise<string> {
  const tx = await program.methods
    .updateOracle({ newRateIndexWad })
    .accounts({
      oracle,
      market,
      pool,
      authority: wallet.publicKey,
    })
    .rpc();

  return tx;
}

Important: Oracle updates must satisfy sanity bounds configured at initialization:

  • dt > 0: Timestamp must be strictly increasing
  • |dF| <= min(max_abs_df_per_sec_wad * dt, max_abs_df_per_update_wad)

Updates violating these bounds are rejected with OracleDeltaOutOfBounds.

Data Fetching

Fetch Pool Data

typescript
const pool = await program.account.pool.fetch(poolPda);
console.log('Quote Mint:', pool.quoteMint.toString());
console.log('Quote Vault:', pool.quoteVault.toString());
console.log('Total Trader Collateral:', pool.totalTraderCollateral.toString());
console.log('Max Rate Move BPS:', pool.maxRateMoveBps);
console.log('Markets:', pool.markets.filter(m => !m.equals(PublicKey.default)));

Fetch Market Data

typescript
const market = await program.account.market.fetch(marketPda);
console.log('Pool:', market.pool.toString());
console.log('Oracle:', market.oracle.toString());
console.log('Status:', market.status);  // 0=Normal, 1=ClosingOnly, 2=Halted
console.log('Pool Net Notional:', market.poolNetNotionalWad.toString());
console.log('Pool Rate Accumulator:', market.poolRateAccumulatorWad.toString());
console.log('Gross OI:', market.oiGrossNotionalWad.toString());
console.log('Gross DV01:', market.dv01GrossWad.toString());
console.log('Swap Fee BPS:', market.swapFeeBps);
console.log('Liquidity Weight BPS:', market.liquidityWeightBps);

Fetch Margin Data

typescript
const margin = await program.account.margin.fetch(marginPda);
console.log('Authority:', margin.authority.toString());
console.log('Pool:', margin.pool.toString());
console.log('Collateral Amount:', margin.collateralAmount.toString());

// Filter active positions (non-default market pubkey)
const activePositions = margin.positions.filter(
  p => !p.market.equals(PublicKey.default)
);
console.log('Active Positions:', activePositions.length);

for (const pos of activePositions) {
  console.log('  Market:', pos.market.toString());
  console.log('  Notional WAD:', pos.notionalWad.toString());
  console.log('  Entry Rate WAD:', pos.entryFixedRateWad.toString());
  console.log('  Realized PnL WAD:', pos.realizedPnlWad.toString());
}

Fetch Oracle Data

typescript
const oracle = await program.account.rateOracle.fetch(oraclePubkey);
console.log('Rate Index WAD:', oracle.rateIndexWad.toString());
console.log('Last Update:', new Date(oracle.lastUpdateTs.toNumber() * 1000));
console.log('Max Staleness:', oracle.maxStalenessSecs.toString(), 'seconds');
console.log('Max Delta/Sec WAD:', oracle.maxAbsDfPerSecWad.toString());
console.log('Max Delta/Update WAD:', oracle.maxAbsDfPerUpdateWad.toString());

PDA Derivation Helpers

typescript
function findPoolPda(sharesMint: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('pool'), sharesMint.toBuffer()],
    program.programId
  );
}

function findMarginPda(pool: PublicKey, authority: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('margin'), pool.toBuffer(), authority.toBuffer()],
    program.programId
  );
}

function findMarketPda(pool: PublicKey, ammSeed: number): [PublicKey, number] {
  const ammSeedBuffer = Buffer.alloc(8);
  ammSeedBuffer.writeBigUInt64LE(BigInt(ammSeed));

  return PublicKey.findProgramAddressSync(
    [Buffer.from('market'), pool.toBuffer(), ammSeedBuffer],
    program.programId
  );
}

Note: The Collateral PDA has been removed. Traders deposit directly to the pool's quote vault.

Constants

typescript
const WAD = new BN(10).pow(new BN(18));  // 1e18
const SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
const MAX_MARGIN_POSITIONS = 8;
const MAX_MARKETS = 16;

Error Handling

typescript
try {
  await swap(marginPda, marketPda, notionalDelta);
} catch (err) {
  if (err.message.includes('UnhealthyPosition')) {
    console.error('Position would be unhealthy after swap');
  } else if (err.message.includes('StaleOracle')) {
    console.error('Oracle data is stale - wait for update');
  } else if (err.message.includes('MarketOICapExceeded')) {
    console.error('Trade would exceed market OI cap');
  } else if (err.message.includes('MarketDV01CapExceeded')) {
    console.error('Trade would exceed market DV01 cap');
  } else if (err.message.includes('MarketHalted')) {
    console.error('Market is halted - only liquidations allowed');
  } else if (err.message.includes('MarketClosingOnly')) {
    console.error('Market is closing-only - can only reduce positions');
  } else if (err.message.includes('InsufficientRiskBudgetDepth')) {
    console.error('Insufficient liquidity for risk-increasing trade');
  } else {
    console.error('Swap failed:', err);
  }
}

// Oracle update errors
try {
  await updateOracle(oraclePubkey, marketPda, poolPda, newRateIndexWad);
} catch (err) {
  if (err.message.includes('OracleTimestampNotIncreasing')) {
    console.error('Oracle timestamp must be strictly increasing');
  } else if (err.message.includes('OracleDeltaOutOfBounds')) {
    console.error('Oracle delta exceeds configured sanity bounds');
  } else if (err.message.includes('MathOverflow')) {
    console.error('Math overflow in oracle validation');
  } else {
    console.error('Oracle update failed:', err);
  }
}

Accounting-Only Settlement

The protocol uses accounting-only settlement, meaning:

  • No SPL token transfers during funding settlement
  • Realized PnL accumulates in position.realizedPnlWad
  • Pool counterparty tracked via market.poolRateAccumulatorWad
  • Tokens only move on explicit deposit/withdraw operations
typescript
// After settlement, PnL is in the position
const margin = await program.account.margin.fetch(marginPda);
const position = margin.positions.find(p => p.market.equals(marketPda));
console.log('Realized PnL:', position.realizedPnlWad.toString());

// To withdraw PnL, close the position and withdraw margin
// The withdraw will include realized PnL in equity calculation

Next Steps

Released under the ISC License.