@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 +1 -1
- package/build/index.cjs +297 -1
- package/build/index.mjs +267 -4
- package/build/types/computationGetters.d.ts +75 -1
- package/build/types/computationGetters.d.ts.map +1 -1
- package/build/types/index.d.ts +1 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/types.d.ts +30 -0
- package/build/types/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/computationGetters.ts +393 -1
- package/src/index.ts +8 -0
- package/src/types.ts +34 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
## When To Use
|
|
15
15
|
|
|
16
|
-
- Listing MXE, cluster, and
|
|
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 {
|
|
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;
|
|
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"}
|
package/build/types/index.d.ts
CHANGED
|
@@ -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"}
|
package/build/types/types.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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 {
|
|
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
|
+
};
|