@arcium-hq/reader
Version:
Reader SDK for fetching onchain data for Arcium network programs
328 lines (320 loc) • 12.5 kB
JavaScript
;
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;