Reading On-Chain State
This guide covers how to fetch and decode auction data using the indexer API and the SDK's Q96 math utilities.
Reading from the Indexer (Recommended)
The indexer provides pre-decoded, queryable data via a REST API. No on-chain deserialization required.
List All Auctions
const INDEXER_URL = "https://cca-indexer-production.up.railway.app";
// Paginated list of auctions
const response = await fetch(`${INDEXER_URL}/api/auctions?limit=20`);
const { data: auctions, next_cursor } = await response.json();
// Next page
if (next_cursor) {
const page2 = await fetch(
`${INDEXER_URL}/api/auctions?limit=20&cursor=${next_cursor}`
);
}
Get Auction Detail
const auctionPubkey = "YOUR_AUCTION_CONFIG_PUBKEY";
const detail = await fetch(
`${INDEXER_URL}/api/auctions/${auctionPubkey}`
).then((r) => r.json());
// detail includes:
// - Config fields: base_mint, quote_mint, total_supply, floor_price, etc.
// - State fields: clearing_price, total_bids, currency_raised, etc.
// - Supply schedule: num_steps, supply_steps
// - Computed: is_graduated, slots_remaining
Decode Q96 Prices
Prices are returned as Q96-encoded strings. Use the SDK to decode them for display.
import { decodeQ96, decodeQ96ToString } from "@runner-protocol/sdk";
// To number (safe for typical prices)
const clearingPrice = decodeQ96(detail.clearing_price);
// => 0.5
// To string with specific decimal places (no precision loss)
const clearingPriceStr = decodeQ96ToString(detail.clearing_price, 6);
// => "0.500000"
const floorPriceStr = decodeQ96ToString(detail.floor_price, 9);
// => "0.010000000"
caution
Never pass Q96 strings through Number() before converting to BigInt. This causes precision loss for values above 2^53.
// WRONG - precision loss
const bad = BigInt(Number(detail.clearing_price));
// CORRECT - direct BigInt
const good = BigInt(detail.clearing_price);
Determine Auction Status
function getAuctionStatus(auction: any): string {
if (auction.is_graduated) return "Graduated";
const slotsRemaining = Number(auction.slots_remaining);
if (slotsRemaining > 0 && Number(auction.start_slot) > 0) return "Active";
if (slotsRemaining <= 0) return "Ended";
return "Not Started";
}
// Approximate time remaining (~400ms per Solana slot)
const slotsRemaining = Number(detail.slots_remaining);
const secondsRemaining = Math.round(slotsRemaining * 0.4);
console.log(`Time remaining: ~${Math.round(secondsRemaining / 60)} minutes`);
Get Bids for a Wallet
const walletPubkey = "YOUR_WALLET_PUBKEY";
const bidsResponse = await fetch(
`${INDEXER_URL}/api/bidder/${walletPubkey}/bids?limit=50`
);
const { data: bids } = await bidsResponse.json();
for (const bid of bids) {
console.log(`Bid ${bid.pubkey}:`);
console.log(` Max price: ${decodeQ96ToString(bid.max_price, 6)}`);
console.log(` Amount: ${bid.raw_amount}`);
console.log(` Eligible: ${bid.is_eligible}`);
console.log(` Claimable: ${bid.is_claimable}`);
console.log(` Tokens filled: ${bid.tokens_filled}`);
}
Get Ticks (Order Book)
const ticksResponse = await fetch(
`${INDEXER_URL}/api/auctions/${auctionPubkey}/ticks`
);
const { data: ticks } = await ticksResponse.json();
// Ticks are sorted by price descending
for (const tick of ticks) {
const price = decodeQ96ToString(tick.price, 6);
console.log(`Tick @ ${price}: demand = ${tick.currency_demand_q96}`);
}
Get Checkpoints
const checkpointsResponse = await fetch(
`${INDEXER_URL}/api/auctions/${auctionPubkey}/checkpoints?status=finalized`
);
const { data: checkpoints, total } = await checkpointsResponse.json();
for (const cp of checkpoints) {
const price = decodeQ96ToString(cp.clearing_price, 6);
console.log(
`Block ${cp.auction_block}: clearing price = ${price}, status = ${cp.status_label}`
);
}
Reading Launches
// List all launches
const launchesResponse = await fetch(
`${INDEXER_URL}/api/launches?limit=20`
);
const { data: launches } = await launchesResponse.json();
// Filter by state
const activeResponse = await fetch(
`${INDEXER_URL}/api/launches?state=AuctionActive`
);
// Get launch detail
const launchDetail = await fetch(
`${INDEXER_URL}/api/launches/${launchPubkey}`
).then((r) => r.json());
// Get protocol config
const protocolConfig = await fetch(
`${INDEXER_URL}/api/protocol`
).then((r) => r.json());
Real-Time Updates
For live data, connect to the WebSocket endpoint instead of polling:
const ws = new WebSocket(
`wss://cca-indexer-production.up.railway.app/ws?auctionId=${auctionPubkey}`
);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case "bid":
console.log("New bid placed");
break;
case "auction_update":
console.log("Clearing price updated");
break;
case "settlement":
console.log("Settlement step completed");
break;
}
};
See the WebSocket documentation for connection limits and reconnection strategies.
Platform Stats
const stats = await fetch(`${INDEXER_URL}/api/stats`).then((r) => r.json());
console.log(`Total auctions: ${stats.total_auctions}`);
console.log(`Active auctions: ${stats.active_auctions}`);
console.log(`Total bids: ${stats.total_bids}`);
console.log(`Total launches: ${stats.total_launches}`);