Skip to main content

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.

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}`);