@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.
- package/dist/compiler/grimoire/transformer.d.ts.map +1 -1
- package/dist/compiler/grimoire/transformer.js +2 -2
- package/dist/compiler/grimoire/transformer.js.map +1 -1
- package/dist/compiler/ir-generator.d.ts.map +1 -1
- package/dist/compiler/ir-generator.js +11 -1
- package/dist/compiler/ir-generator.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/interpreter.d.ts +40 -1
- package/dist/runtime/interpreter.d.ts.map +1 -1
- package/dist/runtime/interpreter.js +412 -92
- package/dist/runtime/interpreter.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/ir.d.ts +3 -0
- package/dist/types/ir.d.ts.map +1 -1
- package/dist/types/receipt.d.ts +12 -0
- package/dist/types/receipt.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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":"AAAA;;;GAGG;
|
|
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:
|
|
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
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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:
|
|
346
|
-
passed:
|
|
347
|
-
previewValue:
|
|
348
|
-
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
|
-
|
|
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);
|