@grimoirelabs/core 0.14.0 → 0.16.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.
@@ -7,7 +7,7 @@ import type { SpellIR } from "../types/ir.js";
7
7
  import type { PolicySet } from "../types/policy.js";
8
8
  import type { Address, ChainId } from "../types/primitives.js";
9
9
  import type { QueryProvider } from "../types/query-provider.js";
10
- import type { CommitResult, DriftKey, DriftPolicy, PreviewResult, Receipt } from "../types/receipt.js";
10
+ import type { BuildTransactionsResult, CommitResult, DriftKey, DriftPolicy, PreviewResult, Receipt } from "../types/receipt.js";
11
11
  import type { VenueAdapter } from "../venues/types.js";
12
12
  import { type ExecutionMode } from "../wallet/executor.js";
13
13
  import { type Provider } from "../wallet/provider.js";
@@ -66,6 +66,8 @@ export interface ExecuteOptions {
66
66
  crossChain?: ActionExecutionOptions["crossChain"];
67
67
  /** Pluggable query provider for balance/price/apy/etc. */
68
68
  queryProvider?: QueryProvider;
69
+ /** Filter execution to a specific trigger handler by type name (e.g., "manual", "hourly") */
70
+ triggerFilter?: string;
69
71
  }
70
72
  /**
71
73
  * Options for previewing a spell (simulation / receipt generation)
@@ -90,6 +92,8 @@ export interface PreviewOptions {
90
92
  crossChain?: ActionExecutionOptions["crossChain"];
91
93
  /** Pluggable query provider for balance/price/apy/etc. */
92
94
  queryProvider?: QueryProvider;
95
+ /** Filter execution to a specific trigger handler by type name (e.g., "manual", "hourly") */
96
+ triggerFilter?: string;
93
97
  }
94
98
  /**
95
99
  * Preview a spell — runs the full step loop in simulation mode,
@@ -118,9 +122,44 @@ export interface CommitOptions {
118
122
  * Commit a receipt — executes planned actions from the preview.
119
123
  */
120
124
  export declare function commit(options: CommitOptions): Promise<CommitResult>;
125
+ /**
126
+ * Options for building unsigned transactions from a preview receipt
127
+ */
128
+ export interface BuildTransactionsOptions {
129
+ receipt: Receipt;
130
+ walletAddress: Address;
131
+ provider?: Provider;
132
+ rpcUrl?: string;
133
+ adapters?: VenueAdapter[];
134
+ driftPolicy?: DriftPolicy;
135
+ driftValues?: Record<string, unknown>;
136
+ resolveDriftValue?: (key: DriftKey) => Promise<unknown>;
137
+ progressCallback?: (message: string) => void;
138
+ /**
139
+ * Secret used to verify receipt integrity for cross-process receipts.
140
+ * Required when the receipt was not issued by this process (i.e. not in
141
+ * the in-memory issuedReceipts map). Use signReceipt() at preview time
142
+ * to generate the matching integrity hash.
143
+ */
144
+ receiptSecret?: string;
145
+ /** HMAC integrity hash produced by signReceipt(receipt, secret) */
146
+ receiptIntegrity?: string;
147
+ }
148
+ /**
149
+ * Build unsigned transactions from a preview receipt.
150
+ * Returns calldata for client-side signing (e.g. Privy SDK).
151
+ * Does NOT commit the receipt — commit() can still be called afterwards.
152
+ */
153
+ export declare function buildTransactions(options: BuildTransactionsOptions): Promise<BuildTransactionsResult>;
121
154
  /**
122
155
  * Execute a compiled spell (backward-compatible wrapper).
123
156
  * Internally uses preview() and, if needed, commit().
124
157
  */
125
158
  export declare function execute(options: ExecuteOptions): Promise<ExecutionResult>;
159
+ /**
160
+ * Sign a receipt for cross-process integrity verification.
161
+ * Call this at preview time, persist the returned hex string alongside
162
+ * the receipt, and pass it as `receiptIntegrity` to buildTransactions().
163
+ */
164
+ export declare function signReceipt(receipt: Receipt, secret: string): string;
126
165
  //# sourceMappingURL=interpreter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"interpreter.d.ts","sourceRoot":"","sources":["../../src/runtime/interpreter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interpreter.d.ts","sourceRoot":"","sources":["../../src/runtime/interpreter.ts"],"names":[],"mappings":"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"}
@@ -2,9 +2,11 @@
2
2
  * Spell Interpreter
3
3
  * Executes compiled SpellIR via the preview/commit model.
4
4
  */
5
+ import { createHmac, timingSafeEqual } from "node:crypto";
5
6
  import { createVenueRegistry } from "../venues/index.js";
6
7
  import { createExecutor } from "../wallet/executor.js";
7
8
  import { createProvider } from "../wallet/provider.js";
9
+ import { TransactionBuilder } from "../wallet/tx-builder.js";
8
10
  import { CircuitBreakerManager } from "./circuit-breaker.js";
9
11
  import { createContext, getPersistentStateObject, InMemoryLedger, incrementAdvisoryCalls, markStepExecuted, } from "./context.js";
10
12
  import { createEvalContext, evaluateAsync } from "./expression-evaluator.js";
@@ -25,12 +27,55 @@ import { evaluatePreviewValueFlow, inferDriftClass, } from "./value-flow.js";
25
27
  // Keep preview-issued receipts in-process so commit only accepts known receipts.
26
28
  const issuedReceipts = new Map();
27
29
  const committedReceipts = new Set();
30
+ /** Check if a trigger matches a filter string (e.g., "manual", "hourly", "daily") */
31
+ function matchesTriggerFilter(trigger, filter) {
32
+ if (trigger.type === filter)
33
+ return true;
34
+ if (filter === "hourly" && trigger.type === "schedule" && trigger.cron === "0 * * * *")
35
+ return true;
36
+ if (filter === "daily" && trigger.type === "schedule" && trigger.cron === "0 0 * * *")
37
+ return true;
38
+ return false;
39
+ }
40
+ /** Describe a trigger for user-facing messages */
41
+ function describeTrigger(trigger) {
42
+ if (trigger.type === "schedule") {
43
+ if (trigger.cron === "0 * * * *")
44
+ return "hourly";
45
+ if (trigger.cron === "0 0 * * *")
46
+ return "daily";
47
+ return `schedule(${trigger.cron})`;
48
+ }
49
+ return trigger.type;
50
+ }
28
51
  /**
29
52
  * Preview a spell — runs the full step loop in simulation mode,
30
53
  * collects PlannedActions and ValueDeltas, and assembles a Receipt.
31
54
  */
32
55
  export async function preview(options) {
33
- const { spell, vault, chain, params = {}, persistentState = {} } = options;
56
+ const { spell: originalSpell, vault, chain, params = {}, persistentState = {} } = options;
57
+ // Apply trigger filter: narrow steps to only those from the matched trigger handler
58
+ let spell = originalSpell;
59
+ let triggerOverride = options.trigger;
60
+ if (options.triggerFilter && originalSpell.triggerStepMap) {
61
+ const anyTrigger = originalSpell.triggers.find((t) => t.type === "any");
62
+ if (anyTrigger && anyTrigger.type === "any") {
63
+ const filter = options.triggerFilter;
64
+ const matchedIndex = anyTrigger.triggers.findIndex((t) => matchesTriggerFilter(t, filter));
65
+ if (matchedIndex === -1) {
66
+ const available = anyTrigger.triggers.map(describeTrigger).join(", ");
67
+ throw new Error(`Unknown trigger "${options.triggerFilter}". Available triggers: ${available}`);
68
+ }
69
+ const allowedStepIds = new Set(originalSpell.triggerStepMap[matchedIndex] ?? []);
70
+ spell = {
71
+ ...originalSpell,
72
+ steps: originalSpell.steps.filter((s) => allowedStepIds.has(s.id)),
73
+ };
74
+ // Set trigger context to the matched sub-trigger instead of "any"
75
+ const matchedTrigger = anyTrigger.triggers[matchedIndex];
76
+ triggerOverride = { type: matchedTrigger.type, source: matchedTrigger.type };
77
+ }
78
+ }
34
79
  // Always simulate during preview
35
80
  const actionExecution = { mode: "simulate" };
36
81
  if (options.adapters && options.adapters.length > 0) {
@@ -53,7 +98,7 @@ export async function preview(options) {
53
98
  vault,
54
99
  chain,
55
100
  runId: options.runId,
56
- trigger: options.trigger,
101
+ trigger: triggerOverride,
57
102
  params,
58
103
  persistentState,
59
104
  queryProvider: options.queryProvider,
@@ -275,98 +320,32 @@ export async function commit(options) {
275
320
  };
276
321
  }
277
322
  }
278
- const driftChecks = [];
279
- for (const driftKey of receipt.driftKeys) {
280
- if (options.driftPolicy?.maxAge) {
281
- const keyAgeSec = Math.max(0, Math.floor((Date.now() - driftKey.timestamp) / 1000));
282
- if (keyAgeSec > options.driftPolicy.maxAge) {
283
- const structuredError = createStructuredError("commit", "DRIFT_KEY_STALE", `Drift key '${driftKey.field}' is stale (${keyAgeSec}s > ${options.driftPolicy.maxAge}s)`, {
284
- constraint: "drift_key_freshness",
285
- actual: keyAgeSec,
286
- limit: options.driftPolicy.maxAge,
287
- path: driftKey.field,
288
- suggestion: "Run preview again to refresh drift keys.",
289
- });
290
- ledger.emit({ type: "commit_completed", runId, receiptId: receipt.id, success: false });
291
- return {
292
- success: false,
293
- receiptId: receipt.id,
294
- transactions: [],
295
- driftChecks,
296
- finalState: receipt.finalState,
297
- ledgerEvents: ledger.getEntries(),
298
- error: structuredError,
299
- };
300
- }
301
- }
302
- let resolvedValue;
303
- try {
304
- resolvedValue = await resolveCommitDriftValue(driftKey, options);
305
- }
306
- catch (error) {
307
- const message = error instanceof Error ? error.message : String(error);
308
- const structuredError = createStructuredError("commit", "DRIFT_RESOLUTION_FAILED", `Failed to resolve drift value for '${driftKey.field}': ${message}`, {
309
- constraint: "drift_keys",
310
- path: driftKey.field,
311
- });
312
- ledger.emit({ type: "commit_completed", runId, receiptId: receipt.id, success: false });
313
- return {
314
- success: false,
315
- receiptId: receipt.id,
316
- transactions: [],
317
- driftChecks,
318
- finalState: receipt.finalState,
319
- ledgerEvents: ledger.getEntries(),
320
- error: structuredError,
321
- };
322
- }
323
- if (!resolvedValue.found && options.driftPolicy) {
324
- const structuredError = createStructuredError("commit", "DRIFT_VALUE_MISSING", `Missing commit-time drift value for '${driftKey.field}'`, {
325
- constraint: "drift_keys",
326
- path: driftKey.field,
327
- suggestion: "Provide driftValues for this key or configure resolveDriftValue to fetch commit-time values.",
328
- });
329
- ledger.emit({ type: "commit_completed", runId, receiptId: receipt.id, success: false });
330
- return {
331
- success: false,
332
- receiptId: receipt.id,
333
- transactions: [],
334
- driftChecks,
335
- finalState: receipt.finalState,
336
- ledgerEvents: ledger.getEntries(),
337
- error: structuredError,
338
- };
339
- }
340
- const commitValue = resolvedValue.found ? resolvedValue.value : driftKey.previewValue;
341
- const driftResult = evaluateDriftKey(driftKey, commitValue, options.driftPolicy);
342
- driftChecks.push(driftResult);
323
+ const driftResult = await performDriftChecks(receipt.driftKeys, {
324
+ driftPolicy: options.driftPolicy,
325
+ driftValues: options.driftValues,
326
+ resolveDriftValue: options.resolveDriftValue,
327
+ });
328
+ if (driftResult.error) {
329
+ ledger.emit({ type: "commit_completed", runId, receiptId: receipt.id, success: false });
330
+ return {
331
+ success: false,
332
+ receiptId: receipt.id,
333
+ transactions: [],
334
+ driftChecks: driftResult.driftChecks,
335
+ finalState: receipt.finalState,
336
+ ledgerEvents: ledger.getEntries(),
337
+ error: driftResult.error,
338
+ };
339
+ }
340
+ const driftChecks = driftResult.driftChecks;
341
+ for (const check of driftChecks) {
343
342
  ledger.emit({
344
343
  type: "drift_check",
345
- field: driftKey.field,
346
- passed: driftResult.passed,
347
- previewValue: driftResult.previewValue,
348
- commitValue: driftResult.commitValue,
344
+ field: check.field,
345
+ passed: check.passed,
346
+ previewValue: check.previewValue,
347
+ commitValue: check.commitValue,
349
348
  });
350
- if (!driftResult.passed) {
351
- const tolerance = resolveToleranceBps(driftKey, options.driftPolicy);
352
- const structuredError = createStructuredError("commit", "DRIFT_EXCEEDED", `Drift exceeded for '${driftKey.field}'`, {
353
- constraint: "drift_policy",
354
- actual: driftResult.driftBps,
355
- limit: tolerance,
356
- path: driftKey.field,
357
- suggestion: "Run preview again or increase drift tolerance for this key class.",
358
- });
359
- ledger.emit({ type: "commit_completed", runId, receiptId: receipt.id, success: false });
360
- return {
361
- success: false,
362
- receiptId: receipt.id,
363
- transactions: [],
364
- driftChecks,
365
- finalState: receipt.finalState,
366
- ledgerEvents: ledger.getEntries(),
367
- error: structuredError,
368
- };
369
- }
370
349
  }
371
350
  // Execute planned actions
372
351
  const { chainId } = receipt.chainContext;
@@ -462,6 +441,124 @@ export async function commit(options) {
462
441
  ledgerEvents: ledger.getEntries(),
463
442
  };
464
443
  }
444
+ /**
445
+ * Build unsigned transactions from a preview receipt.
446
+ * Returns calldata for client-side signing (e.g. Privy SDK).
447
+ * Does NOT commit the receipt — commit() can still be called afterwards.
448
+ */
449
+ export async function buildTransactions(options) {
450
+ const { receipt } = options;
451
+ // Validate receipt status
452
+ if (receipt.status !== "ready") {
453
+ return {
454
+ success: false,
455
+ receiptId: receipt.id,
456
+ transactions: [],
457
+ driftChecks: [],
458
+ error: createStructuredError("commit", "RECEIPT_INVALID_STATUS", `Receipt status is '${receipt.status}', expected 'ready'`),
459
+ };
460
+ }
461
+ // Validate receipt shape, integrity, and committed status
462
+ const validationError = validateBuildReceipt(receipt, {
463
+ receiptSecret: options.receiptSecret,
464
+ receiptIntegrity: options.receiptIntegrity,
465
+ });
466
+ if (validationError) {
467
+ return {
468
+ success: false,
469
+ receiptId: receipt.id,
470
+ transactions: [],
471
+ driftChecks: [],
472
+ error: validationError,
473
+ };
474
+ }
475
+ // Check receipt age
476
+ if (options.driftPolicy?.maxAge) {
477
+ const ageSec = (Date.now() - receipt.timestamp) / 1000;
478
+ if (ageSec > options.driftPolicy.maxAge) {
479
+ return {
480
+ success: false,
481
+ receiptId: receipt.id,
482
+ transactions: [],
483
+ driftChecks: [],
484
+ error: createStructuredError("commit", "RECEIPT_EXPIRED", `Receipt expired: age ${Math.round(ageSec)}s exceeds maxAge ${options.driftPolicy.maxAge}s`, {
485
+ actual: Math.round(ageSec),
486
+ limit: options.driftPolicy.maxAge,
487
+ suggestion: "Run preview again to generate a fresh receipt.",
488
+ }),
489
+ };
490
+ }
491
+ }
492
+ // Drift checks
493
+ const driftResult = await performDriftChecks(receipt.driftKeys, {
494
+ driftPolicy: options.driftPolicy,
495
+ driftValues: options.driftValues,
496
+ resolveDriftValue: options.resolveDriftValue,
497
+ });
498
+ if (driftResult.error) {
499
+ return {
500
+ success: false,
501
+ receiptId: receipt.id,
502
+ transactions: [],
503
+ driftChecks: driftResult.driftChecks,
504
+ error: driftResult.error,
505
+ };
506
+ }
507
+ // Build transactions for each planned action.
508
+ // Defer provider creation until we actually need it for EVM gas estimation.
509
+ const { chainId } = receipt.chainContext;
510
+ // Reject mismatched provider early — adapter context must use the receipt's
511
+ // chain so calldata (router addresses, token addresses) matches the preview.
512
+ if (options.provider && options.provider.chainId !== chainId) {
513
+ return {
514
+ success: false,
515
+ receiptId: receipt.id,
516
+ transactions: [],
517
+ driftChecks: driftResult.driftChecks,
518
+ error: createStructuredError("commit", "CHAIN_MISMATCH", `Provider chain ${options.provider.chainId} does not match receipt chain ${chainId}. ` +
519
+ `Provide a provider for chain ${chainId} or omit it to auto-create one.`),
520
+ };
521
+ }
522
+ const registry = createVenueRegistry(options.adapters ?? []);
523
+ let lazyProvider = options.provider;
524
+ const getProvider = () => {
525
+ if (!lazyProvider) {
526
+ lazyProvider = createProvider(chainId, options.rpcUrl);
527
+ }
528
+ return lazyProvider;
529
+ };
530
+ const transactions = [];
531
+ for (const planned of receipt.plannedActions) {
532
+ try {
533
+ options.progressCallback?.(`Building transactions for step '${planned.stepId}'...`);
534
+ const builtTxs = await buildActionTransactions(planned.action, chainId, getProvider, options.walletAddress, receipt.chainContext.vault, registry);
535
+ transactions.push({
536
+ stepId: planned.stepId,
537
+ builtTransactions: builtTxs,
538
+ });
539
+ }
540
+ catch (error) {
541
+ const message = error instanceof Error ? error.message : String(error);
542
+ if (planned.onFailure === "skip") {
543
+ options.progressCallback?.(`Skipping step '${planned.stepId}' during transaction build: ${message}`);
544
+ continue;
545
+ }
546
+ return {
547
+ success: false,
548
+ receiptId: receipt.id,
549
+ transactions,
550
+ driftChecks: driftResult.driftChecks,
551
+ error: createStructuredError("commit", "BUILD_TRANSACTIONS_FAILED", `Failed to build transactions for step '${planned.stepId}': ${message}`),
552
+ };
553
+ }
554
+ }
555
+ return {
556
+ success: true,
557
+ receiptId: receipt.id,
558
+ transactions,
559
+ driftChecks: driftResult.driftChecks,
560
+ };
561
+ }
465
562
  // =============================================================================
466
563
  // EXECUTE (backward-compatible wrapper)
467
564
  // =============================================================================
@@ -492,6 +589,7 @@ export async function execute(options) {
492
589
  warningCallback: options.warningCallback,
493
590
  crossChain: options.crossChain,
494
591
  queryProvider: options.queryProvider,
592
+ triggerFilter: options.triggerFilter,
495
593
  });
496
594
  if (!previewResult.success || !previewResult.receipt) {
497
595
  return convertPreviewToExecutionResult(previewResult, spell);
@@ -746,6 +844,54 @@ function registerIssuedReceipt(receipt) {
746
844
  timestamp: receipt.timestamp,
747
845
  });
748
846
  }
847
+ // =============================================================================
848
+ // RECEIPT INTEGRITY (cross-process verification)
849
+ // =============================================================================
850
+ /**
851
+ * Deterministic serialization of the receipt fields that must not change
852
+ * between preview and buildTransactions. Used as the HMAC payload.
853
+ */
854
+ function canonicalizeReceiptFields(receipt) {
855
+ const critical = {
856
+ id: receipt.id,
857
+ spellId: receipt.spellId,
858
+ chainId: receipt.chainContext.chainId,
859
+ vault: receipt.chainContext.vault,
860
+ timestamp: receipt.timestamp,
861
+ status: receipt.status,
862
+ plannedActions: receipt.plannedActions.map((pa) => ({
863
+ stepId: pa.stepId,
864
+ venue: pa.venue,
865
+ action: pa.action,
866
+ onFailure: pa.onFailure,
867
+ })),
868
+ };
869
+ return JSON.stringify(critical, (_key, value) => typeof value === "bigint" ? `__bigint__${value.toString()}` : value);
870
+ }
871
+ /**
872
+ * Sign a receipt for cross-process integrity verification.
873
+ * Call this at preview time, persist the returned hex string alongside
874
+ * the receipt, and pass it as `receiptIntegrity` to buildTransactions().
875
+ */
876
+ export function signReceipt(receipt, secret) {
877
+ const payload = canonicalizeReceiptFields(receipt);
878
+ return createHmac("sha256", secret).update(payload).digest("hex");
879
+ }
880
+ /**
881
+ * Verify a receipt's HMAC integrity against the expected hash.
882
+ * Uses constant-time comparison to prevent timing attacks.
883
+ */
884
+ function verifyReceiptIntegrity(receipt, secret, integrity) {
885
+ const expected = signReceipt(receipt, secret);
886
+ if (expected.length !== integrity.length)
887
+ return false;
888
+ try {
889
+ return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(integrity, "hex"));
890
+ }
891
+ catch {
892
+ return false;
893
+ }
894
+ }
749
895
  function validateCommitReceipt(receipt) {
750
896
  if (receipt.phase !== "preview") {
751
897
  return createStructuredError("commit", "RECEIPT_INVALID_PHASE", `Receipt phase is '${receipt.phase}', expected 'preview'`);
@@ -768,7 +914,114 @@ function validateCommitReceipt(receipt) {
768
914
  }
769
915
  return undefined;
770
916
  }
771
- async function resolveCommitDriftValue(driftKey, options) {
917
+ /**
918
+ * Receipt validation for buildTransactions().
919
+ *
920
+ * Same-process receipts (in issuedReceipts) are verified via the in-memory
921
+ * tamper check, exactly like commit().
922
+ *
923
+ * Cross-process receipts (not in issuedReceipts) require an HMAC integrity
924
+ * proof produced by signReceipt(). Without this, a caller could fabricate
925
+ * or modify plannedActions and obtain signable calldata for actions that
926
+ * were never previewed.
927
+ *
928
+ * We also check committedReceipts to prevent building calldata for a
929
+ * receipt that has already been committed.
930
+ */
931
+ function validateBuildReceipt(receipt, integrity) {
932
+ if (receipt.phase !== "preview") {
933
+ return createStructuredError("commit", "RECEIPT_INVALID_PHASE", `Receipt phase is '${receipt.phase}', expected 'preview'`);
934
+ }
935
+ if (!receipt.id.startsWith("rcpt_")) {
936
+ return createStructuredError("commit", "RECEIPT_INVALID_ID", "Receipt ID must start with 'rcpt_'");
937
+ }
938
+ if (committedReceipts.has(receipt.id)) {
939
+ return createStructuredError("commit", "RECEIPT_ALREADY_COMMITTED", "Receipt has already been committed.");
940
+ }
941
+ // Same-process: verify against in-memory provenance
942
+ const issuedReceipt = issuedReceipts.get(receipt.id);
943
+ if (issuedReceipt) {
944
+ if (issuedReceipt.spellId !== receipt.spellId ||
945
+ issuedReceipt.chainId !== receipt.chainContext.chainId ||
946
+ issuedReceipt.vault !== receipt.chainContext.vault ||
947
+ issuedReceipt.timestamp !== receipt.timestamp) {
948
+ return createStructuredError("commit", "PREVIEW_RECEIPT_TAMPERED", "Receipt identity does not match the preview-generated artifact.");
949
+ }
950
+ return undefined;
951
+ }
952
+ // Cross-process: require HMAC integrity proof
953
+ if (!integrity.receiptSecret || !integrity.receiptIntegrity) {
954
+ return createStructuredError("commit", "RECEIPT_INTEGRITY_MISSING", "Cross-process receipts require receiptSecret and receiptIntegrity for verification. " +
955
+ "Use signReceipt() at preview time to generate the integrity hash.");
956
+ }
957
+ if (!verifyReceiptIntegrity(receipt, integrity.receiptSecret, integrity.receiptIntegrity)) {
958
+ return createStructuredError("commit", "RECEIPT_INTEGRITY_FAILED", "Receipt integrity verification failed. The receipt may have been modified.");
959
+ }
960
+ return undefined;
961
+ }
962
+ /** Run drift checks for a set of drift keys. Returns accumulated checks and optional error. */
963
+ async function performDriftChecks(driftKeys, options) {
964
+ const driftChecks = [];
965
+ for (const driftKey of driftKeys) {
966
+ if (options.driftPolicy?.maxAge) {
967
+ const keyAgeSec = Math.max(0, Math.floor((Date.now() - driftKey.timestamp) / 1000));
968
+ if (keyAgeSec > options.driftPolicy.maxAge) {
969
+ return {
970
+ driftChecks,
971
+ error: createStructuredError("commit", "DRIFT_KEY_STALE", `Drift key '${driftKey.field}' is stale (${keyAgeSec}s > ${options.driftPolicy.maxAge}s)`, {
972
+ constraint: "drift_key_freshness",
973
+ actual: keyAgeSec,
974
+ limit: options.driftPolicy.maxAge,
975
+ path: driftKey.field,
976
+ suggestion: "Run preview again to refresh drift keys.",
977
+ }),
978
+ };
979
+ }
980
+ }
981
+ let resolvedValue;
982
+ try {
983
+ resolvedValue = await resolveDriftValue(driftKey, options);
984
+ }
985
+ catch (error) {
986
+ const message = error instanceof Error ? error.message : String(error);
987
+ return {
988
+ driftChecks,
989
+ error: createStructuredError("commit", "DRIFT_RESOLUTION_FAILED", `Failed to resolve drift value for '${driftKey.field}': ${message}`, {
990
+ constraint: "drift_keys",
991
+ path: driftKey.field,
992
+ }),
993
+ };
994
+ }
995
+ if (!resolvedValue.found && options.driftPolicy) {
996
+ return {
997
+ driftChecks,
998
+ error: createStructuredError("commit", "DRIFT_VALUE_MISSING", `Missing commit-time drift value for '${driftKey.field}'`, {
999
+ constraint: "drift_keys",
1000
+ path: driftKey.field,
1001
+ suggestion: "Provide driftValues for this key or configure resolveDriftValue to fetch commit-time values.",
1002
+ }),
1003
+ };
1004
+ }
1005
+ const commitValue = resolvedValue.found ? resolvedValue.value : driftKey.previewValue;
1006
+ const driftResult = evaluateDriftKey(driftKey, commitValue, options.driftPolicy);
1007
+ driftChecks.push(driftResult);
1008
+ if (!driftResult.passed) {
1009
+ const tolerance = resolveToleranceBps(driftKey, options.driftPolicy);
1010
+ return {
1011
+ driftChecks,
1012
+ error: createStructuredError("commit", "DRIFT_EXCEEDED", `Drift exceeded for '${driftKey.field}'`, {
1013
+ constraint: "drift_policy",
1014
+ actual: driftResult.driftBps,
1015
+ limit: tolerance,
1016
+ path: driftKey.field,
1017
+ suggestion: "Run preview again or increase drift tolerance for this key class.",
1018
+ }),
1019
+ };
1020
+ }
1021
+ }
1022
+ return { driftChecks };
1023
+ }
1024
+ async function resolveDriftValue(driftKey, options) {
772
1025
  if (options.driftValues && Object.hasOwn(options.driftValues, driftKey.field)) {
773
1026
  return { found: true, value: options.driftValues[driftKey.field] };
774
1027
  }
@@ -780,6 +1033,73 @@ async function resolveCommitDriftValue(driftKey, options) {
780
1033
  }
781
1034
  return { found: false, value: undefined };
782
1035
  }
1036
+ /**
1037
+ * Build unsigned transactions for a single action.
1038
+ * Mirrors Executor.buildAction() for EVM actions without a wallet, and
1039
+ * rejects offchain adapters because they do not produce signable calldata.
1040
+ *
1041
+ * The provider is lazy so we never instantiate it for receipts that
1042
+ * contain only offchain or zero planned actions.
1043
+ *
1044
+ * Always uses the receipt's chainId for adapter context (not provider.chainId)
1045
+ * so calldata matches the previewed chain even if a mismatched provider
1046
+ * sneaks through.
1047
+ */
1048
+ async function buildActionTransactions(action, chainId, getProvider, walletAddress, vault, registry) {
1049
+ const normalizeBuildResult = (result) => Array.isArray(result) ? result : [result];
1050
+ if (action.type === "custom") {
1051
+ const adapter = registry.get(action.venue);
1052
+ if (!adapter) {
1053
+ throw new Error(`Adapter '${action.venue}' is not registered for custom action '${action.op}'.`);
1054
+ }
1055
+ if (!adapter.meta.supportedChains.includes(chainId)) {
1056
+ throw new Error(`Adapter '${adapter.meta.name}' does not support chain ${chainId} for custom action '${action.op}'.`);
1057
+ }
1058
+ if (adapter.meta.executionType === "offchain") {
1059
+ throw new Error(`Adapter '${adapter.meta.name}' is offchain and does not produce signable transactions for custom action '${action.op}'. ` +
1060
+ `Use commit() instead for offchain execution.`);
1061
+ }
1062
+ const provider = getProvider();
1063
+ if (!adapter.buildAction) {
1064
+ throw new Error(`Adapter '${adapter.meta.name}' does not build custom EVM actions.`);
1065
+ }
1066
+ return normalizeBuildResult(await adapter.buildAction(action, {
1067
+ provider,
1068
+ walletAddress,
1069
+ vault,
1070
+ chainId,
1071
+ mode: "execute",
1072
+ }));
1073
+ }
1074
+ if ("venue" in action && action.venue) {
1075
+ const adapter = registry.get(action.venue);
1076
+ if (!adapter) {
1077
+ throw new Error(`Adapter '${action.venue}' is not registered`);
1078
+ }
1079
+ if (!adapter.meta.supportedChains.includes(chainId)) {
1080
+ throw new Error(`Adapter '${adapter.meta.name}' does not support chain ${chainId}.`);
1081
+ }
1082
+ if (adapter.meta.executionType === "offchain") {
1083
+ throw new Error(`Adapter '${adapter.meta.name}' is offchain and does not produce signable transactions. ` +
1084
+ `Use commit() instead for offchain execution.`);
1085
+ }
1086
+ const provider = getProvider();
1087
+ if (!adapter.buildAction) {
1088
+ throw new Error(`Adapter '${adapter.meta.name}' does not support EVM actions`);
1089
+ }
1090
+ return normalizeBuildResult(await adapter.buildAction(action, {
1091
+ provider,
1092
+ walletAddress,
1093
+ vault,
1094
+ chainId,
1095
+ mode: "execute",
1096
+ }));
1097
+ }
1098
+ // Built-in action types (transfer, approve, etc.)
1099
+ const provider = getProvider();
1100
+ const txBuilder = new TransactionBuilder(provider, walletAddress);
1101
+ return [await txBuilder.buildAction(action)];
1102
+ }
783
1103
  function evaluateDriftKey(driftKey, commitValue, policy) {
784
1104
  const tolerance = resolveToleranceBps(driftKey, policy);
785
1105
  const numericPreview = toNumeric(driftKey.previewValue);