arc402-cli 0.6.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.
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/ui/format.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGrD;AAID;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAC7B,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAMR;AAID;;GAEG;AACH,wBAAgB,aAAa,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAe9D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAEhE"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/ui/format.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGrD;AAID;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAC7B,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAMR;AAID;;GAEG;AACH,wBAAgB,aAAa,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAgB9D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAEhE"}
package/dist/ui/format.js CHANGED
@@ -40,6 +40,8 @@ function formatBalance(ethAmount, tokenAmount, tokenSymbol) {
40
40
  function formatTimeAgo(timestampSeconds) {
41
41
  const now = Math.floor(Date.now() / 1000);
42
42
  const delta = now - timestampSeconds;
43
+ if (delta < 0)
44
+ return "in " + formatDuration(Math.abs(delta));
43
45
  if (delta < 60)
44
46
  return "just now";
45
47
  if (delta < 3600) {
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/ui/format.ts"],"names":[],"mappings":";AAAA,iFAAiF;;AAMjF,sCAGC;AAOD,kCAEC;AAKD,sCAUC;AAOD,sCAeC;AAKD,wCASC;AAKD,0CAEC;AA1ED;;;GAGG;AACH,SAAgB,aAAa,CAAC,OAAe;IAC3C,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE;QAAE,OAAO,OAAO,CAAC;IACzC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACzD,CAAC;AAED,iFAAiF;AAEjF;;GAEG;AACH,SAAgB,WAAW,CAAC,MAAuB,EAAE,KAAa;IAChE,OAAO,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,SAAgB,aAAa,CAC3B,SAA0B,EAC1B,WAA6B,EAC7B,WAAoB;IAEpB,MAAM,GAAG,GAAG,GAAG,SAAS,MAAM,CAAC;IAC/B,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,EAAE,CAAC;QAC7C,OAAO,GAAG,GAAG,MAAM,WAAW,IAAI,WAAW,EAAE,CAAC;IAClD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AAEjF;;GAEG;AACH,SAAgB,aAAa,CAAC,gBAAwB;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,GAAG,GAAG,gBAAgB,CAAC;IAErC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,UAAU,CAAC;IAClC,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;QACjB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;QACjC,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IACD,IAAI,KAAK,GAAG,KAAK,EAAE,CAAC;QAClB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QACnC,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,OAAe;IAC5C,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACnC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC;IACtC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAE5C,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAClD,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAClD,OAAO,GAAG,CAAC,GAAG,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAgB,eAAe,CAAC,gBAAwB;IACtD,OAAO,IAAI,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;AAC5D,CAAC"}
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/ui/format.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arc402-cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "ARC-402 CLI for discovery, negotiation payloads, hire/remediation/dispute workflows, and network reads",
5
5
  "bin": {
6
6
  "arc402": "./dist/index.js"
@@ -1,7 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import prompts from "prompts";
3
3
  import chalk from "chalk";
4
- import { Arc402Config, NETWORK_DEFAULTS, configExists, loadConfig, saveConfig, getSubdomainApi } from "../config";
4
+ import { Arc402Config, NETWORK_DEFAULTS, configExists, loadConfig, saveConfig, getSubdomainApi, getWcProjectId } from "../config";
5
5
  import { c } from '../ui/colors';
6
6
 
7
7
  export function registerConfigCommands(program: Command): void {
@@ -17,7 +17,7 @@ export function registerConfigCommands(program: Command): void {
17
17
  const defaults = NETWORK_DEFAULTS[answers.network] ?? {};
18
18
  const cfg: Arc402Config = {
19
19
  network: answers.network,
20
- walletConnectProjectId: "455e9425343b9156fce1428250c9a54a",
20
+ walletConnectProjectId: getWcProjectId(),
21
21
  rpcUrl: defaults.rpcUrl ?? "https://mainnet.base.org",
22
22
  trustRegistryAddress: defaults.trustRegistryAddress ?? "",
23
23
  agentRegistryAddress: defaults.agentRegistryV2Address ?? defaults.agentRegistryAddress,
@@ -50,6 +50,16 @@ export function registerConfigCommands(program: Command): void {
50
50
  console.log(JSON.stringify({ ...cfg, privateKey: cfg.privateKey ? "***" : undefined, subdomainApi: getSubdomainApi(cfg) }, null, 2));
51
51
  });
52
52
  config.command("set <key> <value>").description("Set a config value: arc402 config set <key> <value>").action((key: string, value: string) => {
53
+ const BLOCKED_KEYS = ["privateKey", "guardianPrivateKey", "cdpPrivateKey", "machineKey"];
54
+ const ALLOWED_KEYS = ["network", "rpcUrl", "walletContractAddress", "ownerAddress", "walletConnectProjectId", "subdomainApi", "telegramBotToken", "telegramChatId", "telegramThreadId"];
55
+ if (BLOCKED_KEYS.includes(key)) {
56
+ console.error(chalk.red("Cannot set sensitive keys via config set. Edit ~/.arc402/config.json directly."));
57
+ process.exit(1);
58
+ }
59
+ if (!ALLOWED_KEYS.includes(key)) {
60
+ console.error(chalk.red(`Unknown config key: ${key}. Allowed keys: ${ALLOWED_KEYS.join(", ")}`));
61
+ process.exit(1);
62
+ }
53
63
  const cfg = loadConfig();
54
64
  (cfg as unknown as Record<string, unknown>)[key] = value;
55
65
  saveConfig(cfg);
@@ -0,0 +1,172 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import * as os from "os";
6
+ import { spawnSync } from "child_process";
7
+ import { configExists, loadConfig } from "../config";
8
+ import { c } from "../ui/colors";
9
+
10
+ const ARC402_DIR = path.join(os.homedir(), ".arc402");
11
+ const CONFIG_PATH = path.join(ARC402_DIR, "config.json");
12
+ const DAEMON_PID_PATH = path.join(ARC402_DIR, "daemon.pid");
13
+
14
+ function ok(label: string, detail?: string): void {
15
+ process.stdout.write(
16
+ " " + chalk.green("✓") + " " + chalk.white(label) + (detail ? chalk.dim(" " + detail) : "") + "\n"
17
+ );
18
+ }
19
+
20
+ function fail(label: string, detail?: string): void {
21
+ process.stdout.write(
22
+ " " + chalk.red("✗") + " " + chalk.white(label) + (detail ? chalk.dim(" " + detail) : "") + "\n"
23
+ );
24
+ }
25
+
26
+ function warn(label: string, detail?: string): void {
27
+ process.stdout.write(
28
+ " " + chalk.yellow("⚠") + " " + chalk.white(label) + (detail ? chalk.dim(" " + detail) : "") + "\n"
29
+ );
30
+ }
31
+
32
+ async function fetchJson(url: string, timeoutMs = 4000): Promise<unknown> {
33
+ const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
34
+ return res.json();
35
+ }
36
+
37
+ export function registerDoctorCommand(program: Command): void {
38
+ program
39
+ .command("doctor")
40
+ .description("Check ARC-402 environment health")
41
+ .action(async () => {
42
+ process.stdout.write("\n" + c.cyan("◈ ") + chalk.white("arc402 doctor") + "\n\n");
43
+
44
+ // ── 1. Config exists ───────────────────────────────────────────────────
45
+ if (configExists()) {
46
+ ok("Config found", CONFIG_PATH);
47
+ } else {
48
+ fail("Config not found", "Run: arc402 config init");
49
+ process.stdout.write("\n");
50
+ return;
51
+ }
52
+
53
+ const config = loadConfig();
54
+
55
+ // ── 2. RPC reachable ───────────────────────────────────────────────────
56
+ try {
57
+ const body = JSON.stringify({ jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1 });
58
+ const res = await fetch(config.rpcUrl, {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body,
62
+ signal: AbortSignal.timeout(5000),
63
+ });
64
+ const json = await res.json() as { result?: string };
65
+ if (json.result) {
66
+ const block = parseInt(json.result, 16);
67
+ ok("RPC reachable", `block #${block.toLocaleString()}`);
68
+ } else {
69
+ fail("RPC returned no block", config.rpcUrl);
70
+ }
71
+ } catch (err) {
72
+ fail("RPC unreachable", config.rpcUrl + " — " + (err instanceof Error ? err.message : String(err)));
73
+ }
74
+
75
+ // ── 3. Wallet deployed ─────────────────────────────────────────────────
76
+ if (config.walletContractAddress) {
77
+ try {
78
+ const body = JSON.stringify({
79
+ jsonrpc: "2.0",
80
+ method: "eth_getCode",
81
+ params: [config.walletContractAddress, "latest"],
82
+ id: 1,
83
+ });
84
+ const res = await fetch(config.rpcUrl, {
85
+ method: "POST",
86
+ headers: { "Content-Type": "application/json" },
87
+ body,
88
+ signal: AbortSignal.timeout(5000),
89
+ });
90
+ const json = await res.json() as { result?: string };
91
+ if (json.result && json.result !== "0x" && json.result.length > 2) {
92
+ ok("Wallet deployed", config.walletContractAddress);
93
+ } else {
94
+ fail("Wallet not deployed", config.walletContractAddress + " — no code at address");
95
+ }
96
+ } catch (err) {
97
+ warn("Wallet check failed", err instanceof Error ? err.message : String(err));
98
+ }
99
+ } else {
100
+ warn("Wallet not configured", "Run: arc402 wallet deploy");
101
+ }
102
+
103
+ // ── 4. Machine key authorized ──────────────────────────────────────────
104
+ if (config.privateKey && config.walletContractAddress) {
105
+ try {
106
+ const { ethers } = await import("ethers");
107
+ const machineKey = new ethers.Wallet(config.privateKey);
108
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl);
109
+ const iface = new ethers.Interface(["function authorizedMachineKeys(address) external view returns (bool)"]);
110
+ const data = iface.encodeFunctionData("authorizedMachineKeys", [machineKey.address]);
111
+ const result = await Promise.race([
112
+ provider.call({ to: config.walletContractAddress, data }),
113
+ new Promise<never>((_, r) => setTimeout(() => r(new Error("timeout")), 4000)),
114
+ ]);
115
+ const authorized = iface.decodeFunctionResult("authorizedMachineKeys", result)[0] as boolean;
116
+ if (authorized) {
117
+ ok("Machine key authorized", machineKey.address);
118
+ } else {
119
+ fail("Machine key not authorized", machineKey.address + " — run: arc402 wallet onboard");
120
+ }
121
+ } catch (err) {
122
+ warn("Machine key check failed", err instanceof Error ? err.message : String(err));
123
+ }
124
+ } else if (!config.privateKey) {
125
+ warn("Machine key not configured", "No privateKey in config");
126
+ }
127
+
128
+ // ── 5. Daemon running ──────────────────────────────────────────────────
129
+ let daemonRunning = false;
130
+ if (fs.existsSync(DAEMON_PID_PATH)) {
131
+ try {
132
+ const pid = parseInt(fs.readFileSync(DAEMON_PID_PATH, "utf-8").trim(), 10);
133
+ // Check if process is alive
134
+ process.kill(pid, 0);
135
+ daemonRunning = true;
136
+ ok("Daemon running", `PID ${pid}`);
137
+ } catch {
138
+ fail("Daemon PID file stale", "Run: arc402 daemon start");
139
+ }
140
+ } else {
141
+ fail("Daemon not running", "Run: arc402 daemon start");
142
+ }
143
+
144
+ // ── 6. Docker available ────────────────────────────────────────────────
145
+ try {
146
+ const docker = spawnSync("docker", ["--version"], { encoding: "utf-8", timeout: 3000 });
147
+ if (docker.status === 0) {
148
+ ok("Docker available", docker.stdout.trim().split("\n")[0]);
149
+ } else {
150
+ warn("Docker not found", "Install from https://docs.docker.com/get-docker/");
151
+ }
152
+ } catch {
153
+ warn("Docker not found", "Install from https://docs.docker.com/get-docker/");
154
+ }
155
+
156
+ // ── 7. HTTP relay reachable (if daemon is running) ─────────────────────
157
+ if (daemonRunning) {
158
+ try {
159
+ const health = await fetchJson("http://localhost:4402/health") as { status?: string };
160
+ if (health.status === "online") {
161
+ ok("HTTP relay reachable", "http://localhost:4402");
162
+ } else {
163
+ warn("HTTP relay responded", JSON.stringify(health));
164
+ }
165
+ } catch {
166
+ fail("HTTP relay not reachable", "http://localhost:4402 — daemon may still be starting");
167
+ }
168
+ }
169
+
170
+ process.stdout.write("\n");
171
+ });
172
+ }
@@ -267,12 +267,15 @@ async function runCompleteOnboardingCeremony(
267
267
  const guardianInput = (guardianAns.guardian as string | undefined)?.trim() ?? "";
268
268
  if (guardianInput.toLowerCase() === "g") {
269
269
  const generatedGuardian = ethers.Wallet.createRandom();
270
- config.guardianPrivateKey = generatedGuardian.privateKey;
270
+ // Save guardian private key to a separate restricted file, NOT config.json
271
+ const guardianKeyPath = path.join(os.homedir(), ".arc402", "guardian.key");
272
+ fs.mkdirSync(path.dirname(guardianKeyPath), { recursive: true, mode: 0o700 });
273
+ fs.writeFileSync(guardianKeyPath, generatedGuardian.privateKey + "\n", { mode: 0o400 });
274
+ // Only save address (not private key) to config
271
275
  config.guardianAddress = generatedGuardian.address;
272
276
  saveConfig(config);
273
277
  guardianAddress = generatedGuardian.address;
274
- console.log("\n " + c.warning + " IMPORTANT: Save this guardian private key (emergency freeze key):");
275
- console.log(" " + c.dim(generatedGuardian.privateKey));
278
+ console.log("\n " + c.warning + " Guardian key saved to ~/.arc402/guardian.key move offline for security");
276
279
  console.log(" " + c.dim("Address: ") + c.white(generatedGuardian.address) + "\n");
277
280
  const guardianIface = new ethers.Interface(["function setGuardian(address _guardian) external"]);
278
281
  await sendTx(
@@ -686,16 +689,40 @@ export function registerWalletCommands(program: Command): void {
686
689
 
687
690
  // ─── import ────────────────────────────────────────────────────────────────
688
691
 
689
- wallet.command("import <privateKey>")
690
- .description("Import an existing private key")
692
+ wallet.command("import")
693
+ .description("Import an existing private key (use --key-file or stdin prompt)")
691
694
  .option("--network <network>", "Network (base-mainnet or base-sepolia)", "base-sepolia")
692
- .action(async (privateKey, opts) => {
695
+ .option("--key-file <path>", "Read private key from file instead of prompting")
696
+ .action(async (opts) => {
693
697
  const network = opts.network as "base-mainnet" | "base-sepolia";
694
698
  const defaults = NETWORK_DEFAULTS[network];
695
699
  if (!defaults) {
696
700
  console.error(`Unknown network: ${network}. Use base-mainnet or base-sepolia.`);
697
701
  process.exit(1);
698
702
  }
703
+
704
+ let privateKey: string;
705
+ if (opts.keyFile) {
706
+ try {
707
+ privateKey = fs.readFileSync(opts.keyFile, "utf-8").trim();
708
+ } catch (e) {
709
+ console.error(`Cannot read key file: ${e instanceof Error ? e.message : String(e)}`);
710
+ process.exit(1);
711
+ }
712
+ } else {
713
+ // Interactive prompt — hidden input avoids shell history
714
+ const answer = await prompts({
715
+ type: "password",
716
+ name: "key",
717
+ message: "Paste private key (hidden):",
718
+ });
719
+ privateKey = ((answer.key as string | undefined) ?? "").trim();
720
+ if (!privateKey) {
721
+ console.error("No private key entered.");
722
+ process.exit(1);
723
+ }
724
+ }
725
+
699
726
  let imported: ethers.Wallet;
700
727
  try {
701
728
  imported = new ethers.Wallet(privateKey);
@@ -850,8 +877,32 @@ export function registerWalletCommands(program: Command): void {
850
877
  .option("--smart-wallet", "Connect via Base Smart Wallet (Coinbase Wallet SDK) instead of WalletConnect")
851
878
  .option("--hardware", "Hardware wallet mode: show raw wc: URI only (for Ledger Live, Trezor Suite, etc.)")
852
879
  .option("--sponsored", "Use CDP paymaster for gas sponsorship (requires paymasterUrl + cdpKeyName + CDP_PRIVATE_KEY env)")
880
+ .option("--dry-run", "Simulate the deployment ceremony without sending transactions")
853
881
  .action(async (opts) => {
854
882
  const config = loadConfig();
883
+
884
+ if (opts.dryRun) {
885
+ const factoryAddr = config.walletFactoryAddress ?? NETWORK_DEFAULTS[config.network]?.walletFactoryAddress ?? "(not configured)";
886
+ const chainId = config.network === "base-mainnet" ? 8453 : 84532;
887
+ console.log();
888
+ console.log(" " + c.dim("── Dry run: wallet deploy ──────────────────────────────────────"));
889
+ console.log(" " + c.dim("Network: ") + c.white(config.network));
890
+ console.log(" " + c.dim("Chain ID: ") + c.white(String(chainId)));
891
+ console.log(" " + c.dim("RPC: ") + c.white(config.rpcUrl));
892
+ console.log(" " + c.dim("WalletFactory: ") + c.white(factoryAddr));
893
+ console.log(" " + c.dim("Signing method: ") + c.white(opts.smartWallet ? "Base Smart Wallet" : opts.hardware ? "Hardware (WC URI)" : "WalletConnect"));
894
+ console.log(" " + c.dim("Sponsored: ") + c.white(opts.sponsored ? "yes" : "no"));
895
+ console.log();
896
+ console.log(" " + c.dim("Steps that would run:"));
897
+ console.log(" 1. Connect " + (opts.smartWallet ? "Coinbase Smart Wallet" : "WalletConnect") + " session");
898
+ console.log(" 2. Call WalletFactory.createWallet() → deploy ARC402Wallet");
899
+ console.log(" 3. Save walletContractAddress to config");
900
+ console.log(" 4. Run onboarding ceremony (PolicyEngine, machine key, agent registration)");
901
+ console.log();
902
+ console.log(" " + c.dim("No transactions sent (--dry-run mode)."));
903
+ console.log();
904
+ return;
905
+ }
855
906
  const factoryAddress = config.walletFactoryAddress ?? NETWORK_DEFAULTS[config.network]?.walletFactoryAddress;
856
907
  if (!factoryAddress) {
857
908
  console.error("walletFactoryAddress not found in config or NETWORK_DEFAULTS. Add walletFactoryAddress to your config.");
package/src/config.ts CHANGED
@@ -51,6 +51,10 @@ export interface Arc402Config {
51
51
  const CONFIG_DIR = path.join(os.homedir(), ".arc402");
52
52
  const CONFIG_PATH = process.env.ARC402_CONFIG || path.join(CONFIG_DIR, "config.json");
53
53
 
54
+ // WalletConnect project ID — get your own at cloud.walletconnect.com
55
+ const DEFAULT_WC_PROJECT_ID = "455e9425343b9156fce1428250c9a54a";
56
+ export const getWcProjectId = () => process.env.WC_PROJECT_ID ?? DEFAULT_WC_PROJECT_ID;
57
+
54
58
  export const getConfigPath = () => CONFIG_PATH;
55
59
 
56
60
  export function loadConfig(): Arc402Config {
@@ -61,7 +65,7 @@ export function loadConfig(): Arc402Config {
61
65
  const autoConfig: Arc402Config = {
62
66
  network: "base-mainnet",
63
67
  rpcUrl: defaults.rpcUrl ?? "https://mainnet.base.org",
64
- walletConnectProjectId: "455e9425343b9156fce1428250c9a54a",
68
+ walletConnectProjectId: getWcProjectId(),
65
69
  ownerAddress: undefined,
66
70
  policyEngineAddress: defaults.policyEngineAddress,
67
71
  trustRegistryAddress: defaults.trustRegistryAddress ?? "",
@@ -77,6 +81,7 @@ export function loadConfig(): Arc402Config {
77
81
  };
78
82
  saveConfig(autoConfig);
79
83
  console.log(`◈ Config auto-created at ${CONFIG_PATH} (Base Mainnet)`);
84
+ console.log("⚠ Base Mainnet — real funds at risk. Use arc402 config init for testnet.");
80
85
  return autoConfig;
81
86
  }
82
87
  return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")) as Arc402Config;
@@ -86,6 +91,9 @@ export function saveConfig(config: Arc402Config): void {
86
91
  const configDir = path.dirname(CONFIG_PATH);
87
92
  fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
88
93
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
94
+ if (config.privateKey) {
95
+ console.warn("⚠ Private key stored in plaintext at ~/.arc402/config.json");
96
+ }
89
97
  }
90
98
 
91
99
  export const configExists = () => fs.existsSync(CONFIG_PATH);