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.
Files changed (53) hide show
  1. package/dist/commands/backup.d.ts +3 -0
  2. package/dist/commands/backup.d.ts.map +1 -0
  3. package/dist/commands/backup.js +106 -0
  4. package/dist/commands/backup.js.map +1 -0
  5. package/dist/commands/daemon.d.ts.map +1 -1
  6. package/dist/commands/daemon.js +67 -0
  7. package/dist/commands/daemon.js.map +1 -1
  8. package/dist/commands/discover.d.ts.map +1 -1
  9. package/dist/commands/discover.js +60 -15
  10. package/dist/commands/discover.js.map +1 -1
  11. package/dist/commands/wallet.d.ts.map +1 -1
  12. package/dist/commands/wallet.js +136 -52
  13. package/dist/commands/wallet.js.map +1 -1
  14. package/dist/commands/watch.d.ts.map +1 -1
  15. package/dist/commands/watch.js +146 -9
  16. package/dist/commands/watch.js.map +1 -1
  17. package/dist/config.d.ts +8 -0
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +25 -1
  20. package/dist/config.js.map +1 -1
  21. package/dist/daemon/config.d.ts +15 -1
  22. package/dist/daemon/config.d.ts.map +1 -1
  23. package/dist/daemon/config.js +32 -0
  24. package/dist/daemon/config.js.map +1 -1
  25. package/dist/daemon/index.d.ts.map +1 -1
  26. package/dist/daemon/index.js +66 -21
  27. package/dist/daemon/index.js.map +1 -1
  28. package/dist/daemon/notify.d.ts +35 -6
  29. package/dist/daemon/notify.d.ts.map +1 -1
  30. package/dist/daemon/notify.js +176 -48
  31. package/dist/daemon/notify.js.map +1 -1
  32. package/dist/endpoint-notify.d.ts +2 -1
  33. package/dist/endpoint-notify.d.ts.map +1 -1
  34. package/dist/endpoint-notify.js +12 -3
  35. package/dist/endpoint-notify.js.map +1 -1
  36. package/dist/index.js +26 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/program.d.ts.map +1 -1
  39. package/dist/program.js +2 -0
  40. package/dist/program.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/commands/backup.ts +117 -0
  43. package/src/commands/daemon.ts +73 -0
  44. package/src/commands/discover.ts +74 -21
  45. package/src/commands/wallet.ts +137 -51
  46. package/src/commands/watch.ts +207 -10
  47. package/src/config.ts +39 -1
  48. package/src/daemon/config.ts +35 -0
  49. package/src/daemon/index.ts +65 -26
  50. package/src/daemon/notify.ts +199 -59
  51. package/src/endpoint-notify.ts +13 -3
  52. package/src/index.ts +26 -0
  53. package/src/program.ts +2 -0
@@ -26,6 +26,26 @@ const tree_1 = require("../ui/tree");
26
26
  const spinner_1 = require("../ui/spinner");
27
27
  const colors_1 = require("../ui/colors");
28
28
  const POLICY_ENGINE_DEFAULT = "0x44102e70c2A366632d98Fe40d892a2501fC7fFF2";
29
+ const GUARDIAN_KEY_PATH = path_1.default.join(os_1.default.homedir(), ".arc402", "guardian.key");
30
+ /** Save guardian private key to a restricted standalone file (never to config.json). */
31
+ function saveGuardianKey(privateKey) {
32
+ fs_1.default.mkdirSync(path_1.default.dirname(GUARDIAN_KEY_PATH), { recursive: true, mode: 0o700 });
33
+ fs_1.default.writeFileSync(GUARDIAN_KEY_PATH, privateKey + "\n", { mode: 0o400 });
34
+ }
35
+ /** Load guardian private key from file, falling back to config for backwards compat. */
36
+ function loadGuardianKey(config) {
37
+ try {
38
+ return fs_1.default.readFileSync(GUARDIAN_KEY_PATH, "utf-8").trim();
39
+ }
40
+ catch {
41
+ // Migration: if key still in config, migrate it to the file now
42
+ if (config.guardianPrivateKey) {
43
+ saveGuardianKey(config.guardianPrivateKey);
44
+ return config.guardianPrivateKey;
45
+ }
46
+ return null;
47
+ }
48
+ }
29
49
  function parseAmount(raw) {
30
50
  const lower = raw.toLowerCase();
31
51
  if (lower.endsWith("eth")) {
@@ -161,6 +181,9 @@ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config
161
181
  await sendTx({ to: walletAddress, data: mkIface.encodeFunctionData("authorizeMachineKey", [machineKeyAddress]), value: "0x0" }, "authorizeMachineKey");
162
182
  console.log(" " + colors_1.c.success + " Machine key authorized");
163
183
  }
184
+ // Save progress after machine key step
185
+ config.onboardingProgress = { walletAddress, step: 2, completedSteps: ["machineKey"] };
186
+ (0, config_1.saveConfig)(config);
164
187
  // ── Step 3: Passkey ───────────────────────────────────────────────────────
165
188
  console.log("\n" + colors_1.c.dim("── Step 3: Passkey (Face ID / WebAuthn) ──────────────────────"));
166
189
  let passkeyActive = false;
@@ -192,6 +215,9 @@ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config
192
215
  console.log(" " + colors_1.c.success + " Passkey set (via browser)");
193
216
  }
194
217
  }
218
+ // Save progress after passkey step
219
+ config.onboardingProgress = { walletAddress, step: 3, completedSteps: ["machineKey", "passkey"] };
220
+ (0, config_1.saveConfig)(config);
195
221
  // ── Step 4: Policy ────────────────────────────────────────────────────────
196
222
  console.log("\n" + colors_1.c.dim("── Step 4: Policy ─────────────────────────────────────────────"));
197
223
  // 4a) setVelocityLimit
@@ -300,6 +326,9 @@ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config
300
326
  await sendTx({ to: policyAddress, data: contractInteractionIface.encodeFunctionData("enableContractInteraction", [walletAddress, handshakeAddress]), value: "0x0" }, "enableContractInteraction: Handshake");
301
327
  (0, config_1.saveConfig)(config);
302
328
  console.log(" " + colors_1.c.success + " Policy configured");
329
+ // Save progress after policy step
330
+ config.onboardingProgress = { walletAddress, step: 4, completedSteps: ["machineKey", "passkey", "policy"] };
331
+ (0, config_1.saveConfig)(config);
303
332
  // ── Step 5: Agent Registration ─────────────────────────────────────────────
304
333
  console.log("\n" + colors_1.c.dim("── Step 5: Agent Registration ─────────────────────────────────"));
305
334
  let agentAlreadyRegistered = false;
@@ -377,6 +406,9 @@ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config
377
406
  else {
378
407
  console.log(" " + colors_1.c.warning + " AgentRegistry address not configured — skipping");
379
408
  }
409
+ // Save progress after agent step, then clear on ceremony complete
410
+ config.onboardingProgress = { walletAddress, step: 5, completedSteps: ["machineKey", "passkey", "policy", "agent"] };
411
+ (0, config_1.saveConfig)(config);
380
412
  // ── Step 7: Workroom Init ─────────────────────────────────────────────────
381
413
  console.log("\n" + colors_1.c.dim("── Step 7: Workroom ────────────────────────────────────────────"));
382
414
  let workroomInitialized = false;
@@ -478,6 +510,9 @@ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config
478
510
  const endpointLabel = agentEndpoint
479
511
  ? colors_1.c.white(agentEndpoint) + colors_1.c.dim(` → localhost:${relayPort}`)
480
512
  : colors_1.c.dim("—");
513
+ // Clear onboarding progress — ceremony complete
514
+ delete config.onboardingProgress;
515
+ (0, config_1.saveConfig)(config);
481
516
  console.log("\n " + colors_1.c.success + colors_1.c.white(" Onboarding complete"));
482
517
  (0, tree_1.renderTree)([
483
518
  { label: "Wallet", value: colors_1.c.white(walletAddress) },
@@ -986,12 +1021,44 @@ function registerWalletCommands(program) {
986
1021
  const telegramOpts = config.telegramBotToken && config.telegramChatId
987
1022
  ? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
988
1023
  : undefined;
1024
+ // ── Resume check ──────────────────────────────────────────────────────
1025
+ const resumeProgress = config.onboardingProgress;
1026
+ const isResuming = !!(resumeProgress?.walletAddress &&
1027
+ resumeProgress.walletAddress === config.walletContractAddress &&
1028
+ config.ownerAddress);
1029
+ if (isResuming) {
1030
+ const stepNames = {
1031
+ 2: "machine key", 3: "passkey", 4: "policy setup", 5: "agent registration",
1032
+ };
1033
+ const nextStep = (resumeProgress.step ?? 1) + 1;
1034
+ console.log(" " + colors_1.c.dim(`◈ Resuming onboarding from step ${nextStep} (${stepNames[nextStep] ?? "ceremony"})...`));
1035
+ }
1036
+ // ── Gas estimation ─────────────────────────────────────────────────────
1037
+ if (!isResuming) {
1038
+ let gasMsg = "~0.003 ETH (6 transactions on Base)";
1039
+ try {
1040
+ const feeData = await provider.getFeeData();
1041
+ const gasPrice = feeData.maxFeePerGas ?? feeData.gasPrice ?? BigInt(1500000000);
1042
+ const deployGas = await provider.estimateGas({
1043
+ to: factoryAddress,
1044
+ data: factoryInterface.encodeFunctionData("createWallet", ["0x0000000071727De22E5E9d8BAf0edAc6f37da032"]),
1045
+ }).catch(() => BigInt(280000));
1046
+ const ceremonyGas = BigInt(700000); // ~5 ceremony txs × ~140k each
1047
+ const totalGasEth = parseFloat(ethers_1.ethers.formatEther((deployGas + ceremonyGas) * gasPrice));
1048
+ gasMsg = `~${totalGasEth.toFixed(4)} ETH (6 transactions on Base)`;
1049
+ }
1050
+ catch { /* use default */ }
1051
+ console.log(" " + colors_1.c.dim(`◈ Estimated gas: ${gasMsg}`));
1052
+ }
989
1053
  // ── Step 1: Connect ────────────────────────────────────────────────────
990
- const { client, session, account } = await (0, walletconnect_1.connectPhoneWallet)(config.walletConnectProjectId, chainId, config, { telegramOpts, prompt: "Approve ARC402Wallet deployment — you will be set as owner", hardware: !!opts.hardware });
1054
+ const connectPrompt = isResuming
1055
+ ? "Connect wallet to resume onboarding"
1056
+ : "Approve ARC402Wallet deployment — you will be set as owner";
1057
+ const { client, session, account } = await (0, walletconnect_1.connectPhoneWallet)(config.walletConnectProjectId, chainId, config, { telegramOpts, prompt: connectPrompt, hardware: !!opts.hardware });
991
1058
  const networkName = chainId === 8453 ? "Base" : "Base Sepolia";
992
1059
  const shortAddr = `${account.slice(0, 6)}...${account.slice(-5)}`;
993
1060
  console.log("\n" + colors_1.c.success + colors_1.c.white(` Connected: ${shortAddr} on ${networkName}`));
994
- if (telegramOpts) {
1061
+ if (telegramOpts && !isResuming) {
995
1062
  // Send "connected" message with a deploy confirmation button.
996
1063
  // TODO: wire up full callback_data round-trip when a persistent bot process is available.
997
1064
  await (0, telegram_notify_1.sendTelegramMessage)({
@@ -1002,50 +1069,59 @@ function registerWalletCommands(program) {
1002
1069
  buttons: [[{ text: "🚀 Deploy ARC-402 Wallet", callback_data: "arc402_deploy_confirm" }]],
1003
1070
  });
1004
1071
  }
1005
- // ── Step 2: Confirm & Deploy ───────────────────────────────────────────
1006
- // WalletConnect approval already confirmed intent — sending automatically
1007
- console.log("Deploying...");
1008
- const txHash = await (0, walletconnect_1.sendTransactionWithSession)(client, session, account, chainId, {
1009
- to: factoryAddress,
1010
- data: factoryInterface.encodeFunctionData("createWallet", ["0x0000000071727De22E5E9d8BAf0edAc6f37da032"]),
1011
- value: "0x0",
1012
- });
1013
- console.log(`\nTransaction submitted: ${txHash}`);
1014
- console.log("Waiting for confirmation...");
1015
- const receipt = await provider.waitForTransaction(txHash);
1016
- if (!receipt) {
1017
- console.error("Transaction not confirmed. Check on-chain.");
1018
- process.exit(1);
1072
+ let walletAddress;
1073
+ if (isResuming) {
1074
+ // Resume: skip deploy, use existing wallet
1075
+ walletAddress = config.walletContractAddress;
1076
+ console.log(" " + colors_1.c.dim(`◈ Using existing wallet: ${walletAddress}`));
1019
1077
  }
1020
- let walletAddress = null;
1021
- const factoryContract = new ethers_1.ethers.Contract(factoryAddress, abis_1.WALLET_FACTORY_ABI, provider);
1022
- for (const log of receipt.logs) {
1023
- try {
1024
- const parsed = factoryContract.interface.parseLog(log);
1025
- if (parsed?.name === "WalletCreated") {
1026
- walletAddress = parsed.args.walletAddress;
1027
- break;
1078
+ else {
1079
+ // ── Step 2: Confirm & Deploy ─────────────────────────────────────────
1080
+ // WalletConnect approval already confirmed intent — sending automatically
1081
+ console.log("Deploying...");
1082
+ const txHash = await (0, walletconnect_1.sendTransactionWithSession)(client, session, account, chainId, {
1083
+ to: factoryAddress,
1084
+ data: factoryInterface.encodeFunctionData("createWallet", ["0x0000000071727De22E5E9d8BAf0edAc6f37da032"]),
1085
+ value: "0x0",
1086
+ });
1087
+ console.log(`\nTransaction submitted: ${txHash}`);
1088
+ console.log("Waiting for confirmation...");
1089
+ const receipt = await provider.waitForTransaction(txHash);
1090
+ if (!receipt) {
1091
+ console.error("Transaction not confirmed. Check on-chain.");
1092
+ process.exit(1);
1093
+ }
1094
+ let deployedWallet = null;
1095
+ const factoryContract = new ethers_1.ethers.Contract(factoryAddress, abis_1.WALLET_FACTORY_ABI, provider);
1096
+ for (const log of receipt.logs) {
1097
+ try {
1098
+ const parsed = factoryContract.interface.parseLog(log);
1099
+ if (parsed?.name === "WalletCreated") {
1100
+ deployedWallet = parsed.args.walletAddress;
1101
+ break;
1102
+ }
1028
1103
  }
1104
+ catch { /* skip unparseable logs */ }
1029
1105
  }
1030
- catch { /* skip unparseable logs */ }
1031
- }
1032
- if (!walletAddress) {
1033
- console.error("Could not find WalletCreated event in receipt. Check the transaction on-chain.");
1034
- process.exit(1);
1035
- }
1036
- // ── Step 1 complete: save wallet + owner immediately ─────────────────
1037
- config.walletContractAddress = walletAddress;
1038
- config.ownerAddress = account;
1039
- (0, config_1.saveConfig)(config);
1040
- try {
1041
- fs_1.default.chmodSync((0, config_1.getConfigPath)(), 0o600);
1106
+ if (!deployedWallet) {
1107
+ console.error("Could not find WalletCreated event in receipt. Check the transaction on-chain.");
1108
+ process.exit(1);
1109
+ }
1110
+ walletAddress = deployedWallet;
1111
+ // ── Step 1 complete: save wallet + owner immediately ─────────────────
1112
+ config.walletContractAddress = walletAddress;
1113
+ config.ownerAddress = account;
1114
+ (0, config_1.saveConfig)(config);
1115
+ try {
1116
+ fs_1.default.chmodSync((0, config_1.getConfigPath)(), 0o600);
1117
+ }
1118
+ catch { /* best-effort */ }
1119
+ console.log("\n " + colors_1.c.success + colors_1.c.white(" Wallet deployed"));
1120
+ (0, tree_1.renderTree)([
1121
+ { label: "Wallet", value: walletAddress },
1122
+ { label: "Owner", value: account, last: true },
1123
+ ]);
1042
1124
  }
1043
- catch { /* best-effort */ }
1044
- console.log("\n " + colors_1.c.success + colors_1.c.white(" Wallet deployed"));
1045
- (0, tree_1.renderTree)([
1046
- { label: "Wallet", value: walletAddress },
1047
- { label: "Owner", value: account, last: true },
1048
- ]);
1049
1125
  // ── Steps 2–6: Complete onboarding ceremony (same WalletConnect session)
1050
1126
  const sendTxCeremony = async (call, description) => {
1051
1127
  console.log(" " + colors_1.c.dim(`◈ ${description}`));
@@ -1086,8 +1162,11 @@ function registerWalletCommands(program) {
1086
1162
  const guardianWallet = ethers_1.ethers.Wallet.createRandom();
1087
1163
  config.walletContractAddress = walletAddress;
1088
1164
  config.ownerAddress = address;
1089
- config.guardianPrivateKey = guardianWallet.privateKey;
1090
1165
  config.guardianAddress = guardianWallet.address;
1166
+ // Save key to restricted file — never store in config.json
1167
+ saveGuardianKey(guardianWallet.privateKey);
1168
+ if (config.guardianPrivateKey)
1169
+ delete config.guardianPrivateKey;
1091
1170
  (0, config_1.saveConfig)(config);
1092
1171
  // Call setGuardian on the deployed wallet
1093
1172
  const walletContract = new ethers_1.ethers.Contract(walletAddress, abis_1.ARC402_WALLET_GUARDIAN_ABI, signer);
@@ -1108,7 +1187,7 @@ function registerWalletCommands(program) {
1108
1187
  { label: "Wallet", value: walletAddress },
1109
1188
  { label: "Guardian", value: guardianWallet.address, last: true },
1110
1189
  ]);
1111
- console.log(`Guardian private key saved to config (keep it safe used for emergency freeze only)`);
1190
+ console.log(`Guardian private key saved to ~/.arc402/guardian.key (chmod 400 — keep it safe, used for emergency freeze only)`);
1112
1191
  console.log(`Your wallet contract is ready for policy enforcement`);
1113
1192
  printOpenShellHint();
1114
1193
  }
@@ -1305,12 +1384,13 @@ function registerWalletCommands(program) {
1305
1384
  console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
1306
1385
  process.exit(1);
1307
1386
  }
1308
- if (!config.guardianPrivateKey) {
1309
- console.error("guardianPrivateKey not set in config. Guardian key was generated during `arc402 wallet deploy`.");
1387
+ const guardianKey = loadGuardianKey(config);
1388
+ if (!guardianKey) {
1389
+ console.error(`Guardian key not found. Expected at ~/.arc402/guardian.key (or guardianPrivateKey in config for legacy setups).`);
1310
1390
  process.exit(1);
1311
1391
  }
1312
1392
  const provider = new ethers_1.ethers.JsonRpcProvider(config.rpcUrl);
1313
- const guardianSigner = new ethers_1.ethers.Wallet(config.guardianPrivateKey, provider);
1393
+ const guardianSigner = new ethers_1.ethers.Wallet(guardianKey, provider);
1314
1394
  const walletContract = new ethers_1.ethers.Contract(config.walletContractAddress, abis_1.ARC402_WALLET_GUARDIAN_ABI, guardianSigner);
1315
1395
  let tx;
1316
1396
  if (opts.drain) {
@@ -1430,12 +1510,14 @@ function registerWalletCommands(program) {
1430
1510
  value: "0x0",
1431
1511
  });
1432
1512
  await provider.waitForTransaction(txHash);
1433
- config.guardianPrivateKey = guardianWallet.privateKey;
1513
+ saveGuardianKey(guardianWallet.privateKey);
1514
+ if (config.guardianPrivateKey)
1515
+ delete config.guardianPrivateKey;
1434
1516
  config.guardianAddress = guardianWallet.address;
1435
1517
  (0, config_1.saveConfig)(config);
1436
1518
  console.log("\n" + colors_1.c.success + colors_1.c.white(` Guardian set to: ${guardianWallet.address}`));
1437
1519
  console.log(" " + colors_1.c.dim("Tx:") + " " + colors_1.c.white(txHash));
1438
- console.log(" " + colors_1.c.dim("Guardian private key saved to config."));
1520
+ console.log(" " + colors_1.c.dim("Guardian private key saved to ~/.arc402/guardian.key (chmod 400)."));
1439
1521
  console.log(" " + colors_1.c.warning + " " + colors_1.c.yellow("The guardian key can freeze your wallet. Store it separately from your hot key."));
1440
1522
  });
1441
1523
  // ─── policy-engine freeze / unfreeze (legacy — for PolicyEngine-level freeze) ──
@@ -2146,9 +2228,11 @@ function registerWalletCommands(program) {
2146
2228
  txHashes.push(txHash);
2147
2229
  }
2148
2230
  }
2149
- // Persist guardian key if generated
2231
+ // Persist guardian key if generated — save to restricted file, not config.json
2150
2232
  if (guardianWallet) {
2151
- config.guardianPrivateKey = guardianWallet.privateKey;
2233
+ saveGuardianKey(guardianWallet.privateKey);
2234
+ if (config.guardianPrivateKey)
2235
+ delete config.guardianPrivateKey;
2152
2236
  config.guardianAddress = guardianWallet.address;
2153
2237
  (0, config_1.saveConfig)(config);
2154
2238
  }
@@ -2160,7 +2244,7 @@ function registerWalletCommands(program) {
2160
2244
  txHashes.forEach((h, i) => console.log(" " + colors_1.c.dim(`Tx ${i + 1}:`) + " " + colors_1.c.white(h)));
2161
2245
  }
2162
2246
  if (guardianWallet) {
2163
- console.log(" " + colors_1.c.success + colors_1.c.dim(` Guardian key saved to config — address: ${guardianWallet.address}`));
2247
+ console.log(" " + colors_1.c.success + colors_1.c.dim(` Guardian key saved to ~/.arc402/guardian.key — address: ${guardianWallet.address}`));
2164
2248
  console.log(" " + colors_1.c.warning + " " + colors_1.c.yellow("Store the guardian private key separately from your hot key."));
2165
2249
  }
2166
2250
  console.log(colors_1.c.dim("\nVerify with: arc402 wallet status && arc402 wallet policy show"));