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.
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +11 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +205 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +56 -6
- package/dist/commands/wallet.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -2
- package/dist/config.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +294 -208
- package/dist/daemon/index.js.map +1 -1
- package/dist/endpoint-notify.d.ts +7 -0
- package/dist/endpoint-notify.d.ts.map +1 -1
- package/dist/endpoint-notify.js +104 -0
- package/dist/endpoint-notify.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/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +45 -34
- package/dist/repl.js.map +1 -1
- package/dist/ui/format.d.ts.map +1 -1
- package/dist/ui/format.js +2 -0
- package/dist/ui/format.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +12 -2
- package/src/commands/doctor.ts +172 -0
- package/src/commands/wallet.ts +57 -6
- package/src/config.ts +9 -1
- package/src/daemon/index.ts +234 -140
- package/src/endpoint-notify.ts +73 -0
- package/src/program.ts +2 -0
- package/src/repl.ts +53 -42
- package/src/ui/format.ts +1 -0
package/dist/ui/format.d.ts.map
CHANGED
|
@@ -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,
|
|
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) {
|
package/dist/ui/format.js.map
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/ui/format.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/commands/config.ts
CHANGED
|
@@ -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:
|
|
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
|
+
}
|
package/src/commands/wallet.ts
CHANGED
|
@@ -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
|
-
|
|
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 + "
|
|
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
|
|
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
|
-
.
|
|
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:
|
|
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);
|