@txfence/cli 0.1.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/index.js ADDED
@@ -0,0 +1,1281 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/index.ts
5
+ import { Command as Command15 } from "commander";
6
+
7
+ // src/commands/simulate.ts
8
+ import { Command as Command2 } from "commander";
9
+ import { resolve } from "path";
10
+
11
+ // src/action-options.ts
12
+ import "commander";
13
+ function addActionOptions(cmd) {
14
+ return cmd.option("--kind <kind>", "action kind: swap, transfer, contract_call (required)").option("--chain <chain>", "target chain: ethereum, solana, etc. (required)").option("--to <address>", "recipient address (for transfer) or output token (for swap)").option("--from-token <token>", "input token symbol (for swap)").option("--from-amount <amt>", "input amount as integer (for swap, in token base units)").option("--token <token>", "token symbol (for transfer)").option("--amount <amt>", "amount as integer in base units (for transfer)").option("--via <address>", "router contract address (for swap)").option("--slippage <bps>", "max slippage in basis points (for swap, default 50)", "50").option("--contract <address>", "contract address (for contract_call)").option("--method <name>", "method name (for contract_call)").option("--value-token <token>", "value token (for contract_call)").option("--value-amount <amt>", "value amount in base units (for contract_call)");
15
+ }
16
+ function buildActionFromOptions(opts) {
17
+ const chain = opts["chain"];
18
+ const kind = opts["kind"];
19
+ if (kind === "transfer") {
20
+ const to = opts["to"];
21
+ const token = opts["token"];
22
+ const amount = opts["amount"];
23
+ if (to === void 0 || token === void 0 || amount === void 0) {
24
+ throw new Error("transfer requires --to, --token, and --amount");
25
+ }
26
+ return {
27
+ kind: "transfer",
28
+ chain,
29
+ token: { token, amount: BigInt(amount), decimals: 18 },
30
+ to
31
+ };
32
+ }
33
+ if (kind === "swap") {
34
+ const to = opts["to"];
35
+ const fromToken = opts["fromToken"];
36
+ const fromAmount = opts["fromAmount"];
37
+ const via = opts["via"];
38
+ if (to === void 0 || fromToken === void 0 || fromAmount === void 0 || via === void 0) {
39
+ throw new Error("swap requires --to, --from-token, --from-amount, and --via");
40
+ }
41
+ return {
42
+ kind: "swap",
43
+ chain,
44
+ from: { token: fromToken, amount: BigInt(fromAmount), decimals: 18 },
45
+ to,
46
+ via,
47
+ maxSlippage: Number(opts["slippage"] ?? "50")
48
+ };
49
+ }
50
+ if (kind === "contract_call") {
51
+ const contract = opts["contract"];
52
+ const method = opts["method"];
53
+ if (contract === void 0 || method === void 0) {
54
+ throw new Error("contract_call requires --contract and --method");
55
+ }
56
+ const valueToken = opts["valueToken"];
57
+ const valueAmount = opts["valueAmount"];
58
+ const base = {
59
+ kind: "contract_call",
60
+ chain,
61
+ contract,
62
+ method,
63
+ args: []
64
+ };
65
+ if (valueToken !== void 0 && valueAmount !== void 0) {
66
+ return { ...base, value: { token: valueToken, amount: BigInt(valueAmount), decimals: 18 } };
67
+ }
68
+ return base;
69
+ }
70
+ throw new Error("--kind must be one of: swap, transfer, contract_call");
71
+ }
72
+
73
+ // src/format.ts
74
+ import { formatExecutionFailureReason } from "@txfence/core";
75
+ function formatSimulationResult(result) {
76
+ const lines = [
77
+ `Simulation: ${result.success ? "SUCCESS" : "FAILED"}`,
78
+ `Chain: ${result.chain}`,
79
+ `Block: ${result.simulatedAtBlock}`,
80
+ `Gas: ${result.gasEstimate.toString()} (buffer: ${result.gasBufferApplied}x)`,
81
+ `Coverage: ${result.coverageLevel}`,
82
+ `Caveats: ${result.caveats.length > 0 ? result.caveats.join(", ") : "none"}`
83
+ ];
84
+ return lines.join("\n");
85
+ }
86
+ function formatPolicyEvaluation(evaluation) {
87
+ const lines = [
88
+ `Policy: ${evaluation.passed ? "PASSED" : "FAILED"}`,
89
+ `Checks: ${evaluation.checksRun.join(", ")}`
90
+ ];
91
+ if (!evaluation.passed && evaluation.rejectionReason !== void 0) {
92
+ lines.push(`Reason: ${evaluation.rejectionReason}`);
93
+ }
94
+ return lines.join("\n");
95
+ }
96
+ function formatExecutionResult(result) {
97
+ switch (result.status) {
98
+ case "success": {
99
+ const r = result.receipt;
100
+ return [
101
+ "Transaction: SUCCESS",
102
+ `Hash: ${r.txHash}`,
103
+ `Block: ${r.confirmedAtBlock}`,
104
+ `Gas used: ${r.gasUsed.toString()}`,
105
+ formatPolicyEvaluation(r.policyEvaluation)
106
+ ].join("\n");
107
+ }
108
+ case "policy_rejected":
109
+ return [
110
+ "Policy: REJECTED",
111
+ `Reason: ${result.evaluation.rejectionReason ?? "unknown"}`,
112
+ `Checks: ${result.evaluation.checksRun.join(", ")}`
113
+ ].join("\n");
114
+ case "simulation_failed":
115
+ return [
116
+ "Simulation: FAILED",
117
+ formatSimulationResult(result.simulation)
118
+ ].join("\n");
119
+ case "simulation_stale":
120
+ return [
121
+ "Status: STALE SIMULATION",
122
+ `Stale by: ${result.stalenessMs}ms`,
123
+ `Threshold: ${result.simulation.simulatedAtBlock} (simulated at block)`,
124
+ "Action: Re-simulate before retrying."
125
+ ].join("\n");
126
+ case "approval_timeout":
127
+ return [
128
+ "Status: APPROVAL REQUIRED",
129
+ "The action exceeds the human approval threshold. Resubmit with explicit approval."
130
+ ].join("\n");
131
+ case "execution_failed":
132
+ return [
133
+ "Status: FAILED",
134
+ `Reason: ${formatExecutionFailureReason(result.reason)}`
135
+ ].join("\n");
136
+ }
137
+ }
138
+
139
+ // src/commands/simulate.ts
140
+ import { loadConfig } from "@txfence/mcp";
141
+ function makeSimulateCommand() {
142
+ const cmd = new Command2("simulate").description("Simulate an on-chain action without executing it").option("--config <path>", "path to txfence.config.ts", "./txfence.config.ts").option("--rpc <url>", "RPC endpoint override");
143
+ addActionOptions(cmd);
144
+ cmd.action(async (opts) => {
145
+ try {
146
+ const config = await loadConfig(resolve(opts["config"] ?? "./txfence.config.ts"));
147
+ const action = buildActionFromOptions(opts);
148
+ const adapter = config.adapters[action.chain];
149
+ if (adapter === void 0) {
150
+ throw new Error("no adapter for chain: " + action.chain);
151
+ }
152
+ const rpcUrl = opts["rpc"] ?? config.rpcUrls[action.chain];
153
+ if (rpcUrl === void 0) {
154
+ throw new Error("no rpcUrl for chain: " + action.chain);
155
+ }
156
+ const result = await adapter.simulate(action, action.chain, rpcUrl);
157
+ console.log(formatSimulationResult(result));
158
+ process.exit(0);
159
+ } catch (err) {
160
+ console.error(err instanceof Error ? err.message : String(err));
161
+ process.exit(1);
162
+ }
163
+ });
164
+ return cmd;
165
+ }
166
+
167
+ // src/commands/check-policy.ts
168
+ import { Command as Command3 } from "commander";
169
+ import { resolve as resolve2 } from "path";
170
+ import { loadConfig as loadConfig2 } from "@txfence/mcp";
171
+ import { evaluate, validateConfig } from "@txfence/core";
172
+ function makeCheckPolicyCommand() {
173
+ const cmd = new Command3("check-policy").description("Evaluate an action against the configured policy without simulation or execution").option("--config <path>", "path to txfence.config.ts", "./txfence.config.ts");
174
+ addActionOptions(cmd);
175
+ cmd.action(async (opts) => {
176
+ try {
177
+ const config = await loadConfig2(resolve2(opts["config"] ?? "./txfence.config.ts"));
178
+ const validation = validateConfig(config.policy);
179
+ if (validation.errors.length > 0) {
180
+ console.error("\nPolicy configuration errors (must fix):");
181
+ for (const err of validation.errors) {
182
+ console.error(` [ERROR] ${err.field}: ${err.message}`);
183
+ }
184
+ }
185
+ if (validation.warnings.length > 0) {
186
+ console.warn("\nPolicy configuration warnings (should fix):");
187
+ for (const warn of validation.warnings) {
188
+ console.warn(` [WARN] ${warn.field}: ${warn.message}`);
189
+ }
190
+ }
191
+ if (!validation.valid) {
192
+ console.error("\nPolicy has errors. Fix them before running agents.");
193
+ process.exit(1);
194
+ }
195
+ if (validation.warnings.length > 0) {
196
+ console.warn("");
197
+ }
198
+ const action = buildActionFromOptions(opts);
199
+ const boundAction = { action, policy: config.policy };
200
+ const evaluation = evaluate(boundAction);
201
+ console.log(formatPolicyEvaluation(evaluation));
202
+ process.exit(evaluation.passed ? 0 : 1);
203
+ } catch (err) {
204
+ console.error(err instanceof Error ? err.message : String(err));
205
+ process.exit(1);
206
+ }
207
+ });
208
+ return cmd;
209
+ }
210
+
211
+ // src/commands/submit.ts
212
+ import { Command as Command4 } from "commander";
213
+ import { resolve as resolve3 } from "path";
214
+ import { loadConfig as loadConfig3 } from "@txfence/mcp";
215
+ import { runPipeline } from "@txfence/core";
216
+ import { executeEvmAction } from "@txfence/evm";
217
+ function makeSubmitCommand() {
218
+ const cmd = new Command4("submit").description("Run the full txfence pipeline. Dry run by default.").option("--config <path>", "path to txfence.config.ts", "./txfence.config.ts").option("--dry-run", "simulate the full pipeline without executing (default)", true).option("--execute", "execute the transaction (requires signer in config)");
219
+ addActionOptions(cmd);
220
+ cmd.action(async (opts) => {
221
+ try {
222
+ const config = await loadConfig3(resolve3(opts["config"] ?? "./txfence.config.ts"));
223
+ const action = buildActionFromOptions(opts);
224
+ const dryRun = !opts["execute"];
225
+ if (!dryRun && config.signer === void 0) {
226
+ console.error("execution requires a signer configured in txfence.config.ts");
227
+ process.exit(1);
228
+ }
229
+ const executor = dryRun ? void 0 : (action2, chainId, rpcUrl, evaluation, simulation) => executeEvmAction(action2, chainId, rpcUrl, config.signer, evaluation, simulation);
230
+ const result = await runPipeline(
231
+ action,
232
+ config.policy,
233
+ config.adapters,
234
+ config.rpcUrls,
235
+ executor
236
+ );
237
+ if (!dryRun && result.status === "execution_failed" && result.reason.code === "no_executor") {
238
+ console.error("signing not available for this chain");
239
+ process.exit(1);
240
+ }
241
+ console.log(formatExecutionResult(result));
242
+ process.exit(result.status === "success" ? 0 : 1);
243
+ } catch (err) {
244
+ console.error(err instanceof Error ? err.message : String(err));
245
+ process.exit(1);
246
+ }
247
+ });
248
+ return cmd;
249
+ }
250
+
251
+ // src/commands/receipt.ts
252
+ import { Command as Command5 } from "commander";
253
+ import { resolve as resolve4 } from "path";
254
+ import { createPublicClient, http } from "viem";
255
+ import { getViemChain } from "@txfence/evm";
256
+ import { loadConfig as loadConfig4 } from "@txfence/mcp";
257
+ function makeReceiptCommand() {
258
+ const cmd = new Command5("receipt").description("Retrieve on-chain details for a transaction by hash").option("--config <path>", "path to txfence.config.ts", "./txfence.config.ts").requiredOption("--hash <txHash>", "transaction hash").requiredOption("--chain <chain>", "chain the transaction was submitted to").option("--rpc <url>", "RPC endpoint override");
259
+ cmd.action(async (opts) => {
260
+ try {
261
+ const config = await loadConfig4(resolve4(opts["config"] ?? "./txfence.config.ts"));
262
+ const rpcUrl = opts["rpc"] ?? config.rpcUrls[opts["chain"]];
263
+ if (rpcUrl === void 0) {
264
+ console.error("no rpcUrl for chain: " + opts["chain"]);
265
+ process.exit(1);
266
+ }
267
+ let chain;
268
+ try {
269
+ chain = getViemChain(opts["chain"]);
270
+ } catch (err) {
271
+ console.error(err instanceof Error ? err.message : String(err));
272
+ process.exit(1);
273
+ }
274
+ const client = createPublicClient({ chain, transport: http(rpcUrl) });
275
+ const receipt = await client.getTransactionReceipt({ hash: opts["hash"] });
276
+ console.log(`Transaction: ${opts["hash"]}`);
277
+ console.log(`Chain: ${opts["chain"]}`);
278
+ console.log(`Status: ${receipt.status}`);
279
+ console.log(`Block: ${receipt.blockNumber.toString()}`);
280
+ console.log(`Gas used: ${receipt.gasUsed.toString()}`);
281
+ process.exit(0);
282
+ } catch (err) {
283
+ console.error(err instanceof Error ? err.message : String(err));
284
+ process.exit(1);
285
+ }
286
+ });
287
+ return cmd;
288
+ }
289
+
290
+ // src/commands/init.ts
291
+ import { Command as Command6 } from "commander";
292
+ import { existsSync, writeFileSync } from "fs";
293
+ import { resolve as resolve5 } from "path";
294
+ function makeInitCommand() {
295
+ const cmd = new Command6("init").description("Scaffold a txfence.config.ts in the current directory").option("--force", "overwrite existing config file");
296
+ cmd.action((opts) => {
297
+ const configPath = resolve5("./txfence.config.ts");
298
+ if (existsSync(configPath) && !opts.force) {
299
+ console.error("txfence.config.ts already exists. Use --force to overwrite.");
300
+ process.exit(1);
301
+ }
302
+ const template = `import { defineConfig, env } from '@txfence/mcp'
303
+ import { simulateEvmAction, executeEvmAction, privateKeySigner } from '@txfence/evm'
304
+
305
+ export default defineConfig({
306
+ // chains this agent is allowed to operate on
307
+ chains: ['ethereum'],
308
+
309
+ // chain adapters: one per chain
310
+ adapters: {
311
+ ethereum: { simulate: simulateEvmAction },
312
+ },
313
+
314
+ // RPC endpoints: use a private endpoint in production
315
+ rpcUrls: {
316
+ ethereum: 'https://ethereum.publicnode.com',
317
+ },
318
+
319
+ // base policy: all agent actions are evaluated against this
320
+ policy: {
321
+ chains: ['ethereum'],
322
+
323
+ // maximum spend per transaction
324
+ maxSpendPerTx: { token: 'USDC', amount: 1000n, decimals: 6 },
325
+
326
+ // contracts this agent is allowed to interact with
327
+ // add entries with optional bytecodeHash and ownerAddress for metadata verification
328
+ allowedContracts: [],
329
+
330
+ // require simulation before every execution
331
+ requireSimulation: true,
332
+
333
+ // minimum gas buffer multiplier applied to simulation estimates
334
+ gasBufferMultiplier: 1.2,
335
+
336
+ // transactions above this threshold require human approval
337
+ humanApprovalThreshold: { token: 'USDC', amount: 10000n, decimals: 6 },
338
+
339
+ // how long to wait for human approval before cancelling (ms)
340
+ humanApprovalTimeoutMs: 30000,
341
+
342
+ // cap lock mode: per-agent (default) or shared (for multi-agent environments)
343
+ capLockMode: 'per-agent',
344
+ },
345
+
346
+ // signer: required for live execution (dryRun: false)
347
+ // never hardcode private keys -- use environment variables
348
+ // signer: privateKeySigner(env('AGENT_PRIVATE_KEY') as \`0x\${string}\`),
349
+ })
350
+ `;
351
+ writeFileSync(configPath, template, "utf-8");
352
+ console.log("created txfence.config.ts");
353
+ console.log("");
354
+ console.log("next steps:");
355
+ console.log(" 1. add your RPC endpoint to rpcUrls");
356
+ console.log(" 2. add allowed contracts to policy.allowedContracts");
357
+ console.log(" 3. set AGENT_PRIVATE_KEY in your .env file and uncomment the signer");
358
+ console.log(" 4. run: txfence check-policy --kind transfer --chain ethereum --to 0x... --token ETH --amount 1000000000000000000");
359
+ process.exit(0);
360
+ });
361
+ return cmd;
362
+ }
363
+
364
+ // src/commands/diff.ts
365
+ import { Command as Command7 } from "commander";
366
+ import { resolve as resolve6 } from "path";
367
+ import { readFileSync } from "fs";
368
+ import { loadConfig as loadConfig5 } from "@txfence/mcp";
369
+ import { diffPolicies, createTestActions } from "@txfence/core";
370
+ function makeDiffCommand() {
371
+ const cmd = new Command7("diff").description("Compare two policy configurations and show which actions are affected").requiredOption("--config-a <path>", "path to the current txfence config").requiredOption("--config-b <path>", "path to the proposed txfence config").option("--actions-file <path>", "JSON file containing actions to test (array of Action objects)").option("--generate-actions", "auto-generate test actions from policy A");
372
+ cmd.action(async (opts) => {
373
+ try {
374
+ const configA = await loadConfig5(resolve6(opts["configA"]));
375
+ const configB = await loadConfig5(resolve6(opts["configB"]));
376
+ let actions = [];
377
+ if (opts["actionsFile"] !== void 0) {
378
+ const filePath = opts["actionsFile"];
379
+ const raw = JSON.parse(readFileSync(resolve6(filePath), "utf-8"));
380
+ if (!Array.isArray(raw)) {
381
+ console.error("actions file must be a JSON array");
382
+ process.exit(1);
383
+ }
384
+ actions = raw.map((a) => ({ action: a }));
385
+ } else if (opts["generateActions"] !== void 0) {
386
+ actions = createTestActions(configA.policy);
387
+ } else {
388
+ console.error("provide --actions-file or --generate-actions");
389
+ process.exit(1);
390
+ }
391
+ const diff = diffPolicies({
392
+ policyA: configA.policy,
393
+ policyB: configB.policy,
394
+ actions
395
+ });
396
+ console.log("\nPolicy Diff Summary");
397
+ console.log("===================");
398
+ console.log(`Total actions tested: ${diff.summary.total}`);
399
+ console.log(`Changed: ${diff.summary.changed}`);
400
+ console.log(` Newly allowed: ${diff.summary.newlyAllowed}`);
401
+ console.log(` Newly rejected: ${diff.summary.newlyRejected}`);
402
+ console.log(` Rejection reason changed: ${diff.summary.rejectionReasonChanged}`);
403
+ console.log(`Unchanged: ${diff.summary.unchanged}`);
404
+ if (diff.summary.requiresSimulation > 0) {
405
+ console.log(
406
+ `
407
+ WARNING: ${diff.summary.requiresSimulation} action(s) have simulation-dependent checks that were skipped (no simulationResult provided)`
408
+ );
409
+ }
410
+ if (diff.summary.changed === 0) {
411
+ console.log("\nNo changes detected.");
412
+ process.exit(0);
413
+ }
414
+ console.log("\nChanged Actions");
415
+ console.log("===============");
416
+ for (const result of diff.results.filter((r) => r.changed)) {
417
+ const act = result.action;
418
+ const actionDesc = act.kind === "transfer" ? `transfer ${act.token.amount} ${act.token.token} on ${act.chain}` : act.kind === "swap" ? `swap ${act.from.amount} ${act.from.token} on ${act.chain}` : `contract_call ${act.contract} on ${act.chain}`;
419
+ console.log(`
420
+ [${result.direction?.toUpperCase()}] ${actionDesc}`);
421
+ console.log(
422
+ ` Policy A: ${result.evaluationA.passed ? "PASSED" : `REJECTED (${result.evaluationA.rejectionReason})`}`
423
+ );
424
+ console.log(
425
+ ` Policy B: ${result.evaluationB.passed ? "PASSED" : `REJECTED (${result.evaluationB.rejectionReason})`}`
426
+ );
427
+ if (result.changedChecks.length > 0) {
428
+ console.log(` Changed checks: ${result.changedChecks.map((c) => c.checkName).join(", ")}`);
429
+ }
430
+ }
431
+ process.exit(diff.summary.newlyRejected > 0 ? 1 : 0);
432
+ } catch (err) {
433
+ console.error(err.message);
434
+ process.exit(1);
435
+ }
436
+ });
437
+ return cmd;
438
+ }
439
+
440
+ // src/commands/dry-run.ts
441
+ import { Command as Command8 } from "commander";
442
+ import { resolve as resolve7 } from "path";
443
+ import { loadConfig as loadConfig6 } from "@txfence/mcp";
444
+ import { runDryRun } from "@txfence/core";
445
+ function makeDryRunCommand() {
446
+ const cmd = new Command8("dry-run").description("Run the full pipeline without executing \u2014 shows what would happen").option("--config <path>", "path to txfence.config.ts", "./txfence.config.ts");
447
+ addActionOptions(cmd);
448
+ cmd.action(async (opts) => {
449
+ try {
450
+ const config = await loadConfig6(resolve7(opts["config"] ?? "./txfence.config.ts"));
451
+ const action = buildActionFromOptions(opts);
452
+ const result = await runDryRun(
453
+ action,
454
+ config.policy,
455
+ config.adapters,
456
+ config.rpcUrls
457
+ );
458
+ console.log("\n=== Dry Run Report ===\n");
459
+ console.log(`Action: ${action.kind} on ${action.chain}`);
460
+ console.log(`Would proceed: ${result.wouldProceed ? "YES" : "NO"}`);
461
+ console.log("");
462
+ console.log("Policy evaluation:");
463
+ console.log(` Result: ${result.evaluation.passed ? "PASSED" : "FAILED"}`);
464
+ console.log(` Checks: ${result.evaluation.checksRun.join(", ")}`);
465
+ if (result.evaluation.rejectionReason !== void 0) {
466
+ console.log(` Rejected: ${result.evaluation.rejectionReason}`);
467
+ }
468
+ if (result.simulation !== void 0) {
469
+ console.log("");
470
+ console.log("Simulation:");
471
+ console.log(` Success: ${result.simulation.success}`);
472
+ console.log(` Coverage: ${result.simulation.coverageLevel}`);
473
+ console.log(` Gas est: ${result.simulation.gasEstimate}`);
474
+ if (result.simulation.caveats.length > 0) {
475
+ console.log(` Caveats: ${result.simulation.caveats.join(", ")}`);
476
+ }
477
+ }
478
+ console.log("");
479
+ console.log(`Approval required: ${result.approvalRequired ? "YES" : "no"}`);
480
+ if (result.approvalThreshold !== void 0) {
481
+ console.log(` Threshold: ${result.approvalThreshold.amount} ${result.approvalThreshold.token}`);
482
+ }
483
+ console.log(`Cap lock available: ${result.capLockAvailable ? "yes" : "NO"}`);
484
+ if (result.blockers.length > 0) {
485
+ console.log("");
486
+ console.log("Blockers:");
487
+ for (const blocker of result.blockers) {
488
+ console.log(` ${formatBlocker(blocker)}`);
489
+ }
490
+ }
491
+ console.log("");
492
+ process.exit(result.wouldProceed ? 0 : 1);
493
+ } catch (err) {
494
+ console.error(err instanceof Error ? err.message : String(err));
495
+ process.exit(1);
496
+ }
497
+ });
498
+ return cmd;
499
+ }
500
+ function formatBlocker(blocker) {
501
+ switch (blocker.kind) {
502
+ case "policy_rejected":
503
+ return `[BLOCKED] Policy rejected: ${blocker.reason}`;
504
+ case "simulation_failed":
505
+ return "[BLOCKED] Simulation failed";
506
+ case "simulation_stale":
507
+ return `[BLOCKED] Simulation stale by ${blocker.stalenessMs}ms`;
508
+ case "approval_required":
509
+ return `[BLOCKED] Human approval required (threshold: ${blocker.thresholdAmount} ${blocker.thresholdToken})`;
510
+ case "cap_lock_unavailable":
511
+ return `[BLOCKED] Cap lock unavailable: ${blocker.capId}`;
512
+ default: {
513
+ const _ = blocker;
514
+ return "";
515
+ }
516
+ }
517
+ }
518
+
519
+ // src/commands/policy-snapshot.ts
520
+ import { Command as Command9 } from "commander";
521
+ import { resolve as resolve8 } from "path";
522
+ import { loadConfig as loadConfig7 } from "@txfence/mcp";
523
+ import { createPolicyVersion } from "@txfence/core";
524
+ function makePolicySnapshotCommand() {
525
+ const cmd = new Command9("policy-snapshot").description("Print the version ID and hash for the active policy config").requiredOption("--config <path>", "path to txfence config").option("--label <label>", "human-readable label for this version").option("--author <author>", "author of this version").option("--json", "output as JSON");
526
+ cmd.action(async (opts) => {
527
+ try {
528
+ const config = await loadConfig7(resolve8(opts["config"]));
529
+ const version = createPolicyVersion(config.policy, {
530
+ ...typeof opts["label"] === "string" ? { label: opts["label"] } : {},
531
+ ...typeof opts["author"] === "string" ? { author: opts["author"] } : {}
532
+ });
533
+ if (opts["json"] === true) {
534
+ console.log(JSON.stringify({
535
+ id: version.id,
536
+ ...version.label !== void 0 ? { label: version.label } : {},
537
+ ...version.author !== void 0 ? { author: version.author } : {},
538
+ createdAt: version.createdAt,
539
+ policy: {
540
+ chains: version.policy.chains,
541
+ maxSpendPerTx: {
542
+ token: version.policy.maxSpendPerTx.token,
543
+ amount: version.policy.maxSpendPerTx.amount.toString(),
544
+ decimals: version.policy.maxSpendPerTx.decimals
545
+ },
546
+ requireSimulation: version.policy.requireSimulation,
547
+ gasBufferMultiplier: version.policy.gasBufferMultiplier,
548
+ allowedContracts: version.policy.allowedContracts.length
549
+ }
550
+ }, null, 2));
551
+ } else {
552
+ console.log("\n=== Policy Snapshot ===\n");
553
+ console.log(`ID: ${version.id}`);
554
+ if (version.label !== void 0) console.log(`Label: ${version.label}`);
555
+ if (version.author !== void 0) console.log(`Author: ${version.author}`);
556
+ console.log(`Created: ${new Date(version.createdAt).toISOString()}`);
557
+ console.log("");
558
+ console.log("Policy summary:");
559
+ console.log(` Chains: ${version.policy.chains.join(", ")}`);
560
+ console.log(` Max spend per tx: ${version.policy.maxSpendPerTx.amount} ${version.policy.maxSpendPerTx.token}`);
561
+ console.log(` Require simulation: ${version.policy.requireSimulation}`);
562
+ console.log(` Gas buffer: ${version.policy.gasBufferMultiplier}x`);
563
+ console.log(` Allowed contracts: ${version.policy.allowedContracts.length}`);
564
+ console.log("");
565
+ console.log("Use this ID to reference this policy version in audit queries.");
566
+ }
567
+ process.exit(0);
568
+ } catch (err) {
569
+ console.error(err.message);
570
+ process.exit(1);
571
+ }
572
+ });
573
+ return cmd;
574
+ }
575
+
576
+ // src/commands/intent.ts
577
+ import { Command as Command10 } from "commander";
578
+ import { resolve as resolve9 } from "path";
579
+ import { readFileSync as readFileSync2 } from "fs";
580
+ import { loadConfig as loadConfig8 } from "@txfence/mcp";
581
+ import {
582
+ executeIntent,
583
+ evaluateIntent,
584
+ validateIntentGraph,
585
+ formatExecutionFailureReason as formatExecutionFailureReason2,
586
+ bigintReplacer
587
+ } from "@txfence/core";
588
+ import { executeEvmAction as executeEvmAction2, simulateIntentOnFork } from "@txfence/evm";
589
+ function reviveIntent(raw) {
590
+ const steps = raw["steps"].map((step) => {
591
+ const action = step["action"];
592
+ if (action["kind"] === "transfer") {
593
+ const token = action["token"];
594
+ if (typeof token["amount"] === "string") token["amount"] = BigInt(token["amount"]);
595
+ }
596
+ if (action["kind"] === "swap") {
597
+ const from = action["from"];
598
+ if (typeof from["amount"] === "string") from["amount"] = BigInt(from["amount"]);
599
+ }
600
+ if (action["kind"] === "contract_call" && action["value"] !== void 0) {
601
+ const value = action["value"];
602
+ if (typeof value["amount"] === "string") value["amount"] = BigInt(value["amount"]);
603
+ }
604
+ return step;
605
+ });
606
+ const intentPolicy = raw["intentPolicy"];
607
+ if (intentPolicy !== void 0) {
608
+ const reviveTokenAmount = (field) => {
609
+ const ta = intentPolicy[field];
610
+ if (ta !== void 0 && typeof ta["amount"] === "string") {
611
+ ta["amount"] = BigInt(ta["amount"]);
612
+ }
613
+ };
614
+ reviveTokenAmount("maxTotalGrossSpend");
615
+ reviveTokenAmount("maxNetSpend");
616
+ reviveTokenAmount("maxIntermediateExposure");
617
+ }
618
+ return {
619
+ id: raw["id"],
620
+ ...raw["label"] !== void 0 ? { label: raw["label"] } : {},
621
+ steps,
622
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
623
+ ...intentPolicy !== void 0 ? { intentPolicy } : {}
624
+ };
625
+ }
626
+ var validateCmd = new Command10("validate").description("Validate an intent file \u2014 check for cycles, missing dependencies, and policy violations").requiredOption("--config <path>", "path to txfence config").requiredOption("--intent <path>", "path to intent JSON file");
627
+ validateCmd.action(async (opts) => {
628
+ try {
629
+ const config = await loadConfig8(resolve9(opts["config"]));
630
+ const intentRaw = JSON.parse(readFileSync2(resolve9(opts["intent"]), "utf-8"));
631
+ const intent = reviveIntent(intentRaw);
632
+ console.log("\n=== Intent Validation ===\n");
633
+ console.log(`Intent ID: ${intent.id}`);
634
+ console.log(`Steps: ${intent.steps.length}`);
635
+ const graphResult = validateIntentGraph(intent);
636
+ if (!graphResult.valid) {
637
+ console.error(`
638
+ [INVALID] Graph error: ${graphResult.detail}`);
639
+ console.error(`Reason: ${graphResult.reason}`);
640
+ process.exit(1);
641
+ }
642
+ console.log(`
643
+ Graph: VALID`);
644
+ console.log(`Execution plan: ${graphResult.executionPlan.join(" \u2192 ")}`);
645
+ const evalResult = evaluateIntent(intent, config.policy);
646
+ if (!evalResult.passed) {
647
+ console.error(`
648
+ [REJECTED] Intent policy evaluation failed`);
649
+ console.error(`Reason: ${evalResult.rejectionReason}`);
650
+ if (evalResult.stepEvaluations.some((s) => !s.evaluation.passed)) {
651
+ console.error("\nFailed steps:");
652
+ for (const step of evalResult.stepEvaluations.filter((s) => !s.evaluation.passed)) {
653
+ console.error(` ${step.stepId}: ${step.evaluation.rejectionReason}`);
654
+ }
655
+ }
656
+ process.exit(1);
657
+ }
658
+ console.log(`
659
+ Policy: PASSED`);
660
+ if (evalResult.intentPolicyEvaluation !== void 0) {
661
+ const pos = evalResult.intentPolicyEvaluation.positionAnalysis;
662
+ console.log(`
663
+ Position analysis:`);
664
+ console.log(` Single token: ${pos.isSingleToken}`);
665
+ if (pos.dominantToken !== void 0) {
666
+ console.log(` Token: ${pos.dominantToken}`);
667
+ }
668
+ console.log(` Total gross outflow: ${pos.totalGrossOutflow}`);
669
+ console.log(` Max intermediate: ${pos.maxIntermediateExposure}`);
670
+ }
671
+ console.log("\nIntent is valid and ready to execute.");
672
+ process.exit(0);
673
+ } catch (err) {
674
+ console.error(err.message);
675
+ process.exit(1);
676
+ }
677
+ });
678
+ var submitCmd = new Command10("submit").description("Execute an intent \u2014 runs all steps in dependency order").requiredOption("--config <path>", "path to txfence config").requiredOption("--intent <path>", "path to intent JSON file").option("--dry-run", "validate and evaluate without executing", false).option("--json", "output results as JSON");
679
+ submitCmd.action(async (opts) => {
680
+ try {
681
+ const config = await loadConfig8(resolve9(opts["config"]));
682
+ const intentRaw = JSON.parse(readFileSync2(resolve9(opts["intent"]), "utf-8"));
683
+ const intent = reviveIntent(intentRaw);
684
+ const isDryRun = opts["dryRun"] === true;
685
+ const isJson = opts["json"] === true;
686
+ console.log(`
687
+ === Intent Submit${isDryRun ? " (dry run)" : ""} ===
688
+ `);
689
+ console.log(`Intent ID: ${intent.id}`);
690
+ if (intent.label !== void 0) console.log(`Label: ${intent.label}`);
691
+ console.log(`Steps: ${intent.steps.length}`);
692
+ if (isDryRun) {
693
+ const evalResult = evaluateIntent(intent, config.policy);
694
+ if (isJson) {
695
+ console.log(JSON.stringify(evalResult, bigintReplacer, 2));
696
+ } else {
697
+ console.log(`
698
+ Evaluation: ${evalResult.passed ? "PASSED" : "REJECTED"}`);
699
+ if (!evalResult.passed) {
700
+ console.error(`Reason: ${evalResult.rejectionReason}`);
701
+ }
702
+ console.log(`Execution plan: ${evalResult.executionPlan.join(" \u2192 ")}`);
703
+ }
704
+ process.exit(evalResult.passed ? 0 : 1);
705
+ }
706
+ const executor = config.signer !== void 0 ? (action, chainId, rpcUrl, evaluation, simulation) => executeEvmAction2(action, chainId, rpcUrl, config.signer, evaluation, simulation) : void 0;
707
+ const result = await executeIntent(intent, config.policy, {
708
+ adapters: config.adapters,
709
+ rpcUrls: config.rpcUrls,
710
+ ...executor !== void 0 ? { executor } : {}
711
+ });
712
+ if (isJson) {
713
+ console.log(JSON.stringify(result, bigintReplacer, 2));
714
+ } else {
715
+ console.log(`
716
+ Status: ${result.status.toUpperCase()}`);
717
+ console.log(`Duration: ${result.durationMs}ms`);
718
+ console.log("");
719
+ if (result.completedStepIds.length > 0) {
720
+ console.log(`Completed (${result.completedStepIds.length}):`);
721
+ for (const stepId of result.completedStepIds) {
722
+ const receipt = result.receipts[stepId];
723
+ console.log(` + ${stepId}: ${receipt?.txHash ?? "no receipt"}`);
724
+ }
725
+ }
726
+ if (result.failedStepIds.length > 0) {
727
+ console.log(`
728
+ Failed (${result.failedStepIds.length}):`);
729
+ for (const stepId of result.failedStepIds) {
730
+ const stepResult = result.stepResults.find((r) => r.stepId === stepId);
731
+ const reason = stepResult?.status === "failed" ? formatExecutionFailureReason2(stepResult.reason) : "unknown";
732
+ console.log(` x ${stepId}: ${reason}`);
733
+ }
734
+ }
735
+ if (result.skippedStepIds.length > 0) {
736
+ console.log(`
737
+ Skipped (${result.skippedStepIds.length}):`);
738
+ for (const stepId of result.skippedStepIds) {
739
+ console.log(` - ${stepId}`);
740
+ }
741
+ }
742
+ console.log("");
743
+ const pos = result.positionAnalysis;
744
+ if (pos.totalGrossOutflow > 0n) {
745
+ console.log(`Total gross outflow: ${pos.totalGrossOutflow}`);
746
+ }
747
+ }
748
+ process.exit(
749
+ result.status === "completed" ? 0 : result.status === "partial" ? 2 : 1
750
+ );
751
+ } catch (err) {
752
+ console.error(err.message);
753
+ process.exit(1);
754
+ }
755
+ });
756
+ var forkSimulateCmd = new Command10("fork-simulate").description("Simulate a multi-step intent on a forked chain state \u2014 shows final position without executing").requiredOption("--config <path>", "path to txfence config").requiredOption("--intent <path>", "path to intent JSON file").requiredOption("--from <address>", "agent address for all simulated transactions").requiredOption("--chain <chain>", "chain to fork (e.g. ethereum, base, arbitrum)").option("--block <number>", "block number to fork at (default: latest)").option("--json", "output results as JSON");
757
+ forkSimulateCmd.action(async (opts) => {
758
+ try {
759
+ const config = await loadConfig8(resolve9(opts.config));
760
+ const intentRaw = JSON.parse(readFileSync2(resolve9(opts.intent), "utf-8"));
761
+ const intent = reviveIntent(intentRaw);
762
+ const tenderlyAccessKey = process.env.TENDERLY_ACCESS_KEY;
763
+ const tenderlyAccountSlug = process.env.TENDERLY_ACCOUNT_SLUG;
764
+ const tenderlyProjectSlug = process.env.TENDERLY_PROJECT_SLUG;
765
+ if (!tenderlyAccessKey || !tenderlyAccountSlug || !tenderlyProjectSlug) {
766
+ console.error(
767
+ "Fork simulation requires Tenderly credentials.\nSet these environment variables:\n TENDERLY_ACCESS_KEY\n TENDERLY_ACCOUNT_SLUG\n TENDERLY_PROJECT_SLUG"
768
+ );
769
+ process.exit(1);
770
+ }
771
+ const forkConfig = {
772
+ provider: "tenderly",
773
+ tenderlyConfig: {
774
+ accessKey: tenderlyAccessKey,
775
+ accountSlug: tenderlyAccountSlug,
776
+ projectSlug: tenderlyProjectSlug
777
+ },
778
+ fromAddress: opts.from,
779
+ ...opts.block !== void 0 ? { blockNumber: parseInt(opts.block, 10) } : {}
780
+ };
781
+ const rpcUrl = config.rpcUrls?.[opts.chain] ?? process.env.ETHEREUM_RPC_URL;
782
+ if (!rpcUrl) {
783
+ console.error(`No RPC URL configured for chain ${opts.chain}`);
784
+ process.exit(1);
785
+ }
786
+ console.log(`
787
+ === Fork Simulation ===
788
+ `);
789
+ console.log(`Intent: ${intent.id}${intent.label ? ` (${intent.label})` : ""}`);
790
+ console.log(`Chain: ${opts.chain}`);
791
+ console.log(`From: ${opts.from}`);
792
+ console.log(`Steps: ${intent.steps.length}`);
793
+ console.log(`
794
+ Creating fork and simulating...`);
795
+ const result = await simulateIntentOnFork(
796
+ intent,
797
+ forkConfig,
798
+ opts.chain,
799
+ rpcUrl
800
+ );
801
+ if (opts.json) {
802
+ console.log(JSON.stringify(result, bigintReplacer, 2));
803
+ process.exit(result.wouldAllSucceed ? 0 : 1);
804
+ return;
805
+ }
806
+ console.log(`
807
+ === Results ===
808
+ `);
809
+ console.log(`Forked at block: ${result.forkedAtBlock}`);
810
+ console.log(`Would all succeed: ${result.wouldAllSucceed ? "YES" : "NO"}`);
811
+ if (result.failingStepId !== void 0) {
812
+ console.log(`Failing step: ${result.failingStepId}`);
813
+ }
814
+ console.log("");
815
+ console.log("Step results:");
816
+ for (const step of result.steps) {
817
+ const status = step.wouldRevert ? "\u2717 REVERT" : "\u2713 SUCCESS";
818
+ console.log(` ${status} ${step.stepId}`);
819
+ if (step.wouldRevert && step.revertReason !== void 0) {
820
+ console.log(` Reason: ${step.revertReason}`);
821
+ }
822
+ if (step.stateChanges.length > 0) {
823
+ console.log(` State changes: ${step.stateChanges.length}`);
824
+ for (const sc of step.stateChanges) {
825
+ const sign = sc.delta >= 0n ? "+" : "";
826
+ console.log(` ${sc.address.slice(0, 10)}... ${sign}${sc.delta}`);
827
+ }
828
+ }
829
+ }
830
+ if (result.finalPosition.length > 0) {
831
+ console.log("\nFinal position delta:");
832
+ for (const pos of result.finalPosition) {
833
+ const sign = pos.amount >= 0n ? "+" : "";
834
+ console.log(` ${pos.token} on ${pos.chain}: ${sign}${pos.amount}`);
835
+ }
836
+ } else {
837
+ console.log("\nFinal position: no changes detected");
838
+ }
839
+ console.log("");
840
+ process.exit(result.wouldAllSucceed ? 0 : 1);
841
+ } catch (err) {
842
+ console.error(err.message);
843
+ process.exit(1);
844
+ }
845
+ });
846
+ function makeIntentCommand() {
847
+ const cmd = new Command10("intent").description("Intent execution commands");
848
+ cmd.addCommand(validateCmd);
849
+ cmd.addCommand(submitCmd);
850
+ cmd.addCommand(forkSimulateCmd);
851
+ return cmd;
852
+ }
853
+
854
+ // src/commands/replay.ts
855
+ import { Command as Command11 } from "commander";
856
+ import { resolve as resolve10 } from "path";
857
+ import { loadConfig as loadConfig9 } from "@txfence/mcp";
858
+ import { replayAuditLog, bigintReplacer as bigintReplacer2 } from "@txfence/core";
859
+ import { createFileAuditLog } from "@txfence/audit";
860
+ function makeReplayCommand() {
861
+ const cmd = new Command11("replay").description("Replay historical audit log entries against a new policy \u2014 shows what would have changed").requiredOption("--audit-log <path>", "path to the audit log JSONL file").requiredOption("--config <path>", "path to the proposed txfence config to test against").option("--from <timestamp>", "replay entries from this timestamp (ms since epoch)").option("--to <timestamp>", "replay entries up to this timestamp (ms since epoch)").option("--kind <kind>", "filter by action kind: transfer, swap, or contract_call").option("--chain <chain>", "filter by chain").option("--only-changed", "only show entries where outcome changed", false).option("--json", "output results as JSON");
862
+ cmd.action(async (opts) => {
863
+ try {
864
+ const config = await loadConfig9(resolve10(opts["config"]));
865
+ const auditLog = createFileAuditLog(resolve10(opts["auditLog"]));
866
+ const fromOpt = opts["from"];
867
+ const toOpt = opts["to"];
868
+ const kindOpt = opts["kind"];
869
+ const chainOpt = opts["chain"];
870
+ const onlyChanged = opts["onlyChanged"] === true;
871
+ const replayOptions = {
872
+ ...fromOpt !== void 0 ? { from: parseInt(fromOpt, 10) } : {},
873
+ ...toOpt !== void 0 ? { to: parseInt(toOpt, 10) } : {},
874
+ ...kindOpt !== void 0 ? { actionKind: kindOpt } : {},
875
+ ...chainOpt !== void 0 ? { chain: chainOpt } : {},
876
+ ...onlyChanged ? { onlyChanged: true } : {}
877
+ };
878
+ console.log("\n=== Policy Replay ===\n");
879
+ console.log(`Audit log: ${opts["auditLog"]}`);
880
+ console.log(`Policy: ${opts["config"]}`);
881
+ if (fromOpt !== void 0) console.log(`From: ${new Date(parseInt(fromOpt, 10)).toISOString()}`);
882
+ if (toOpt !== void 0) console.log(`To: ${new Date(parseInt(toOpt, 10)).toISOString()}`);
883
+ console.log("\nReplaying...");
884
+ const result = await replayAuditLog(auditLog, config.policy, replayOptions);
885
+ if (opts["json"] === true) {
886
+ console.log(JSON.stringify(result, bigintReplacer2, 2));
887
+ process.exit(result.summary.newlyRejected > 0 ? 1 : 0);
888
+ return;
889
+ }
890
+ console.log("\n=== Summary ===\n");
891
+ console.log(`Total entries: ${result.summary.total}`);
892
+ console.log(`Unchanged: ${result.summary.unchanged}`);
893
+ console.log(`Changed: ${result.summary.changed}`);
894
+ console.log(` Newly allowed: ${result.summary.newlyAllowed}`);
895
+ console.log(` Newly rejected: ${result.summary.newlyRejected}`);
896
+ console.log(` Rejection reason changed: ${result.summary.rejectionReasonChanged}`);
897
+ if (result.summary.skipped > 0) {
898
+ console.log(`Skipped (no eval data): ${result.summary.skipped}`);
899
+ }
900
+ if (result.summary.changed === 0) {
901
+ console.log("\nNo changes \u2014 this policy would have produced identical outcomes.");
902
+ process.exit(0);
903
+ }
904
+ console.log("\n=== Changed Entries ===\n");
905
+ const changedEntries = result.entries.filter((e) => e.changed);
906
+ for (const entry of changedEntries) {
907
+ const direction = entry.direction?.toUpperCase().replace(/_/g, " ") ?? "CHANGED";
908
+ const ts = new Date(entry.timestamp).toISOString();
909
+ const act = entry.action;
910
+ const actionDesc = act.kind === "transfer" ? `transfer ${act.token.amount} ${act.token.token} on ${act.chain}` : act.kind === "swap" ? `swap ${act.from.amount} ${act.from.token} on ${act.chain}` : `contract_call on ${act.chain}`;
911
+ console.log(`[${direction}] ${ts}`);
912
+ console.log(` ${actionDesc}`);
913
+ console.log(` Original: ${entry.originalEvaluation.passed ? "PASSED" : `REJECTED (${entry.originalEvaluation.rejectionReason})`}`);
914
+ console.log(` Replay: ${entry.replayEvaluation.passed ? "PASSED" : `REJECTED (${entry.replayEvaluation.rejectionReason})`}`);
915
+ if (entry.changedChecks.length > 0) {
916
+ console.log(` Changed checks: ${entry.changedChecks.map((c) => c.checkName).join(", ")}`);
917
+ }
918
+ console.log();
919
+ }
920
+ process.exit(result.summary.newlyRejected > 0 ? 1 : 0);
921
+ } catch (err) {
922
+ console.error(err.message);
923
+ process.exit(1);
924
+ }
925
+ });
926
+ return cmd;
927
+ }
928
+
929
+ // src/commands/verify.ts
930
+ import { Command as Command12 } from "commander";
931
+ import { resolve as resolve11 } from "path";
932
+ import { loadConfig as loadConfig10 } from "@txfence/mcp";
933
+ import { verify } from "@txfence/verify";
934
+ import { bigintReplacer as bigintReplacer3 } from "@txfence/core";
935
+ function makeVerifyCommand() {
936
+ const cmd = new Command12("verify").description("Verify policy invariants using bounded model checking");
937
+ const rollingWindowCmd = new Command12("rolling-window").description(
938
+ "Check whether N agents sharing a rolling window cap can collectively exceed it. Generates a concrete counterexample if the invariant is violated."
939
+ ).requiredOption("--config <path>", "path to txfence config").requiredOption("--agents <n>", "number of concurrent agents to check").requiredOption("--transactions <n>", "max transactions per agent to check").requiredOption("--cap <amount>", "rolling window cap amount (integer)").requiredOption("--window <ms>", "rolling window duration in milliseconds").requiredOption("--token <symbol>", "token symbol (e.g. USDC)").option("--json", "output results as JSON");
940
+ rollingWindowCmd.action(async (opts) => {
941
+ try {
942
+ const config = await loadConfig10(resolve11(opts["config"]));
943
+ const maxSpendPerTx = config.policy.maxSpendPerTx.amount;
944
+ const property = {
945
+ kind: "rolling_window_saturation",
946
+ agentCount: parseInt(opts["agents"], 10),
947
+ transactionsPerAgent: parseInt(opts["transactions"], 10),
948
+ windowMs: parseInt(opts["window"], 10),
949
+ capAmount: BigInt(opts["cap"]),
950
+ token: opts["token"],
951
+ maxSpendPerTx
952
+ };
953
+ console.log("\n=== Rolling Window Saturation Check ===\n");
954
+ console.log(`Agents: ${property.agentCount}`);
955
+ console.log(`Tx per agent: ${property.transactionsPerAgent}`);
956
+ console.log(`Window: ${property.windowMs}ms`);
957
+ console.log(`Cap: ${property.capAmount} ${property.token}`);
958
+ console.log(`Max spend per tx: ${property.maxSpendPerTx} ${property.token}`);
959
+ console.log("\nVerifying...");
960
+ const result = verify(property);
961
+ if (opts["json"]) {
962
+ console.log(JSON.stringify(result, bigintReplacer3, 2));
963
+ process.exit(result.status === "violated" ? 1 : 0);
964
+ }
965
+ printResult(result);
966
+ process.exit(result.status === "violated" ? 1 : 0);
967
+ } catch (err) {
968
+ console.error(err.message);
969
+ process.exit(1);
970
+ }
971
+ });
972
+ const absoluteCapCmd = new Command12("absolute-cap").description(
973
+ "Check whether N agents can collectively reach or exceed the absolute cap. Produces a minimal counterexample showing how few transactions are needed."
974
+ ).requiredOption("--config <path>", "path to txfence config").requiredOption("--agents <n>", "number of concurrent agents to check").requiredOption("--transactions <n>", "max transactions per agent to check").requiredOption("--cap <amount>", "absolute cap amount (integer)").requiredOption("--token <symbol>", "token symbol (e.g. USDC)").option("--json", "output results as JSON");
975
+ absoluteCapCmd.action(async (opts) => {
976
+ try {
977
+ const config = await loadConfig10(resolve11(opts["config"]));
978
+ const maxSpendPerTx = config.policy.maxSpendPerTx.amount;
979
+ const property = {
980
+ kind: "absolute_cap_reachability",
981
+ agentCount: parseInt(opts["agents"], 10),
982
+ transactionsPerAgent: parseInt(opts["transactions"], 10),
983
+ capAmount: BigInt(opts["cap"]),
984
+ token: opts["token"],
985
+ maxSpendPerTx
986
+ };
987
+ console.log("\n=== Absolute Cap Reachability Check ===\n");
988
+ console.log(`Agents: ${property.agentCount}`);
989
+ console.log(`Tx per agent: ${property.transactionsPerAgent}`);
990
+ console.log(`Cap: ${property.capAmount} ${property.token}`);
991
+ console.log(`Max spend per tx: ${property.maxSpendPerTx} ${property.token}`);
992
+ console.log("\nVerifying...");
993
+ const result = verify(property);
994
+ if (opts["json"]) {
995
+ console.log(JSON.stringify(result, bigintReplacer3, 2));
996
+ process.exit(result.status === "violated" ? 1 : 0);
997
+ }
998
+ printResult(result);
999
+ process.exit(result.status === "violated" ? 1 : 0);
1000
+ } catch (err) {
1001
+ console.error(err.message);
1002
+ process.exit(1);
1003
+ }
1004
+ });
1005
+ const policyContainsCmd = new Command12("policy-contains").description(
1006
+ "Check whether every action allowed by the inner policy is also allowed by the outer policy. Finds concrete actions that inner allows but outer rejects."
1007
+ ).requiredOption("--inner <path>", "path to the inner (narrower) txfence config").requiredOption("--outer <path>", "path to the outer (wider) txfence config").option("--json", "output results as JSON");
1008
+ policyContainsCmd.action(async (opts) => {
1009
+ try {
1010
+ const innerConfig = await loadConfig10(resolve11(opts["inner"]));
1011
+ const outerConfig = await loadConfig10(resolve11(opts["outer"]));
1012
+ const property = {
1013
+ kind: "policy_containment",
1014
+ innerPolicy: innerConfig.policy,
1015
+ outerPolicy: outerConfig.policy
1016
+ };
1017
+ console.log("\n=== Policy Containment Check ===\n");
1018
+ console.log(`Inner policy: ${opts["inner"]}`);
1019
+ console.log(`Outer policy: ${opts["outer"]}`);
1020
+ console.log("\nVerifying...");
1021
+ const result = verify(property);
1022
+ if (opts["json"]) {
1023
+ console.log(JSON.stringify(result, bigintReplacer3, 2));
1024
+ process.exit(result.status === "violated" ? 1 : 0);
1025
+ }
1026
+ printResult(result);
1027
+ process.exit(result.status === "violated" ? 1 : 0);
1028
+ } catch (err) {
1029
+ console.error(err.message);
1030
+ process.exit(1);
1031
+ }
1032
+ });
1033
+ cmd.addCommand(rollingWindowCmd);
1034
+ cmd.addCommand(absoluteCapCmd);
1035
+ cmd.addCommand(policyContainsCmd);
1036
+ return cmd;
1037
+ }
1038
+ function printResult(result) {
1039
+ console.log("");
1040
+ if (result.status === "holds") {
1041
+ console.log(`\u2713 HOLDS \u2014 ${result.property}`);
1042
+ console.log(` Checked: ${result.scenariosChecked} scenario(s)`);
1043
+ console.log(
1044
+ ` Bound: ${result.checkedBound.agentCount} agents, ${result.checkedBound.transactionsPerAgent} tx/agent` + (result.checkedBound.windowMs !== void 0 ? `, ${result.checkedBound.windowMs}ms window` : "")
1045
+ );
1046
+ console.log(` Time: ${result.durationMs}ms`);
1047
+ console.log("");
1048
+ console.log("The invariant holds within the checked bound.");
1049
+ console.log("Increase --agents and --transactions to check larger bounds.");
1050
+ } else if (result.status === "violated") {
1051
+ console.log(`\u2717 VIOLATED \u2014 ${result.property}`);
1052
+ console.log(` Time: ${result.durationMs}ms`);
1053
+ console.log("");
1054
+ console.log("Counterexample:");
1055
+ console.log(` ${result.counterExample.description}`);
1056
+ console.log("");
1057
+ console.log(` Violated at transaction: ${result.counterExample.violatedAt}`);
1058
+ console.log(` Violated amount: ${result.counterExample.violatedAmount}`);
1059
+ console.log(` Cap limit: ${result.counterExample.capLimit}`);
1060
+ console.log("");
1061
+ console.log(
1062
+ ` Transactions in counterexample: ${result.counterExample.transactions.length}`
1063
+ );
1064
+ const preview = result.counterExample.transactions.slice(0, 5);
1065
+ for (const tx of preview) {
1066
+ console.log(` ${tx.agentId} \u2014 ${tx.amount} at t=${tx.timestamp}ms`);
1067
+ }
1068
+ if (result.counterExample.transactions.length > 5) {
1069
+ console.log(
1070
+ ` ... and ${result.counterExample.transactions.length - 5} more`
1071
+ );
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ // src/commands/stress-test.ts
1077
+ import { Command as Command13 } from "commander";
1078
+ import { resolve as resolve12 } from "path";
1079
+ import { loadConfig as loadConfig11 } from "@txfence/mcp";
1080
+ import { stressTest, DEFAULT_VECTORS } from "@txfence/verify";
1081
+ import { bigintReplacer as bigintReplacer4 } from "@txfence/core";
1082
+ function makeStressTestCommand() {
1083
+ const cmd = new Command13("stress-test").description(
1084
+ "Run thousands of adversarial scenarios against a policy configuration. Produces a risk report showing which attack vectors your policy survives."
1085
+ ).requiredOption("--config <path>", "path to txfence config").option("--agents <n>", "number of concurrent agents to simulate", "10").option("--transactions <n>", "transactions per scenario", "20").option("--jitter <ms>", "max timing jitter in milliseconds", "50").option(
1086
+ "--vectors <list>",
1087
+ "comma-separated attack vectors to test: rapid_fire,coordinated_drain,rpc_failure,stale_simulation,cap_boundary,approval_flood"
1088
+ ).option("--timeout <ms>", "per-scenario timeout in milliseconds", "5000").option("--json", "output results as JSON").option("--only-failures", "only show failed scenarios in output");
1089
+ cmd.action(async (opts) => {
1090
+ try {
1091
+ const config = await loadConfig11(resolve12(opts.config));
1092
+ const vectors = opts.vectors !== void 0 ? opts.vectors.split(",").map((v) => v.trim()) : void 0;
1093
+ const stressConfig = {
1094
+ agentCount: parseInt(opts.agents, 10),
1095
+ transactionsPerScenario: parseInt(opts.transactions, 10),
1096
+ timingJitterMs: parseInt(opts.jitter, 10),
1097
+ timeoutMs: parseInt(opts.timeout, 10),
1098
+ ...vectors !== void 0 ? { vectors } : {}
1099
+ };
1100
+ if (opts.json !== true) {
1101
+ console.log("\n=== Policy Stress Test ===\n");
1102
+ console.log(`Config: ${opts.config}`);
1103
+ console.log(`Agents: ${stressConfig.agentCount}`);
1104
+ console.log(`Tx/scenario: ${stressConfig.transactionsPerScenario}`);
1105
+ console.log(`Timing jitter: ${stressConfig.timingJitterMs}ms`);
1106
+ console.log(`Vectors: ${(vectors ?? DEFAULT_VECTORS).join(", ")}`);
1107
+ console.log("\nRunning adversarial scenarios...");
1108
+ }
1109
+ const report = await stressTest(config.policy, stressConfig);
1110
+ if (opts.json === true) {
1111
+ console.log(JSON.stringify(report, bigintReplacer4, 2));
1112
+ process.exit(report.failed > 0 ? 1 : 0);
1113
+ }
1114
+ printRiskReport(report, opts.onlyFailures === true);
1115
+ process.exit(report.failed > 0 ? 1 : 0);
1116
+ } catch (err) {
1117
+ console.error(err.message);
1118
+ process.exit(1);
1119
+ }
1120
+ });
1121
+ return cmd;
1122
+ }
1123
+ function printRiskReport(report, onlyFailures) {
1124
+ console.log("\n=== Risk Report ===\n");
1125
+ const pct = (report.survivalRate * 100).toFixed(1);
1126
+ const statusIcon = report.failed === 0 ? "\u2713" : "\u2717";
1127
+ console.log(
1128
+ `${statusIcon} Survival rate: ${pct}% (${report.survived}/${report.totalScenarios} scenarios)`
1129
+ );
1130
+ console.log(` Duration: ${report.durationMs}ms`);
1131
+ console.log("");
1132
+ console.log("Results by attack vector:");
1133
+ for (const [vector, stats] of Object.entries(report.byVector)) {
1134
+ const failPct = stats.total > 0 ? (stats.failed / stats.total * 100).toFixed(0) : "0";
1135
+ const icon = stats.failed === 0 ? "\u2713" : "\u2717";
1136
+ console.log(
1137
+ ` ${icon} ${vector.padEnd(20)} ${stats.failed}/${stats.total} failed (${failPct}%)`
1138
+ );
1139
+ }
1140
+ console.log("");
1141
+ if (report.failed > 0) {
1142
+ console.log("Failures by severity:");
1143
+ const severityOrder = ["critical", "high", "medium", "low"];
1144
+ for (const severity of severityOrder) {
1145
+ const count = report.bySeverity[severity] ?? 0;
1146
+ if (count > 0) {
1147
+ const icon = severity === "critical" || severity === "high" ? "\u26A0" : "!";
1148
+ console.log(` ${icon} ${severity.padEnd(10)} ${count} scenario(s)`);
1149
+ }
1150
+ }
1151
+ console.log("");
1152
+ }
1153
+ if (report.failed > 0) {
1154
+ console.log(`Failed scenarios (${report.failed}):`);
1155
+ for (const scenario of report.failedScenarios) {
1156
+ console.log(
1157
+ `
1158
+ [${scenario.severity.toUpperCase()}] ${scenario.vector} \u2014 ${scenario.description}`
1159
+ );
1160
+ console.log(` Outcome: ${scenario.outcome}`);
1161
+ console.log(` Details: ${scenario.details}`);
1162
+ console.log(` Time: ${scenario.durationMs}ms`);
1163
+ }
1164
+ console.log("");
1165
+ } else if (!onlyFailures) {
1166
+ console.log("All scenarios passed \u2014 no failures detected.");
1167
+ console.log("");
1168
+ }
1169
+ console.log("Recommendation:");
1170
+ const lines = report.recommendation.split("\n\n");
1171
+ for (const line of lines) {
1172
+ console.log(` ${line}`);
1173
+ if (lines.length > 1) console.log("");
1174
+ }
1175
+ }
1176
+
1177
+ // src/commands/provenance.ts
1178
+ import { Command as Command14 } from "commander";
1179
+ import { resolve as resolve13 } from "path";
1180
+ import { createFileProvenanceChain } from "@txfence/provenance";
1181
+ function makeProvenanceCommand() {
1182
+ const cmd = new Command14("provenance").description(
1183
+ "Provenance chain commands \u2014 verify tamper-evident audit trails"
1184
+ );
1185
+ const verifyCmd = new Command14("verify").description(
1186
+ "Verify a provenance chain file \u2014 checks hash chain integrity and detects tampering"
1187
+ ).requiredOption("--chain <path>", "path to provenance chain JSONL file").option("--json", "output results as JSON");
1188
+ verifyCmd.action(async (opts) => {
1189
+ try {
1190
+ const chain = createFileProvenanceChain(resolve13(opts.chain));
1191
+ const result = await chain.verify();
1192
+ if (opts.json === true) {
1193
+ console.log(JSON.stringify(result, null, 2));
1194
+ process.exit(result.valid ? 0 : 1);
1195
+ }
1196
+ console.log("\n=== Provenance Chain Verification ===\n");
1197
+ console.log(`File: ${opts.chain}`);
1198
+ console.log(`Records: ${result.entryCount}`);
1199
+ console.log(`Valid: ${result.valid ? "YES \u2713" : "NO \u2717"}`);
1200
+ console.log(`Merkle root: ${result.merkleRoot.slice(0, 16)}...`);
1201
+ console.log(`Verified at: ${new Date(result.verifiedAt).toISOString()}`);
1202
+ if (result.entryCount > 0) {
1203
+ console.log(`First hash: ${result.firstHash.slice(0, 16)}...`);
1204
+ console.log(`Last hash: ${result.lastHash.slice(0, 16)}...`);
1205
+ }
1206
+ if (result.violations.length > 0) {
1207
+ console.log(`
1208
+ Violations (${result.violations.length}):`);
1209
+ for (const v of result.violations) {
1210
+ console.log(` [${v.violation.toUpperCase()}] ${v.entryId}`);
1211
+ console.log(` ${v.details}`);
1212
+ }
1213
+ console.log(
1214
+ "\nWARNING: This chain has been tampered with or corrupted."
1215
+ );
1216
+ console.log("Do not rely on this chain for compliance purposes.");
1217
+ } else if (result.entryCount > 0) {
1218
+ console.log("\nAll records verified. Chain integrity confirmed.");
1219
+ } else {
1220
+ console.log("\nEmpty chain.");
1221
+ }
1222
+ process.exit(result.valid ? 0 : 1);
1223
+ } catch (err) {
1224
+ console.error(err.message);
1225
+ process.exit(1);
1226
+ }
1227
+ });
1228
+ const proofCmd = new Command14("proof").description("Generate a Merkle proof for a specific provenance record").requiredOption("--chain <path>", "path to provenance chain JSONL file").requiredOption("--hash <entryHash>", "entry hash to generate proof for").option("--json", "output proof as JSON");
1229
+ proofCmd.action(async (opts) => {
1230
+ try {
1231
+ const chain = createFileProvenanceChain(resolve13(opts.chain));
1232
+ const proof = await chain.generateProof(opts.hash);
1233
+ if (proof === null) {
1234
+ console.error(`Entry hash not found in chain: ${opts.hash}`);
1235
+ process.exit(1);
1236
+ }
1237
+ if (opts.json === true) {
1238
+ console.log(JSON.stringify(proof, null, 2));
1239
+ process.exit(0);
1240
+ }
1241
+ console.log("\n=== Merkle Proof ===\n");
1242
+ console.log(`Entry hash: ${proof.entryHash}`);
1243
+ console.log(`Merkle root: ${proof.root}`);
1244
+ console.log(`Leaf index: ${proof.leafIndex}`);
1245
+ console.log(`Proof depth: ${proof.siblings.length} sibling(s)`);
1246
+ console.log("\nSiblings:");
1247
+ for (let i = 0; i < proof.siblings.length; i++) {
1248
+ const s = proof.siblings[i];
1249
+ console.log(` [${i}] ${s.position.padEnd(5)} ${s.hash.slice(0, 16)}...`);
1250
+ }
1251
+ console.log("\nTo verify this proof:");
1252
+ console.log(` txfence provenance verify --chain ${opts.chain}`);
1253
+ console.log("\nProof JSON (for external verification):");
1254
+ console.log(JSON.stringify(proof));
1255
+ process.exit(0);
1256
+ } catch (err) {
1257
+ console.error(err.message);
1258
+ process.exit(1);
1259
+ }
1260
+ });
1261
+ cmd.addCommand(verifyCmd);
1262
+ cmd.addCommand(proofCmd);
1263
+ return cmd;
1264
+ }
1265
+
1266
+ // src/index.ts
1267
+ var program = new Command15().name("txfence").description("txfence CLI \u2014 policy checking, simulation, and execution for on-chain agents").version("0.0.1");
1268
+ program.addCommand(makeSimulateCommand());
1269
+ program.addCommand(makeCheckPolicyCommand());
1270
+ program.addCommand(makeSubmitCommand());
1271
+ program.addCommand(makeReceiptCommand());
1272
+ program.addCommand(makeInitCommand());
1273
+ program.addCommand(makeDiffCommand());
1274
+ program.addCommand(makeDryRunCommand());
1275
+ program.addCommand(makePolicySnapshotCommand());
1276
+ program.addCommand(makeIntentCommand());
1277
+ program.addCommand(makeReplayCommand());
1278
+ program.addCommand(makeVerifyCommand());
1279
+ program.addCommand(makeStressTestCommand());
1280
+ program.addCommand(makeProvenanceCommand());
1281
+ program.parse(process.argv);