import { PublicKey } from "@solana/web3.js"; import BN from "bn.js"; import { Buffer } from "buffer"; import { MarketState, PoolState } from "../types.js"; import { isTimestampPassed } from "../math.js"; // Expected size constants for buffer validation const POOL_STATE_SIZE = 1 + 1 + 1 + 64 + 16 + 24 + 96 + 96 + 8; // 307 bytes const MARKET_STATE_SIZE = 4 + 32 + 32 + 8 + 8 + 8 + 8 + POOL_STATE_SIZE * 2 + 1 + 1; // 716 bytes /** * Parse a MarketState account from raw buffer data * * This is useful when you've fetched account data directly via connection.getAccountInfo() * without using Anchor's account deserialization. * * @param data - Raw account data buffer (excluding 8-byte discriminator) * @returns Parsed MarketState object * @throws Error if buffer is too small * * @example * ```typescript * const marketPda = getMarketAddress(1); * const accountInfo = await connection.getAccountInfo(marketPda); * if (accountInfo) { * // Skip 8-byte discriminator * const market = parseMarketAccount(accountInfo.data.slice(8)); * } * ``` */ export function parseMarketAccount(data: Uint8Array | Buffer): MarketState { // Convert to Buffer for consistent API (Buffer extends Uint8Array) const buffer = Buffer.from(data); if (buffer.length < MARKET_STATE_SIZE) { throw new Error( `Buffer too small for MarketState: expected at least ${MARKET_STATE_SIZE} bytes, got ${buffer.length}` ); } let offset = 0; // week_number: u32 const weekNumber = buffer.readUInt32LE(offset); offset += 4; // operator: Pubkey (32 bytes) const operator = new PublicKey(buffer.slice(offset, offset + 32)); offset += 32; // wsol_vault: Pubkey (32 bytes) const wsolVault = new PublicKey(buffer.slice(offset, offset + 32)); offset += 32; // start_timestamp: i64 const startTimestamp = new BN(buffer.slice(offset, offset + 8), "le"); offset += 8; // end_timestamp: i64 const endTimestamp = new BN(buffer.slice(offset, offset + 8), "le"); offset += 8; // total_subsidy: u64 const totalSubsidy = new BN(buffer.slice(offset, offset + 8), "le"); offset += 8; // fees_collected: u64 const feesCollected = new BN(buffer.slice(offset, offset + 8), "le"); offset += 8; // high_pool: PoolState const { pool: highPool, bytesRead: highPoolBytes } = parsePoolState(buffer.slice(offset)); offset += highPoolBytes; // low_pool: PoolState const { pool: lowPool, bytesRead: lowPoolBytes } = parsePoolState(buffer.slice(offset)); offset += lowPoolBytes; // bump: u8 const bump = buffer.readUInt8(offset); offset += 1; // is_paused: bool const isPaused = buffer.readUInt8(offset) === 1; return { weekNumber, operator, wsolVault, startTimestamp, endTimestamp, totalSubsidy, feesCollected, isPaused, highPool, lowPool, bump, }; } /** * Parse a PoolState from raw buffer data * * @param data - Raw buffer starting at pool data * @returns Object with parsed pool and bytes read */ function parsePoolState(data: Buffer): { pool: PoolState; bytesRead: number } { let offset = 0; // is_initialized: bool const isInitialized = data.readUInt8(offset) === 1; offset += 1; // is_settled: bool const isSettled = data.readUInt8(offset) === 1; offset += 1; // winning_outcome: u8 const winningOutcome = data.readUInt8(offset); offset += 1; // q_vector: [u128; 4] const qVector: [BN, BN, BN, BN] = [ new BN(data.slice(offset, offset + 16), "le"), new BN(data.slice(offset + 16, offset + 32), "le"), new BN(data.slice(offset + 32, offset + 48), "le"), new BN(data.slice(offset + 48, offset + 64), "le"), ]; offset += 64; // b: u128 const b = new BN(data.slice(offset, offset + 16), "le"); offset += 16; // strikes: [u64; 3] // High: [150, 160, 170] ascending, Low: [160, 150, 140] descending const strikes: [BN, BN, BN] = [ new BN(data.slice(offset, offset + 8), "le"), new BN(data.slice(offset + 8, offset + 16), "le"), new BN(data.slice(offset + 16, offset + 24), "le"), ]; offset += 24; // hit_mints: [Pubkey; 3] const hitMints: [PublicKey, PublicKey, PublicKey] = [ new PublicKey(data.slice(offset, offset + 32)), new PublicKey(data.slice(offset + 32, offset + 64)), new PublicKey(data.slice(offset + 64, offset + 96)), ]; offset += 96; // miss_mints: [Pubkey; 3] const missMints: [PublicKey, PublicKey, PublicKey] = [ new PublicKey(data.slice(offset, offset + 32)), new PublicKey(data.slice(offset + 32, offset + 64)), new PublicKey(data.slice(offset + 64, offset + 96)), ]; offset += 96; // claimed_winnings: u64 const claimedWinnings = new BN(data.slice(offset, offset + 8), "le"); offset += 8; return { pool: { isInitialized, isSettled, winningOutcome, qVector, b, strikes, hitMints, missMints, claimedWinnings, }, bytesRead: offset, }; } /** * Check if a market has ended based on timestamp * * Uses BN comparison to avoid toNumber() on timestamp. * * @param market - MarketState object * @returns true if current time is past end_timestamp */ export function isMarketEnded(market: MarketState): boolean { return isTimestampPassed(market.endTimestamp); } /** * Check if both pools are settled * * @param market - MarketState object * @returns true if both high and low pools are settled */ export function isFullySettled(market: MarketState): boolean { return market.highPool.isSettled && market.lowPool.isSettled; } /** * Get the token mint for a specific position * * @param pool - PoolState object * @param strikeIndex - Strike index (0, 1, or 2) * @param isHit - Whether this is a HIT token * @returns Token mint public key */ export function getTokenMint(pool: PoolState, strikeIndex: number, isHit: boolean): PublicKey { if (strikeIndex < 0 || strikeIndex > 2) { throw new Error("Strike index must be 0, 1, or 2"); } return isHit ? pool.hitMints[strikeIndex] : pool.missMints[strikeIndex]; }