@grimoirelabs/cli 0.8.0 → 0.10.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/commands/cast.d.ts +8 -1
- package/dist/commands/cast.d.ts.map +1 -1
- package/dist/commands/cast.js +564 -2
- package/dist/commands/cast.js.map +1 -1
- package/dist/commands/cross-chain-helpers.d.ts +36 -0
- package/dist/commands/cross-chain-helpers.d.ts.map +1 -0
- package/dist/commands/cross-chain-helpers.js +200 -0
- package/dist/commands/cross-chain-helpers.js.map +1 -0
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +12 -0
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +20 -0
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/resume.d.ts +9 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +280 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/commands/simulate.d.ts +8 -1
- package/dist/commands/simulate.d.ts.map +1 -1
- package/dist/commands/simulate.js +355 -10
- package/dist/commands/simulate.js.map +1 -1
- package/dist/commands/venue-doctor.d.ts.map +1 -1
- package/dist/commands/venue-doctor.js +1 -0
- package/dist/commands/venue-doctor.js.map +1 -1
- package/dist/commands/venue.d.ts.map +1 -1
- package/dist/commands/venue.js +3 -1
- package/dist/commands/venue.js.map +1 -1
- package/dist/index.js +27 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/commands/cast.d.ts
CHANGED
|
@@ -21,7 +21,14 @@ interface CastOptions {
|
|
|
21
21
|
mnemonic?: string;
|
|
22
22
|
keystore?: string;
|
|
23
23
|
passwordEnv?: string;
|
|
24
|
-
rpcUrl?: string;
|
|
24
|
+
rpcUrl?: string | string[];
|
|
25
|
+
destinationSpell?: string;
|
|
26
|
+
destinationChain?: string;
|
|
27
|
+
handoffTimeoutSec?: string;
|
|
28
|
+
pollIntervalSec?: string;
|
|
29
|
+
watch?: boolean;
|
|
30
|
+
morphoMarketId?: string | string[];
|
|
31
|
+
morphoMarketMap?: string;
|
|
25
32
|
gasMultiplier?: string;
|
|
26
33
|
skipConfirm?: boolean;
|
|
27
34
|
verbose?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cast.d.ts","sourceRoot":"","sources":["../../src/commands/cast.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"cast.d.ts","sourceRoot":"","sources":["../../src/commands/cast.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+DH,UAAU,WAAW;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrD,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC3C,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA+IxF"}
|
package/dist/commands/cast.js
CHANGED
|
@@ -7,7 +7,7 @@ import { homedir } from "node:os";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import * as readline from "node:readline";
|
|
9
9
|
import { Writable } from "node:stream";
|
|
10
|
-
import { compileFile, createProvider, createWalletFromConfig, execute, formatWei, getChainName, getNativeCurrencySymbol, isTestnet, loadPrivateKey, } from "@grimoirelabs/core";
|
|
10
|
+
import { SqliteStateStore, compileFile, createProvider, createRunRecord, createWalletFromConfig, execute, formatWei, getChainName, getNativeCurrencySymbol, isTestnet, loadPrivateKey, orchestrateCrossChain, toCrossChainReceipt, } from "@grimoirelabs/core";
|
|
11
11
|
import { adapters, createHyperliquidAdapter } from "@grimoirelabs/venues";
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
import ora from "ora";
|
|
@@ -15,6 +15,7 @@ import { hydrateParamsFromEnsProfile, resolveEnsProfile } from "../lib/ens-profi
|
|
|
15
15
|
import { resolveAdvisorSkillsDirs } from "./advisor-skill-helpers.js";
|
|
16
16
|
import { resolveAdvisoryHandler } from "./advisory-handlers.js";
|
|
17
17
|
import { createAdvisoryLiveTraceLogger } from "./advisory-live-trace.js";
|
|
18
|
+
import { createLogicalRunId, parseMorphoMarketMappings, parseRpcUrlMappings, requireExplicitRpcMappings, resolveRpcUrlForChain, validateMorphoMappingsForSpells, } from "./cross-chain-helpers.js";
|
|
18
19
|
import { buildRuntimeProvenanceManifest, enforceFreshnessPolicy, resolveDataPolicy, resolveReplayParams, } from "./data-provenance.js";
|
|
19
20
|
import { buildRunReportEnvelope, formatRunReportText } from "./run-report.js";
|
|
20
21
|
import { withStatePersistence } from "./state-helpers.js";
|
|
@@ -90,6 +91,21 @@ export async function castCommand(spellPath, options) {
|
|
|
90
91
|
const chainId = Number.parseInt(options.chain ?? "1", 10);
|
|
91
92
|
const chainName = getChainName(chainId);
|
|
92
93
|
const isTest = isTestnet(chainId);
|
|
94
|
+
if (options.destinationSpell) {
|
|
95
|
+
await executeCrossChainCast({
|
|
96
|
+
sourceSpellPath: spellPath,
|
|
97
|
+
sourceSpell: spell,
|
|
98
|
+
sourceChainId: chainId,
|
|
99
|
+
params,
|
|
100
|
+
options,
|
|
101
|
+
noState,
|
|
102
|
+
mode,
|
|
103
|
+
hasKey,
|
|
104
|
+
dataPolicy,
|
|
105
|
+
replayResolution,
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
93
109
|
console.log();
|
|
94
110
|
console.log(chalk.cyan("🔗 Network:"));
|
|
95
111
|
console.log(` ${chalk.dim("Chain:")} ${chainName} (${chainId})`);
|
|
@@ -153,7 +169,7 @@ async function executeWithWallet(spell, params, options, noState, chainId, isTes
|
|
|
153
169
|
catch {
|
|
154
170
|
// Keep default adapters if key extraction fails.
|
|
155
171
|
}
|
|
156
|
-
const rpcUrl = options.rpcUrl
|
|
172
|
+
const rpcUrl = resolveRpcUrlFromOption(chainId, options.rpcUrl);
|
|
157
173
|
if (!rpcUrl) {
|
|
158
174
|
spinner.warn(chalk.yellow("No RPC URL provided, using default public RPC"));
|
|
159
175
|
}
|
|
@@ -365,6 +381,394 @@ async function executeSimulation(spell, params, options, noState, chainId, runti
|
|
|
365
381
|
process.exit(1);
|
|
366
382
|
}
|
|
367
383
|
}
|
|
384
|
+
async function executeCrossChainCast(input) {
|
|
385
|
+
const destinationSpellPath = input.options.destinationSpell;
|
|
386
|
+
if (!destinationSpellPath) {
|
|
387
|
+
throw new Error("Cross-chain mode requires --destination-spell");
|
|
388
|
+
}
|
|
389
|
+
const destinationChainId = parseRequiredNumber(input.options.destinationChain, "--destination-chain");
|
|
390
|
+
const handoffTimeoutSec = parseRequiredNumber(input.options.handoffTimeoutSec, "--handoff-timeout-sec");
|
|
391
|
+
const pollIntervalSec = input.options.pollIntervalSec
|
|
392
|
+
? parseRequiredNumber(input.options.pollIntervalSec, "--poll-interval-sec")
|
|
393
|
+
: 30;
|
|
394
|
+
const destinationCompile = await compileFile(destinationSpellPath);
|
|
395
|
+
if (!destinationCompile.success || !destinationCompile.ir) {
|
|
396
|
+
throw new Error(`Destination spell compilation failed: ${destinationCompile.errors.map((e) => `[${e.code}] ${e.message}`).join("; ")}`);
|
|
397
|
+
}
|
|
398
|
+
const destinationSpell = destinationCompile.ir;
|
|
399
|
+
const rpcMappings = parseRpcUrlMappings(input.options.rpcUrl);
|
|
400
|
+
requireExplicitRpcMappings(rpcMappings, input.sourceChainId, destinationChainId);
|
|
401
|
+
const sourceRpcUrl = resolveRpcUrlForChain(input.sourceChainId, rpcMappings);
|
|
402
|
+
const destinationRpcUrl = resolveRpcUrlForChain(destinationChainId, rpcMappings);
|
|
403
|
+
if (!sourceRpcUrl || !destinationRpcUrl) {
|
|
404
|
+
throw new Error("Could not resolve RPC URLs for both source and destination chains.");
|
|
405
|
+
}
|
|
406
|
+
const morphoMarketIds = parseMorphoMarketMappings({
|
|
407
|
+
morphoMarketId: input.options.morphoMarketId,
|
|
408
|
+
morphoMarketMap: input.options.morphoMarketMap,
|
|
409
|
+
});
|
|
410
|
+
validateMorphoMappingsForSpells(input.sourceSpell, destinationSpell, morphoMarketIds);
|
|
411
|
+
const sourceProvider = createProvider(input.sourceChainId, sourceRpcUrl);
|
|
412
|
+
const destinationProvider = createProvider(destinationChainId, destinationRpcUrl);
|
|
413
|
+
const spinner = ora("Preparing cross-chain orchestration...").start();
|
|
414
|
+
let keyConfig;
|
|
415
|
+
let configuredAdapters = adapters;
|
|
416
|
+
let sourceWallet;
|
|
417
|
+
let destinationWallet;
|
|
418
|
+
if (input.mode === "execute") {
|
|
419
|
+
keyConfig = await resolveKeyConfig(input.options, spinner);
|
|
420
|
+
try {
|
|
421
|
+
const rawKey = loadPrivateKey(keyConfig);
|
|
422
|
+
configuredAdapters = adapters.map((adapter) => {
|
|
423
|
+
if (adapter.meta.name === "hyperliquid") {
|
|
424
|
+
return createHyperliquidAdapter({
|
|
425
|
+
privateKey: rawKey,
|
|
426
|
+
assetMap: { ETH: 4 },
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return adapter;
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
configuredAdapters = adapters;
|
|
434
|
+
}
|
|
435
|
+
sourceWallet = createWalletFromConfig(keyConfig, input.sourceChainId, sourceProvider.rpcUrl);
|
|
436
|
+
destinationWallet = createWalletFromConfig(keyConfig, destinationChainId, destinationProvider.rpcUrl);
|
|
437
|
+
}
|
|
438
|
+
else if (input.hasKey) {
|
|
439
|
+
keyConfig = await resolveKeyConfig(input.options, spinner);
|
|
440
|
+
try {
|
|
441
|
+
const rawKey = loadPrivateKey(keyConfig);
|
|
442
|
+
configuredAdapters = adapters.map((adapter) => {
|
|
443
|
+
if (adapter.meta.name === "hyperliquid") {
|
|
444
|
+
return createHyperliquidAdapter({
|
|
445
|
+
privateKey: rawKey,
|
|
446
|
+
assetMap: { ETH: 4 },
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return adapter;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
configuredAdapters = adapters;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const vault = resolveVaultAddress(input.options.vault, sourceWallet?.address);
|
|
457
|
+
const runId = createLogicalRunId();
|
|
458
|
+
const runtimeFlow = input.mode === "execute"
|
|
459
|
+
? "cast_execute"
|
|
460
|
+
: input.mode === "dry-run"
|
|
461
|
+
? "cast_dry_run"
|
|
462
|
+
: "simulate";
|
|
463
|
+
const advisorSkillsDirs = resolveAdvisorSkillsDirs(input.options.advisorSkillsDir) ?? [];
|
|
464
|
+
const sourceOnAdvisory = await resolveAdvisoryHandler(input.sourceSpell.id, {
|
|
465
|
+
advisoryPi: input.options.advisoryPi,
|
|
466
|
+
advisoryReplay: input.options.advisoryReplay,
|
|
467
|
+
advisoryProvider: input.options.advisoryProvider,
|
|
468
|
+
advisoryModel: input.options.advisoryModel,
|
|
469
|
+
advisoryThinking: input.options.advisoryThinking,
|
|
470
|
+
advisoryTools: input.options.advisoryTools,
|
|
471
|
+
advisoryTraceVerbose: input.options.advisoryTraceVerbose,
|
|
472
|
+
advisoryTraceLogger: input.options.json ? undefined : console.log,
|
|
473
|
+
advisorSkillsDirs,
|
|
474
|
+
stateDir: input.options.stateDir,
|
|
475
|
+
noState: input.noState,
|
|
476
|
+
agentDir: input.options.piAgentDir,
|
|
477
|
+
cwd: process.cwd(),
|
|
478
|
+
});
|
|
479
|
+
const destinationOnAdvisory = await resolveAdvisoryHandler(destinationSpell.id, {
|
|
480
|
+
advisoryPi: input.options.advisoryPi,
|
|
481
|
+
advisoryReplay: input.options.advisoryReplay,
|
|
482
|
+
advisoryProvider: input.options.advisoryProvider,
|
|
483
|
+
advisoryModel: input.options.advisoryModel,
|
|
484
|
+
advisoryThinking: input.options.advisoryThinking,
|
|
485
|
+
advisoryTools: input.options.advisoryTools,
|
|
486
|
+
advisoryTraceVerbose: input.options.advisoryTraceVerbose,
|
|
487
|
+
advisoryTraceLogger: input.options.json ? undefined : console.log,
|
|
488
|
+
advisorSkillsDirs,
|
|
489
|
+
stateDir: input.options.stateDir,
|
|
490
|
+
noState: input.noState,
|
|
491
|
+
agentDir: input.options.piAgentDir,
|
|
492
|
+
cwd: process.cwd(),
|
|
493
|
+
});
|
|
494
|
+
const advisoryEventCallback = input.options.json
|
|
495
|
+
? undefined
|
|
496
|
+
: createAdvisoryLiveTraceLogger(console.log, {
|
|
497
|
+
verbose: input.options.advisoryTraceVerbose,
|
|
498
|
+
});
|
|
499
|
+
const dbPath = input.options.stateDir ? join(input.options.stateDir, "grimoire.db") : undefined;
|
|
500
|
+
let store;
|
|
501
|
+
if (!input.noState) {
|
|
502
|
+
try {
|
|
503
|
+
store = new SqliteStateStore({ dbPath });
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
if (isMissingNodeSqliteBackend(error)) {
|
|
507
|
+
console.log(chalk.yellow("State persistence unavailable in Node (missing better-sqlite3). Continuing without persisted state."));
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
throw error;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
let sourceState = store ? ((await store.load(input.sourceSpell.id)) ?? {}) : {};
|
|
515
|
+
let destinationState = store ? ((await store.load(destinationSpell.id)) ?? {}) : {};
|
|
516
|
+
const lifecycleEvents = [];
|
|
517
|
+
const manifest = {
|
|
518
|
+
schema_version: "grimoire.cross_chain.phase1.v1",
|
|
519
|
+
run_id: runId,
|
|
520
|
+
source_spell_path: input.sourceSpellPath,
|
|
521
|
+
destination_spell_path: destinationSpellPath,
|
|
522
|
+
source_spell_id: input.sourceSpell.id,
|
|
523
|
+
destination_spell_id: destinationSpell.id,
|
|
524
|
+
source_chain_id: input.sourceChainId,
|
|
525
|
+
destination_chain_id: destinationChainId,
|
|
526
|
+
mode: input.mode,
|
|
527
|
+
watch: input.options.watch === true,
|
|
528
|
+
handoff_timeout_sec: handoffTimeoutSec,
|
|
529
|
+
poll_interval_sec: pollIntervalSec,
|
|
530
|
+
rpc_by_chain: {
|
|
531
|
+
[input.sourceChainId]: sourceRpcUrl,
|
|
532
|
+
[destinationChainId]: destinationRpcUrl,
|
|
533
|
+
},
|
|
534
|
+
params: input.params,
|
|
535
|
+
vault,
|
|
536
|
+
morpho_market_ids: morphoMarketIds,
|
|
537
|
+
};
|
|
538
|
+
const gasMultiplier = input.options.gasMultiplier
|
|
539
|
+
? Number.parseFloat(input.options.gasMultiplier)
|
|
540
|
+
: 1.1;
|
|
541
|
+
const confirmCallback = input.options.skipConfirm || isTestnet(input.sourceChainId)
|
|
542
|
+
? async () => true
|
|
543
|
+
: async (message) => {
|
|
544
|
+
console.log(message);
|
|
545
|
+
return await confirmPrompt(chalk.yellow("Proceed? (yes/no): "));
|
|
546
|
+
};
|
|
547
|
+
spinner.text = "Running source/destination orchestration...";
|
|
548
|
+
const orchestration = await orchestrateCrossChain({
|
|
549
|
+
runId,
|
|
550
|
+
sourceSpellId: input.sourceSpell.id,
|
|
551
|
+
destinationSpellId: destinationSpell.id,
|
|
552
|
+
sourceChainId: input.sourceChainId,
|
|
553
|
+
destinationChainId,
|
|
554
|
+
vault: vault,
|
|
555
|
+
sourceParams: input.params,
|
|
556
|
+
destinationParams: input.params,
|
|
557
|
+
mode: input.mode,
|
|
558
|
+
watch: input.options.watch === true,
|
|
559
|
+
handoffTimeoutSec,
|
|
560
|
+
pollIntervalSec,
|
|
561
|
+
executeSource: async () => {
|
|
562
|
+
const result = await execute({
|
|
563
|
+
spell: input.sourceSpell,
|
|
564
|
+
runId,
|
|
565
|
+
vault: vault,
|
|
566
|
+
chain: input.sourceChainId,
|
|
567
|
+
params: input.params,
|
|
568
|
+
persistentState: sourceState,
|
|
569
|
+
simulate: input.mode === "simulate",
|
|
570
|
+
executionMode: input.mode === "simulate" ? undefined : input.mode,
|
|
571
|
+
wallet: input.mode === "execute" ? sourceWallet : undefined,
|
|
572
|
+
provider: sourceProvider,
|
|
573
|
+
gasMultiplier,
|
|
574
|
+
confirmCallback,
|
|
575
|
+
skipTestnetConfirmation: input.options.skipConfirm ?? false,
|
|
576
|
+
adapters: configuredAdapters,
|
|
577
|
+
advisorSkillsDirs: advisorSkillsDirs.length > 0 ? advisorSkillsDirs : undefined,
|
|
578
|
+
onAdvisory: sourceOnAdvisory,
|
|
579
|
+
eventCallback: advisoryEventCallback,
|
|
580
|
+
warningCallback: (message) => console.log(chalk.yellow(`Warning: ${message}`)),
|
|
581
|
+
crossChain: {
|
|
582
|
+
enabled: true,
|
|
583
|
+
runId,
|
|
584
|
+
trackId: "source",
|
|
585
|
+
role: "source",
|
|
586
|
+
morphoMarketIds,
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
sourceState = result.finalState;
|
|
590
|
+
return result;
|
|
591
|
+
},
|
|
592
|
+
executeDestination: async (params) => {
|
|
593
|
+
const result = await execute({
|
|
594
|
+
spell: destinationSpell,
|
|
595
|
+
runId,
|
|
596
|
+
vault: vault,
|
|
597
|
+
chain: destinationChainId,
|
|
598
|
+
params,
|
|
599
|
+
persistentState: destinationState,
|
|
600
|
+
simulate: input.mode === "simulate",
|
|
601
|
+
executionMode: input.mode === "simulate" ? undefined : input.mode,
|
|
602
|
+
wallet: input.mode === "execute" ? destinationWallet : undefined,
|
|
603
|
+
provider: destinationProvider,
|
|
604
|
+
gasMultiplier,
|
|
605
|
+
confirmCallback,
|
|
606
|
+
skipTestnetConfirmation: input.options.skipConfirm ?? false,
|
|
607
|
+
adapters: configuredAdapters,
|
|
608
|
+
advisorSkillsDirs: advisorSkillsDirs.length > 0 ? advisorSkillsDirs : undefined,
|
|
609
|
+
onAdvisory: destinationOnAdvisory,
|
|
610
|
+
eventCallback: advisoryEventCallback,
|
|
611
|
+
warningCallback: (message) => console.log(chalk.yellow(`Warning: ${message}`)),
|
|
612
|
+
crossChain: {
|
|
613
|
+
enabled: true,
|
|
614
|
+
runId,
|
|
615
|
+
trackId: "destination",
|
|
616
|
+
role: "destination",
|
|
617
|
+
morphoMarketIds,
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
destinationState = result.finalState;
|
|
621
|
+
return result;
|
|
622
|
+
},
|
|
623
|
+
resolveHandoffStatus: async (handoff) => {
|
|
624
|
+
const across = configuredAdapters.find((adapter) => adapter.meta.name === "across");
|
|
625
|
+
if (!across) {
|
|
626
|
+
return { status: "pending" };
|
|
627
|
+
}
|
|
628
|
+
const resolver = across.bridgeLifecycle?.resolveHandoffStatus ?? across.resolveHandoffStatus;
|
|
629
|
+
if (!resolver) {
|
|
630
|
+
return { status: "pending" };
|
|
631
|
+
}
|
|
632
|
+
return resolver({
|
|
633
|
+
handoffId: handoff.handoffId,
|
|
634
|
+
originChainId: handoff.originChainId,
|
|
635
|
+
destinationChainId: handoff.destinationChainId,
|
|
636
|
+
originTxHash: handoff.originTxHash,
|
|
637
|
+
reference: handoff.reference,
|
|
638
|
+
asset: handoff.asset,
|
|
639
|
+
submittedAmount: handoff.submittedAmount,
|
|
640
|
+
walletAddress: vault,
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
onLifecycleEvent: (event) => {
|
|
644
|
+
lifecycleEvents.push(event);
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
const crossChainReceipt = toCrossChainReceipt({
|
|
648
|
+
runId,
|
|
649
|
+
sourceSpellId: input.sourceSpell.id,
|
|
650
|
+
destinationSpellId: destinationSpell.id,
|
|
651
|
+
sourceChainId: input.sourceChainId,
|
|
652
|
+
destinationChainId,
|
|
653
|
+
tracks: orchestration.tracks,
|
|
654
|
+
handoffs: orchestration.handoffs,
|
|
655
|
+
});
|
|
656
|
+
const sourceResult = orchestration.sourceResult;
|
|
657
|
+
const destinationResult = orchestration.destinationResult;
|
|
658
|
+
if (sourceResult) {
|
|
659
|
+
sourceResult.crossChain = crossChainReceipt;
|
|
660
|
+
}
|
|
661
|
+
if (destinationResult) {
|
|
662
|
+
destinationResult.crossChain = crossChainReceipt;
|
|
663
|
+
}
|
|
664
|
+
if (orchestration.pending && !store) {
|
|
665
|
+
throw new Error("Cannot leave a run in waiting state without persistence. Disable --no-state.");
|
|
666
|
+
}
|
|
667
|
+
if (store) {
|
|
668
|
+
if (sourceResult) {
|
|
669
|
+
await store.save(input.sourceSpell.id, sourceResult.finalState);
|
|
670
|
+
const sourceProvenance = {
|
|
671
|
+
...buildRuntimeProvenanceManifest({
|
|
672
|
+
runtimeMode: runtimeFlow,
|
|
673
|
+
chainId: input.sourceChainId,
|
|
674
|
+
policy: input.dataPolicy,
|
|
675
|
+
replay: input.replayResolution,
|
|
676
|
+
params: input.params,
|
|
677
|
+
blockNumber: await safeGetBlockNumber(sourceProvider),
|
|
678
|
+
rpcUrl: sourceProvider.rpcUrl,
|
|
679
|
+
}),
|
|
680
|
+
cross_chain: manifest,
|
|
681
|
+
};
|
|
682
|
+
await store.addRun(input.sourceSpell.id, createRunRecord(sourceResult, sourceProvenance));
|
|
683
|
+
await store.saveLedger(input.sourceSpell.id, runId, appendLifecycleLedgerEntries(sourceResult.ledgerEvents, lifecycleEvents, runId, input.sourceSpell.id));
|
|
684
|
+
}
|
|
685
|
+
if (destinationResult) {
|
|
686
|
+
await store.save(destinationSpell.id, destinationResult.finalState);
|
|
687
|
+
const destinationProvenance = {
|
|
688
|
+
...buildRuntimeProvenanceManifest({
|
|
689
|
+
runtimeMode: runtimeFlow,
|
|
690
|
+
chainId: destinationChainId,
|
|
691
|
+
policy: input.dataPolicy,
|
|
692
|
+
replay: input.replayResolution,
|
|
693
|
+
params: input.params,
|
|
694
|
+
blockNumber: await safeGetBlockNumber(destinationProvider),
|
|
695
|
+
rpcUrl: destinationProvider.rpcUrl,
|
|
696
|
+
}),
|
|
697
|
+
cross_chain: manifest,
|
|
698
|
+
};
|
|
699
|
+
await store.addRun(destinationSpell.id, createRunRecord(destinationResult, destinationProvenance));
|
|
700
|
+
await store.saveLedger(destinationSpell.id, runId, destinationResult.ledgerEvents);
|
|
701
|
+
}
|
|
702
|
+
await persistCrossChainState(store, {
|
|
703
|
+
runId,
|
|
704
|
+
tracks: orchestration.tracks,
|
|
705
|
+
handoffs: orchestration.handoffs,
|
|
706
|
+
sourceSpellId: input.sourceSpell.id,
|
|
707
|
+
destinationSpellId: destinationSpell.id,
|
|
708
|
+
sourceResult,
|
|
709
|
+
destinationResult,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
spinner.stop();
|
|
713
|
+
const mergedResult = destinationResult ?? sourceResult;
|
|
714
|
+
if (!mergedResult) {
|
|
715
|
+
throw new Error("Cross-chain orchestration returned no execution results.");
|
|
716
|
+
}
|
|
717
|
+
if (input.options.json) {
|
|
718
|
+
console.log(stringifyJson({
|
|
719
|
+
success: orchestration.success,
|
|
720
|
+
pending: orchestration.pending,
|
|
721
|
+
runId,
|
|
722
|
+
crossChain: crossChainReceipt,
|
|
723
|
+
source: sourceResult
|
|
724
|
+
? {
|
|
725
|
+
success: sourceResult.success,
|
|
726
|
+
error: sourceResult.structuredError,
|
|
727
|
+
receipt: sourceResult.receipt,
|
|
728
|
+
}
|
|
729
|
+
: undefined,
|
|
730
|
+
destination: destinationResult
|
|
731
|
+
? {
|
|
732
|
+
success: destinationResult.success,
|
|
733
|
+
error: destinationResult.structuredError,
|
|
734
|
+
receipt: destinationResult.receipt,
|
|
735
|
+
}
|
|
736
|
+
: undefined,
|
|
737
|
+
}));
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
console.log();
|
|
741
|
+
console.log(chalk.cyan("🔀 Cross-Chain Run:"));
|
|
742
|
+
console.log(` ${chalk.dim("Run ID:")} ${runId}`);
|
|
743
|
+
console.log(` ${chalk.dim("Source:")} ${input.sourceSpell.meta.name} (${input.sourceChainId}) -> ${chalk.dim("Destination:")} ${destinationSpell.meta.name} (${destinationChainId})`);
|
|
744
|
+
console.log(` ${chalk.dim("Watch:")} ${input.options.watch === true ? "Yes" : "No"}`);
|
|
745
|
+
console.log(` ${chalk.dim("Status:")} ${orchestration.pending ? chalk.yellow("waiting") : orchestration.success ? chalk.green("completed") : chalk.red("failed")}`);
|
|
746
|
+
for (const track of orchestration.tracks) {
|
|
747
|
+
const trackStatus = track.status === "completed"
|
|
748
|
+
? chalk.green(track.status)
|
|
749
|
+
: track.status === "failed"
|
|
750
|
+
? chalk.red(track.status)
|
|
751
|
+
: track.status === "waiting"
|
|
752
|
+
? chalk.yellow(track.status)
|
|
753
|
+
: chalk.dim(track.status);
|
|
754
|
+
console.log(` ${chalk.dim(`track:${track.trackId}`)} ${trackStatus} chain=${track.chainId} spell=${track.spellId}`);
|
|
755
|
+
if (track.error) {
|
|
756
|
+
console.log(` ${chalk.red(track.error)}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
for (const handoff of orchestration.handoffs) {
|
|
760
|
+
console.log(` ${chalk.dim(`handoff:${handoff.handoffId}`)} ${handoff.status} submitted=${handoff.submittedAmount.toString()} settled=${handoff.settledAmount?.toString() ?? "n/a"}`);
|
|
761
|
+
}
|
|
762
|
+
if (orchestration.pending) {
|
|
763
|
+
console.log();
|
|
764
|
+
console.log(chalk.yellow("Run is waiting for handoff settlement. Resume with:"));
|
|
765
|
+
console.log(chalk.yellow(` grimoire resume ${runId} --watch`));
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (!orchestration.success && !orchestration.pending) {
|
|
769
|
+
process.exit(1);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
368
772
|
async function safeGetBlockNumber(provider) {
|
|
369
773
|
try {
|
|
370
774
|
return await provider.getBlockNumber();
|
|
@@ -373,6 +777,164 @@ async function safeGetBlockNumber(provider) {
|
|
|
373
777
|
return undefined;
|
|
374
778
|
}
|
|
375
779
|
}
|
|
780
|
+
async function resolveKeyConfig(options, spinner) {
|
|
781
|
+
const hasExplicitKey = !!(options.privateKey || options.mnemonic || options.keystore);
|
|
782
|
+
const hasEnvKey = !!(options.keyEnv && process.env[options.keyEnv]);
|
|
783
|
+
const hasDefaultKeystore = !hasExplicitKey && !hasEnvKey && existsSync(DEFAULT_KEYSTORE_PATH);
|
|
784
|
+
if (options.privateKey) {
|
|
785
|
+
return { type: "raw", source: options.privateKey };
|
|
786
|
+
}
|
|
787
|
+
if (options.keyEnv && process.env[options.keyEnv]) {
|
|
788
|
+
return { type: "env", source: options.keyEnv };
|
|
789
|
+
}
|
|
790
|
+
if (options.mnemonic) {
|
|
791
|
+
return { type: "mnemonic", source: options.mnemonic };
|
|
792
|
+
}
|
|
793
|
+
if (hasDefaultKeystore || options.keystore) {
|
|
794
|
+
const keystorePath = options.keystore ?? DEFAULT_KEYSTORE_PATH;
|
|
795
|
+
if (!existsSync(keystorePath)) {
|
|
796
|
+
spinner.fail(chalk.red(`No key provided and no keystore found at ${keystorePath}`));
|
|
797
|
+
console.log(chalk.dim(" Run 'grimoire wallet generate' to create one."));
|
|
798
|
+
process.exit(1);
|
|
799
|
+
throw new Error("unreachable");
|
|
800
|
+
}
|
|
801
|
+
const password = await resolveKeystorePassword(options, spinner);
|
|
802
|
+
if (!password) {
|
|
803
|
+
process.exit(1);
|
|
804
|
+
throw new Error("unreachable");
|
|
805
|
+
}
|
|
806
|
+
const keystoreJson = readFileSync(keystorePath, "utf-8");
|
|
807
|
+
return { type: "keystore", source: keystoreJson, password };
|
|
808
|
+
}
|
|
809
|
+
throw new Error("Execution mode requires wallet credentials, but no key source was provided");
|
|
810
|
+
}
|
|
811
|
+
function resolveVaultAddress(explicitVault, fallbackWalletAddress) {
|
|
812
|
+
if (explicitVault && explicitVault.length > 0) {
|
|
813
|
+
return explicitVault;
|
|
814
|
+
}
|
|
815
|
+
if (fallbackWalletAddress && fallbackWalletAddress.length > 0) {
|
|
816
|
+
return fallbackWalletAddress;
|
|
817
|
+
}
|
|
818
|
+
return "0x0000000000000000000000000000000000000000";
|
|
819
|
+
}
|
|
820
|
+
function parseRequiredNumber(value, flag) {
|
|
821
|
+
if (!value) {
|
|
822
|
+
throw new Error(`${flag} is required in cross-chain mode`);
|
|
823
|
+
}
|
|
824
|
+
const parsed = Number.parseInt(value, 10);
|
|
825
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
826
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
827
|
+
}
|
|
828
|
+
return parsed;
|
|
829
|
+
}
|
|
830
|
+
function resolveRpcUrlFromOption(chainId, value) {
|
|
831
|
+
const parsed = parseRpcUrlMappings(value);
|
|
832
|
+
return resolveRpcUrlForChain(chainId, parsed);
|
|
833
|
+
}
|
|
834
|
+
function isMissingNodeSqliteBackend(error) {
|
|
835
|
+
if (!(error instanceof Error))
|
|
836
|
+
return false;
|
|
837
|
+
return error.message.includes("SqliteStateStore requires bun:sqlite (Bun) or better-sqlite3 (Node)");
|
|
838
|
+
}
|
|
839
|
+
function appendLifecycleLedgerEntries(entries, lifecycleEvents, runId, spellId) {
|
|
840
|
+
if (lifecycleEvents.length === 0) {
|
|
841
|
+
return entries;
|
|
842
|
+
}
|
|
843
|
+
const start = entries.length;
|
|
844
|
+
const extras = lifecycleEvents.map((event, index) => ({
|
|
845
|
+
id: `evt_cc_${String(start + index).padStart(3, "0")}`,
|
|
846
|
+
timestamp: Date.now(),
|
|
847
|
+
runId,
|
|
848
|
+
spellId,
|
|
849
|
+
event,
|
|
850
|
+
}));
|
|
851
|
+
return [...entries, ...extras];
|
|
852
|
+
}
|
|
853
|
+
async function persistCrossChainState(store, input) {
|
|
854
|
+
const nowIso = new Date().toISOString();
|
|
855
|
+
for (const track of input.tracks) {
|
|
856
|
+
const row = {
|
|
857
|
+
runId: input.runId,
|
|
858
|
+
trackId: track.trackId,
|
|
859
|
+
role: track.role,
|
|
860
|
+
spellId: track.spellId,
|
|
861
|
+
chainId: track.chainId,
|
|
862
|
+
status: track.status,
|
|
863
|
+
lastStepId: track.lastStepId,
|
|
864
|
+
error: track.error,
|
|
865
|
+
updatedAt: nowIso,
|
|
866
|
+
};
|
|
867
|
+
await store.upsertRunTrack(row);
|
|
868
|
+
}
|
|
869
|
+
for (const handoff of input.handoffs) {
|
|
870
|
+
const row = {
|
|
871
|
+
runId: input.runId,
|
|
872
|
+
handoffId: handoff.handoffId,
|
|
873
|
+
sourceTrackId: handoff.sourceTrackId,
|
|
874
|
+
destinationTrackId: handoff.destinationTrackId,
|
|
875
|
+
sourceStepId: handoff.sourceStepId,
|
|
876
|
+
originChainId: handoff.originChainId,
|
|
877
|
+
destinationChainId: handoff.destinationChainId,
|
|
878
|
+
asset: handoff.asset,
|
|
879
|
+
submittedAmount: handoff.submittedAmount.toString(),
|
|
880
|
+
settledAmount: handoff.settledAmount?.toString(),
|
|
881
|
+
status: handoff.status,
|
|
882
|
+
reference: handoff.reference,
|
|
883
|
+
originTxHash: handoff.originTxHash,
|
|
884
|
+
reason: handoff.reason,
|
|
885
|
+
createdAt: nowIso,
|
|
886
|
+
updatedAt: nowIso,
|
|
887
|
+
expiresAt: handoff.status === "expired" ? nowIso : undefined,
|
|
888
|
+
};
|
|
889
|
+
await store.upsertRunHandoff(row);
|
|
890
|
+
}
|
|
891
|
+
const sourceSteps = collectStepStatuses(input.sourceResult, "source", input.runId);
|
|
892
|
+
for (const step of sourceSteps) {
|
|
893
|
+
await store.upsertRunStepResult(step);
|
|
894
|
+
}
|
|
895
|
+
const destinationSteps = collectStepStatuses(input.destinationResult, "destination", input.runId);
|
|
896
|
+
for (const step of destinationSteps) {
|
|
897
|
+
await store.upsertRunStepResult(step);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function collectStepStatuses(result, trackId, runId) {
|
|
901
|
+
if (!result?.receipt) {
|
|
902
|
+
return [];
|
|
903
|
+
}
|
|
904
|
+
const nowIso = new Date().toISOString();
|
|
905
|
+
const byStep = new Map();
|
|
906
|
+
for (const planned of result.receipt.plannedActions) {
|
|
907
|
+
byStep.set(planned.stepId, {
|
|
908
|
+
runId,
|
|
909
|
+
trackId,
|
|
910
|
+
stepId: planned.stepId,
|
|
911
|
+
status: "pending",
|
|
912
|
+
idempotencyKey: `${runId}:${trackId}:${planned.stepId}`,
|
|
913
|
+
updatedAt: nowIso,
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
for (const tx of result.commit?.transactions ?? []) {
|
|
917
|
+
const existing = byStep.get(tx.stepId);
|
|
918
|
+
if (!existing)
|
|
919
|
+
continue;
|
|
920
|
+
existing.status = tx.success ? "confirmed" : "failed";
|
|
921
|
+
existing.reference = tx.hash;
|
|
922
|
+
existing.error = tx.error;
|
|
923
|
+
}
|
|
924
|
+
if (result.success && !result.commit) {
|
|
925
|
+
for (const step of byStep.values()) {
|
|
926
|
+
step.status = "confirmed";
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (!result.success) {
|
|
930
|
+
for (const step of byStep.values()) {
|
|
931
|
+
if (step.status !== "confirmed") {
|
|
932
|
+
step.status = "failed";
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return [...byStep.values()];
|
|
937
|
+
}
|
|
376
938
|
async function resolveKeystorePassword(options, spinner) {
|
|
377
939
|
const envName = options.passwordEnv ?? "KEYSTORE_PASSWORD";
|
|
378
940
|
const envValue = process.env[envName];
|