import { Connection } from "@solana/web3.js"; import { Buffer } from "buffer"; import { RegistryState, MAX_ACTIVE_MARKETS } from "../types.js"; import { getRegistryAddress } from "./pda.js"; // Registry account size (excluding 8-byte discriminator): // - bump: u8 (1 byte) // - active_count: u16 (2 bytes) // - active_weeks: [u32; 1] (4 bytes) // - last_week_number: u32 (4 bytes) // - total_markets_count: u32 (4 bytes) // Total: 15 bytes const REGISTRY_STATE_SIZE = 1 + 2 + 4 * MAX_ACTIVE_MARKETS + 4 + 4; /** * Parse a RegistryState account from raw buffer data * * @param data - Raw account data buffer (excluding 8-byte discriminator) * @returns Parsed RegistryState object * @throws Error if buffer is too small * * @example * ```typescript * const registryPda = getRegistryAddress(); * const accountInfo = await connection.getAccountInfo(registryPda); * if (accountInfo) { * // Skip 8-byte discriminator * const registry = parseRegistryAccount(accountInfo.data.slice(8)); * console.log('Active weeks:', registry.activeWeeks); * } * ``` */ export function parseRegistryAccount(data: Uint8Array | Buffer): RegistryState { // Convert to Buffer for consistent API (Buffer extends Uint8Array) const buffer = Buffer.from(data); if (buffer.length < REGISTRY_STATE_SIZE) { throw new Error( `Buffer too small for RegistryState: expected at least ${REGISTRY_STATE_SIZE} bytes, got ${buffer.length}` ); } let offset = 0; // bump: u8 const bump = buffer.readUInt8(offset); offset += 1; // active_count: u16 const activeCount = buffer.readUInt16LE(offset); offset += 2; // active_weeks: [u32; MAX_ACTIVE_MARKETS] - only read up to activeCount const activeWeeks: number[] = []; for (let i = 0; i < activeCount && i < MAX_ACTIVE_MARKETS; i++) { activeWeeks.push(buffer.readUInt32LE(offset + i * 4)); } offset += 4 * MAX_ACTIVE_MARKETS; // last_week_number: u32 const lastWeekNumber = buffer.readUInt32LE(offset); offset += 4; // total_markets_count: u32 const totalMarketsCount = buffer.readUInt32LE(offset); return { bump, activeCount, activeWeeks, lastWeekNumber, totalMarketsCount, }; } /** * Fetch and parse the registry account * * @param connection - Solana connection * @returns RegistryState or null if account doesn't exist * * @example * ```typescript * const registry = await fetchRegistry(connection); * if (registry) { * console.log(`${registry.activeCount} active markets`); * for (const weekNumber of registry.activeWeeks) { * console.log(` Week ${weekNumber}`); * } * } * ``` */ export async function fetchRegistry(connection: Connection): Promise { const registryPda = getRegistryAddress(); const accountInfo = await connection.getAccountInfo(registryPda); if (!accountInfo) { return null; } // Skip 8-byte Anchor discriminator return parseRegistryAccount(accountInfo.data.slice(8)); } /** * Get all active market week numbers * * @param connection - Solana connection * @returns Array of active week numbers, or empty array if registry doesn't exist * * @example * ```typescript * const activeWeeks = await getActiveMarketWeeks(connection); * for (const weekNumber of activeWeeks) { * const marketPda = getMarketAddress(weekNumber); * // Fetch market data... * } * ``` */ export async function getActiveMarketWeeks(connection: Connection): Promise { const registry = await fetchRegistry(connection); return registry?.activeWeeks ?? []; }