UNPKG

@arcium-hq/reader

Version:

Reader SDK for fetching onchain data for Arcium network programs

328 lines (320 loc) 12.5 kB
'use strict'; var client = require('@arcium-hq/client'); var anchor = require('@coral-xyz/anchor'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var anchor__namespace = /*#__PURE__*/_interopNamespaceDefault(anchor); /** * Prefix for program data logs in Solana transaction logs. * @constant {string} */ const PROGRAM_DATA_PREFIX = 'Program data: '; /** * Prefix for program log messages in Solana transaction logs. * @constant {string} */ const PROGRAM_LOG_PREFIX = 'Program log: '; /** * Valid Arcium event names for computation lifecycle tracking. * @constant {ArciumEventName[]} */ const ARCIUM_EVENT_NAMES = [ 'QueueComputationEvent', 'InitComputationEvent', 'CallbackComputationEvent', 'FinalizeComputationEvent', ]; /** * Discriminator for the ArxNode account type. Used to filter and identify ArxNode accounts on-chain. */ const ARX_NODE_ACC_DISCRIMINATOR = [2, 207, 122, 223, 93, 97, 231, 199]; /** * Discriminator for the Cluster account type. Used to filter and identify Cluster accounts on-chain. */ const CLUSTER_ACC_DISCRIMINATOR = [236, 225, 118, 228, 173, 106, 18, 60]; /** * Discriminator for the MXE account type. Used to filter and identify MXE accounts on-chain. */ const MXE_ACC_DISCRIMINATOR = [103, 26, 85, 250, 179, 159, 17, 117]; /** * The public key of the deployed Arcium program on Solana. */ const ARCIUM_PROGRAM_ID = new anchor__namespace.web3.PublicKey(client.ARCIUM_ADDR); /** * Anchor coder for encoding and decoding Arcium program instructions. */ new anchor__namespace.BorshInstructionCoder(client.ARCIUM_IDL); /** * Anchor event parser for parsing Arcium program events from transaction logs. */ new anchor__namespace.EventParser(ARCIUM_PROGRAM_ID, new anchor__namespace.BorshCoder(client.ARCIUM_IDL)); /** * BorshCoder instance for decoding Arcium events. * Used directly instead of accessing EventParser's private coder property. */ const ARCIUM_BORSH_CODER = new anchor__namespace.BorshCoder(client.ARCIUM_IDL); /** * Returns all MXE account addresses. * @param conn - The Solana connection object. * @returns Array of MXE account public keys. */ async function getMXEAccAddresses(conn) { return getArciumAccPubkeys(conn, MXE_ACC_DISCRIMINATOR); } /** * Returns all Cluster account addresses. * @param conn - The Solana connection object. * @returns Array of Cluster account public keys. */ async function getClusterAccAddresses(conn) { return getArciumAccPubkeys(conn, CLUSTER_ACC_DISCRIMINATOR); } /** * Returns all ArxNode account addresses. * @param conn - The Solana connection object. * @returns Array of ArxNode account public keys. */ async function getArxNodeAccAddresses(conn) { return getArciumAccPubkeys(conn, ARX_NODE_ACC_DISCRIMINATOR); } /** * Fetches and parses a given MXE account. * @param arciumProgram - The Anchor program instance. * @param address - The public key of the MXE account. * @param commitment - (Optional) RPC commitment level. * @returns The MXEAccount object. */ async function getMXEAccInfo(arciumProgram, address, commitment) { return arciumProgram.account.mxeAccount.fetch(address, commitment); } /** * Fetches and parses a given Cluster account. * @param arciumProgram - The Anchor program instance. * @param address - The public key of the Cluster account. * @param commitment - (Optional) RPC commitment level. * @returns The ClusterAccount object. */ async function getClusterAccInfo(arciumProgram, address, commitment) { return arciumProgram.account.cluster.fetch(address, commitment); } /** * Fetches and parses a given ArxNode account. * @param arciumProgram - The Anchor program instance. * @param address - The public key of the ArxNode account. * @param commitment - (Optional) RPC commitment level. * @returns The ArxNodeAccount object. */ async function getArxNodeAccInfo(arciumProgram, address, commitment) { return arciumProgram.account.arxNode.fetch(address, commitment); } /** * Fetches and parses a given ComputationDefinition account. * @param arciumProgram - The Anchor program instance. * @param address - The public key of the ComputationDefinition account. * @param commitment - (Optional) RPC commitment level. * @returns The ComputationDefinitionAccount object. */ async function getCompDefAccInfo(arciumProgram, address, commitment) { return arciumProgram.account.computationDefinitionAccount.fetch(address, commitment); } /** * Fetches and parses a given Computation account. * @param arciumProgram - The Anchor program instance. * @param address - The public key of the Computation account. * @param commitment - (Optional) RPC commitment level. * @returns The Computation object. */ async function getComputationAccInfo(arciumProgram, address, commitment) { return arciumProgram.account.computationAccount.fetch(address, commitment); } async function getArciumAccPubkeys(conn, discriminator) { const accs = await conn.getProgramAccounts(ARCIUM_PROGRAM_ID, { dataSlice: { offset: 0, length: 0 }, filters: [ { memcmp: { offset: 0, encoding: 'base64', bytes: Buffer.from(discriminator).toString('base64'), }, }, ], }); return accs.map((acc) => acc.pubkey); } /** * Subscribes to computation-related events for a given MXE program ID. * @param conn - The Solana connection object. * @param mxeProgramId - The public key of the MXE program. * @param callback - Callback function to handle each computation event and its name. * @returns The subscription ID for the logs listener. */ async function subscribeComputations(conn, mxeProgramId, callback) { return conn.onLogs(mxeProgramId, (logs) => { const events = getComputationEventsFromLogs(logs.logs); for (const event of events) { callback(event.event, event.name); } }); } /** * Unsubscribes from computation-related events using the subscription ID. * @param conn - The Solana connection object. * @param subscriptionId - The subscription ID returned by subscribeComputations. */ async function unsubscribeComputations(conn, subscriptionId) { conn.removeOnLogsListener(subscriptionId); } /** * Gets the computation offset from a transaction. * @param tx - The transaction to get the computation offset from. * @returns The computation offset if one is found, otherwise undefined. * @throws Error if multiple computation offsets are found in the transaction. */ function getComputationOffset(tx) { const events = getComputationEventsFromLogs(tx.meta?.logMessages ?? []); if (events.length === 0) { return undefined; } const computationOffsets = events.map((e) => e.event.computationOffset); const computationOffset = computationOffsets[0]; if (computationOffsets.some((offset) => !offset.eq(computationOffset))) { throw new Error(`Multiple computation offsets found in computation: ${JSON.stringify(computationOffsets)}`); } return computationOffset; } /** * Get the events related to arcium computations from a transaction's logs * * Note: This implements a direct decode approach instead of using Anchor's EventParser.parseLogs(). * * BREAKING CHANGE IN ANCHOR v0.32.0 (PR #3657, commit 34b6d19): * EventParser.parseLogs was hardened to prevent malicious log injection attacks. It now: * 1. Requires the first log to match "Program X invoke [1]" (root invocation) * 2. Pre-seeds the execution stack with program X from the first log * 3. Only parses events when execution.program() === EventParser.programId * * This breaks our use case: * - We subscribe to MXE program logs (line 29) via conn.onLogs(mxeProgramId, ...) * - But we need to parse Arcium events emitted during MXE→Arcium CPI calls * - When MXE is the root program, execution.program() = MXE_PROGRAM_ID * - EventParser expects execution.program() = ARCIUM_PROGRAM_ID * - Result: All Arcium events are silently skipped * * Our discriminator-based approach: * - Scans all logs for "Program data:" and "Program log:" prefixes * - Uses event discriminators to identify valid Arcium events (no program context check) * - Works regardless of which program is root or subscription target * - Handles both MXE→Arcium and Arcium→MXE CPI patterns * * @param logs - The logs to get the events from * @returns The events in the logs. */ function getComputationEventsFromLogs(logs) { const events = []; // Scan all event logs and rely on discriminators to identify valid Arcium events. // This works for both MXE→Arcium CPI and Arcium→MXE CPI transactions. for (const log of logs) { if (log.startsWith(PROGRAM_DATA_PREFIX)) { const eventData = log.slice(PROGRAM_DATA_PREFIX.length); const decoded = ARCIUM_BORSH_CODER.events.decode(eventData); if (decoded && ARCIUM_EVENT_NAMES.includes(decoded.name)) { events.push(decoded); } } else if (log.startsWith(PROGRAM_LOG_PREFIX)) { const eventData = log.slice(PROGRAM_LOG_PREFIX.length); const decoded = ARCIUM_BORSH_CODER.events.decode(eventData); if (decoded && ARCIUM_EVENT_NAMES.includes(decoded.name)) { events.push(decoded); } } } // Map to existing return format return events.map((e) => { const eventData = { computationOffset: e.data.computation_offset, ...e.data, }; if (e.data.mxe_program_id) { eventData.mxeProgramId = e.data.mxe_program_id; } return { event: eventData, name: e.name, }; }); } Object.defineProperty(exports, "getArciumProgram", { enumerable: true, get: function () { return client.getArciumProgram; } }); Object.defineProperty(exports, "getArxNodeAccAddress", { enumerable: true, get: function () { return client.getArxNodeAccAddress; } }); Object.defineProperty(exports, "getClockAccAddress", { enumerable: true, get: function () { return client.getClockAccAddress; } }); Object.defineProperty(exports, "getClusterAccAddress", { enumerable: true, get: function () { return client.getClusterAccAddress; } }); Object.defineProperty(exports, "getCompDefAccAddress", { enumerable: true, get: function () { return client.getCompDefAccAddress; } }); Object.defineProperty(exports, "getComputationAccAddress", { enumerable: true, get: function () { return client.getComputationAccAddress; } }); Object.defineProperty(exports, "getComputationsInMempool", { enumerable: true, get: function () { return client.getComputationsInMempool; } }); Object.defineProperty(exports, "getExecutingPoolAccAddress", { enumerable: true, get: function () { return client.getExecutingPoolAccAddress; } }); Object.defineProperty(exports, "getFeePoolAccAddress", { enumerable: true, get: function () { return client.getFeePoolAccAddress; } }); Object.defineProperty(exports, "getMXEAccAddress", { enumerable: true, get: function () { return client.getMXEAccAddress; } }); Object.defineProperty(exports, "getMempoolAccAddress", { enumerable: true, get: function () { return client.getMempoolAccAddress; } }); Object.defineProperty(exports, "getMempoolPriorityFeeStats", { enumerable: true, get: function () { return client.getMempoolPriorityFeeStats; } }); exports.getArxNodeAccAddresses = getArxNodeAccAddresses; exports.getArxNodeAccInfo = getArxNodeAccInfo; exports.getClusterAccAddresses = getClusterAccAddresses; exports.getClusterAccInfo = getClusterAccInfo; exports.getCompDefAccInfo = getCompDefAccInfo; exports.getComputationAccInfo = getComputationAccInfo; exports.getComputationOffset = getComputationOffset; exports.getMXEAccAddresses = getMXEAccAddresses; exports.getMXEAccInfo = getMXEAccInfo; exports.subscribeComputations = subscribeComputations; exports.unsubscribeComputations = unsubscribeComputations;