arc402-cli 0.5.0 → 0.7.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.
Files changed (52) hide show
  1. package/dist/commands/config.d.ts.map +1 -1
  2. package/dist/commands/config.js +17 -1
  3. package/dist/commands/config.js.map +1 -1
  4. package/dist/commands/doctor.d.ts +3 -0
  5. package/dist/commands/doctor.d.ts.map +1 -0
  6. package/dist/commands/doctor.js +205 -0
  7. package/dist/commands/doctor.js.map +1 -0
  8. package/dist/commands/wallet.d.ts.map +1 -1
  9. package/dist/commands/wallet.js +467 -23
  10. package/dist/commands/wallet.js.map +1 -1
  11. package/dist/config.d.ts +1 -0
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +11 -2
  14. package/dist/config.js.map +1 -1
  15. package/dist/daemon/index.d.ts.map +1 -1
  16. package/dist/daemon/index.js +294 -208
  17. package/dist/daemon/index.js.map +1 -1
  18. package/dist/endpoint-notify.d.ts +7 -0
  19. package/dist/endpoint-notify.d.ts.map +1 -1
  20. package/dist/endpoint-notify.js +104 -0
  21. package/dist/endpoint-notify.js.map +1 -1
  22. package/dist/index.js +15 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/program.d.ts.map +1 -1
  25. package/dist/program.js +2 -0
  26. package/dist/program.js.map +1 -1
  27. package/dist/repl.d.ts.map +1 -1
  28. package/dist/repl.js +565 -162
  29. package/dist/repl.js.map +1 -1
  30. package/dist/ui/banner.d.ts +2 -0
  31. package/dist/ui/banner.d.ts.map +1 -1
  32. package/dist/ui/banner.js +27 -18
  33. package/dist/ui/banner.js.map +1 -1
  34. package/dist/ui/format.d.ts.map +1 -1
  35. package/dist/ui/format.js +2 -0
  36. package/dist/ui/format.js.map +1 -1
  37. package/dist/ui/spinner.d.ts.map +1 -1
  38. package/dist/ui/spinner.js +11 -0
  39. package/dist/ui/spinner.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/commands/config.ts +18 -2
  42. package/src/commands/doctor.ts +172 -0
  43. package/src/commands/wallet.ts +512 -35
  44. package/src/config.ts +10 -1
  45. package/src/daemon/index.ts +234 -140
  46. package/src/endpoint-notify.ts +73 -0
  47. package/src/index.ts +15 -1
  48. package/src/program.ts +2 -0
  49. package/src/repl.ts +673 -197
  50. package/src/ui/banner.ts +26 -19
  51. package/src/ui/format.ts +1 -0
  52. package/src/ui/spinner.ts +10 -0
@@ -114,6 +114,388 @@ async function runWalletOnboardingCeremony(walletAddress, ownerAddress, config,
114
114
  console.log(" " + colors_1.c.dim("arc402 wallet set-velocity-limit <eth> — wallet-level hourly ETH cap"));
115
115
  console.log(" " + colors_1.c.dim("arc402 wallet policy set-daily-limit --category general --amount <eth> — daily per-category cap"));
116
116
  }
117
+ /**
118
+ * Complete ARC-402 onboarding ceremony — matches web flow at app.arc402.xyz/onboard.
119
+ * Runs in a single WalletConnect session (or any sendTx provider). Idempotent.
120
+ *
121
+ * Steps:
122
+ * 2. Machine key — generate + authorizeMachineKey
123
+ * 3. Passkey — CLI shows browser URL (WebAuthn requires browser)
124
+ * 4. Policy — v5 protocol bypass; set hire limit + optional guardian
125
+ * 5. Agent — register on AgentRegistry via executeContractCall
126
+ * 6. Summary — branded tree
127
+ */
128
+ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config, provider, sendTx) {
129
+ const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
130
+ const agentRegistryAddress = config.agentRegistryV2Address ??
131
+ config_1.NETWORK_DEFAULTS[config.network]?.agentRegistryV2Address;
132
+ const handshakeAddress = config.handshakeAddress ??
133
+ config_1.NETWORK_DEFAULTS[config.network]?.handshakeAddress ??
134
+ "0x4F5A38Bb746d7E5d49d8fd26CA6beD141Ec2DDb3";
135
+ // ── Step 2: Machine Key ────────────────────────────────────────────────────
136
+ console.log("\n" + colors_1.c.dim("── Step 2: Machine Key ────────────────────────────────────────"));
137
+ let machineKeyAddress;
138
+ if (config.privateKey) {
139
+ machineKeyAddress = new ethers_1.ethers.Wallet(config.privateKey).address;
140
+ }
141
+ else {
142
+ const mk = ethers_1.ethers.Wallet.createRandom();
143
+ machineKeyAddress = mk.address;
144
+ config.privateKey = mk.privateKey;
145
+ (0, config_1.saveConfig)(config);
146
+ console.log(" " + colors_1.c.dim("Machine key generated: ") + colors_1.c.white(machineKeyAddress));
147
+ console.log(" " + colors_1.c.dim("Private key saved to ~/.arc402/config.json (chmod 600)"));
148
+ }
149
+ let mkAlreadyAuthorized = false;
150
+ try {
151
+ const mkContract = new ethers_1.ethers.Contract(walletAddress, abis_1.ARC402_WALLET_MACHINE_KEY_ABI, provider);
152
+ mkAlreadyAuthorized = await mkContract.authorizedMachineKeys(machineKeyAddress);
153
+ }
154
+ catch { /* older wallet — will try to authorize */ }
155
+ if (mkAlreadyAuthorized) {
156
+ console.log(" " + colors_1.c.success + colors_1.c.dim(" Machine key already authorized: ") + colors_1.c.white(machineKeyAddress));
157
+ }
158
+ else {
159
+ console.log(" " + colors_1.c.dim("Authorizing machine key: ") + colors_1.c.white(machineKeyAddress));
160
+ const mkIface = new ethers_1.ethers.Interface(abis_1.ARC402_WALLET_MACHINE_KEY_ABI);
161
+ await sendTx({ to: walletAddress, data: mkIface.encodeFunctionData("authorizeMachineKey", [machineKeyAddress]), value: "0x0" }, "authorizeMachineKey");
162
+ console.log(" " + colors_1.c.success + " Machine key authorized");
163
+ }
164
+ // ── Step 3: Passkey ───────────────────────────────────────────────────────
165
+ console.log("\n" + colors_1.c.dim("── Step 3: Passkey (Face ID / WebAuthn) ──────────────────────"));
166
+ let passkeyActive = false;
167
+ try {
168
+ const walletC = new ethers_1.ethers.Contract(walletAddress, abis_1.ARC402_WALLET_PASSKEY_ABI, provider);
169
+ const auth = await walletC.ownerAuth();
170
+ passkeyActive = Number(auth[0]) === 1;
171
+ }
172
+ catch { /* ignore */ }
173
+ if (passkeyActive) {
174
+ console.log(" " + colors_1.c.success + colors_1.c.dim(" Passkey already active"));
175
+ }
176
+ else {
177
+ const passkeyUrl = `https://app.arc402.xyz/passkey-setup?wallet=${walletAddress}`;
178
+ console.log("\n " + colors_1.c.white("Open this URL in your browser to set up Face ID:"));
179
+ console.log(" " + colors_1.c.cyan(passkeyUrl));
180
+ console.log(" " + colors_1.c.dim("Complete Face ID registration, then press Enter. Type 'n' + Enter to skip.\n"));
181
+ const passkeyAns = await (0, prompts_1.default)({
182
+ type: "text",
183
+ name: "done",
184
+ message: "Press Enter when done (or type 'n' to skip)",
185
+ initial: "",
186
+ });
187
+ if (passkeyAns.done?.toLowerCase() === "n") {
188
+ console.log(" " + colors_1.c.warning + " Passkey skipped");
189
+ }
190
+ else {
191
+ passkeyActive = true;
192
+ console.log(" " + colors_1.c.success + " Passkey set (via browser)");
193
+ }
194
+ }
195
+ // ── Step 4: Policy ────────────────────────────────────────────────────────
196
+ console.log("\n" + colors_1.c.dim("── Step 4: Policy ─────────────────────────────────────────────"));
197
+ // 4a) setVelocityLimit
198
+ let velocityLimitEth = "0.05";
199
+ let velocityAlreadySet = false;
200
+ try {
201
+ const ownerC = new ethers_1.ethers.Contract(walletAddress, abis_1.ARC402_WALLET_OWNER_ABI, provider);
202
+ const existing = await ownerC.velocityLimit();
203
+ if (existing > 0n) {
204
+ velocityAlreadySet = true;
205
+ velocityLimitEth = ethers_1.ethers.formatEther(existing);
206
+ console.log(" " + colors_1.c.success + colors_1.c.dim(` Velocity limit already set: ${velocityLimitEth} ETH`));
207
+ }
208
+ }
209
+ catch { /* ignore */ }
210
+ if (!velocityAlreadySet) {
211
+ const velAns = await (0, prompts_1.default)({
212
+ type: "text",
213
+ name: "limit",
214
+ message: "Velocity limit (ETH / rolling window)?",
215
+ initial: "0.05",
216
+ });
217
+ velocityLimitEth = velAns.limit || "0.05";
218
+ const velIface = new ethers_1.ethers.Interface(["function setVelocityLimit(uint256 limit) external"]);
219
+ await sendTx({ to: walletAddress, data: velIface.encodeFunctionData("setVelocityLimit", [ethers_1.ethers.parseEther(velocityLimitEth)]), value: "0x0" }, `setVelocityLimit: ${velocityLimitEth} ETH`);
220
+ (0, config_1.saveConfig)(config);
221
+ }
222
+ // 4b) setGuardian (optional — address, 'g' to generate, or skip)
223
+ let guardianAddress = null;
224
+ try {
225
+ const guardianC = new ethers_1.ethers.Contract(walletAddress, abis_1.ARC402_WALLET_GUARDIAN_ABI, provider);
226
+ const existing = await guardianC.guardian();
227
+ if (existing && existing !== ethers_1.ethers.ZeroAddress) {
228
+ guardianAddress = existing;
229
+ console.log(" " + colors_1.c.success + colors_1.c.dim(` Guardian already set: ${existing}`));
230
+ }
231
+ }
232
+ catch { /* ignore */ }
233
+ if (!guardianAddress) {
234
+ const guardianAns = await (0, prompts_1.default)({
235
+ type: "text",
236
+ name: "guardian",
237
+ message: "Guardian address? (address, 'g' to generate, Enter to skip)",
238
+ initial: "",
239
+ });
240
+ const guardianInput = guardianAns.guardian?.trim() ?? "";
241
+ if (guardianInput.toLowerCase() === "g") {
242
+ const generatedGuardian = ethers_1.ethers.Wallet.createRandom();
243
+ // Save guardian private key to a separate restricted file, NOT config.json
244
+ const guardianKeyPath = path_1.default.join(os_1.default.homedir(), ".arc402", "guardian.key");
245
+ fs_1.default.mkdirSync(path_1.default.dirname(guardianKeyPath), { recursive: true, mode: 0o700 });
246
+ fs_1.default.writeFileSync(guardianKeyPath, generatedGuardian.privateKey + "\n", { mode: 0o400 });
247
+ // Only save address (not private key) to config
248
+ config.guardianAddress = generatedGuardian.address;
249
+ (0, config_1.saveConfig)(config);
250
+ guardianAddress = generatedGuardian.address;
251
+ console.log("\n " + colors_1.c.warning + " Guardian key saved to ~/.arc402/guardian.key — move offline for security");
252
+ console.log(" " + colors_1.c.dim("Address: ") + colors_1.c.white(generatedGuardian.address) + "\n");
253
+ const guardianIface = new ethers_1.ethers.Interface(["function setGuardian(address _guardian) external"]);
254
+ await sendTx({ to: walletAddress, data: guardianIface.encodeFunctionData("setGuardian", [guardianAddress]), value: "0x0" }, "setGuardian");
255
+ (0, config_1.saveConfig)(config);
256
+ }
257
+ else if (guardianInput && ethers_1.ethers.isAddress(guardianInput)) {
258
+ guardianAddress = guardianInput;
259
+ config.guardianAddress = guardianInput;
260
+ const guardianIface = new ethers_1.ethers.Interface(["function setGuardian(address _guardian) external"]);
261
+ await sendTx({ to: walletAddress, data: guardianIface.encodeFunctionData("setGuardian", [guardianAddress]), value: "0x0" }, "setGuardian");
262
+ (0, config_1.saveConfig)(config);
263
+ }
264
+ else if (guardianInput) {
265
+ console.log(" " + colors_1.c.warning + " Invalid address — guardian skipped");
266
+ }
267
+ }
268
+ // 4c) setCategoryLimitFor('hire')
269
+ let hireLimit = "0.1";
270
+ let hireLimitAlreadySet = false;
271
+ try {
272
+ const limitsContract = new ethers_1.ethers.Contract(policyAddress, abis_1.POLICY_ENGINE_LIMITS_ABI, provider);
273
+ const existing = await limitsContract.categoryLimits(walletAddress, "hire");
274
+ if (existing > 0n) {
275
+ hireLimitAlreadySet = true;
276
+ hireLimit = ethers_1.ethers.formatEther(existing);
277
+ console.log(" " + colors_1.c.success + colors_1.c.dim(` Hire limit already set: ${hireLimit} ETH`));
278
+ }
279
+ }
280
+ catch { /* ignore */ }
281
+ if (!hireLimitAlreadySet) {
282
+ const limitAns = await (0, prompts_1.default)({
283
+ type: "text",
284
+ name: "limit",
285
+ message: "Max price per hire (ETH)?",
286
+ initial: "0.1",
287
+ });
288
+ hireLimit = limitAns.limit || "0.1";
289
+ const hireLimitWei = ethers_1.ethers.parseEther(hireLimit);
290
+ const policyIface = new ethers_1.ethers.Interface([
291
+ "function setCategoryLimitFor(address wallet, string category, uint256 limitPerTx) external",
292
+ ]);
293
+ await sendTx({ to: policyAddress, data: policyIface.encodeFunctionData("setCategoryLimitFor", [walletAddress, "hire", hireLimitWei]), value: "0x0" }, `setCategoryLimitFor: hire → ${hireLimit} ETH`);
294
+ (0, config_1.saveConfig)(config);
295
+ }
296
+ // 4d) enableContractInteraction(wallet, Handshake)
297
+ const contractInteractionIface = new ethers_1.ethers.Interface([
298
+ "function enableContractInteraction(address wallet, address target) external",
299
+ ]);
300
+ await sendTx({ to: policyAddress, data: contractInteractionIface.encodeFunctionData("enableContractInteraction", [walletAddress, handshakeAddress]), value: "0x0" }, "enableContractInteraction: Handshake");
301
+ (0, config_1.saveConfig)(config);
302
+ console.log(" " + colors_1.c.success + " Policy configured");
303
+ // ── Step 5: Agent Registration ─────────────────────────────────────────────
304
+ console.log("\n" + colors_1.c.dim("── Step 5: Agent Registration ─────────────────────────────────"));
305
+ let agentAlreadyRegistered = false;
306
+ let agentName = "";
307
+ let agentServiceType = "";
308
+ let agentEndpoint = "";
309
+ if (agentRegistryAddress) {
310
+ try {
311
+ const regContract = new ethers_1.ethers.Contract(agentRegistryAddress, abis_1.AGENT_REGISTRY_ABI, provider);
312
+ agentAlreadyRegistered = await regContract.isRegistered(walletAddress);
313
+ if (agentAlreadyRegistered) {
314
+ const info = await regContract.getAgent(walletAddress);
315
+ agentName = info.name;
316
+ agentServiceType = info.serviceType;
317
+ agentEndpoint = info.endpoint;
318
+ console.log(" " + colors_1.c.success + colors_1.c.dim(` Agent already registered: ${agentName}`));
319
+ }
320
+ }
321
+ catch { /* ignore */ }
322
+ if (!agentAlreadyRegistered) {
323
+ const rawHostname = os_1.default.hostname();
324
+ const cleanHostname = rawHostname.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
325
+ const answers = await (0, prompts_1.default)([
326
+ { type: "text", name: "name", message: "Agent name?", initial: cleanHostname },
327
+ { type: "text", name: "serviceType", message: "Service type?", initial: "intelligence" },
328
+ { type: "text", name: "caps", message: "Capabilities? (comma-separated)", initial: "research" },
329
+ { type: "text", name: "endpoint", message: "Endpoint?", initial: `https://${cleanHostname}.arc402.xyz` },
330
+ ]);
331
+ agentName = answers.name || cleanHostname;
332
+ agentServiceType = answers.serviceType || "intelligence";
333
+ agentEndpoint = answers.endpoint || `https://${cleanHostname}.arc402.xyz`;
334
+ const capabilities = (answers.caps || "research")
335
+ .split(",").map((s) => s.trim()).filter(Boolean);
336
+ // 5a) enableDefiAccess (check first)
337
+ const peExtIface = new ethers_1.ethers.Interface([
338
+ "function enableDefiAccess(address wallet) external",
339
+ "function whitelistContract(address wallet, address target) external",
340
+ "function defiAccessEnabled(address) external view returns (bool)",
341
+ "function isContractWhitelisted(address wallet, address target) external view returns (bool)",
342
+ ]);
343
+ const peContract = new ethers_1.ethers.Contract(policyAddress, peExtIface, provider);
344
+ const defiEnabled = await peContract.defiAccessEnabled(walletAddress).catch(() => false);
345
+ if (!defiEnabled) {
346
+ await sendTx({ to: policyAddress, data: peExtIface.encodeFunctionData("enableDefiAccess", [walletAddress]), value: "0x0" }, "enableDefiAccess on PolicyEngine");
347
+ (0, config_1.saveConfig)(config);
348
+ }
349
+ else {
350
+ console.log(" " + colors_1.c.success + colors_1.c.dim(" enableDefiAccess — already done"));
351
+ }
352
+ // 5b) whitelistContract for AgentRegistry (check first)
353
+ const whitelisted = await peContract.isContractWhitelisted(walletAddress, agentRegistryAddress).catch(() => false);
354
+ if (!whitelisted) {
355
+ await sendTx({ to: policyAddress, data: peExtIface.encodeFunctionData("whitelistContract", [walletAddress, agentRegistryAddress]), value: "0x0" }, "whitelistContract: AgentRegistry on PolicyEngine");
356
+ (0, config_1.saveConfig)(config);
357
+ }
358
+ else {
359
+ console.log(" " + colors_1.c.success + colors_1.c.dim(" whitelistContract(AgentRegistry) — already done"));
360
+ }
361
+ // 5c+d) executeContractCall → register
362
+ const regIface = new ethers_1.ethers.Interface(abis_1.AGENT_REGISTRY_ABI);
363
+ const execIface = new ethers_1.ethers.Interface(abis_1.ARC402_WALLET_EXECUTE_ABI);
364
+ const regData = regIface.encodeFunctionData("register", [agentName, capabilities, agentServiceType, agentEndpoint, ""]);
365
+ const execData = execIface.encodeFunctionData("executeContractCall", [{
366
+ target: agentRegistryAddress,
367
+ data: regData,
368
+ value: 0n,
369
+ minReturnValue: 0n,
370
+ maxApprovalAmount: 0n,
371
+ approvalToken: ethers_1.ethers.ZeroAddress,
372
+ }]);
373
+ await sendTx({ to: walletAddress, data: execData, value: "0x0" }, `register agent: ${agentName}`);
374
+ console.log(" " + colors_1.c.success + " Agent registered: " + agentName);
375
+ }
376
+ }
377
+ else {
378
+ console.log(" " + colors_1.c.warning + " AgentRegistry address not configured — skipping");
379
+ }
380
+ // ── Step 7: Workroom Init ─────────────────────────────────────────────────
381
+ console.log("\n" + colors_1.c.dim("── Step 7: Workroom ────────────────────────────────────────────"));
382
+ let workroomInitialized = false;
383
+ let dockerFound = false;
384
+ try {
385
+ const dockerCheck = (0, child_process_1.spawnSync)("docker", ["--version"], { encoding: "utf-8", timeout: 5000 });
386
+ dockerFound = dockerCheck.status === 0;
387
+ }
388
+ catch {
389
+ dockerFound = false;
390
+ }
391
+ if (dockerFound) {
392
+ console.log(" " + colors_1.c.dim("Docker found — initializing workroom..."));
393
+ try {
394
+ const initResult = (0, child_process_1.spawnSync)(process.execPath, [process.argv[1], "workroom", "init"], {
395
+ encoding: "utf-8",
396
+ timeout: 120000,
397
+ env: { ...process.env },
398
+ });
399
+ workroomInitialized = initResult.status === 0;
400
+ if (workroomInitialized) {
401
+ console.log(" " + colors_1.c.success + " Workroom initialized");
402
+ }
403
+ else {
404
+ console.log(" " + colors_1.c.warning + " Workroom init incomplete — run: arc402 workroom init");
405
+ }
406
+ }
407
+ catch {
408
+ console.log(" " + colors_1.c.warning + " Workroom init failed — run: arc402 workroom init");
409
+ }
410
+ }
411
+ else {
412
+ console.log(" " + colors_1.c.warning + " Docker not found");
413
+ console.log(" Install Docker: https://docs.docker.com/get-docker/");
414
+ console.log(" Or run daemon in host mode: " + colors_1.c.white("arc402 daemon start --host"));
415
+ }
416
+ // ── Step 8: Daemon Start ──────────────────────────────────────────────────
417
+ console.log("\n" + colors_1.c.dim("── Step 8: Daemon ──────────────────────────────────────────────"));
418
+ let daemonRunning = false;
419
+ const relayPort = 4402;
420
+ if (workroomInitialized) {
421
+ try {
422
+ const startResult = (0, child_process_1.spawnSync)(process.execPath, [process.argv[1], "workroom", "start"], {
423
+ encoding: "utf-8",
424
+ timeout: 30000,
425
+ env: { ...process.env },
426
+ });
427
+ daemonRunning = startResult.status === 0;
428
+ if (daemonRunning) {
429
+ console.log(" " + colors_1.c.success + " Daemon online (port " + relayPort + ")");
430
+ }
431
+ else {
432
+ console.log(" " + colors_1.c.warning + " Daemon start failed — run: arc402 workroom start");
433
+ }
434
+ }
435
+ catch {
436
+ console.log(" " + colors_1.c.warning + " Daemon start failed — run: arc402 workroom start");
437
+ }
438
+ }
439
+ else if (!dockerFound) {
440
+ try {
441
+ const startResult = (0, child_process_1.spawnSync)(process.execPath, [process.argv[1], "daemon", "start", "--host"], {
442
+ encoding: "utf-8",
443
+ timeout: 30000,
444
+ env: { ...process.env },
445
+ });
446
+ daemonRunning = startResult.status === 0;
447
+ if (daemonRunning) {
448
+ console.log(" " + colors_1.c.success + " Daemon online — host mode (port " + relayPort + ")");
449
+ }
450
+ else {
451
+ console.log(" " + colors_1.c.warning + " Daemon not started — run: arc402 daemon start --host");
452
+ }
453
+ }
454
+ catch {
455
+ console.log(" " + colors_1.c.warning + " Daemon not started — run: arc402 daemon start --host");
456
+ }
457
+ }
458
+ else {
459
+ console.log(" " + colors_1.c.warning + " Daemon not started — run: arc402 workroom init first");
460
+ }
461
+ // ── Step 6: Summary ───────────────────────────────────────────────────────
462
+ const trustScore = await (async () => {
463
+ try {
464
+ const trust = new ethers_1.ethers.Contract(config.trustRegistryAddress, abis_1.TRUST_REGISTRY_ABI, provider);
465
+ const s = await trust.getScore(walletAddress);
466
+ return s.toString();
467
+ }
468
+ catch {
469
+ return "100";
470
+ }
471
+ })();
472
+ const workroomLabel = dockerFound
473
+ ? (workroomInitialized ? (daemonRunning ? colors_1.c.green("✓ Running") : colors_1.c.yellow("✓ Initialized")) : colors_1.c.yellow("⚠ Init needed"))
474
+ : colors_1.c.yellow("⚠ No Docker");
475
+ const daemonLabel = daemonRunning
476
+ ? colors_1.c.green("✓ Online (port " + relayPort + ")")
477
+ : colors_1.c.dim("not started");
478
+ const endpointLabel = agentEndpoint
479
+ ? colors_1.c.white(agentEndpoint) + colors_1.c.dim(` → localhost:${relayPort}`)
480
+ : colors_1.c.dim("—");
481
+ console.log("\n " + colors_1.c.success + colors_1.c.white(" Onboarding complete"));
482
+ (0, tree_1.renderTree)([
483
+ { label: "Wallet", value: colors_1.c.white(walletAddress) },
484
+ { label: "Owner", value: colors_1.c.white(ownerAddress) },
485
+ { label: "Machine", value: colors_1.c.white(machineKeyAddress) + colors_1.c.dim(" (authorized)") },
486
+ { label: "Passkey", value: passkeyActive ? colors_1.c.green("✓ set") : colors_1.c.yellow("⚠ skipped") },
487
+ { label: "Velocity", value: colors_1.c.white(velocityLimitEth + " ETH") },
488
+ { label: "Guardian", value: guardianAddress ? colors_1.c.white(guardianAddress) : colors_1.c.dim("none") },
489
+ { label: "Hire limit", value: colors_1.c.white(hireLimit + " ETH") },
490
+ { label: "Agent", value: agentName ? colors_1.c.white(agentName) : colors_1.c.dim("not registered") },
491
+ { label: "Service", value: agentServiceType ? colors_1.c.white(agentServiceType) : colors_1.c.dim("—") },
492
+ { label: "Workroom", value: workroomLabel },
493
+ { label: "Daemon", value: daemonLabel },
494
+ { label: "Endpoint", value: endpointLabel },
495
+ { label: "Trust", value: colors_1.c.white(`${trustScore}`), last: true },
496
+ ]);
497
+ console.log("\n " + colors_1.c.dim("Next: fund your wallet with 0.002 ETH on Base"));
498
+ }
117
499
  function printOpenShellHint() {
118
500
  const r = (0, child_process_1.spawnSync)("which", ["openshell"], { encoding: "utf-8" });
119
501
  if (r.status === 0 && r.stdout.trim()) {
@@ -258,16 +640,40 @@ function registerWalletCommands(program) {
258
640
  console.log(colors_1.c.dim("Next: fund your wallet with ETH, then run: arc402 wallet deploy"));
259
641
  });
260
642
  // ─── import ────────────────────────────────────────────────────────────────
261
- wallet.command("import <privateKey>")
262
- .description("Import an existing private key")
643
+ wallet.command("import")
644
+ .description("Import an existing private key (use --key-file or stdin prompt)")
263
645
  .option("--network <network>", "Network (base-mainnet or base-sepolia)", "base-sepolia")
264
- .action(async (privateKey, opts) => {
646
+ .option("--key-file <path>", "Read private key from file instead of prompting")
647
+ .action(async (opts) => {
265
648
  const network = opts.network;
266
649
  const defaults = config_1.NETWORK_DEFAULTS[network];
267
650
  if (!defaults) {
268
651
  console.error(`Unknown network: ${network}. Use base-mainnet or base-sepolia.`);
269
652
  process.exit(1);
270
653
  }
654
+ let privateKey;
655
+ if (opts.keyFile) {
656
+ try {
657
+ privateKey = fs_1.default.readFileSync(opts.keyFile, "utf-8").trim();
658
+ }
659
+ catch (e) {
660
+ console.error(`Cannot read key file: ${e instanceof Error ? e.message : String(e)}`);
661
+ process.exit(1);
662
+ }
663
+ }
664
+ else {
665
+ // Interactive prompt — hidden input avoids shell history
666
+ const answer = await (0, prompts_1.default)({
667
+ type: "password",
668
+ name: "key",
669
+ message: "Paste private key (hidden):",
670
+ });
671
+ privateKey = (answer.key ?? "").trim();
672
+ if (!privateKey) {
673
+ console.error("No private key entered.");
674
+ process.exit(1);
675
+ }
676
+ }
271
677
  let imported;
272
678
  try {
273
679
  imported = new ethers_1.ethers.Wallet(privateKey);
@@ -415,8 +821,31 @@ function registerWalletCommands(program) {
415
821
  .option("--smart-wallet", "Connect via Base Smart Wallet (Coinbase Wallet SDK) instead of WalletConnect")
416
822
  .option("--hardware", "Hardware wallet mode: show raw wc: URI only (for Ledger Live, Trezor Suite, etc.)")
417
823
  .option("--sponsored", "Use CDP paymaster for gas sponsorship (requires paymasterUrl + cdpKeyName + CDP_PRIVATE_KEY env)")
824
+ .option("--dry-run", "Simulate the deployment ceremony without sending transactions")
418
825
  .action(async (opts) => {
419
826
  const config = (0, config_1.loadConfig)();
827
+ if (opts.dryRun) {
828
+ const factoryAddr = config.walletFactoryAddress ?? config_1.NETWORK_DEFAULTS[config.network]?.walletFactoryAddress ?? "(not configured)";
829
+ const chainId = config.network === "base-mainnet" ? 8453 : 84532;
830
+ console.log();
831
+ console.log(" " + colors_1.c.dim("── Dry run: wallet deploy ──────────────────────────────────────"));
832
+ console.log(" " + colors_1.c.dim("Network: ") + colors_1.c.white(config.network));
833
+ console.log(" " + colors_1.c.dim("Chain ID: ") + colors_1.c.white(String(chainId)));
834
+ console.log(" " + colors_1.c.dim("RPC: ") + colors_1.c.white(config.rpcUrl));
835
+ console.log(" " + colors_1.c.dim("WalletFactory: ") + colors_1.c.white(factoryAddr));
836
+ console.log(" " + colors_1.c.dim("Signing method: ") + colors_1.c.white(opts.smartWallet ? "Base Smart Wallet" : opts.hardware ? "Hardware (WC URI)" : "WalletConnect"));
837
+ console.log(" " + colors_1.c.dim("Sponsored: ") + colors_1.c.white(opts.sponsored ? "yes" : "no"));
838
+ console.log();
839
+ console.log(" " + colors_1.c.dim("Steps that would run:"));
840
+ console.log(" 1. Connect " + (opts.smartWallet ? "Coinbase Smart Wallet" : "WalletConnect") + " session");
841
+ console.log(" 2. Call WalletFactory.createWallet() → deploy ARC402Wallet");
842
+ console.log(" 3. Save walletContractAddress to config");
843
+ console.log(" 4. Run onboarding ceremony (PolicyEngine, machine key, agent registration)");
844
+ console.log();
845
+ console.log(" " + colors_1.c.dim("No transactions sent (--dry-run mode)."));
846
+ console.log();
847
+ return;
848
+ }
420
849
  const factoryAddress = config.walletFactoryAddress ?? config_1.NETWORK_DEFAULTS[config.network]?.walletFactoryAddress;
421
850
  if (!factoryAddress) {
422
851
  console.error("walletFactoryAddress not found in config or NETWORK_DEFAULTS. Add walletFactoryAddress to your config.");
@@ -604,30 +1033,29 @@ function registerWalletCommands(program) {
604
1033
  console.error("Could not find WalletCreated event in receipt. Check the transaction on-chain.");
605
1034
  process.exit(1);
606
1035
  }
1036
+ // ── Step 1 complete: save wallet + owner immediately ─────────────────
607
1037
  config.walletContractAddress = walletAddress;
608
1038
  config.ownerAddress = account;
609
1039
  (0, config_1.saveConfig)(config);
610
- console.log("\n" + colors_1.c.success + colors_1.c.white(" ARC402Wallet deployed"));
1040
+ try {
1041
+ fs_1.default.chmodSync((0, config_1.getConfigPath)(), 0o600);
1042
+ }
1043
+ catch { /* best-effort */ }
1044
+ console.log("\n " + colors_1.c.success + colors_1.c.white(" Wallet deployed"));
611
1045
  (0, tree_1.renderTree)([
612
1046
  { label: "Wallet", value: walletAddress },
613
- { label: "Owner", value: account + colors_1.c.dim(" (phone wallet)"), last: true },
1047
+ { label: "Owner", value: account, last: true },
614
1048
  ]);
615
- // ── Mandatory onboarding ceremony (same WalletConnect session) ────────
616
- console.log("\nStarting mandatory onboarding ceremony in this WalletConnect session...");
617
- await runWalletOnboardingCeremony(walletAddress, account, config, provider, async (call, description) => {
618
- console.log(" " + colors_1.c.dim(`Sending: ${description}`));
1049
+ // ── Steps 2–6: Complete onboarding ceremony (same WalletConnect session)
1050
+ const sendTxCeremony = async (call, description) => {
1051
+ console.log(" " + colors_1.c.dim(`◈ ${description}`));
619
1052
  const hash = await (0, walletconnect_1.sendTransactionWithSession)(client, session, account, chainId, call);
1053
+ console.log(" " + colors_1.c.dim(" waiting for confirmation..."));
620
1054
  await provider.waitForTransaction(hash, 1);
621
- console.log(" " + colors_1.c.success + " " + colors_1.c.dim(description) + " " + colors_1.c.dim(hash));
1055
+ console.log(" " + colors_1.c.success + " " + colors_1.c.white(description));
622
1056
  return hash;
623
- });
624
- console.log(colors_1.c.dim("Your wallet contract is ready for policy enforcement"));
625
- const paymasterUrl2 = config.paymasterUrl ?? config_1.NETWORK_DEFAULTS[config.network]?.paymasterUrl;
626
- const deployedBalance = await provider.getBalance(walletAddress);
627
- if (paymasterUrl2 && deployedBalance < BigInt(1000000000000000)) {
628
- console.log(colors_1.c.dim("Gas sponsorship active — initial setup ops are free"));
629
- }
630
- console.log(colors_1.c.dim("\nNext: run 'arc402 wallet set-guardian' to configure the emergency guardian key."));
1057
+ };
1058
+ await runCompleteOnboardingCeremony(walletAddress, account, config, provider, sendTxCeremony);
631
1059
  printOpenShellHint();
632
1060
  }
633
1061
  else {
@@ -657,6 +1085,7 @@ function registerWalletCommands(program) {
657
1085
  // Generate guardian key (separate from hot key) and call setGuardian
658
1086
  const guardianWallet = ethers_1.ethers.Wallet.createRandom();
659
1087
  config.walletContractAddress = walletAddress;
1088
+ config.ownerAddress = address;
660
1089
  config.guardianPrivateKey = guardianWallet.privateKey;
661
1090
  config.guardianAddress = guardianWallet.address;
662
1091
  (0, config_1.saveConfig)(config);
@@ -1367,14 +1796,29 @@ function registerWalletCommands(program) {
1367
1796
  console.error("walletConnectProjectId not set in config. Run `arc402 config set walletConnectProjectId <id>`.");
1368
1797
  process.exit(1);
1369
1798
  }
1370
- const ownerAddress = config.ownerAddress;
1371
- if (!ownerAddress) {
1372
- console.error("ownerAddress not set in config. Run `arc402 wallet deploy` first.");
1373
- process.exit(1);
1374
- }
1375
1799
  const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
1376
1800
  const chainId = config.network === "base-mainnet" ? 8453 : 84532;
1377
1801
  const provider = new ethers_1.ethers.JsonRpcProvider(config.rpcUrl);
1802
+ let ownerAddress = config.ownerAddress;
1803
+ if (!ownerAddress) {
1804
+ // Fallback 1: WalletConnect session account
1805
+ if (config.wcSession?.account) {
1806
+ ownerAddress = config.wcSession.account;
1807
+ console.log(colors_1.c.dim(`Owner resolved from WalletConnect session: ${ownerAddress}`));
1808
+ }
1809
+ else {
1810
+ // Fallback 2: call owner() on the wallet contract
1811
+ try {
1812
+ const walletOwnerContract = new ethers_1.ethers.Contract(config.walletContractAddress, ["function owner() external view returns (address)"], provider);
1813
+ ownerAddress = await walletOwnerContract.owner();
1814
+ console.log(colors_1.c.dim(`Owner resolved from contract: ${ownerAddress}`));
1815
+ }
1816
+ catch {
1817
+ console.error("ownerAddress not set in config and could not be resolved from contract or WalletConnect session.");
1818
+ process.exit(1);
1819
+ }
1820
+ }
1821
+ }
1378
1822
  // Encode registerWallet(wallet, owner) calldata — called on PolicyEngine
1379
1823
  const policyInterface = new ethers_1.ethers.Interface([
1380
1824
  "function registerWallet(address wallet, address owner) external",