arc402-cli 0.7.0 → 0.7.2
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/backup.d.ts +3 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +106 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +67 -0
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/discover.d.ts.map +1 -1
- package/dist/commands/discover.js +60 -15
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +136 -52
- package/dist/commands/wallet.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +146 -9
- package/dist/commands/watch.js.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon/config.d.ts +15 -1
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +32 -0
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +66 -21
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/notify.d.ts +35 -6
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +176 -48
- package/dist/daemon/notify.js.map +1 -1
- package/dist/endpoint-notify.d.ts +2 -1
- package/dist/endpoint-notify.d.ts.map +1 -1
- package/dist/endpoint-notify.js +12 -3
- package/dist/endpoint-notify.js.map +1 -1
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +2 -0
- package/dist/program.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/backup.ts +117 -0
- package/src/commands/daemon.ts +73 -0
- package/src/commands/discover.ts +74 -21
- package/src/commands/wallet.ts +137 -51
- package/src/commands/watch.ts +207 -10
- package/src/config.ts +39 -1
- package/src/daemon/config.ts +35 -0
- package/src/daemon/index.ts +65 -26
- package/src/daemon/notify.ts +199 -59
- package/src/endpoint-notify.ts +13 -3
- package/src/index.ts +26 -0
- package/src/program.ts +2 -0
package/src/commands/daemon.ts
CHANGED
|
@@ -19,7 +19,9 @@ import {
|
|
|
19
19
|
DAEMON_SOCK,
|
|
20
20
|
DAEMON_TOML,
|
|
21
21
|
TEMPLATE_DAEMON_TOML,
|
|
22
|
+
loadDaemonConfig,
|
|
22
23
|
} from "../daemon/config";
|
|
24
|
+
import { buildNotifier } from "../daemon/notify";
|
|
23
25
|
import {
|
|
24
26
|
buildOpenShellSecretExports,
|
|
25
27
|
buildOpenShellSshConfig,
|
|
@@ -943,6 +945,77 @@ export function registerDaemonCommands(program: Command): void {
|
|
|
943
945
|
console.log(" 5. Start the OpenShell-owned ARC-402 runtime: arc402 daemon start");
|
|
944
946
|
});
|
|
945
947
|
|
|
948
|
+
// ── daemon notifications ──────────────────────────────────────────────────────
|
|
949
|
+
const notifications = daemon
|
|
950
|
+
.command("notifications")
|
|
951
|
+
.description("Show or test configured notification channels");
|
|
952
|
+
|
|
953
|
+
notifications
|
|
954
|
+
.command("show")
|
|
955
|
+
.description("Show all configured notification channels")
|
|
956
|
+
.action(() => {
|
|
957
|
+
if (!fs.existsSync(DAEMON_TOML)) {
|
|
958
|
+
console.error("daemon.toml not found. Run `arc402 daemon init` first.");
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
const cfg = loadDaemonConfig();
|
|
962
|
+
const notif = cfg.notifications;
|
|
963
|
+
const channels: string[] = [];
|
|
964
|
+
|
|
965
|
+
if (notif.telegram_bot_token && notif.telegram_chat_id) {
|
|
966
|
+
channels.push(`telegram chat_id=${notif.telegram_chat_id}`);
|
|
967
|
+
}
|
|
968
|
+
if (notif.discord?.webhook_url) {
|
|
969
|
+
const u = new URL(notif.discord.webhook_url);
|
|
970
|
+
channels.push(`discord ${u.hostname}${u.pathname.slice(0, 30)}...`);
|
|
971
|
+
}
|
|
972
|
+
if (notif.webhook?.url) {
|
|
973
|
+
channels.push(`webhook ${notif.webhook.url}`);
|
|
974
|
+
}
|
|
975
|
+
if (notif.email?.smtp_host && notif.email?.to) {
|
|
976
|
+
channels.push(`email ${notif.email.smtp_host}:${notif.email.smtp_port} → ${notif.email.to}`);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (channels.length === 0) {
|
|
980
|
+
console.log("No notification channels configured.");
|
|
981
|
+
console.log("Edit ~/.arc402/daemon.toml to add Telegram, Discord, webhook, or email.");
|
|
982
|
+
} else {
|
|
983
|
+
console.log(`Configured channels (${channels.length}):`);
|
|
984
|
+
for (const ch of channels) console.log(` ${ch}`);
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
notifications
|
|
989
|
+
.command("test")
|
|
990
|
+
.description("Send a test message to all configured channels")
|
|
991
|
+
.action(async () => {
|
|
992
|
+
if (!fs.existsSync(DAEMON_TOML)) {
|
|
993
|
+
console.error("daemon.toml not found. Run `arc402 daemon init` first.");
|
|
994
|
+
process.exit(1);
|
|
995
|
+
}
|
|
996
|
+
const cfg = loadDaemonConfig();
|
|
997
|
+
const notifier = buildNotifier(cfg);
|
|
998
|
+
if (!notifier.isEnabled()) {
|
|
999
|
+
console.log("No notification channels configured. Nothing to test.");
|
|
1000
|
+
process.exit(0);
|
|
1001
|
+
}
|
|
1002
|
+
console.log("Sending test notification to all channels...");
|
|
1003
|
+
try {
|
|
1004
|
+
await notifier.send("daemon_started", "ARC-402 Test Notification",
|
|
1005
|
+
"This is a test message from arc402 daemon notifications test."
|
|
1006
|
+
);
|
|
1007
|
+
console.log("Test notification sent successfully.");
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
console.error(`Test notification failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1010
|
+
process.exit(1);
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
// Default action: show (arc402 daemon notifications → arc402 daemon notifications show)
|
|
1015
|
+
notifications.action(() => {
|
|
1016
|
+
notifications.help();
|
|
1017
|
+
});
|
|
1018
|
+
|
|
946
1019
|
// ── daemon channel-watch ─────────────────────────────────────────────────────
|
|
947
1020
|
daemon
|
|
948
1021
|
.command("channel-watch")
|
package/src/commands/discover.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { loadConfig } from "../config";
|
|
|
5
5
|
import { getClient } from "../client";
|
|
6
6
|
import { getTrustTier, printTable, truncateAddress } from "../utils/format";
|
|
7
7
|
import { c } from '../ui/colors';
|
|
8
|
+
import { renderTree } from '../ui/tree';
|
|
8
9
|
|
|
9
10
|
// Minimal ABI for the new getAgentsWithCapability function (Spec 18)
|
|
10
11
|
const CAPABILITY_REGISTRY_EXTRA_ABI = [
|
|
@@ -66,6 +67,21 @@ function computeCompositeScores(agents: Omit<ScoredAgent, "compositeScore" | "ra
|
|
|
66
67
|
}));
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
// ─── Endpoint health check ────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
async function pingEndpoint(endpoint: string): Promise<"online" | "offline"> {
|
|
73
|
+
if (!endpoint || !/^https?:\/\//.test(endpoint)) return "offline";
|
|
74
|
+
try {
|
|
75
|
+
const ctrl = new AbortController();
|
|
76
|
+
const tid = setTimeout(() => ctrl.abort(), 3000);
|
|
77
|
+
const resp = await fetch(`${endpoint.replace(/\/$/, "")}/health`, { signal: ctrl.signal });
|
|
78
|
+
clearTimeout(tid);
|
|
79
|
+
return resp.ok ? "online" : "offline";
|
|
80
|
+
} catch {
|
|
81
|
+
return "offline";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
69
85
|
// ─── Command ──────────────────────────────────────────────────────────────────
|
|
70
86
|
|
|
71
87
|
export function registerDiscoverCommand(program: Command): void {
|
|
@@ -84,6 +100,7 @@ export function registerDiscoverCommand(program: Command): void {
|
|
|
84
100
|
.option("--top <n>", "Show top N agents by trust score")
|
|
85
101
|
.option("--sort <field>", "Sort by: trust | price | jobs | stake | composite", "composite")
|
|
86
102
|
.option("--limit <n>", "Max results", "20")
|
|
103
|
+
.option("--online", "Only show agents whose /health endpoint responds")
|
|
87
104
|
.option("--json", "Machine-parseable output")
|
|
88
105
|
.action(async (opts) => {
|
|
89
106
|
const config = loadConfig();
|
|
@@ -264,34 +281,70 @@ export function registerDiscoverCommand(program: Command): void {
|
|
|
264
281
|
// Assign 1-based ranks after sort
|
|
265
282
|
scored = scored.slice(0, limit).map((a, i) => ({ ...a, rank: i + 1 }));
|
|
266
283
|
|
|
267
|
-
// ── Step 6:
|
|
284
|
+
// ── Step 6: Endpoint health checks ────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
type ScoredWithStatus = ScoredAgent & { endpointStatus: "online" | "offline" | "unknown" };
|
|
287
|
+
|
|
288
|
+
let withStatus: ScoredWithStatus[];
|
|
289
|
+
|
|
290
|
+
if (opts.online || /* always ping for tree display */ true) {
|
|
291
|
+
const statuses = await Promise.all(
|
|
292
|
+
scored.map(async (agent) => {
|
|
293
|
+
if (!agent.endpoint) return "unknown" as const;
|
|
294
|
+
return pingEndpoint(agent.endpoint);
|
|
295
|
+
})
|
|
296
|
+
);
|
|
297
|
+
withStatus = scored.map((agent, i) => ({ ...agent, endpointStatus: statuses[i] as "online" | "offline" | "unknown" }));
|
|
298
|
+
} else {
|
|
299
|
+
withStatus = scored.map((agent) => ({ ...agent, endpointStatus: "unknown" as const }));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Apply --online filter
|
|
303
|
+
if (opts.online) {
|
|
304
|
+
withStatus = withStatus.filter((a) => a.endpointStatus === "online");
|
|
305
|
+
if (withStatus.length === 0) {
|
|
306
|
+
console.log(`\n ${c.warning} No agents with responding /health endpoints found.`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ── Step 7: Output ─────────────────────────────────────────────────────
|
|
268
312
|
|
|
269
313
|
if (opts.json) {
|
|
270
314
|
return console.log(JSON.stringify(
|
|
271
|
-
|
|
315
|
+
withStatus,
|
|
272
316
|
(_k, value) => typeof value === "bigint" ? value.toString() : value,
|
|
273
317
|
2
|
|
274
318
|
));
|
|
275
319
|
}
|
|
276
320
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
321
|
+
const onlineCount = withStatus.filter((a) => a.endpointStatus === "online").length;
|
|
322
|
+
console.log('\n ' + c.mark + c.white(' Discover Results') + c.dim(` — ${withStatus.length} agent${withStatus.length !== 1 ? 's' : ''} found, ${onlineCount} online`));
|
|
323
|
+
|
|
324
|
+
// Tree output per agent
|
|
325
|
+
for (const agent of withStatus) {
|
|
326
|
+
const caps = (agent.canonicalCapabilities.length
|
|
327
|
+
? agent.canonicalCapabilities
|
|
328
|
+
: agent.capabilities
|
|
329
|
+
).slice(0, 3).join(", ") || c.dim("none");
|
|
330
|
+
|
|
331
|
+
const statusIcon = agent.endpointStatus === "online"
|
|
332
|
+
? c.green("● online")
|
|
333
|
+
: agent.endpointStatus === "offline"
|
|
334
|
+
? c.red("○ offline")
|
|
335
|
+
: c.dim("? unknown");
|
|
336
|
+
|
|
337
|
+
const tierStr = getTrustTier(agent.trustScore);
|
|
338
|
+
|
|
339
|
+
console.log(`\n ${c.dim(`#${agent.rank}`)} ${c.white(agent.name)} ${c.dim(truncateAddress(agent.wallet))}`);
|
|
340
|
+
renderTree([
|
|
341
|
+
{ label: "service", value: agent.serviceType },
|
|
342
|
+
{ label: "trust", value: `${agent.trustScore} ${tierStr}` },
|
|
343
|
+
{ label: "score", value: agent.compositeScore.toFixed(3) },
|
|
344
|
+
{ label: "caps", value: caps },
|
|
345
|
+
{ label: "endpoint", value: statusIcon, last: true },
|
|
346
|
+
]);
|
|
347
|
+
}
|
|
348
|
+
console.log();
|
|
296
349
|
});
|
|
297
350
|
}
|
package/src/commands/wallet.ts
CHANGED
|
@@ -23,6 +23,27 @@ import { c } from "../ui/colors";
|
|
|
23
23
|
import { formatAddress } from "../ui/format";
|
|
24
24
|
|
|
25
25
|
const POLICY_ENGINE_DEFAULT = "0x44102e70c2A366632d98Fe40d892a2501fC7fFF2";
|
|
26
|
+
const GUARDIAN_KEY_PATH = path.join(os.homedir(), ".arc402", "guardian.key");
|
|
27
|
+
|
|
28
|
+
/** Save guardian private key to a restricted standalone file (never to config.json). */
|
|
29
|
+
function saveGuardianKey(privateKey: string): void {
|
|
30
|
+
fs.mkdirSync(path.dirname(GUARDIAN_KEY_PATH), { recursive: true, mode: 0o700 });
|
|
31
|
+
fs.writeFileSync(GUARDIAN_KEY_PATH, privateKey + "\n", { mode: 0o400 });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Load guardian private key from file, falling back to config for backwards compat. */
|
|
35
|
+
function loadGuardianKey(config: Arc402Config): string | null {
|
|
36
|
+
try {
|
|
37
|
+
return fs.readFileSync(GUARDIAN_KEY_PATH, "utf-8").trim();
|
|
38
|
+
} catch {
|
|
39
|
+
// Migration: if key still in config, migrate it to the file now
|
|
40
|
+
if (config.guardianPrivateKey) {
|
|
41
|
+
saveGuardianKey(config.guardianPrivateKey);
|
|
42
|
+
return config.guardianPrivateKey;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
26
47
|
|
|
27
48
|
function parseAmount(raw: string): bigint {
|
|
28
49
|
const lower = raw.toLowerCase();
|
|
@@ -182,6 +203,9 @@ async function runCompleteOnboardingCeremony(
|
|
|
182
203
|
);
|
|
183
204
|
console.log(" " + c.success + " Machine key authorized");
|
|
184
205
|
}
|
|
206
|
+
// Save progress after machine key step
|
|
207
|
+
config.onboardingProgress = { walletAddress, step: 2, completedSteps: ["machineKey"] };
|
|
208
|
+
saveConfig(config);
|
|
185
209
|
|
|
186
210
|
// ── Step 3: Passkey ───────────────────────────────────────────────────────
|
|
187
211
|
console.log("\n" + c.dim("── Step 3: Passkey (Face ID / WebAuthn) ──────────────────────"));
|
|
@@ -213,6 +237,9 @@ async function runCompleteOnboardingCeremony(
|
|
|
213
237
|
console.log(" " + c.success + " Passkey set (via browser)");
|
|
214
238
|
}
|
|
215
239
|
}
|
|
240
|
+
// Save progress after passkey step
|
|
241
|
+
config.onboardingProgress = { walletAddress, step: 3, completedSteps: ["machineKey", "passkey"] };
|
|
242
|
+
saveConfig(config);
|
|
216
243
|
|
|
217
244
|
// ── Step 4: Policy ────────────────────────────────────────────────────────
|
|
218
245
|
console.log("\n" + c.dim("── Step 4: Policy ─────────────────────────────────────────────"));
|
|
@@ -340,6 +367,9 @@ async function runCompleteOnboardingCeremony(
|
|
|
340
367
|
saveConfig(config);
|
|
341
368
|
|
|
342
369
|
console.log(" " + c.success + " Policy configured");
|
|
370
|
+
// Save progress after policy step
|
|
371
|
+
config.onboardingProgress = { walletAddress, step: 4, completedSteps: ["machineKey", "passkey", "policy"] };
|
|
372
|
+
saveConfig(config);
|
|
343
373
|
|
|
344
374
|
// ── Step 5: Agent Registration ─────────────────────────────────────────────
|
|
345
375
|
console.log("\n" + c.dim("── Step 5: Agent Registration ─────────────────────────────────"));
|
|
@@ -428,6 +458,9 @@ async function runCompleteOnboardingCeremony(
|
|
|
428
458
|
} else {
|
|
429
459
|
console.log(" " + c.warning + " AgentRegistry address not configured — skipping");
|
|
430
460
|
}
|
|
461
|
+
// Save progress after agent step, then clear on ceremony complete
|
|
462
|
+
config.onboardingProgress = { walletAddress, step: 5, completedSteps: ["machineKey", "passkey", "policy", "agent"] };
|
|
463
|
+
saveConfig(config);
|
|
431
464
|
|
|
432
465
|
// ── Step 7: Workroom Init ─────────────────────────────────────────────────
|
|
433
466
|
console.log("\n" + c.dim("── Step 7: Workroom ────────────────────────────────────────────"));
|
|
@@ -523,6 +556,10 @@ async function runCompleteOnboardingCeremony(
|
|
|
523
556
|
? c.white(agentEndpoint) + c.dim(` → localhost:${relayPort}`)
|
|
524
557
|
: c.dim("—");
|
|
525
558
|
|
|
559
|
+
// Clear onboarding progress — ceremony complete
|
|
560
|
+
delete (config as unknown as Record<string, unknown>).onboardingProgress;
|
|
561
|
+
saveConfig(config);
|
|
562
|
+
|
|
526
563
|
console.log("\n " + c.success + c.white(" Onboarding complete"));
|
|
527
564
|
renderTree([
|
|
528
565
|
{ label: "Wallet", value: c.white(walletAddress) },
|
|
@@ -1066,19 +1103,54 @@ export function registerWalletCommands(program: Command): void {
|
|
|
1066
1103
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1067
1104
|
: undefined;
|
|
1068
1105
|
|
|
1106
|
+
// ── Resume check ──────────────────────────────────────────────────────
|
|
1107
|
+
const resumeProgress = config.onboardingProgress;
|
|
1108
|
+
const isResuming = !!(
|
|
1109
|
+
resumeProgress?.walletAddress &&
|
|
1110
|
+
resumeProgress.walletAddress === config.walletContractAddress &&
|
|
1111
|
+
config.ownerAddress
|
|
1112
|
+
);
|
|
1113
|
+
if (isResuming) {
|
|
1114
|
+
const stepNames: Record<number, string> = {
|
|
1115
|
+
2: "machine key", 3: "passkey", 4: "policy setup", 5: "agent registration",
|
|
1116
|
+
};
|
|
1117
|
+
const nextStep = (resumeProgress!.step ?? 1) + 1;
|
|
1118
|
+
console.log(" " + c.dim(`◈ Resuming onboarding from step ${nextStep} (${stepNames[nextStep] ?? "ceremony"})...`));
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// ── Gas estimation ─────────────────────────────────────────────────────
|
|
1122
|
+
if (!isResuming) {
|
|
1123
|
+
let gasMsg = "~0.003 ETH (6 transactions on Base)";
|
|
1124
|
+
try {
|
|
1125
|
+
const feeData = await provider.getFeeData();
|
|
1126
|
+
const gasPrice = feeData.maxFeePerGas ?? feeData.gasPrice ?? BigInt(1_500_000_000);
|
|
1127
|
+
const deployGas = await provider.estimateGas({
|
|
1128
|
+
to: factoryAddress,
|
|
1129
|
+
data: factoryInterface.encodeFunctionData("createWallet", ["0x0000000071727De22E5E9d8BAf0edAc6f37da032"]),
|
|
1130
|
+
}).catch(() => BigInt(280_000));
|
|
1131
|
+
const ceremonyGas = BigInt(700_000); // ~5 ceremony txs × ~140k each
|
|
1132
|
+
const totalGasEth = parseFloat(ethers.formatEther((deployGas + ceremonyGas) * gasPrice));
|
|
1133
|
+
gasMsg = `~${totalGasEth.toFixed(4)} ETH (6 transactions on Base)`;
|
|
1134
|
+
} catch { /* use default */ }
|
|
1135
|
+
console.log(" " + c.dim(`◈ Estimated gas: ${gasMsg}`));
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1069
1138
|
// ── Step 1: Connect ────────────────────────────────────────────────────
|
|
1139
|
+
const connectPrompt = isResuming
|
|
1140
|
+
? "Connect wallet to resume onboarding"
|
|
1141
|
+
: "Approve ARC402Wallet deployment — you will be set as owner";
|
|
1070
1142
|
const { client, session, account } = await connectPhoneWallet(
|
|
1071
1143
|
config.walletConnectProjectId,
|
|
1072
1144
|
chainId,
|
|
1073
1145
|
config,
|
|
1074
|
-
{ telegramOpts, prompt:
|
|
1146
|
+
{ telegramOpts, prompt: connectPrompt, hardware: !!opts.hardware }
|
|
1075
1147
|
);
|
|
1076
1148
|
|
|
1077
1149
|
const networkName = chainId === 8453 ? "Base" : "Base Sepolia";
|
|
1078
1150
|
const shortAddr = `${account.slice(0, 6)}...${account.slice(-5)}`;
|
|
1079
1151
|
console.log("\n" + c.success + c.white(` Connected: ${shortAddr} on ${networkName}`));
|
|
1080
1152
|
|
|
1081
|
-
if (telegramOpts) {
|
|
1153
|
+
if (telegramOpts && !isResuming) {
|
|
1082
1154
|
// Send "connected" message with a deploy confirmation button.
|
|
1083
1155
|
// TODO: wire up full callback_data round-trip when a persistent bot process is available.
|
|
1084
1156
|
await sendTelegramMessage({
|
|
@@ -1090,48 +1162,57 @@ export function registerWalletCommands(program: Command): void {
|
|
|
1090
1162
|
});
|
|
1091
1163
|
}
|
|
1092
1164
|
|
|
1093
|
-
|
|
1094
|
-
// WalletConnect approval already confirmed intent — sending automatically
|
|
1165
|
+
let walletAddress: string;
|
|
1095
1166
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1167
|
+
if (isResuming) {
|
|
1168
|
+
// Resume: skip deploy, use existing wallet
|
|
1169
|
+
walletAddress = config.walletContractAddress!;
|
|
1170
|
+
console.log(" " + c.dim(`◈ Using existing wallet: ${walletAddress}`));
|
|
1171
|
+
} else {
|
|
1172
|
+
// ── Step 2: Confirm & Deploy ─────────────────────────────────────────
|
|
1173
|
+
// WalletConnect approval already confirmed intent — sending automatically
|
|
1102
1174
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1175
|
+
console.log("Deploying...");
|
|
1176
|
+
const txHash = await sendTransactionWithSession(client, session, account, chainId, {
|
|
1177
|
+
to: factoryAddress,
|
|
1178
|
+
data: factoryInterface.encodeFunctionData("createWallet", ["0x0000000071727De22E5E9d8BAf0edAc6f37da032"]),
|
|
1179
|
+
value: "0x0",
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
console.log(`\nTransaction submitted: ${txHash}`);
|
|
1183
|
+
console.log("Waiting for confirmation...");
|
|
1184
|
+
const receipt = await provider.waitForTransaction(txHash);
|
|
1185
|
+
if (!receipt) {
|
|
1186
|
+
console.error("Transaction not confirmed. Check on-chain.");
|
|
1187
|
+
process.exit(1);
|
|
1188
|
+
}
|
|
1189
|
+
let deployedWallet: string | null = null;
|
|
1190
|
+
const factoryContract = new ethers.Contract(factoryAddress, WALLET_FACTORY_ABI, provider);
|
|
1191
|
+
for (const log of receipt.logs) {
|
|
1192
|
+
try {
|
|
1193
|
+
const parsed = factoryContract.interface.parseLog(log);
|
|
1194
|
+
if (parsed?.name === "WalletCreated") {
|
|
1195
|
+
deployedWallet = parsed.args.walletAddress as string;
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
} catch { /* skip unparseable logs */ }
|
|
1199
|
+
}
|
|
1200
|
+
if (!deployedWallet) {
|
|
1201
|
+
console.error("Could not find WalletCreated event in receipt. Check the transaction on-chain.");
|
|
1202
|
+
process.exit(1);
|
|
1203
|
+
}
|
|
1204
|
+
walletAddress = deployedWallet;
|
|
1205
|
+
// ── Step 1 complete: save wallet + owner immediately ─────────────────
|
|
1206
|
+
config.walletContractAddress = walletAddress;
|
|
1207
|
+
config.ownerAddress = account;
|
|
1208
|
+
saveConfig(config);
|
|
1209
|
+
try { fs.chmodSync(getConfigPath(), 0o600); } catch { /* best-effort */ }
|
|
1210
|
+
console.log("\n " + c.success + c.white(" Wallet deployed"));
|
|
1211
|
+
renderTree([
|
|
1212
|
+
{ label: "Wallet", value: walletAddress },
|
|
1213
|
+
{ label: "Owner", value: account, last: true },
|
|
1214
|
+
]);
|
|
1124
1215
|
}
|
|
1125
|
-
// ── Step 1 complete: save wallet + owner immediately ─────────────────
|
|
1126
|
-
config.walletContractAddress = walletAddress;
|
|
1127
|
-
config.ownerAddress = account;
|
|
1128
|
-
saveConfig(config);
|
|
1129
|
-
try { fs.chmodSync(getConfigPath(), 0o600); } catch { /* best-effort */ }
|
|
1130
|
-
console.log("\n " + c.success + c.white(" Wallet deployed"));
|
|
1131
|
-
renderTree([
|
|
1132
|
-
{ label: "Wallet", value: walletAddress },
|
|
1133
|
-
{ label: "Owner", value: account, last: true },
|
|
1134
|
-
]);
|
|
1135
1216
|
|
|
1136
1217
|
// ── Steps 2–6: Complete onboarding ceremony (same WalletConnect session)
|
|
1137
1218
|
const sendTxCeremony = async (
|
|
@@ -1175,8 +1256,10 @@ export function registerWalletCommands(program: Command): void {
|
|
|
1175
1256
|
const guardianWallet = ethers.Wallet.createRandom();
|
|
1176
1257
|
config.walletContractAddress = walletAddress;
|
|
1177
1258
|
config.ownerAddress = address;
|
|
1178
|
-
config.guardianPrivateKey = guardianWallet.privateKey;
|
|
1179
1259
|
config.guardianAddress = guardianWallet.address;
|
|
1260
|
+
// Save key to restricted file — never store in config.json
|
|
1261
|
+
saveGuardianKey(guardianWallet.privateKey);
|
|
1262
|
+
if (config.guardianPrivateKey) delete (config as unknown as Record<string, unknown>).guardianPrivateKey;
|
|
1180
1263
|
saveConfig(config);
|
|
1181
1264
|
|
|
1182
1265
|
// Call setGuardian on the deployed wallet
|
|
@@ -1206,7 +1289,7 @@ export function registerWalletCommands(program: Command): void {
|
|
|
1206
1289
|
{ label: "Wallet", value: walletAddress },
|
|
1207
1290
|
{ label: "Guardian", value: guardianWallet.address, last: true },
|
|
1208
1291
|
]);
|
|
1209
|
-
console.log(`Guardian private key saved to
|
|
1292
|
+
console.log(`Guardian private key saved to ~/.arc402/guardian.key (chmod 400 — keep it safe, used for emergency freeze only)`);
|
|
1210
1293
|
console.log(`Your wallet contract is ready for policy enforcement`);
|
|
1211
1294
|
printOpenShellHint();
|
|
1212
1295
|
}
|
|
@@ -1438,12 +1521,13 @@ export function registerWalletCommands(program: Command): void {
|
|
|
1438
1521
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1439
1522
|
process.exit(1);
|
|
1440
1523
|
}
|
|
1441
|
-
|
|
1442
|
-
|
|
1524
|
+
const guardianKey = loadGuardianKey(config);
|
|
1525
|
+
if (!guardianKey) {
|
|
1526
|
+
console.error(`Guardian key not found. Expected at ~/.arc402/guardian.key (or guardianPrivateKey in config for legacy setups).`);
|
|
1443
1527
|
process.exit(1);
|
|
1444
1528
|
}
|
|
1445
1529
|
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1446
|
-
const guardianSigner = new ethers.Wallet(
|
|
1530
|
+
const guardianSigner = new ethers.Wallet(guardianKey, provider);
|
|
1447
1531
|
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_GUARDIAN_ABI, guardianSigner);
|
|
1448
1532
|
|
|
1449
1533
|
let tx;
|
|
@@ -1587,12 +1671,13 @@ export function registerWalletCommands(program: Command): void {
|
|
|
1587
1671
|
});
|
|
1588
1672
|
|
|
1589
1673
|
await provider.waitForTransaction(txHash);
|
|
1590
|
-
|
|
1674
|
+
saveGuardianKey(guardianWallet.privateKey);
|
|
1675
|
+
if (config.guardianPrivateKey) delete (config as unknown as Record<string, unknown>).guardianPrivateKey;
|
|
1591
1676
|
config.guardianAddress = guardianWallet.address;
|
|
1592
1677
|
saveConfig(config);
|
|
1593
1678
|
console.log("\n" + c.success + c.white(` Guardian set to: ${guardianWallet.address}`));
|
|
1594
1679
|
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1595
|
-
console.log(" " + c.dim("Guardian private key saved to
|
|
1680
|
+
console.log(" " + c.dim("Guardian private key saved to ~/.arc402/guardian.key (chmod 400)."));
|
|
1596
1681
|
console.log(" " + c.warning + " " + c.yellow("The guardian key can freeze your wallet. Store it separately from your hot key."));
|
|
1597
1682
|
});
|
|
1598
1683
|
|
|
@@ -2419,9 +2504,10 @@ export function registerWalletCommands(program: Command): void {
|
|
|
2419
2504
|
}
|
|
2420
2505
|
}
|
|
2421
2506
|
|
|
2422
|
-
// Persist guardian key if generated
|
|
2507
|
+
// Persist guardian key if generated — save to restricted file, not config.json
|
|
2423
2508
|
if (guardianWallet) {
|
|
2424
|
-
|
|
2509
|
+
saveGuardianKey(guardianWallet.privateKey);
|
|
2510
|
+
if (config.guardianPrivateKey) delete (config as unknown as Record<string, unknown>).guardianPrivateKey;
|
|
2425
2511
|
config.guardianAddress = guardianWallet.address;
|
|
2426
2512
|
saveConfig(config);
|
|
2427
2513
|
}
|
|
@@ -2433,7 +2519,7 @@ export function registerWalletCommands(program: Command): void {
|
|
|
2433
2519
|
txHashes.forEach((h, i) => console.log(" " + c.dim(`Tx ${i + 1}:`) + " " + c.white(h)));
|
|
2434
2520
|
}
|
|
2435
2521
|
if (guardianWallet) {
|
|
2436
|
-
console.log(" " + c.success + c.dim(` Guardian key saved to
|
|
2522
|
+
console.log(" " + c.success + c.dim(` Guardian key saved to ~/.arc402/guardian.key — address: ${guardianWallet.address}`));
|
|
2437
2523
|
console.log(" " + c.warning + " " + c.yellow("Store the guardian private key separately from your hot key."));
|
|
2438
2524
|
}
|
|
2439
2525
|
console.log(c.dim("\nVerify with: arc402 wallet status && arc402 wallet policy show"));
|