@arcium-hq/reader 0.10.3 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  ## When To Use
15
15
 
16
- - Listing MXE, cluster, and ARX node accounts
16
+ - Listing MXE (MPC eXecution Environment), cluster, and Arx node accounts
17
17
  - Fetching typed account state from the Arcium program
18
18
  - Monitoring computation activity without writing transactions
19
19
 
package/build/index.cjs CHANGED
@@ -61,7 +61,7 @@ const ARCIUM_PROGRAM_ID = new anchor__namespace.web3.PublicKey(client.ARCIUM_ADD
61
61
  /**
62
62
  * Anchor coder for encoding and decoding Arcium program instructions.
63
63
  */
64
- new anchor__namespace.BorshInstructionCoder(client.ARCIUM_IDL);
64
+ const ARCIUM_IX_CODER = new anchor__namespace.BorshInstructionCoder(client.ARCIUM_IDL);
65
65
  /**
66
66
  * Anchor event parser for parsing Arcium program events from transaction logs.
67
67
  */
@@ -191,6 +191,7 @@ async function getArciumAccPubkeys(conn, discriminator) {
191
191
  return accs.map((acc) => acc.pubkey);
192
192
  }
193
193
 
194
+ const { BN } = anchor;
194
195
  /**
195
196
  * Subscribe to computation events for an MXE program.
196
197
  *
@@ -311,11 +312,276 @@ function getComputationEventsFromLogs(logs) {
311
312
  };
312
313
  });
313
314
  }
315
+ function formatUnknownError(error) {
316
+ return error instanceof Error ? error.message : String(error);
317
+ }
318
+ /**
319
+ * `getTransaction` rejects the `processed` commitment, so map down to a
320
+ * `Finality`: `finalized` passes through, everything else reads `confirmed`.
321
+ */
322
+ function mapCommitmentToFinality(commitment) {
323
+ return commitment === 'finalized' ? 'finalized' : 'confirmed';
324
+ }
325
+ /**
326
+ * Type guard for {@link DecodedQueueIxFields}. Anchor's coder types
327
+ * `decode().data` as the unspecific `Object`, so the surfaced fields must be
328
+ * proven present and correctly shaped before they are read.
329
+ */
330
+ function isDecodedQueueIx(data) {
331
+ if (data === null || typeof data !== 'object') {
332
+ return false;
333
+ }
334
+ if (!('comp_offset' in data)
335
+ || !('cu_price_micro' in data)
336
+ || !('callback_cu_limit' in data)
337
+ || !('padding' in data)
338
+ || !('computation_definition_offset' in data)
339
+ || !('callback_transactions_required' in data)
340
+ || !('custom_callback_instructions' in data)
341
+ || !('mxe_program' in data)) {
342
+ return false;
343
+ }
344
+ return (BN.isBN(data.comp_offset)
345
+ && BN.isBN(data.cu_price_micro)
346
+ && typeof data.callback_cu_limit === 'number'
347
+ && typeof data.padding === 'number'
348
+ && typeof data.computation_definition_offset === 'number'
349
+ && typeof data.callback_transactions_required === 'number'
350
+ && Array.isArray(data.custom_callback_instructions)
351
+ && data.mxe_program instanceof anchor__namespace.web3.PublicKey);
352
+ }
353
+ function feeFromQueueIx(decoded, queueTxSignature) {
354
+ return {
355
+ source: 'queueTx',
356
+ cuPriceMicro: decoded.cu_price_micro,
357
+ callbackCuLimit: decoded.callback_cu_limit,
358
+ customCallbackInstructionCount: decoded.custom_callback_instructions.length,
359
+ computationDefinitionOffset: decoded.computation_definition_offset,
360
+ callbackTransactionsRequired: decoded.callback_transactions_required,
361
+ queueTxSignature,
362
+ };
363
+ }
364
+ /**
365
+ * Find the `queue_computation` ix in a transaction whose decoded `mxe_program`
366
+ * and `comp_offset` match, scanning outer ixs first and inner CPI ixs second.
367
+ *
368
+ * The ix must carry a `comp` account at IDL index 2. When `match.compPDA` is
369
+ * given, that account must also equal it: this binds the result to one
370
+ * historical lifecycle and rejects a same-tx queue for a different cluster's
371
+ * PDA. When it is omitted, the index-2 slot only has to exist, so an
372
+ * exact-signature decode needs no current cluster state.
373
+ *
374
+ * `queue_computation` is normally a CPI from the MXE program, so the inner-ix
375
+ * list is the primary path. Outers are scanned first so a present outer match
376
+ * is never masked by an omitted inner list; only when no outer matched and the
377
+ * RPC gave no inner list (`innerInstructions` null or absent) do we report
378
+ * `innerInstructionsOmitted`, distinct from "no queue ix exists".
379
+ *
380
+ * Outer ix `data` is `Uint8Array`; inner ix `data` is base58 `string`, routed
381
+ * by JS type. Anchor's `decode` returns `null` on discriminator mismatch, so
382
+ * the name check doubles as a discriminator check.
383
+ */
384
+ function findArciumQueueIx(tx, signature, match) {
385
+ if (!tx.meta) {
386
+ throw new Error(`Transaction metadata missing for queue tx ${signature}; cannot resolve account keys.`);
387
+ }
388
+ let keys;
389
+ try {
390
+ keys = tx.transaction.message.getAccountKeys({
391
+ accountKeysFromLookups: tx.meta.loadedAddresses,
392
+ });
393
+ }
394
+ catch (error) {
395
+ throw new Error(`Account keys could not be resolved for queue tx ${signature} `
396
+ + `(RPC may be missing address-lookup-table metadata): ${formatUnknownError(error)}`);
397
+ }
398
+ const tryMatch = (ix) => {
399
+ const programId = keys.get(ix.programIdIndex);
400
+ if (!programId || !programId.equals(ARCIUM_PROGRAM_ID)) {
401
+ return null;
402
+ }
403
+ // Index 2 is `comp`; enforce equality to compPDA only when one was supplied.
404
+ const accountIndexes = ix.accountKeyIndexes ?? ix.accounts;
405
+ const compIndex = accountIndexes?.[2];
406
+ const compIxKey = compIndex === undefined ? undefined : keys.get(compIndex);
407
+ if (!compIxKey) {
408
+ return null;
409
+ }
410
+ if (match.compPDA && !compIxKey.equals(match.compPDA)) {
411
+ return null;
412
+ }
413
+ const decoded = typeof ix.data === 'string'
414
+ ? ARCIUM_IX_CODER.decode(ix.data, 'base58')
415
+ : ARCIUM_IX_CODER.decode(Buffer.from(ix.data));
416
+ if (decoded?.name !== 'queue_computation') {
417
+ return null;
418
+ }
419
+ if (!isDecodedQueueIx(decoded.data)) {
420
+ return { kind: 'idlMismatch' };
421
+ }
422
+ if (!decoded.data.mxe_program.equals(match.mxeProgramId)) {
423
+ return null;
424
+ }
425
+ if (!decoded.data.comp_offset.eq(match.computationOffset)) {
426
+ return null;
427
+ }
428
+ return { kind: 'found', data: decoded.data };
429
+ };
430
+ for (const ix of tx.transaction.message.compiledInstructions) {
431
+ const result = tryMatch(ix);
432
+ if (result) {
433
+ return result;
434
+ }
435
+ }
436
+ if (tx.meta.innerInstructions == null) {
437
+ return { kind: 'innerInstructionsOmitted' };
438
+ }
439
+ for (const inner of tx.meta.innerInstructions) {
440
+ for (const ix of inner.instructions) {
441
+ const result = tryMatch(ix);
442
+ if (result) {
443
+ return result;
444
+ }
445
+ }
446
+ }
447
+ return { kind: 'notFound' };
448
+ }
449
+ /**
450
+ * Decode the matching `queue_computation` fee inputs from a fetched tx, or throw
451
+ * a descriptive `Error` for each non-success outcome.
452
+ */
453
+ function decodeQueueFeeFromTx(tx, signature, match) {
454
+ const result = findArciumQueueIx(tx, signature, match);
455
+ switch (result.kind) {
456
+ case 'found':
457
+ return feeFromQueueIx(result.data, signature);
458
+ case 'idlMismatch':
459
+ throw new Error(`queue_computation instruction in tx ${signature} does not match the current Arcium IDL.`);
460
+ case 'innerInstructionsOmitted':
461
+ throw new Error(`Cannot read inner instructions for tx ${signature}: the RPC omitted them, so the `
462
+ + `queue_computation CPI is not visible. Use an RPC that records inner instructions.`);
463
+ case 'notFound':
464
+ throw new Error(`No matching queue_computation instruction found in tx ${signature} `
465
+ + `for computation ${match.computationOffset.toString()}.`);
466
+ }
467
+ }
468
+ /**
469
+ * Fetch a live computation's on-chain fee by its offset.
470
+ *
471
+ * Returns the {@link ComputationFee} `account` variant while the computation
472
+ * account is alive, or `null` once it has been closed (`claim_computation_rent`,
473
+ * `reclaim_expired_computation_fee`, or a failure-claim flow). There is no
474
+ * automatic fallback for closed accounts: the on-chain data is gone, so a caller
475
+ * that stored the queue tx signature must pass it to
476
+ * {@link getComputationFeeFromQueueTx} to reconstruct the fee inputs.
477
+ *
478
+ * Throws if the live account at `(cluster, offset)` belongs to a different MXE:
479
+ * the computation PDA is keyed on `(cluster, comp_offset)` only, so two MXEs on
480
+ * a shared cluster can collide on it across lifecycles, and silently returning
481
+ * the wrong MXE's fee would be a correctness bug.
482
+ *
483
+ * @param arciumProgram - Anchor program instance (Arcium IDL).
484
+ * @param mxeProgramId - MXE program the computation belongs to; derives the
485
+ * cluster and computation PDA.
486
+ * @param computationOffset - Computation offset.
487
+ * @param commitment - RPC commitment for the account reads.
488
+ * @returns The live account fee, or `null` if the account is closed.
489
+ *
490
+ * @example
491
+ * import { getArciumProgram, getComputationFee, getComputationFeeFromQueueTx } from '@arcium-hq/reader';
492
+ *
493
+ * const program = getArciumProgram(provider);
494
+ * const fee = await getComputationFee(program, mxeProgramId, computationOffset);
495
+ * if (fee) {
496
+ * console.log('Live fee:', fee.baseFee.add(fee.priorityFee).toString());
497
+ * }
498
+ * else {
499
+ * // Closed: reconstruct from the stored queue tx signature.
500
+ * const queued = await getComputationFeeFromQueueTx(program, queueTxSignature, mxeProgramId, computationOffset);
501
+ * console.log('cuPriceMicro:', queued.cuPriceMicro.toString());
502
+ * }
503
+ */
504
+ async function getComputationFee(arciumProgram, mxeProgramId, computationOffset, commitment) {
505
+ const mxe = await arciumProgram.account.mxeAccount.fetch(client.getMXEAccAddress(mxeProgramId), commitment);
506
+ const compPDA = client.getComputationAccAddress(mxe.cluster, computationOffset);
507
+ const acc = await arciumProgram.account.computationAccount.fetchNullable(compPDA, commitment);
508
+ if (!acc) {
509
+ return null;
510
+ }
511
+ if (!acc.mxeProgramId.equals(mxeProgramId)) {
512
+ throw new Error(`Computation at offset ${computationOffset.toString()} (cluster ${mxe.cluster}) is owned by `
513
+ + `MXE ${acc.mxeProgramId.toBase58()}, not the requested ${mxeProgramId.toBase58()}.`);
514
+ }
515
+ return {
516
+ source: 'account',
517
+ baseFee: acc.executionFee.baseFee,
518
+ priorityFee: acc.executionFee.priorityFee,
519
+ callbackCuLimit: acc.executionFee.callbackCuLimit,
520
+ };
521
+ }
522
+ /**
523
+ * Decode a closed computation's raw fee inputs from its `queue_computation`
524
+ * transaction.
525
+ *
526
+ * Fetches the exact `queueTxSignature` (no signature scanning), rejects a failed
527
+ * or unavailable tx, and decodes the `queue_computation` ix matching
528
+ * `mxeProgramId` + `computationOffset`. Reads no current MXE/cluster state, so it
529
+ * stays correct across cluster migration.
530
+ *
531
+ * Pass `opts.cluster` to additionally bind the match to the comp PDA derived
532
+ * from that historical cluster (rejects a same-tx queue for a different
533
+ * cluster's PDA). Omit it for the common case of a single queue per tx.
534
+ *
535
+ * Returns raw queue-time ix inputs, not lamport fees. Exact reconstruction also
536
+ * needs queue-time state absent from the ix: cluster `cu_price`, comp-def
537
+ * `cuAmount`, and the callback context: when `callbackCuLimit` is 0 the callback
538
+ * priority term defaults from `customCallbackInstructionCount`.
539
+ *
540
+ * @param arciumProgram - Anchor program instance (Arcium IDL).
541
+ * @param queueTxSignature - Signature of the tx that queued the computation.
542
+ * @param mxeProgramId - MXE program the computation belongs to.
543
+ * @param computationOffset - Computation offset.
544
+ * @param opts - `cluster` binds the match to a historical cluster's comp PDA;
545
+ * `commitment` maps to `confirmed`/`finalized` for the tx fetch.
546
+ * @returns The `queueTx` fee variant.
547
+ * @throws `Error` if the tx is unavailable, failed, or carries no matching
548
+ * `queue_computation` instruction.
549
+ */
550
+ async function getComputationFeeFromQueueTx(arciumProgram, queueTxSignature, mxeProgramId, computationOffset, opts = {}) {
551
+ const rpcFinality = mapCommitmentToFinality(opts.commitment);
552
+ let tx;
553
+ try {
554
+ tx = await arciumProgram.provider.connection.getTransaction(queueTxSignature, {
555
+ maxSupportedTransactionVersion: 0,
556
+ commitment: rpcFinality,
557
+ });
558
+ }
559
+ catch (error) {
560
+ throw new Error(`Failed to fetch queue tx ${queueTxSignature}: ${formatUnknownError(error)}`);
561
+ }
562
+ if (!tx) {
563
+ throw new Error(`Queue transaction ${queueTxSignature} is unavailable from RPC. Use an archive RPC.`);
564
+ }
565
+ if (tx.meta?.err != null) {
566
+ throw new Error(`Queue transaction ${queueTxSignature} failed and did not pay the queue fee.`);
567
+ }
568
+ return decodeQueueFeeFromTx(tx, queueTxSignature, {
569
+ mxeProgramId,
570
+ computationOffset,
571
+ compPDA: opts.cluster === undefined
572
+ ? undefined
573
+ : client.getComputationAccAddress(opts.cluster, computationOffset),
574
+ });
575
+ }
314
576
 
315
577
  Object.defineProperty(exports, "getArciumProgram", {
316
578
  enumerable: true,
317
579
  get: function () { return client.getArciumProgram; }
318
580
  });
581
+ Object.defineProperty(exports, "getArciumSignerAccAddress", {
582
+ enumerable: true,
583
+ get: function () { return client.getArciumSignerAccAddress; }
584
+ });
319
585
  Object.defineProperty(exports, "getArxNodeAccAddress", {
320
586
  enumerable: true,
321
587
  get: function () { return client.getArxNodeAccAddress; }
@@ -344,10 +610,18 @@ Object.defineProperty(exports, "getExecutingPoolAccAddress", {
344
610
  enumerable: true,
345
611
  get: function () { return client.getExecutingPoolAccAddress; }
346
612
  });
613
+ Object.defineProperty(exports, "getFailureClaimAccAddress", {
614
+ enumerable: true,
615
+ get: function () { return client.getFailureClaimAccAddress; }
616
+ });
347
617
  Object.defineProperty(exports, "getFeePoolAccAddress", {
348
618
  enumerable: true,
349
619
  get: function () { return client.getFeePoolAccAddress; }
350
620
  });
621
+ Object.defineProperty(exports, "getLookupTableAddress", {
622
+ enumerable: true,
623
+ get: function () { return client.getLookupTableAddress; }
624
+ });
351
625
  Object.defineProperty(exports, "getMXEAccAddress", {
352
626
  enumerable: true,
353
627
  get: function () { return client.getMXEAccAddress; }
@@ -360,12 +634,34 @@ Object.defineProperty(exports, "getMempoolPriorityFeeStats", {
360
634
  enumerable: true,
361
635
  get: function () { return client.getMempoolPriorityFeeStats; }
362
636
  });
637
+ Object.defineProperty(exports, "getMxeRecoveryAccAddress", {
638
+ enumerable: true,
639
+ get: function () { return client.getMxeRecoveryAccAddress; }
640
+ });
641
+ Object.defineProperty(exports, "getOperatorAccAddress", {
642
+ enumerable: true,
643
+ get: function () { return client.getOperatorAccAddress; }
644
+ });
645
+ Object.defineProperty(exports, "getRawCircuitAccAddress", {
646
+ enumerable: true,
647
+ get: function () { return client.getRawCircuitAccAddress; }
648
+ });
649
+ Object.defineProperty(exports, "getRecoveryClusterAccAddress", {
650
+ enumerable: true,
651
+ get: function () { return client.getRecoveryClusterAccAddress; }
652
+ });
653
+ Object.defineProperty(exports, "getRecoveryPeerAccAddress", {
654
+ enumerable: true,
655
+ get: function () { return client.getRecoveryPeerAccAddress; }
656
+ });
363
657
  exports.getArxNodeAccAddresses = getArxNodeAccAddresses;
364
658
  exports.getArxNodeAccInfo = getArxNodeAccInfo;
365
659
  exports.getClusterAccAddresses = getClusterAccAddresses;
366
660
  exports.getClusterAccInfo = getClusterAccInfo;
367
661
  exports.getCompDefAccInfo = getCompDefAccInfo;
368
662
  exports.getComputationAccInfo = getComputationAccInfo;
663
+ exports.getComputationFee = getComputationFee;
664
+ exports.getComputationFeeFromQueueTx = getComputationFeeFromQueueTx;
369
665
  exports.getComputationOffset = getComputationOffset;
370
666
  exports.getMXEAccAddresses = getMXEAccAddresses;
371
667
  exports.getMXEAccInfo = getMXEAccInfo;
package/build/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import { ARCIUM_ADDR, ARCIUM_IDL } from '@arcium-hq/client';
2
- export { getArciumProgram, getArxNodeAccAddress, getClockAccAddress, getClusterAccAddress, getCompDefAccAddress, getComputationAccAddress, getComputationsInMempool, getExecutingPoolAccAddress, getFeePoolAccAddress, getMXEAccAddress, getMempoolAccAddress, getMempoolPriorityFeeStats } from '@arcium-hq/client';
1
+ import { ARCIUM_ADDR, ARCIUM_IDL, getMXEAccAddress, getComputationAccAddress } from '@arcium-hq/client';
2
+ export { getArciumProgram, getArciumSignerAccAddress, getArxNodeAccAddress, getClockAccAddress, getClusterAccAddress, getCompDefAccAddress, getComputationAccAddress, getComputationsInMempool, getExecutingPoolAccAddress, getFailureClaimAccAddress, getFeePoolAccAddress, getLookupTableAddress, getMXEAccAddress, getMempoolAccAddress, getMempoolPriorityFeeStats, getMxeRecoveryAccAddress, getOperatorAccAddress, getRawCircuitAccAddress, getRecoveryClusterAccAddress, getRecoveryPeerAccAddress } from '@arcium-hq/client';
3
3
  import * as anchor from '@anchor-lang/core';
4
+ import anchor__default from '@anchor-lang/core';
4
5
 
5
6
  /**
6
7
  * Prefix for program data logs in Solana transaction logs.
@@ -41,7 +42,7 @@ const ARCIUM_PROGRAM_ID = new anchor.web3.PublicKey(ARCIUM_ADDR);
41
42
  /**
42
43
  * Anchor coder for encoding and decoding Arcium program instructions.
43
44
  */
44
- new anchor.BorshInstructionCoder(ARCIUM_IDL);
45
+ const ARCIUM_IX_CODER = new anchor.BorshInstructionCoder(ARCIUM_IDL);
45
46
  /**
46
47
  * Anchor event parser for parsing Arcium program events from transaction logs.
47
48
  */
@@ -171,6 +172,7 @@ async function getArciumAccPubkeys(conn, discriminator) {
171
172
  return accs.map((acc) => acc.pubkey);
172
173
  }
173
174
 
175
+ const { BN } = anchor__default;
174
176
  /**
175
177
  * Subscribe to computation events for an MXE program.
176
178
  *
@@ -291,5 +293,266 @@ function getComputationEventsFromLogs(logs) {
291
293
  };
292
294
  });
293
295
  }
296
+ function formatUnknownError(error) {
297
+ return error instanceof Error ? error.message : String(error);
298
+ }
299
+ /**
300
+ * `getTransaction` rejects the `processed` commitment, so map down to a
301
+ * `Finality`: `finalized` passes through, everything else reads `confirmed`.
302
+ */
303
+ function mapCommitmentToFinality(commitment) {
304
+ return commitment === 'finalized' ? 'finalized' : 'confirmed';
305
+ }
306
+ /**
307
+ * Type guard for {@link DecodedQueueIxFields}. Anchor's coder types
308
+ * `decode().data` as the unspecific `Object`, so the surfaced fields must be
309
+ * proven present and correctly shaped before they are read.
310
+ */
311
+ function isDecodedQueueIx(data) {
312
+ if (data === null || typeof data !== 'object') {
313
+ return false;
314
+ }
315
+ if (!('comp_offset' in data)
316
+ || !('cu_price_micro' in data)
317
+ || !('callback_cu_limit' in data)
318
+ || !('padding' in data)
319
+ || !('computation_definition_offset' in data)
320
+ || !('callback_transactions_required' in data)
321
+ || !('custom_callback_instructions' in data)
322
+ || !('mxe_program' in data)) {
323
+ return false;
324
+ }
325
+ return (BN.isBN(data.comp_offset)
326
+ && BN.isBN(data.cu_price_micro)
327
+ && typeof data.callback_cu_limit === 'number'
328
+ && typeof data.padding === 'number'
329
+ && typeof data.computation_definition_offset === 'number'
330
+ && typeof data.callback_transactions_required === 'number'
331
+ && Array.isArray(data.custom_callback_instructions)
332
+ && data.mxe_program instanceof anchor.web3.PublicKey);
333
+ }
334
+ function feeFromQueueIx(decoded, queueTxSignature) {
335
+ return {
336
+ source: 'queueTx',
337
+ cuPriceMicro: decoded.cu_price_micro,
338
+ callbackCuLimit: decoded.callback_cu_limit,
339
+ customCallbackInstructionCount: decoded.custom_callback_instructions.length,
340
+ computationDefinitionOffset: decoded.computation_definition_offset,
341
+ callbackTransactionsRequired: decoded.callback_transactions_required,
342
+ queueTxSignature,
343
+ };
344
+ }
345
+ /**
346
+ * Find the `queue_computation` ix in a transaction whose decoded `mxe_program`
347
+ * and `comp_offset` match, scanning outer ixs first and inner CPI ixs second.
348
+ *
349
+ * The ix must carry a `comp` account at IDL index 2. When `match.compPDA` is
350
+ * given, that account must also equal it: this binds the result to one
351
+ * historical lifecycle and rejects a same-tx queue for a different cluster's
352
+ * PDA. When it is omitted, the index-2 slot only has to exist, so an
353
+ * exact-signature decode needs no current cluster state.
354
+ *
355
+ * `queue_computation` is normally a CPI from the MXE program, so the inner-ix
356
+ * list is the primary path. Outers are scanned first so a present outer match
357
+ * is never masked by an omitted inner list; only when no outer matched and the
358
+ * RPC gave no inner list (`innerInstructions` null or absent) do we report
359
+ * `innerInstructionsOmitted`, distinct from "no queue ix exists".
360
+ *
361
+ * Outer ix `data` is `Uint8Array`; inner ix `data` is base58 `string`, routed
362
+ * by JS type. Anchor's `decode` returns `null` on discriminator mismatch, so
363
+ * the name check doubles as a discriminator check.
364
+ */
365
+ function findArciumQueueIx(tx, signature, match) {
366
+ if (!tx.meta) {
367
+ throw new Error(`Transaction metadata missing for queue tx ${signature}; cannot resolve account keys.`);
368
+ }
369
+ let keys;
370
+ try {
371
+ keys = tx.transaction.message.getAccountKeys({
372
+ accountKeysFromLookups: tx.meta.loadedAddresses,
373
+ });
374
+ }
375
+ catch (error) {
376
+ throw new Error(`Account keys could not be resolved for queue tx ${signature} `
377
+ + `(RPC may be missing address-lookup-table metadata): ${formatUnknownError(error)}`);
378
+ }
379
+ const tryMatch = (ix) => {
380
+ const programId = keys.get(ix.programIdIndex);
381
+ if (!programId || !programId.equals(ARCIUM_PROGRAM_ID)) {
382
+ return null;
383
+ }
384
+ // Index 2 is `comp`; enforce equality to compPDA only when one was supplied.
385
+ const accountIndexes = ix.accountKeyIndexes ?? ix.accounts;
386
+ const compIndex = accountIndexes?.[2];
387
+ const compIxKey = compIndex === undefined ? undefined : keys.get(compIndex);
388
+ if (!compIxKey) {
389
+ return null;
390
+ }
391
+ if (match.compPDA && !compIxKey.equals(match.compPDA)) {
392
+ return null;
393
+ }
394
+ const decoded = typeof ix.data === 'string'
395
+ ? ARCIUM_IX_CODER.decode(ix.data, 'base58')
396
+ : ARCIUM_IX_CODER.decode(Buffer.from(ix.data));
397
+ if (decoded?.name !== 'queue_computation') {
398
+ return null;
399
+ }
400
+ if (!isDecodedQueueIx(decoded.data)) {
401
+ return { kind: 'idlMismatch' };
402
+ }
403
+ if (!decoded.data.mxe_program.equals(match.mxeProgramId)) {
404
+ return null;
405
+ }
406
+ if (!decoded.data.comp_offset.eq(match.computationOffset)) {
407
+ return null;
408
+ }
409
+ return { kind: 'found', data: decoded.data };
410
+ };
411
+ for (const ix of tx.transaction.message.compiledInstructions) {
412
+ const result = tryMatch(ix);
413
+ if (result) {
414
+ return result;
415
+ }
416
+ }
417
+ if (tx.meta.innerInstructions == null) {
418
+ return { kind: 'innerInstructionsOmitted' };
419
+ }
420
+ for (const inner of tx.meta.innerInstructions) {
421
+ for (const ix of inner.instructions) {
422
+ const result = tryMatch(ix);
423
+ if (result) {
424
+ return result;
425
+ }
426
+ }
427
+ }
428
+ return { kind: 'notFound' };
429
+ }
430
+ /**
431
+ * Decode the matching `queue_computation` fee inputs from a fetched tx, or throw
432
+ * a descriptive `Error` for each non-success outcome.
433
+ */
434
+ function decodeQueueFeeFromTx(tx, signature, match) {
435
+ const result = findArciumQueueIx(tx, signature, match);
436
+ switch (result.kind) {
437
+ case 'found':
438
+ return feeFromQueueIx(result.data, signature);
439
+ case 'idlMismatch':
440
+ throw new Error(`queue_computation instruction in tx ${signature} does not match the current Arcium IDL.`);
441
+ case 'innerInstructionsOmitted':
442
+ throw new Error(`Cannot read inner instructions for tx ${signature}: the RPC omitted them, so the `
443
+ + `queue_computation CPI is not visible. Use an RPC that records inner instructions.`);
444
+ case 'notFound':
445
+ throw new Error(`No matching queue_computation instruction found in tx ${signature} `
446
+ + `for computation ${match.computationOffset.toString()}.`);
447
+ }
448
+ }
449
+ /**
450
+ * Fetch a live computation's on-chain fee by its offset.
451
+ *
452
+ * Returns the {@link ComputationFee} `account` variant while the computation
453
+ * account is alive, or `null` once it has been closed (`claim_computation_rent`,
454
+ * `reclaim_expired_computation_fee`, or a failure-claim flow). There is no
455
+ * automatic fallback for closed accounts: the on-chain data is gone, so a caller
456
+ * that stored the queue tx signature must pass it to
457
+ * {@link getComputationFeeFromQueueTx} to reconstruct the fee inputs.
458
+ *
459
+ * Throws if the live account at `(cluster, offset)` belongs to a different MXE:
460
+ * the computation PDA is keyed on `(cluster, comp_offset)` only, so two MXEs on
461
+ * a shared cluster can collide on it across lifecycles, and silently returning
462
+ * the wrong MXE's fee would be a correctness bug.
463
+ *
464
+ * @param arciumProgram - Anchor program instance (Arcium IDL).
465
+ * @param mxeProgramId - MXE program the computation belongs to; derives the
466
+ * cluster and computation PDA.
467
+ * @param computationOffset - Computation offset.
468
+ * @param commitment - RPC commitment for the account reads.
469
+ * @returns The live account fee, or `null` if the account is closed.
470
+ *
471
+ * @example
472
+ * import { getArciumProgram, getComputationFee, getComputationFeeFromQueueTx } from '@arcium-hq/reader';
473
+ *
474
+ * const program = getArciumProgram(provider);
475
+ * const fee = await getComputationFee(program, mxeProgramId, computationOffset);
476
+ * if (fee) {
477
+ * console.log('Live fee:', fee.baseFee.add(fee.priorityFee).toString());
478
+ * }
479
+ * else {
480
+ * // Closed: reconstruct from the stored queue tx signature.
481
+ * const queued = await getComputationFeeFromQueueTx(program, queueTxSignature, mxeProgramId, computationOffset);
482
+ * console.log('cuPriceMicro:', queued.cuPriceMicro.toString());
483
+ * }
484
+ */
485
+ async function getComputationFee(arciumProgram, mxeProgramId, computationOffset, commitment) {
486
+ const mxe = await arciumProgram.account.mxeAccount.fetch(getMXEAccAddress(mxeProgramId), commitment);
487
+ const compPDA = getComputationAccAddress(mxe.cluster, computationOffset);
488
+ const acc = await arciumProgram.account.computationAccount.fetchNullable(compPDA, commitment);
489
+ if (!acc) {
490
+ return null;
491
+ }
492
+ if (!acc.mxeProgramId.equals(mxeProgramId)) {
493
+ throw new Error(`Computation at offset ${computationOffset.toString()} (cluster ${mxe.cluster}) is owned by `
494
+ + `MXE ${acc.mxeProgramId.toBase58()}, not the requested ${mxeProgramId.toBase58()}.`);
495
+ }
496
+ return {
497
+ source: 'account',
498
+ baseFee: acc.executionFee.baseFee,
499
+ priorityFee: acc.executionFee.priorityFee,
500
+ callbackCuLimit: acc.executionFee.callbackCuLimit,
501
+ };
502
+ }
503
+ /**
504
+ * Decode a closed computation's raw fee inputs from its `queue_computation`
505
+ * transaction.
506
+ *
507
+ * Fetches the exact `queueTxSignature` (no signature scanning), rejects a failed
508
+ * or unavailable tx, and decodes the `queue_computation` ix matching
509
+ * `mxeProgramId` + `computationOffset`. Reads no current MXE/cluster state, so it
510
+ * stays correct across cluster migration.
511
+ *
512
+ * Pass `opts.cluster` to additionally bind the match to the comp PDA derived
513
+ * from that historical cluster (rejects a same-tx queue for a different
514
+ * cluster's PDA). Omit it for the common case of a single queue per tx.
515
+ *
516
+ * Returns raw queue-time ix inputs, not lamport fees. Exact reconstruction also
517
+ * needs queue-time state absent from the ix: cluster `cu_price`, comp-def
518
+ * `cuAmount`, and the callback context: when `callbackCuLimit` is 0 the callback
519
+ * priority term defaults from `customCallbackInstructionCount`.
520
+ *
521
+ * @param arciumProgram - Anchor program instance (Arcium IDL).
522
+ * @param queueTxSignature - Signature of the tx that queued the computation.
523
+ * @param mxeProgramId - MXE program the computation belongs to.
524
+ * @param computationOffset - Computation offset.
525
+ * @param opts - `cluster` binds the match to a historical cluster's comp PDA;
526
+ * `commitment` maps to `confirmed`/`finalized` for the tx fetch.
527
+ * @returns The `queueTx` fee variant.
528
+ * @throws `Error` if the tx is unavailable, failed, or carries no matching
529
+ * `queue_computation` instruction.
530
+ */
531
+ async function getComputationFeeFromQueueTx(arciumProgram, queueTxSignature, mxeProgramId, computationOffset, opts = {}) {
532
+ const rpcFinality = mapCommitmentToFinality(opts.commitment);
533
+ let tx;
534
+ try {
535
+ tx = await arciumProgram.provider.connection.getTransaction(queueTxSignature, {
536
+ maxSupportedTransactionVersion: 0,
537
+ commitment: rpcFinality,
538
+ });
539
+ }
540
+ catch (error) {
541
+ throw new Error(`Failed to fetch queue tx ${queueTxSignature}: ${formatUnknownError(error)}`);
542
+ }
543
+ if (!tx) {
544
+ throw new Error(`Queue transaction ${queueTxSignature} is unavailable from RPC. Use an archive RPC.`);
545
+ }
546
+ if (tx.meta?.err != null) {
547
+ throw new Error(`Queue transaction ${queueTxSignature} failed and did not pay the queue fee.`);
548
+ }
549
+ return decodeQueueFeeFromTx(tx, queueTxSignature, {
550
+ mxeProgramId,
551
+ computationOffset,
552
+ compPDA: opts.cluster === undefined
553
+ ? undefined
554
+ : getComputationAccAddress(opts.cluster, computationOffset),
555
+ });
556
+ }
294
557
 
295
- export { getArxNodeAccAddresses, getArxNodeAccInfo, getClusterAccAddresses, getClusterAccInfo, getCompDefAccInfo, getComputationAccInfo, getComputationOffset, getMXEAccAddresses, getMXEAccInfo, getMxeRecoveryAccInfo, getRecoveryClusterAccInfo, subscribeComputations, unsubscribeComputations };
558
+ export { getArxNodeAccAddresses, getArxNodeAccInfo, getClusterAccAddresses, getClusterAccInfo, getCompDefAccInfo, getComputationAccInfo, getComputationFee, getComputationFeeFromQueueTx, getComputationOffset, getMXEAccAddresses, getMXEAccInfo, getMxeRecoveryAccInfo, getRecoveryClusterAccInfo, subscribeComputations, unsubscribeComputations };
@@ -1,5 +1,6 @@
1
1
  import * as anchor from '@anchor-lang/core';
2
- import { ArciumEventName, ComputationEventData, Connection, PublicKey } from './types.js';
2
+ import { ArciumIdlType } from '@arcium-hq/client';
3
+ import type { ArciumEventName, ComputationEventData, ComputationFee, Connection, PublicKey } from './types.js';
3
4
  /**
4
5
  * Subscribe to computation events for an MXE program.
5
6
  *
@@ -38,4 +39,77 @@ export declare function unsubscribeComputations(conn: Connection, subscriptionId
38
39
  * @throws Error if multiple computation offsets are found in the transaction.
39
40
  */
40
41
  export declare function getComputationOffset(tx: anchor.web3.VersionedTransactionResponse): anchor.BN | undefined;
42
+ /**
43
+ * Fetch a live computation's on-chain fee by its offset.
44
+ *
45
+ * Returns the {@link ComputationFee} `account` variant while the computation
46
+ * account is alive, or `null` once it has been closed (`claim_computation_rent`,
47
+ * `reclaim_expired_computation_fee`, or a failure-claim flow). There is no
48
+ * automatic fallback for closed accounts: the on-chain data is gone, so a caller
49
+ * that stored the queue tx signature must pass it to
50
+ * {@link getComputationFeeFromQueueTx} to reconstruct the fee inputs.
51
+ *
52
+ * Throws if the live account at `(cluster, offset)` belongs to a different MXE:
53
+ * the computation PDA is keyed on `(cluster, comp_offset)` only, so two MXEs on
54
+ * a shared cluster can collide on it across lifecycles, and silently returning
55
+ * the wrong MXE's fee would be a correctness bug.
56
+ *
57
+ * @param arciumProgram - Anchor program instance (Arcium IDL).
58
+ * @param mxeProgramId - MXE program the computation belongs to; derives the
59
+ * cluster and computation PDA.
60
+ * @param computationOffset - Computation offset.
61
+ * @param commitment - RPC commitment for the account reads.
62
+ * @returns The live account fee, or `null` if the account is closed.
63
+ *
64
+ * @example
65
+ * import { getArciumProgram, getComputationFee, getComputationFeeFromQueueTx } from '@arcium-hq/reader';
66
+ *
67
+ * const program = getArciumProgram(provider);
68
+ * const fee = await getComputationFee(program, mxeProgramId, computationOffset);
69
+ * if (fee) {
70
+ * console.log('Live fee:', fee.baseFee.add(fee.priorityFee).toString());
71
+ * }
72
+ * else {
73
+ * // Closed: reconstruct from the stored queue tx signature.
74
+ * const queued = await getComputationFeeFromQueueTx(program, queueTxSignature, mxeProgramId, computationOffset);
75
+ * console.log('cuPriceMicro:', queued.cuPriceMicro.toString());
76
+ * }
77
+ */
78
+ export declare function getComputationFee(arciumProgram: anchor.Program<ArciumIdlType>, mxeProgramId: PublicKey, computationOffset: anchor.BN, commitment?: anchor.web3.Commitment): Promise<Extract<ComputationFee, {
79
+ source: 'account';
80
+ }> | null>;
81
+ /**
82
+ * Decode a closed computation's raw fee inputs from its `queue_computation`
83
+ * transaction.
84
+ *
85
+ * Fetches the exact `queueTxSignature` (no signature scanning), rejects a failed
86
+ * or unavailable tx, and decodes the `queue_computation` ix matching
87
+ * `mxeProgramId` + `computationOffset`. Reads no current MXE/cluster state, so it
88
+ * stays correct across cluster migration.
89
+ *
90
+ * Pass `opts.cluster` to additionally bind the match to the comp PDA derived
91
+ * from that historical cluster (rejects a same-tx queue for a different
92
+ * cluster's PDA). Omit it for the common case of a single queue per tx.
93
+ *
94
+ * Returns raw queue-time ix inputs, not lamport fees. Exact reconstruction also
95
+ * needs queue-time state absent from the ix: cluster `cu_price`, comp-def
96
+ * `cuAmount`, and the callback context: when `callbackCuLimit` is 0 the callback
97
+ * priority term defaults from `customCallbackInstructionCount`.
98
+ *
99
+ * @param arciumProgram - Anchor program instance (Arcium IDL).
100
+ * @param queueTxSignature - Signature of the tx that queued the computation.
101
+ * @param mxeProgramId - MXE program the computation belongs to.
102
+ * @param computationOffset - Computation offset.
103
+ * @param opts - `cluster` binds the match to a historical cluster's comp PDA;
104
+ * `commitment` maps to `confirmed`/`finalized` for the tx fetch.
105
+ * @returns The `queueTx` fee variant.
106
+ * @throws `Error` if the tx is unavailable, failed, or carries no matching
107
+ * `queue_computation` instruction.
108
+ */
109
+ export declare function getComputationFeeFromQueueTx(arciumProgram: anchor.Program<ArciumIdlType>, queueTxSignature: string, mxeProgramId: PublicKey, computationOffset: anchor.BN, opts?: {
110
+ cluster?: number;
111
+ commitment?: anchor.web3.Commitment;
112
+ }): Promise<Extract<ComputationFee, {
113
+ source: 'queueTx';
114
+ }>>;
41
115
  //# sourceMappingURL=computationGetters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"computationGetters.d.ts","sourceRoot":"","sources":["../../src/computationGetters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EACH,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,SAAS,EACZ,MAAM,YAAY,CAAC;AAGpB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,qBAAqB,CACvC,IAAI,EAAE,UAAU,EAChB,YAAY,EAAE,SAAS,EACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,EAAE,IAAI,EAAE,eAAe,KAAK,IAAI,GACvE,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CACzC,IAAI,EAAE,UAAU,EAChB,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAChC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,4BAA4B,GAC7C,MAAM,CAAC,EAAE,GAAG,SAAS,CAkBvB"}
1
+ {"version":3,"file":"computationGetters.d.ts","sourceRoot":"","sources":["../../src/computationGetters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAM5C,OAAO,EACH,aAAa,EAGhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACR,eAAe,EACf,oBAAoB,EACpB,cAAc,EACd,UAAU,EACV,SAAS,EACZ,MAAM,YAAY,CAAC;AAYpB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,qBAAqB,CACvC,IAAI,EAAE,UAAU,EAChB,YAAY,EAAE,SAAS,EACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,EAAE,IAAI,EAAE,eAAe,KAAK,IAAI,GACvE,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CACzC,IAAI,EAAE,UAAU,EAChB,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAChC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,4BAA4B,GAC7C,MAAM,CAAC,EAAE,GAAG,SAAS,CAkBvB;AAwTD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,iBAAiB,CACnC,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAC5C,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,MAAM,CAAC,EAAE,EAC5B,UAAU,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,GACpC,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC,GAAG,IAAI,CAAC,CAyBhE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,4BAA4B,CAC9C,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAC5C,gBAAgB,EAAE,MAAM,EACxB,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,MAAM,CAAC,EAAE,EAC5B,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAA;CAAO,GACrE,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC,CA6BzD"}
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @packageDocumentation
10
10
  */
11
- export { getClusterAccAddress, getArxNodeAccAddress, getMXEAccAddress, getArciumProgram, getComputationAccAddress, getMempoolAccAddress, getClockAccAddress, getFeePoolAccAddress, getExecutingPoolAccAddress, getCompDefAccAddress, getComputationsInMempool, getMempoolPriorityFeeStats, } from '@arcium-hq/client';
11
+ export { getClusterAccAddress, getArxNodeAccAddress, getMXEAccAddress, getArciumProgram, getComputationAccAddress, getMempoolAccAddress, getClockAccAddress, getFeePoolAccAddress, getExecutingPoolAccAddress, getCompDefAccAddress, getRecoveryClusterAccAddress, getMxeRecoveryAccAddress, getRecoveryPeerAccAddress, getOperatorAccAddress, getFailureClaimAccAddress, getArciumSignerAccAddress, getRawCircuitAccAddress, getLookupTableAddress, getComputationsInMempool, getMempoolPriorityFeeStats, } from '@arcium-hq/client';
12
12
  export * from './accGetters.js';
13
13
  export * from './computationGetters.js';
14
14
  export * from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACH,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,GAC7B,MAAM,mBAAmB,CAAC;AAE3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACH,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EACpB,4BAA4B,EAC5B,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,wBAAwB,EACxB,0BAA0B,GAC7B,MAAM,mBAAmB,CAAC;AAE3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC"}
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { ArciumIdlType } from '@arcium-hq/client';
6
6
  import * as anchor from '@anchor-lang/core';
7
+ export type { MempoolPriorityFeeStats } from '@arcium-hq/client';
7
8
  /**
8
9
  * Solana PublicKey type alias for convenience.
9
10
  */
@@ -95,4 +96,33 @@ export type ArciumInstructionName = ArciumIdlType['instructions'][number]['name'
95
96
  * Status values for a computation, as defined by the Arcium protocol.
96
97
  */
97
98
  export type ComputationStatus = 'queued' | 'executing' | 'executed' | 'finalized' | 'failed';
99
+ /**
100
+ * Fee information for a computation, tagged by the source it was read from.
101
+ *
102
+ * - `account`: read from the live on-chain `ComputationAccount.executionFee`.
103
+ * - `queueTx`: the raw queue-time ix inputs decoded from the original
104
+ * `queueComputation` instruction, for callers reconstructing a closed
105
+ * computation's fee from its queue transaction.
106
+ *
107
+ * The `queueTx` variant surfaces raw inputs, not lamport fees. Exact
108
+ * reconstruction needs queue-time state absent from the ix: `baseFee` from
109
+ * `cluster.cu_price` x comp-def `cuAmount`, and `priorityFee` from
110
+ * `cuPriceMicro` x `cuAmount` plus a callback term. When `callbackCuLimit` is 0
111
+ * (no explicit budget) that callback term defaults from
112
+ * `customCallbackInstructionCount`, so the count is surfaced for that case.
113
+ */
114
+ export type ComputationFee = {
115
+ source: 'account';
116
+ baseFee: anchor.BN;
117
+ priorityFee: anchor.BN;
118
+ callbackCuLimit: number;
119
+ } | {
120
+ source: 'queueTx';
121
+ cuPriceMicro: anchor.BN;
122
+ callbackCuLimit: number;
123
+ customCallbackInstructionCount: number;
124
+ computationDefinitionOffset: number;
125
+ callbackTransactionsRequired: number;
126
+ queueTxSignature: string;
127
+ };
98
128
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAE5C;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;AAE9C;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;AAEhD;;GAEG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAElH;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,MAAM,WAAW,CAAC,CAAC;AAE5D;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,WAAW,CAAC,CAAC;AAE7D;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,GAAG,sBAAsB,GAAG,0BAA0B,GAAG,0BAA0B,CAAC;AAE9I;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,oBAAoB,CAAC,CAAC;AAErE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,WAAW,CAAC,wBAAwB,CAAC,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,oBAAoB,CAAC,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,oBAAoB,CAAC,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,sBAAsB,CAAC,CAAC;AAEvE;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,WAAW,CAAC,8BAA8B,CAAC,CAAC;AAEvF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC,kBAAkB,CAAC,CAAC;AAErE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC,qBAAqB,CAAC,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAElF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAE5C,YAAY,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;AAE9C;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;AAEhD;;GAEG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAElH;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,MAAM,WAAW,CAAC,CAAC;AAE5D;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,WAAW,CAAC,CAAC;AAE7D;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,GAAG,sBAAsB,GAAG,0BAA0B,GAAG,0BAA0B,CAAC;AAE9I;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,oBAAoB,CAAC,CAAC;AAErE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,WAAW,CAAC,wBAAwB,CAAC,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,oBAAoB,CAAC,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,oBAAoB,CAAC,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,sBAAsB,CAAC,CAAC;AAEvE;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,WAAW,CAAC,8BAA8B,CAAC,CAAC;AAEvF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC,kBAAkB,CAAC,CAAC;AAErE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC,qBAAqB,CAAC,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAElF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE7F;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,cAAc,GACpB;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CAC3B,GACC;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,8BAA8B,EAAE,MAAM,CAAC;IACvC,2BAA2B,EAAE,MAAM,CAAC;IACpC,4BAA4B,EAAE,MAAM,CAAC;IACrC,gBAAgB,EAAE,MAAM,CAAC;CAC5B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcium-hq/reader",
3
- "version": "0.10.3",
3
+ "version": "0.11.0",
4
4
  "description": "Reader SDK for fetching onchain data for Arcium network programs",
5
5
  "author": "Arcium",
6
6
  "license": "GPL-3.0-only",
@@ -58,7 +58,7 @@
58
58
  "@anchor-lang/core": "^1.0.2",
59
59
  "@noble/curves": "^1.9.5",
60
60
  "@noble/hashes": "^1.7.1",
61
- "@arcium-hq/client": "0.10.3"
61
+ "@arcium-hq/client": "0.11.0"
62
62
  },
63
63
  "keywords": [
64
64
  "Cryptography",
@@ -1,11 +1,31 @@
1
1
  import * as anchor from '@anchor-lang/core';
2
+ // `anchor.BN` is exposed on the CJS module via `Object.defineProperty` with a
3
+ // runtime getter, which Node's ESM-CJS interop does not hoist into the
4
+ // namespace import. The default import bypasses this and lands the constructor
5
+ // reliably under both Rollup bundling and ts-node/esm test execution.
6
+ import anchorPkg from '@anchor-lang/core';
2
7
  import {
8
+ ArciumIdlType,
9
+ getComputationAccAddress,
10
+ getMXEAccAddress,
11
+ } from '@arcium-hq/client';
12
+ import type {
3
13
  ArciumEventName,
4
14
  ComputationEventData,
15
+ ComputationFee,
5
16
  Connection,
6
17
  PublicKey,
7
18
  } from './types.js';
8
- import { ARCIUM_BORSH_CODER, PROGRAM_DATA_PREFIX, PROGRAM_LOG_PREFIX, ARCIUM_EVENT_NAMES } from './constants.js';
19
+ import {
20
+ ARCIUM_BORSH_CODER,
21
+ ARCIUM_IX_CODER,
22
+ ARCIUM_PROGRAM_ID,
23
+ PROGRAM_DATA_PREFIX,
24
+ PROGRAM_LOG_PREFIX,
25
+ ARCIUM_EVENT_NAMES,
26
+ } from './constants.js';
27
+
28
+ const { BN } = anchorPkg;
9
29
 
10
30
  /**
11
31
  * Subscribe to computation events for an MXE program.
@@ -154,3 +174,375 @@ function getComputationEventsFromLogs(
154
174
  };
155
175
  });
156
176
  }
177
+
178
+ /**
179
+ * Fields decoded from a `queue_computation` instruction.
180
+ *
181
+ * snake_case because the runtime IDL (`arcium.json`) names args in snake_case,
182
+ * even though the camelCase TS IDL types differ. Runtime narrowing target for
183
+ * the {@link anchor.BorshInstructionCoder} `decode` result, whose `data` is
184
+ * typed as the unspecific `Object`.
185
+ */
186
+ type DecodedQueueIxFields = {
187
+ comp_offset: anchor.BN;
188
+ cu_price_micro: anchor.BN;
189
+ callback_cu_limit: number;
190
+ padding: number;
191
+ computation_definition_offset: number;
192
+ callback_transactions_required: number;
193
+ custom_callback_instructions: unknown[];
194
+ mxe_program: anchor.web3.PublicKey;
195
+ };
196
+
197
+ type QueueIxSearchResult =
198
+ | { kind: 'found'; data: DecodedQueueIxFields }
199
+ | { kind: 'notFound' }
200
+ | { kind: 'idlMismatch' }
201
+ | { kind: 'innerInstructionsOmitted' };
202
+
203
+ function formatUnknownError(error: unknown): string {
204
+ return error instanceof Error ? error.message : String(error);
205
+ }
206
+
207
+ /**
208
+ * `getTransaction` rejects the `processed` commitment, so map down to a
209
+ * `Finality`: `finalized` passes through, everything else reads `confirmed`.
210
+ */
211
+ function mapCommitmentToFinality(commitment: anchor.web3.Commitment | undefined): anchor.web3.Finality {
212
+ return commitment === 'finalized' ? 'finalized' : 'confirmed';
213
+ }
214
+
215
+ /**
216
+ * Type guard for {@link DecodedQueueIxFields}. Anchor's coder types
217
+ * `decode().data` as the unspecific `Object`, so the surfaced fields must be
218
+ * proven present and correctly shaped before they are read.
219
+ */
220
+ function isDecodedQueueIx(data: unknown): data is DecodedQueueIxFields {
221
+ if (data === null || typeof data !== 'object') {
222
+ return false;
223
+ }
224
+
225
+ if (
226
+ !('comp_offset' in data)
227
+ || !('cu_price_micro' in data)
228
+ || !('callback_cu_limit' in data)
229
+ || !('padding' in data)
230
+ || !('computation_definition_offset' in data)
231
+ || !('callback_transactions_required' in data)
232
+ || !('custom_callback_instructions' in data)
233
+ || !('mxe_program' in data)
234
+ ) {
235
+ return false;
236
+ }
237
+
238
+ return (
239
+ BN.isBN(data.comp_offset)
240
+ && BN.isBN(data.cu_price_micro)
241
+ && typeof data.callback_cu_limit === 'number'
242
+ && typeof data.padding === 'number'
243
+ && typeof data.computation_definition_offset === 'number'
244
+ && typeof data.callback_transactions_required === 'number'
245
+ && Array.isArray(data.custom_callback_instructions)
246
+ && data.mxe_program instanceof anchor.web3.PublicKey
247
+ );
248
+ }
249
+
250
+ function feeFromQueueIx(
251
+ decoded: DecodedQueueIxFields,
252
+ queueTxSignature: string,
253
+ ): Extract<ComputationFee, { source: 'queueTx' }> {
254
+ return {
255
+ source: 'queueTx',
256
+ cuPriceMicro: decoded.cu_price_micro,
257
+ callbackCuLimit: decoded.callback_cu_limit,
258
+ customCallbackInstructionCount: decoded.custom_callback_instructions.length,
259
+ computationDefinitionOffset: decoded.computation_definition_offset,
260
+ callbackTransactionsRequired: decoded.callback_transactions_required,
261
+ queueTxSignature,
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Find the `queue_computation` ix in a transaction whose decoded `mxe_program`
267
+ * and `comp_offset` match, scanning outer ixs first and inner CPI ixs second.
268
+ *
269
+ * The ix must carry a `comp` account at IDL index 2. When `match.compPDA` is
270
+ * given, that account must also equal it: this binds the result to one
271
+ * historical lifecycle and rejects a same-tx queue for a different cluster's
272
+ * PDA. When it is omitted, the index-2 slot only has to exist, so an
273
+ * exact-signature decode needs no current cluster state.
274
+ *
275
+ * `queue_computation` is normally a CPI from the MXE program, so the inner-ix
276
+ * list is the primary path. Outers are scanned first so a present outer match
277
+ * is never masked by an omitted inner list; only when no outer matched and the
278
+ * RPC gave no inner list (`innerInstructions` null or absent) do we report
279
+ * `innerInstructionsOmitted`, distinct from "no queue ix exists".
280
+ *
281
+ * Outer ix `data` is `Uint8Array`; inner ix `data` is base58 `string`, routed
282
+ * by JS type. Anchor's `decode` returns `null` on discriminator mismatch, so
283
+ * the name check doubles as a discriminator check.
284
+ */
285
+ function findArciumQueueIx(
286
+ tx: anchor.web3.VersionedTransactionResponse,
287
+ signature: string,
288
+ match: {
289
+ mxeProgramId: PublicKey;
290
+ computationOffset: anchor.BN;
291
+ compPDA?: PublicKey;
292
+ },
293
+ ): QueueIxSearchResult {
294
+ if (!tx.meta) {
295
+ throw new Error(`Transaction metadata missing for queue tx ${signature}; cannot resolve account keys.`);
296
+ }
297
+
298
+ let keys: anchor.web3.MessageAccountKeys;
299
+ try {
300
+ keys = tx.transaction.message.getAccountKeys({
301
+ accountKeysFromLookups: tx.meta.loadedAddresses,
302
+ });
303
+ }
304
+ catch (error) {
305
+ throw new Error(
306
+ `Account keys could not be resolved for queue tx ${signature} `
307
+ + `(RPC may be missing address-lookup-table metadata): ${formatUnknownError(error)}`,
308
+ );
309
+ }
310
+
311
+ // Outer (compiled) ixs expose `accountKeyIndexes`; inner ixs expose
312
+ // `accounts`. Both are `number[]` indexing the same resolved key map.
313
+ type MaybeIx = {
314
+ programIdIndex: number;
315
+ data: Uint8Array | string;
316
+ accountKeyIndexes?: number[];
317
+ accounts?: number[];
318
+ };
319
+
320
+ const tryMatch = (ix: MaybeIx): QueueIxSearchResult | null => {
321
+ const programId = keys.get(ix.programIdIndex);
322
+ if (!programId || !programId.equals(ARCIUM_PROGRAM_ID)) {
323
+ return null;
324
+ }
325
+
326
+ // Index 2 is `comp`; enforce equality to compPDA only when one was supplied.
327
+ const accountIndexes = ix.accountKeyIndexes ?? ix.accounts;
328
+ const compIndex = accountIndexes?.[2];
329
+ const compIxKey = compIndex === undefined ? undefined : keys.get(compIndex);
330
+ if (!compIxKey) {
331
+ return null;
332
+ }
333
+ if (match.compPDA && !compIxKey.equals(match.compPDA)) {
334
+ return null;
335
+ }
336
+
337
+ const decoded = typeof ix.data === 'string'
338
+ ? ARCIUM_IX_CODER.decode(ix.data, 'base58')
339
+ : ARCIUM_IX_CODER.decode(Buffer.from(ix.data));
340
+
341
+ if (decoded?.name !== 'queue_computation') {
342
+ return null;
343
+ }
344
+
345
+ if (!isDecodedQueueIx(decoded.data)) {
346
+ return { kind: 'idlMismatch' };
347
+ }
348
+
349
+ if (!decoded.data.mxe_program.equals(match.mxeProgramId)) {
350
+ return null;
351
+ }
352
+
353
+ if (!decoded.data.comp_offset.eq(match.computationOffset)) {
354
+ return null;
355
+ }
356
+
357
+ return { kind: 'found', data: decoded.data };
358
+ };
359
+
360
+ for (const ix of tx.transaction.message.compiledInstructions) {
361
+ const result = tryMatch(ix);
362
+ if (result) {
363
+ return result;
364
+ }
365
+ }
366
+
367
+ if (tx.meta.innerInstructions == null) {
368
+ return { kind: 'innerInstructionsOmitted' };
369
+ }
370
+
371
+ for (const inner of tx.meta.innerInstructions) {
372
+ for (const ix of inner.instructions) {
373
+ const result = tryMatch(ix);
374
+ if (result) {
375
+ return result;
376
+ }
377
+ }
378
+ }
379
+
380
+ return { kind: 'notFound' };
381
+ }
382
+
383
+ /**
384
+ * Decode the matching `queue_computation` fee inputs from a fetched tx, or throw
385
+ * a descriptive `Error` for each non-success outcome.
386
+ */
387
+ function decodeQueueFeeFromTx(
388
+ tx: anchor.web3.VersionedTransactionResponse,
389
+ signature: string,
390
+ match: {
391
+ mxeProgramId: PublicKey;
392
+ computationOffset: anchor.BN;
393
+ compPDA?: PublicKey;
394
+ },
395
+ ): Extract<ComputationFee, { source: 'queueTx' }> {
396
+ const result = findArciumQueueIx(tx, signature, match);
397
+ switch (result.kind) {
398
+ case 'found':
399
+ return feeFromQueueIx(result.data, signature);
400
+ case 'idlMismatch':
401
+ throw new Error(
402
+ `queue_computation instruction in tx ${signature} does not match the current Arcium IDL.`,
403
+ );
404
+ case 'innerInstructionsOmitted':
405
+ throw new Error(
406
+ `Cannot read inner instructions for tx ${signature}: the RPC omitted them, so the `
407
+ + `queue_computation CPI is not visible. Use an RPC that records inner instructions.`,
408
+ );
409
+ case 'notFound':
410
+ throw new Error(
411
+ `No matching queue_computation instruction found in tx ${signature} `
412
+ + `for computation ${match.computationOffset.toString()}.`,
413
+ );
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Fetch a live computation's on-chain fee by its offset.
419
+ *
420
+ * Returns the {@link ComputationFee} `account` variant while the computation
421
+ * account is alive, or `null` once it has been closed (`claim_computation_rent`,
422
+ * `reclaim_expired_computation_fee`, or a failure-claim flow). There is no
423
+ * automatic fallback for closed accounts: the on-chain data is gone, so a caller
424
+ * that stored the queue tx signature must pass it to
425
+ * {@link getComputationFeeFromQueueTx} to reconstruct the fee inputs.
426
+ *
427
+ * Throws if the live account at `(cluster, offset)` belongs to a different MXE:
428
+ * the computation PDA is keyed on `(cluster, comp_offset)` only, so two MXEs on
429
+ * a shared cluster can collide on it across lifecycles, and silently returning
430
+ * the wrong MXE's fee would be a correctness bug.
431
+ *
432
+ * @param arciumProgram - Anchor program instance (Arcium IDL).
433
+ * @param mxeProgramId - MXE program the computation belongs to; derives the
434
+ * cluster and computation PDA.
435
+ * @param computationOffset - Computation offset.
436
+ * @param commitment - RPC commitment for the account reads.
437
+ * @returns The live account fee, or `null` if the account is closed.
438
+ *
439
+ * @example
440
+ * import { getArciumProgram, getComputationFee, getComputationFeeFromQueueTx } from '@arcium-hq/reader';
441
+ *
442
+ * const program = getArciumProgram(provider);
443
+ * const fee = await getComputationFee(program, mxeProgramId, computationOffset);
444
+ * if (fee) {
445
+ * console.log('Live fee:', fee.baseFee.add(fee.priorityFee).toString());
446
+ * }
447
+ * else {
448
+ * // Closed: reconstruct from the stored queue tx signature.
449
+ * const queued = await getComputationFeeFromQueueTx(program, queueTxSignature, mxeProgramId, computationOffset);
450
+ * console.log('cuPriceMicro:', queued.cuPriceMicro.toString());
451
+ * }
452
+ */
453
+ export async function getComputationFee(
454
+ arciumProgram: anchor.Program<ArciumIdlType>,
455
+ mxeProgramId: PublicKey,
456
+ computationOffset: anchor.BN,
457
+ commitment?: anchor.web3.Commitment,
458
+ ): Promise<Extract<ComputationFee, { source: 'account' }> | null> {
459
+ const mxe = await arciumProgram.account.mxeAccount.fetch(
460
+ getMXEAccAddress(mxeProgramId),
461
+ commitment,
462
+ );
463
+ const compPDA = getComputationAccAddress(mxe.cluster, computationOffset);
464
+
465
+ const acc = await arciumProgram.account.computationAccount.fetchNullable(compPDA, commitment);
466
+ if (!acc) {
467
+ return null;
468
+ }
469
+
470
+ if (!acc.mxeProgramId.equals(mxeProgramId)) {
471
+ throw new Error(
472
+ `Computation at offset ${computationOffset.toString()} (cluster ${mxe.cluster}) is owned by `
473
+ + `MXE ${acc.mxeProgramId.toBase58()}, not the requested ${mxeProgramId.toBase58()}.`,
474
+ );
475
+ }
476
+
477
+ return {
478
+ source: 'account',
479
+ baseFee: acc.executionFee.baseFee,
480
+ priorityFee: acc.executionFee.priorityFee,
481
+ callbackCuLimit: acc.executionFee.callbackCuLimit,
482
+ };
483
+ }
484
+
485
+ /**
486
+ * Decode a closed computation's raw fee inputs from its `queue_computation`
487
+ * transaction.
488
+ *
489
+ * Fetches the exact `queueTxSignature` (no signature scanning), rejects a failed
490
+ * or unavailable tx, and decodes the `queue_computation` ix matching
491
+ * `mxeProgramId` + `computationOffset`. Reads no current MXE/cluster state, so it
492
+ * stays correct across cluster migration.
493
+ *
494
+ * Pass `opts.cluster` to additionally bind the match to the comp PDA derived
495
+ * from that historical cluster (rejects a same-tx queue for a different
496
+ * cluster's PDA). Omit it for the common case of a single queue per tx.
497
+ *
498
+ * Returns raw queue-time ix inputs, not lamport fees. Exact reconstruction also
499
+ * needs queue-time state absent from the ix: cluster `cu_price`, comp-def
500
+ * `cuAmount`, and the callback context: when `callbackCuLimit` is 0 the callback
501
+ * priority term defaults from `customCallbackInstructionCount`.
502
+ *
503
+ * @param arciumProgram - Anchor program instance (Arcium IDL).
504
+ * @param queueTxSignature - Signature of the tx that queued the computation.
505
+ * @param mxeProgramId - MXE program the computation belongs to.
506
+ * @param computationOffset - Computation offset.
507
+ * @param opts - `cluster` binds the match to a historical cluster's comp PDA;
508
+ * `commitment` maps to `confirmed`/`finalized` for the tx fetch.
509
+ * @returns The `queueTx` fee variant.
510
+ * @throws `Error` if the tx is unavailable, failed, or carries no matching
511
+ * `queue_computation` instruction.
512
+ */
513
+ export async function getComputationFeeFromQueueTx(
514
+ arciumProgram: anchor.Program<ArciumIdlType>,
515
+ queueTxSignature: string,
516
+ mxeProgramId: PublicKey,
517
+ computationOffset: anchor.BN,
518
+ opts: { cluster?: number; commitment?: anchor.web3.Commitment } = {},
519
+ ): Promise<Extract<ComputationFee, { source: 'queueTx' }>> {
520
+ const rpcFinality = mapCommitmentToFinality(opts.commitment);
521
+
522
+ let tx: anchor.web3.VersionedTransactionResponse | null;
523
+ try {
524
+ tx = await arciumProgram.provider.connection.getTransaction(queueTxSignature, {
525
+ maxSupportedTransactionVersion: 0,
526
+ commitment: rpcFinality,
527
+ });
528
+ }
529
+ catch (error) {
530
+ throw new Error(`Failed to fetch queue tx ${queueTxSignature}: ${formatUnknownError(error)}`);
531
+ }
532
+
533
+ if (!tx) {
534
+ throw new Error(`Queue transaction ${queueTxSignature} is unavailable from RPC. Use an archive RPC.`);
535
+ }
536
+
537
+ if (tx.meta?.err != null) {
538
+ throw new Error(`Queue transaction ${queueTxSignature} failed and did not pay the queue fee.`);
539
+ }
540
+
541
+ return decodeQueueFeeFromTx(tx, queueTxSignature, {
542
+ mxeProgramId,
543
+ computationOffset,
544
+ compPDA: opts.cluster === undefined
545
+ ? undefined
546
+ : getComputationAccAddress(opts.cluster, computationOffset),
547
+ });
548
+ }
package/src/index.ts CHANGED
@@ -19,6 +19,14 @@ export {
19
19
  getFeePoolAccAddress,
20
20
  getExecutingPoolAccAddress,
21
21
  getCompDefAccAddress,
22
+ getRecoveryClusterAccAddress,
23
+ getMxeRecoveryAccAddress,
24
+ getRecoveryPeerAccAddress,
25
+ getOperatorAccAddress,
26
+ getFailureClaimAccAddress,
27
+ getArciumSignerAccAddress,
28
+ getRawCircuitAccAddress,
29
+ getLookupTableAddress,
22
30
  getComputationsInMempool,
23
31
  getMempoolPriorityFeeStats,
24
32
  } from '@arcium-hq/client';
package/src/types.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  import { ArciumIdlType } from '@arcium-hq/client';
6
6
  import * as anchor from '@anchor-lang/core';
7
7
 
8
+ export type { MempoolPriorityFeeStats } from '@arcium-hq/client';
9
+
8
10
  /**
9
11
  * Solana PublicKey type alias for convenience.
10
12
  */
@@ -115,3 +117,35 @@ export type ArciumInstructionName = ArciumIdlType['instructions'][number]['name'
115
117
  * Status values for a computation, as defined by the Arcium protocol.
116
118
  */
117
119
  export type ComputationStatus = 'queued' | 'executing' | 'executed' | 'finalized' | 'failed';
120
+
121
+ /**
122
+ * Fee information for a computation, tagged by the source it was read from.
123
+ *
124
+ * - `account`: read from the live on-chain `ComputationAccount.executionFee`.
125
+ * - `queueTx`: the raw queue-time ix inputs decoded from the original
126
+ * `queueComputation` instruction, for callers reconstructing a closed
127
+ * computation's fee from its queue transaction.
128
+ *
129
+ * The `queueTx` variant surfaces raw inputs, not lamport fees. Exact
130
+ * reconstruction needs queue-time state absent from the ix: `baseFee` from
131
+ * `cluster.cu_price` x comp-def `cuAmount`, and `priorityFee` from
132
+ * `cuPriceMicro` x `cuAmount` plus a callback term. When `callbackCuLimit` is 0
133
+ * (no explicit budget) that callback term defaults from
134
+ * `customCallbackInstructionCount`, so the count is surfaced for that case.
135
+ */
136
+ export type ComputationFee =
137
+ | {
138
+ source: 'account';
139
+ baseFee: anchor.BN;
140
+ priorityFee: anchor.BN;
141
+ callbackCuLimit: number;
142
+ }
143
+ | {
144
+ source: 'queueTx';
145
+ cuPriceMicro: anchor.BN;
146
+ callbackCuLimit: number;
147
+ customCallbackInstructionCount: number;
148
+ computationDefinitionOffset: number;
149
+ callbackTransactionsRequired: number;
150
+ queueTxSignature: string;
151
+ };