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 calculationNext Steps
- Development Guide - Build and test locally
- Testing - Test suite examples
- Smart Contracts - Contract internals