arc402-cli 0.7.5 → 0.9.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/INK6-UX-SPEC.md +446 -0
- package/MIGRATION-SPEC.md +108 -0
- package/dist/abis.js +14 -17
- package/dist/abis.js.map +1 -1
- package/dist/bundler.d.ts +1 -1
- package/dist/bundler.d.ts.map +1 -1
- package/dist/bundler.js +27 -61
- package/dist/bundler.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -9
- package/dist/client.js.map +1 -1
- package/dist/coinbase-smart-wallet.js +1 -4
- package/dist/coinbase-smart-wallet.js.map +1 -1
- package/dist/commands/accept.js +25 -28
- package/dist/commands/accept.js.map +1 -1
- package/dist/commands/agent-handshake.js +15 -18
- package/dist/commands/agent-handshake.js.map +1 -1
- package/dist/commands/agent.js +98 -104
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/agreements.js +62 -98
- package/dist/commands/agreements.js.map +1 -1
- package/dist/commands/arbitrator.js +45 -81
- package/dist/commands/arbitrator.js.map +1 -1
- package/dist/commands/arena-handshake.js +27 -30
- package/dist/commands/arena-handshake.js.map +1 -1
- package/dist/commands/arena.js +12 -18
- package/dist/commands/arena.js.map +1 -1
- package/dist/commands/backup.js +30 -36
- package/dist/commands/backup.js.map +1 -1
- package/dist/commands/cancel.js +15 -18
- package/dist/commands/cancel.js.map +1 -1
- package/dist/commands/channel.js +45 -81
- package/dist/commands/channel.js.map +1 -1
- package/dist/commands/coldstart.js +31 -34
- package/dist/commands/coldstart.js.map +1 -1
- package/dist/commands/config.js +23 -29
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/contract-interaction.js +12 -15
- package/dist/commands/contract-interaction.js.map +1 -1
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +98 -135
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/deliver.js +37 -76
- package/dist/commands/deliver.js.map +1 -1
- package/dist/commands/discover.js +24 -27
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/dispute.js +104 -110
- package/dist/commands/dispute.js.map +1 -1
- package/dist/commands/doctor.js +16 -55
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/endpoint.js +56 -95
- package/dist/commands/endpoint.js.map +1 -1
- package/dist/commands/feed.js +11 -18
- package/dist/commands/feed.js.map +1 -1
- package/dist/commands/hire.js +37 -40
- package/dist/commands/hire.js.map +1 -1
- package/dist/commands/migrate.js +30 -33
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/negotiate.d.ts.map +1 -1
- package/dist/commands/negotiate.js +34 -36
- package/dist/commands/negotiate.js.map +1 -1
- package/dist/commands/openshell.js +68 -104
- package/dist/commands/openshell.js.map +1 -1
- package/dist/commands/owner.js +17 -20
- package/dist/commands/owner.js.map +1 -1
- package/dist/commands/policy.js +41 -43
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/relay.d.ts.map +1 -1
- package/dist/commands/relay.js +18 -51
- package/dist/commands/relay.js.map +1 -1
- package/dist/commands/remediate.js +20 -23
- package/dist/commands/remediate.js.map +1 -1
- package/dist/commands/reputation.js +25 -27
- package/dist/commands/reputation.js.map +1 -1
- package/dist/commands/setup.js +65 -104
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/trust.js +17 -20
- package/dist/commands/trust.js.map +1 -1
- package/dist/commands/verify.js +18 -21
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/wallet.js +619 -625
- package/dist/commands/wallet.js.map +1 -1
- package/dist/commands/watch.js +33 -36
- package/dist/commands/watch.js.map +1 -1
- package/dist/commands/watchtower.js +37 -73
- package/dist/commands/watchtower.js.map +1 -1
- package/dist/commands/workroom.d.ts.map +1 -1
- package/dist/commands/workroom.js +138 -171
- package/dist/commands/workroom.js.map +1 -1
- package/dist/config.js +21 -65
- package/dist/config.js.map +1 -1
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +16 -53
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/hire-listener.d.ts +3 -3
- package/dist/daemon/hire-listener.d.ts.map +1 -1
- package/dist/daemon/hire-listener.js +13 -47
- package/dist/daemon/hire-listener.js.map +1 -1
- package/dist/daemon/index.d.ts +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +50 -88
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/job-lifecycle.d.ts +1 -1
- package/dist/daemon/job-lifecycle.d.ts.map +1 -1
- package/dist/daemon/job-lifecycle.js +11 -51
- package/dist/daemon/job-lifecycle.js.map +1 -1
- package/dist/daemon/notify.d.ts +1 -1
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +19 -53
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/token-metering.js +8 -47
- package/dist/daemon/token-metering.js.map +1 -1
- package/dist/daemon/userops.d.ts +2 -2
- package/dist/daemon/userops.d.ts.map +1 -1
- package/dist/daemon/userops.js +23 -27
- package/dist/daemon/userops.js.map +1 -1
- package/dist/daemon/wallet-monitor.d.ts +1 -1
- package/dist/daemon/wallet-monitor.d.ts.map +1 -1
- package/dist/daemon/wallet-monitor.js +8 -12
- package/dist/daemon/wallet-monitor.js.map +1 -1
- package/dist/drain-v4.js +26 -64
- package/dist/drain-v4.js.map +1 -1
- package/dist/endpoint-config.js +20 -63
- package/dist/endpoint-config.js.map +1 -1
- package/dist/endpoint-notify.js +9 -48
- package/dist/endpoint-notify.js.map +1 -1
- package/dist/index.js +16 -50
- package/dist/index.js.map +1 -1
- package/dist/openshell-runtime.d.ts.map +1 -1
- package/dist/openshell-runtime.js +38 -82
- package/dist/openshell-runtime.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +77 -83
- package/dist/program.js.map +1 -1
- package/dist/repl.js +25 -31
- package/dist/repl.js.map +1 -1
- package/dist/signing.js +3 -6
- package/dist/signing.js.map +1 -1
- package/dist/telegram-notify.js +3 -40
- package/dist/telegram-notify.js.map +1 -1
- package/dist/tui/App.d.ts +1 -9
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +87 -65
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Footer.js +4 -7
- package/dist/tui/Footer.js.map +1 -1
- package/dist/tui/Header.d.ts +1 -2
- package/dist/tui/Header.d.ts.map +1 -1
- package/dist/tui/Header.js +9 -14
- package/dist/tui/Header.js.map +1 -1
- package/dist/tui/InputLine.d.ts +1 -2
- package/dist/tui/InputLine.d.ts.map +1 -1
- package/dist/tui/InputLine.js +92 -46
- package/dist/tui/InputLine.js.map +1 -1
- package/dist/tui/Viewport.d.ts +5 -4
- package/dist/tui/Viewport.d.ts.map +1 -1
- package/dist/tui/Viewport.js +20 -13
- package/dist/tui/Viewport.js.map +1 -1
- package/dist/tui/WalletConnectPairing.d.ts +23 -0
- package/dist/tui/WalletConnectPairing.d.ts.map +1 -0
- package/dist/tui/WalletConnectPairing.js +75 -0
- package/dist/tui/WalletConnectPairing.js.map +1 -0
- package/dist/tui/components/Button.d.ts +7 -0
- package/dist/tui/components/Button.d.ts.map +1 -0
- package/dist/tui/components/Button.js +18 -0
- package/dist/tui/components/Button.js.map +1 -0
- package/dist/tui/components/CeremonyView.d.ts +13 -0
- package/dist/tui/components/CeremonyView.d.ts.map +1 -0
- package/dist/tui/components/CeremonyView.js +7 -0
- package/dist/tui/components/CeremonyView.js.map +1 -0
- package/dist/tui/components/CompletionDropdown.d.ts +7 -0
- package/dist/tui/components/CompletionDropdown.d.ts.map +1 -0
- package/dist/tui/components/CompletionDropdown.js +20 -0
- package/dist/tui/components/CompletionDropdown.js.map +1 -0
- package/dist/tui/components/ConfirmPrompt.d.ts +9 -0
- package/dist/tui/components/ConfirmPrompt.d.ts.map +1 -0
- package/dist/tui/components/ConfirmPrompt.js +7 -0
- package/dist/tui/components/ConfirmPrompt.js.map +1 -0
- package/dist/tui/components/InteractiveTable.d.ts +14 -0
- package/dist/tui/components/InteractiveTable.d.ts.map +1 -0
- package/dist/tui/components/InteractiveTable.js +58 -0
- package/dist/tui/components/InteractiveTable.js.map +1 -0
- package/dist/tui/components/StepSpinner.d.ts +11 -0
- package/dist/tui/components/StepSpinner.d.ts.map +1 -0
- package/dist/tui/components/StepSpinner.js +29 -0
- package/dist/tui/components/StepSpinner.js.map +1 -0
- package/dist/tui/components/Toast.d.ts +18 -0
- package/dist/tui/components/Toast.d.ts.map +1 -0
- package/dist/tui/components/Toast.js +25 -0
- package/dist/tui/components/Toast.js.map +1 -0
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +28 -21
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/useChat.js +13 -19
- package/dist/tui/useChat.js.map +1 -1
- package/dist/tui/useCommand.d.ts +2 -7
- package/dist/tui/useCommand.d.ts.map +1 -1
- package/dist/tui/useCommand.js +77 -165
- package/dist/tui/useCommand.js.map +1 -1
- package/dist/tui/useNotifications.d.ts +9 -0
- package/dist/tui/useNotifications.d.ts.map +1 -0
- package/dist/tui/useNotifications.js +14 -0
- package/dist/tui/useNotifications.js.map +1 -0
- package/dist/tui/useScroll.js +9 -12
- package/dist/tui/useScroll.js.map +1 -1
- package/dist/ui/banner.d.ts +12 -0
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +35 -19
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/colors.js +13 -19
- package/dist/ui/colors.js.map +1 -1
- package/dist/ui/format.js +6 -14
- package/dist/ui/format.js.map +1 -1
- package/dist/ui/spinner.js +6 -12
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/tree.js +3 -6
- package/dist/ui/tree.js.map +1 -1
- package/dist/utils/format.js +27 -41
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/hash.js +4 -42
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/time.js +2 -6
- package/dist/utils/time.js.map +1 -1
- package/dist/wallet-router.d.ts +1 -1
- package/dist/wallet-router.d.ts.map +1 -1
- package/dist/wallet-router.js +12 -19
- package/dist/wallet-router.js.map +1 -1
- package/dist/walletconnect-session.d.ts +1 -1
- package/dist/walletconnect-session.d.ts.map +1 -1
- package/dist/walletconnect-session.js +6 -11
- package/dist/walletconnect-session.js.map +1 -1
- package/dist/walletconnect.d.ts +6 -1
- package/dist/walletconnect.d.ts.map +1 -1
- package/dist/walletconnect.js +32 -35
- package/dist/walletconnect.js.map +1 -1
- package/package.json +7 -6
- package/src/bundler.ts +1 -1
- package/src/client.ts +1 -1
- package/src/commands/accept.ts +7 -7
- package/src/commands/agent-handshake.ts +4 -4
- package/src/commands/agent.ts +9 -9
- package/src/commands/agreements.ts +8 -8
- package/src/commands/arbitrator.ts +5 -5
- package/src/commands/arena-handshake.ts +6 -6
- package/src/commands/arena.ts +2 -2
- package/src/commands/backup.ts +1 -1
- package/src/commands/cancel.ts +6 -6
- package/src/commands/channel.ts +6 -6
- package/src/commands/coldstart.ts +5 -5
- package/src/commands/config.ts +2 -2
- package/src/commands/contract-interaction.ts +2 -2
- package/src/commands/daemon.ts +14 -11
- package/src/commands/deliver.ts +9 -9
- package/src/commands/discover.ts +5 -5
- package/src/commands/dispute.ts +7 -7
- package/src/commands/doctor.ts +2 -2
- package/src/commands/endpoint.ts +6 -6
- package/src/commands/feed.ts +1 -1
- package/src/commands/hire.ts +10 -10
- package/src/commands/migrate.ts +7 -7
- package/src/commands/negotiate.ts +6 -5
- package/src/commands/openshell.ts +4 -4
- package/src/commands/owner.ts +5 -5
- package/src/commands/policy.ts +5 -5
- package/src/commands/relay.ts +5 -1
- package/src/commands/remediate.ts +5 -5
- package/src/commands/reputation.ts +6 -6
- package/src/commands/setup.ts +1 -1
- package/src/commands/trust.ts +6 -6
- package/src/commands/verify.ts +6 -6
- package/src/commands/wallet.ts +15 -15
- package/src/commands/watch.ts +3 -3
- package/src/commands/watchtower.ts +6 -6
- package/src/commands/workroom.ts +14 -10
- package/src/daemon/config.ts +2 -1
- package/src/daemon/hire-listener.ts +3 -3
- package/src/daemon/index.ts +10 -9
- package/src/daemon/job-lifecycle.ts +1 -1
- package/src/daemon/notify.ts +4 -4
- package/src/daemon/userops.ts +4 -4
- package/src/daemon/wallet-monitor.ts +2 -2
- package/src/endpoint-notify.ts +1 -1
- package/src/index.ts +8 -7
- package/src/openshell-runtime.ts +5 -1
- package/src/program.ts +36 -36
- package/src/repl.ts +3 -3
- package/src/tui/App.tsx +75 -52
- package/src/tui/Header.tsx +26 -12
- package/src/tui/InputLine.tsx +108 -33
- package/src/tui/Viewport.tsx +22 -18
- package/src/tui/WalletConnectPairing.tsx +131 -0
- package/src/tui/components/Button.tsx +38 -0
- package/src/tui/components/CeremonyView.tsx +39 -0
- package/src/tui/components/CompletionDropdown.tsx +59 -0
- package/src/tui/components/ConfirmPrompt.tsx +36 -0
- package/src/tui/components/InteractiveTable.tsx +112 -0
- package/src/tui/components/StepSpinner.tsx +84 -0
- package/src/tui/components/Toast.tsx +59 -0
- package/src/tui/index.tsx +27 -9
- package/src/tui/useChat.ts +1 -1
- package/src/tui/useCommand.ts +86 -183
- package/src/tui/useNotifications.ts +28 -0
- package/src/ui/banner.ts +29 -2
- package/src/ui/tree.ts +1 -1
- package/src/wallet-router.ts +2 -2
- package/src/walletconnect-session.ts +1 -1
- package/src/walletconnect.ts +20 -5
- package/tsconfig.json +16 -7
package/dist/commands/wallet.js
CHANGED
|
@@ -1,41 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const wallet_router_1 = require("../wallet-router");
|
|
23
|
-
const coinbase_smart_wallet_1 = require("../coinbase-smart-wallet");
|
|
24
|
-
const telegram_notify_1 = require("../telegram-notify");
|
|
25
|
-
const tree_1 = require("../ui/tree");
|
|
26
|
-
const spinner_1 = require("../ui/spinner");
|
|
27
|
-
const colors_1 = require("../ui/colors");
|
|
1
|
+
import { PolicyClient, TrustClient } from "@arc402/sdk";
|
|
2
|
+
import { ethers } from "ethers";
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import os from "os";
|
|
7
|
+
import { spawnSync } from "child_process";
|
|
8
|
+
import { getConfigPath, getUsdcAddress, loadConfig, NETWORK_DEFAULTS, saveConfig } from "../config.js";
|
|
9
|
+
import { getClient, requireSigner } from "../client.js";
|
|
10
|
+
import { getTrustTier } from "../utils/format.js";
|
|
11
|
+
import { AGENT_REGISTRY_ABI, ARC402_WALLET_EXECUTE_ABI, ARC402_WALLET_GUARDIAN_ABI, ARC402_WALLET_MACHINE_KEY_ABI, ARC402_WALLET_OWNER_ABI, ARC402_WALLET_PASSKEY_ABI, ARC402_WALLET_PROTOCOL_ABI, ARC402_WALLET_REGISTRY_ABI, POLICY_ENGINE_GOVERNANCE_ABI, POLICY_ENGINE_LIMITS_ABI, TRUST_REGISTRY_ABI, WALLET_FACTORY_ABI } from "../abis.js";
|
|
12
|
+
import { warnIfPublicRpc } from "../config.js";
|
|
13
|
+
import { connectPhoneWallet, sendTransactionWithSession, requestPhoneWalletSignature } from "../walletconnect.js";
|
|
14
|
+
import { BundlerClient, PaymasterClient, DEFAULT_ENTRY_POINT } from "../bundler.js";
|
|
15
|
+
import { clearWCSession } from "../walletconnect-session.js";
|
|
16
|
+
import { handleWalletError } from "../wallet-router.js";
|
|
17
|
+
import { requestCoinbaseSmartWalletSignature } from "../coinbase-smart-wallet.js";
|
|
18
|
+
import { sendTelegramMessage } from "../telegram-notify.js";
|
|
19
|
+
import { renderTree } from "../ui/tree.js";
|
|
20
|
+
import { startSpinner } from "../ui/spinner.js";
|
|
21
|
+
import { c } from "../ui/colors.js";
|
|
28
22
|
const POLICY_ENGINE_DEFAULT = "0x44102e70c2A366632d98Fe40d892a2501fC7fFF2";
|
|
29
|
-
const GUARDIAN_KEY_PATH =
|
|
23
|
+
const GUARDIAN_KEY_PATH = path.join(os.homedir(), ".arc402", "guardian.key");
|
|
30
24
|
/** Save guardian private key to a restricted standalone file (never to config.json). */
|
|
31
25
|
function saveGuardianKey(privateKey) {
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
fs.mkdirSync(path.dirname(GUARDIAN_KEY_PATH), { recursive: true, mode: 0o700 });
|
|
27
|
+
fs.writeFileSync(GUARDIAN_KEY_PATH, privateKey + "\n", { mode: 0o400 });
|
|
34
28
|
}
|
|
35
29
|
/** Load guardian private key from file, falling back to config for backwards compat. */
|
|
36
30
|
function loadGuardianKey(config) {
|
|
37
31
|
try {
|
|
38
|
-
return
|
|
32
|
+
return fs.readFileSync(GUARDIAN_KEY_PATH, "utf-8").trim();
|
|
39
33
|
}
|
|
40
34
|
catch {
|
|
41
35
|
// Migration: if key still in config, migrate it to the file now
|
|
@@ -49,7 +43,7 @@ function loadGuardianKey(config) {
|
|
|
49
43
|
function parseAmount(raw) {
|
|
50
44
|
const lower = raw.toLowerCase();
|
|
51
45
|
if (lower.endsWith("eth")) {
|
|
52
|
-
return
|
|
46
|
+
return ethers.parseEther(lower.slice(0, -3).trim());
|
|
53
47
|
}
|
|
54
48
|
return BigInt(raw);
|
|
55
49
|
}
|
|
@@ -68,25 +62,25 @@ const ONBOARDING_CATEGORIES = [
|
|
|
68
62
|
*/
|
|
69
63
|
async function runWalletOnboardingCeremony(walletAddress, ownerAddress, config, provider, sendTx) {
|
|
70
64
|
const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
|
|
71
|
-
const executeIface = new
|
|
72
|
-
const govIface = new
|
|
73
|
-
const limitsIface = new
|
|
74
|
-
const policyGov = new
|
|
65
|
+
const executeIface = new ethers.Interface(ARC402_WALLET_EXECUTE_ABI);
|
|
66
|
+
const govIface = new ethers.Interface(POLICY_ENGINE_GOVERNANCE_ABI);
|
|
67
|
+
const limitsIface = new ethers.Interface(POLICY_ENGINE_LIMITS_ABI);
|
|
68
|
+
const policyGov = new ethers.Contract(policyAddress, POLICY_ENGINE_GOVERNANCE_ABI, provider);
|
|
75
69
|
// Check what's already done (constructor may have done registerWallet + enableDefiAccess)
|
|
76
70
|
let alreadyRegistered = false;
|
|
77
71
|
let alreadyDefiEnabled = false;
|
|
78
72
|
try {
|
|
79
73
|
const registeredOwner = await policyGov.walletOwners(walletAddress);
|
|
80
|
-
alreadyRegistered = registeredOwner !==
|
|
74
|
+
alreadyRegistered = registeredOwner !== ethers.ZeroAddress;
|
|
81
75
|
}
|
|
82
76
|
catch { /* older PolicyEngine without this getter — assume not registered */ }
|
|
83
77
|
try {
|
|
84
78
|
alreadyDefiEnabled = await policyGov.defiAccessEnabled(walletAddress);
|
|
85
79
|
}
|
|
86
80
|
catch { /* assume not enabled */ }
|
|
87
|
-
console.log("\n" +
|
|
88
|
-
console.log(" " +
|
|
89
|
-
console.log(" " +
|
|
81
|
+
console.log("\n" + c.dim("── Onboarding ceremony ────────────────────────────────────────"));
|
|
82
|
+
console.log(" " + c.dim("PolicyEngine:") + " " + c.white(policyAddress));
|
|
83
|
+
console.log(" " + c.dim("Wallet: ") + " " + c.white(walletAddress));
|
|
90
84
|
// Step 1: registerWallet (if not already done)
|
|
91
85
|
if (!alreadyRegistered) {
|
|
92
86
|
const registerCalldata = govIface.encodeFunctionData("registerWallet", [walletAddress, ownerAddress]);
|
|
@@ -98,13 +92,13 @@ async function runWalletOnboardingCeremony(walletAddress, ownerAddress, config,
|
|
|
98
92
|
value: 0n,
|
|
99
93
|
minReturnValue: 0n,
|
|
100
94
|
maxApprovalAmount: 0n,
|
|
101
|
-
approvalToken:
|
|
95
|
+
approvalToken: ethers.ZeroAddress,
|
|
102
96
|
}]),
|
|
103
97
|
value: "0x0",
|
|
104
98
|
}, "registerWallet on PolicyEngine");
|
|
105
99
|
}
|
|
106
100
|
else {
|
|
107
|
-
console.log(" " +
|
|
101
|
+
console.log(" " + c.success + c.dim(" registerWallet — already done by constructor"));
|
|
108
102
|
}
|
|
109
103
|
// Step 2: enableDefiAccess (if not already done)
|
|
110
104
|
if (!alreadyDefiEnabled) {
|
|
@@ -115,7 +109,7 @@ async function runWalletOnboardingCeremony(walletAddress, ownerAddress, config,
|
|
|
115
109
|
}, "enableDefiAccess on PolicyEngine");
|
|
116
110
|
}
|
|
117
111
|
else {
|
|
118
|
-
console.log(" " +
|
|
112
|
+
console.log(" " + c.success + c.dim(" enableDefiAccess — already done by constructor"));
|
|
119
113
|
}
|
|
120
114
|
// Steps 3–6: category limits (always set — idempotent)
|
|
121
115
|
for (const { name, amountEth } of ONBOARDING_CATEGORIES) {
|
|
@@ -124,15 +118,15 @@ async function runWalletOnboardingCeremony(walletAddress, ownerAddress, config,
|
|
|
124
118
|
data: limitsIface.encodeFunctionData("setCategoryLimitFor", [
|
|
125
119
|
walletAddress,
|
|
126
120
|
name,
|
|
127
|
-
|
|
121
|
+
ethers.parseEther(amountEth),
|
|
128
122
|
]),
|
|
129
123
|
value: "0x0",
|
|
130
124
|
}, `setCategoryLimitFor: ${name} → ${amountEth} ETH`);
|
|
131
125
|
}
|
|
132
|
-
console.log(
|
|
133
|
-
console.log(
|
|
134
|
-
console.log(" " +
|
|
135
|
-
console.log(" " +
|
|
126
|
+
console.log(c.dim("── Onboarding complete ─────────────────────────────────────────"));
|
|
127
|
+
console.log(c.dim("Tip: For production security, also configure:"));
|
|
128
|
+
console.log(" " + c.dim("arc402 wallet set-velocity-limit <eth> — wallet-level hourly ETH cap"));
|
|
129
|
+
console.log(" " + c.dim("arc402 wallet policy set-daily-limit --category general --amount <eth> — daily per-category cap"));
|
|
136
130
|
}
|
|
137
131
|
/**
|
|
138
132
|
* Complete ARC-402 onboarding ceremony — matches web flow at app.arc402.xyz/onboard.
|
|
@@ -148,116 +142,116 @@ async function runWalletOnboardingCeremony(walletAddress, ownerAddress, config,
|
|
|
148
142
|
async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config, provider, sendTx) {
|
|
149
143
|
const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
|
|
150
144
|
const agentRegistryAddress = config.agentRegistryV2Address ??
|
|
151
|
-
|
|
145
|
+
NETWORK_DEFAULTS[config.network]?.agentRegistryV2Address;
|
|
152
146
|
const handshakeAddress = config.handshakeAddress ??
|
|
153
|
-
|
|
147
|
+
NETWORK_DEFAULTS[config.network]?.handshakeAddress ??
|
|
154
148
|
"0x4F5A38Bb746d7E5d49d8fd26CA6beD141Ec2DDb3";
|
|
155
149
|
// ── Step 2: Machine Key ────────────────────────────────────────────────────
|
|
156
|
-
console.log("\n" +
|
|
150
|
+
console.log("\n" + c.dim("── Step 2: Machine Key ────────────────────────────────────────"));
|
|
157
151
|
let machineKeyAddress;
|
|
158
152
|
if (config.privateKey) {
|
|
159
|
-
machineKeyAddress = new
|
|
153
|
+
machineKeyAddress = new ethers.Wallet(config.privateKey).address;
|
|
160
154
|
}
|
|
161
155
|
else {
|
|
162
|
-
const mk =
|
|
156
|
+
const mk = ethers.Wallet.createRandom();
|
|
163
157
|
machineKeyAddress = mk.address;
|
|
164
158
|
config.privateKey = mk.privateKey;
|
|
165
|
-
|
|
166
|
-
console.log(" " +
|
|
167
|
-
console.log(" " +
|
|
159
|
+
saveConfig(config);
|
|
160
|
+
console.log(" " + c.dim("Machine key generated: ") + c.white(machineKeyAddress));
|
|
161
|
+
console.log(" " + c.dim("Private key saved to ~/.arc402/config.json (chmod 600)"));
|
|
168
162
|
}
|
|
169
163
|
let mkAlreadyAuthorized = false;
|
|
170
164
|
try {
|
|
171
|
-
const mkContract = new
|
|
165
|
+
const mkContract = new ethers.Contract(walletAddress, ARC402_WALLET_MACHINE_KEY_ABI, provider);
|
|
172
166
|
mkAlreadyAuthorized = await mkContract.authorizedMachineKeys(machineKeyAddress);
|
|
173
167
|
}
|
|
174
168
|
catch { /* older wallet — will try to authorize */ }
|
|
175
169
|
if (mkAlreadyAuthorized) {
|
|
176
|
-
console.log(" " +
|
|
170
|
+
console.log(" " + c.success + c.dim(" Machine key already authorized: ") + c.white(machineKeyAddress));
|
|
177
171
|
}
|
|
178
172
|
else {
|
|
179
|
-
console.log(" " +
|
|
180
|
-
const mkIface = new
|
|
173
|
+
console.log(" " + c.dim("Authorizing machine key: ") + c.white(machineKeyAddress));
|
|
174
|
+
const mkIface = new ethers.Interface(ARC402_WALLET_MACHINE_KEY_ABI);
|
|
181
175
|
await sendTx({ to: walletAddress, data: mkIface.encodeFunctionData("authorizeMachineKey", [machineKeyAddress]), value: "0x0" }, "authorizeMachineKey");
|
|
182
|
-
console.log(" " +
|
|
176
|
+
console.log(" " + c.success + " Machine key authorized");
|
|
183
177
|
}
|
|
184
178
|
// Save progress after machine key step
|
|
185
179
|
config.onboardingProgress = { walletAddress, step: 2, completedSteps: ["machineKey"] };
|
|
186
|
-
|
|
180
|
+
saveConfig(config);
|
|
187
181
|
// ── Step 3: Passkey ───────────────────────────────────────────────────────
|
|
188
|
-
console.log("\n" +
|
|
182
|
+
console.log("\n" + c.dim("── Step 3: Passkey (Face ID / WebAuthn) ──────────────────────"));
|
|
189
183
|
let passkeyActive = false;
|
|
190
184
|
try {
|
|
191
|
-
const walletC = new
|
|
185
|
+
const walletC = new ethers.Contract(walletAddress, ARC402_WALLET_PASSKEY_ABI, provider);
|
|
192
186
|
const auth = await walletC.ownerAuth();
|
|
193
187
|
passkeyActive = Number(auth[0]) === 1;
|
|
194
188
|
}
|
|
195
189
|
catch { /* ignore */ }
|
|
196
190
|
if (passkeyActive) {
|
|
197
|
-
console.log(" " +
|
|
191
|
+
console.log(" " + c.success + c.dim(" Passkey already active"));
|
|
198
192
|
}
|
|
199
193
|
else {
|
|
200
194
|
const passkeyUrl = `https://app.arc402.xyz/passkey-setup?wallet=${walletAddress}`;
|
|
201
|
-
console.log("\n " +
|
|
202
|
-
console.log(" " +
|
|
203
|
-
console.log(" " +
|
|
204
|
-
const passkeyAns = await (
|
|
195
|
+
console.log("\n " + c.white("Open this URL in your browser to set up Face ID:"));
|
|
196
|
+
console.log(" " + c.cyan(passkeyUrl));
|
|
197
|
+
console.log(" " + c.dim("Complete Face ID registration, then press Enter. Type 'n' + Enter to skip.\n"));
|
|
198
|
+
const passkeyAns = await prompts({
|
|
205
199
|
type: "text",
|
|
206
200
|
name: "done",
|
|
207
201
|
message: "Press Enter when done (or type 'n' to skip)",
|
|
208
202
|
initial: "",
|
|
209
203
|
});
|
|
210
204
|
if (passkeyAns.done?.toLowerCase() === "n") {
|
|
211
|
-
console.log(" " +
|
|
205
|
+
console.log(" " + c.warning + " Passkey skipped");
|
|
212
206
|
}
|
|
213
207
|
else {
|
|
214
208
|
passkeyActive = true;
|
|
215
|
-
console.log(" " +
|
|
209
|
+
console.log(" " + c.success + " Passkey set (via browser)");
|
|
216
210
|
}
|
|
217
211
|
}
|
|
218
212
|
// Save progress after passkey step
|
|
219
213
|
config.onboardingProgress = { walletAddress, step: 3, completedSteps: ["machineKey", "passkey"] };
|
|
220
|
-
|
|
214
|
+
saveConfig(config);
|
|
221
215
|
// ── Step 4: Policy ────────────────────────────────────────────────────────
|
|
222
|
-
console.log("\n" +
|
|
216
|
+
console.log("\n" + c.dim("── Step 4: Policy ─────────────────────────────────────────────"));
|
|
223
217
|
// 4a) setVelocityLimit
|
|
224
218
|
let velocityLimitEth = "0.05";
|
|
225
219
|
let velocityAlreadySet = false;
|
|
226
220
|
try {
|
|
227
|
-
const ownerC = new
|
|
221
|
+
const ownerC = new ethers.Contract(walletAddress, ARC402_WALLET_OWNER_ABI, provider);
|
|
228
222
|
const existing = await ownerC.velocityLimit();
|
|
229
223
|
if (existing > 0n) {
|
|
230
224
|
velocityAlreadySet = true;
|
|
231
|
-
velocityLimitEth =
|
|
232
|
-
console.log(" " +
|
|
225
|
+
velocityLimitEth = ethers.formatEther(existing);
|
|
226
|
+
console.log(" " + c.success + c.dim(` Velocity limit already set: ${velocityLimitEth} ETH`));
|
|
233
227
|
}
|
|
234
228
|
}
|
|
235
229
|
catch { /* ignore */ }
|
|
236
230
|
if (!velocityAlreadySet) {
|
|
237
|
-
const velAns = await (
|
|
231
|
+
const velAns = await prompts({
|
|
238
232
|
type: "text",
|
|
239
233
|
name: "limit",
|
|
240
234
|
message: "Velocity limit (ETH / rolling window)?",
|
|
241
235
|
initial: "0.05",
|
|
242
236
|
});
|
|
243
237
|
velocityLimitEth = velAns.limit || "0.05";
|
|
244
|
-
const velIface = new
|
|
245
|
-
await sendTx({ to: walletAddress, data: velIface.encodeFunctionData("setVelocityLimit", [
|
|
246
|
-
|
|
238
|
+
const velIface = new ethers.Interface(["function setVelocityLimit(uint256 limit) external"]);
|
|
239
|
+
await sendTx({ to: walletAddress, data: velIface.encodeFunctionData("setVelocityLimit", [ethers.parseEther(velocityLimitEth)]), value: "0x0" }, `setVelocityLimit: ${velocityLimitEth} ETH`);
|
|
240
|
+
saveConfig(config);
|
|
247
241
|
}
|
|
248
242
|
// 4b) setGuardian (optional — address, 'g' to generate, or skip)
|
|
249
243
|
let guardianAddress = null;
|
|
250
244
|
try {
|
|
251
|
-
const guardianC = new
|
|
245
|
+
const guardianC = new ethers.Contract(walletAddress, ARC402_WALLET_GUARDIAN_ABI, provider);
|
|
252
246
|
const existing = await guardianC.guardian();
|
|
253
|
-
if (existing && existing !==
|
|
247
|
+
if (existing && existing !== ethers.ZeroAddress) {
|
|
254
248
|
guardianAddress = existing;
|
|
255
|
-
console.log(" " +
|
|
249
|
+
console.log(" " + c.success + c.dim(` Guardian already set: ${existing}`));
|
|
256
250
|
}
|
|
257
251
|
}
|
|
258
252
|
catch { /* ignore */ }
|
|
259
253
|
if (!guardianAddress) {
|
|
260
|
-
const guardianAns = await (
|
|
254
|
+
const guardianAns = await prompts({
|
|
261
255
|
type: "text",
|
|
262
256
|
name: "guardian",
|
|
263
257
|
message: "Guardian address? (address, 'g' to generate, Enter to skip)",
|
|
@@ -265,93 +259,93 @@ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config
|
|
|
265
259
|
});
|
|
266
260
|
const guardianInput = guardianAns.guardian?.trim() ?? "";
|
|
267
261
|
if (guardianInput.toLowerCase() === "g") {
|
|
268
|
-
const generatedGuardian =
|
|
262
|
+
const generatedGuardian = ethers.Wallet.createRandom();
|
|
269
263
|
// Save guardian private key to a separate restricted file, NOT config.json
|
|
270
|
-
const guardianKeyPath =
|
|
271
|
-
|
|
272
|
-
|
|
264
|
+
const guardianKeyPath = path.join(os.homedir(), ".arc402", "guardian.key");
|
|
265
|
+
fs.mkdirSync(path.dirname(guardianKeyPath), { recursive: true, mode: 0o700 });
|
|
266
|
+
fs.writeFileSync(guardianKeyPath, generatedGuardian.privateKey + "\n", { mode: 0o400 });
|
|
273
267
|
// Only save address (not private key) to config
|
|
274
268
|
config.guardianAddress = generatedGuardian.address;
|
|
275
|
-
|
|
269
|
+
saveConfig(config);
|
|
276
270
|
guardianAddress = generatedGuardian.address;
|
|
277
|
-
console.log("\n " +
|
|
278
|
-
console.log(" " +
|
|
279
|
-
const guardianIface = new
|
|
271
|
+
console.log("\n " + c.warning + " Guardian key saved to ~/.arc402/guardian.key — move offline for security");
|
|
272
|
+
console.log(" " + c.dim("Address: ") + c.white(generatedGuardian.address) + "\n");
|
|
273
|
+
const guardianIface = new ethers.Interface(["function setGuardian(address _guardian) external"]);
|
|
280
274
|
await sendTx({ to: walletAddress, data: guardianIface.encodeFunctionData("setGuardian", [guardianAddress]), value: "0x0" }, "setGuardian");
|
|
281
|
-
|
|
275
|
+
saveConfig(config);
|
|
282
276
|
}
|
|
283
|
-
else if (guardianInput &&
|
|
277
|
+
else if (guardianInput && ethers.isAddress(guardianInput)) {
|
|
284
278
|
guardianAddress = guardianInput;
|
|
285
279
|
config.guardianAddress = guardianInput;
|
|
286
|
-
const guardianIface = new
|
|
280
|
+
const guardianIface = new ethers.Interface(["function setGuardian(address _guardian) external"]);
|
|
287
281
|
await sendTx({ to: walletAddress, data: guardianIface.encodeFunctionData("setGuardian", [guardianAddress]), value: "0x0" }, "setGuardian");
|
|
288
|
-
|
|
282
|
+
saveConfig(config);
|
|
289
283
|
}
|
|
290
284
|
else if (guardianInput) {
|
|
291
|
-
console.log(" " +
|
|
285
|
+
console.log(" " + c.warning + " Invalid address — guardian skipped");
|
|
292
286
|
}
|
|
293
287
|
}
|
|
294
288
|
// 4c) setCategoryLimitFor('hire')
|
|
295
289
|
let hireLimit = "0.1";
|
|
296
290
|
let hireLimitAlreadySet = false;
|
|
297
291
|
try {
|
|
298
|
-
const limitsContract = new
|
|
292
|
+
const limitsContract = new ethers.Contract(policyAddress, POLICY_ENGINE_LIMITS_ABI, provider);
|
|
299
293
|
const existing = await limitsContract.categoryLimits(walletAddress, "hire");
|
|
300
294
|
if (existing > 0n) {
|
|
301
295
|
hireLimitAlreadySet = true;
|
|
302
|
-
hireLimit =
|
|
303
|
-
console.log(" " +
|
|
296
|
+
hireLimit = ethers.formatEther(existing);
|
|
297
|
+
console.log(" " + c.success + c.dim(` Hire limit already set: ${hireLimit} ETH`));
|
|
304
298
|
}
|
|
305
299
|
}
|
|
306
300
|
catch { /* ignore */ }
|
|
307
301
|
if (!hireLimitAlreadySet) {
|
|
308
|
-
const limitAns = await (
|
|
302
|
+
const limitAns = await prompts({
|
|
309
303
|
type: "text",
|
|
310
304
|
name: "limit",
|
|
311
305
|
message: "Max price per hire (ETH)?",
|
|
312
306
|
initial: "0.1",
|
|
313
307
|
});
|
|
314
308
|
hireLimit = limitAns.limit || "0.1";
|
|
315
|
-
const hireLimitWei =
|
|
316
|
-
const policyIface = new
|
|
309
|
+
const hireLimitWei = ethers.parseEther(hireLimit);
|
|
310
|
+
const policyIface = new ethers.Interface([
|
|
317
311
|
"function setCategoryLimitFor(address wallet, string category, uint256 limitPerTx) external",
|
|
318
312
|
]);
|
|
319
313
|
await sendTx({ to: policyAddress, data: policyIface.encodeFunctionData("setCategoryLimitFor", [walletAddress, "hire", hireLimitWei]), value: "0x0" }, `setCategoryLimitFor: hire → ${hireLimit} ETH`);
|
|
320
|
-
|
|
314
|
+
saveConfig(config);
|
|
321
315
|
}
|
|
322
316
|
// 4d) enableContractInteraction(wallet, Handshake)
|
|
323
|
-
const contractInteractionIface = new
|
|
317
|
+
const contractInteractionIface = new ethers.Interface([
|
|
324
318
|
"function enableContractInteraction(address wallet, address target) external",
|
|
325
319
|
]);
|
|
326
320
|
await sendTx({ to: policyAddress, data: contractInteractionIface.encodeFunctionData("enableContractInteraction", [walletAddress, handshakeAddress]), value: "0x0" }, "enableContractInteraction: Handshake");
|
|
327
|
-
|
|
328
|
-
console.log(" " +
|
|
321
|
+
saveConfig(config);
|
|
322
|
+
console.log(" " + c.success + " Policy configured");
|
|
329
323
|
// Save progress after policy step
|
|
330
324
|
config.onboardingProgress = { walletAddress, step: 4, completedSteps: ["machineKey", "passkey", "policy"] };
|
|
331
|
-
|
|
325
|
+
saveConfig(config);
|
|
332
326
|
// ── Step 5: Agent Registration ─────────────────────────────────────────────
|
|
333
|
-
console.log("\n" +
|
|
327
|
+
console.log("\n" + c.dim("── Step 5: Agent Registration ─────────────────────────────────"));
|
|
334
328
|
let agentAlreadyRegistered = false;
|
|
335
329
|
let agentName = "";
|
|
336
330
|
let agentServiceType = "";
|
|
337
331
|
let agentEndpoint = "";
|
|
338
332
|
if (agentRegistryAddress) {
|
|
339
333
|
try {
|
|
340
|
-
const regContract = new
|
|
334
|
+
const regContract = new ethers.Contract(agentRegistryAddress, AGENT_REGISTRY_ABI, provider);
|
|
341
335
|
agentAlreadyRegistered = await regContract.isRegistered(walletAddress);
|
|
342
336
|
if (agentAlreadyRegistered) {
|
|
343
337
|
const info = await regContract.getAgent(walletAddress);
|
|
344
338
|
agentName = info.name;
|
|
345
339
|
agentServiceType = info.serviceType;
|
|
346
340
|
agentEndpoint = info.endpoint;
|
|
347
|
-
console.log(" " +
|
|
341
|
+
console.log(" " + c.success + c.dim(` Agent already registered: ${agentName}`));
|
|
348
342
|
}
|
|
349
343
|
}
|
|
350
344
|
catch { /* ignore */ }
|
|
351
345
|
if (!agentAlreadyRegistered) {
|
|
352
|
-
const rawHostname =
|
|
346
|
+
const rawHostname = os.hostname();
|
|
353
347
|
const cleanHostname = rawHostname.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
354
|
-
const answers = await (
|
|
348
|
+
const answers = await prompts([
|
|
355
349
|
{ type: "text", name: "name", message: "Agent name?", initial: cleanHostname },
|
|
356
350
|
{ type: "text", name: "serviceType", message: "Service type?", initial: "intelligence" },
|
|
357
351
|
{ type: "text", name: "caps", message: "Capabilities? (comma-separated)", initial: "research" },
|
|
@@ -363,33 +357,33 @@ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config
|
|
|
363
357
|
const capabilities = (answers.caps || "research")
|
|
364
358
|
.split(",").map((s) => s.trim()).filter(Boolean);
|
|
365
359
|
// 5a) enableDefiAccess (check first)
|
|
366
|
-
const peExtIface = new
|
|
360
|
+
const peExtIface = new ethers.Interface([
|
|
367
361
|
"function enableDefiAccess(address wallet) external",
|
|
368
362
|
"function whitelistContract(address wallet, address target) external",
|
|
369
363
|
"function defiAccessEnabled(address) external view returns (bool)",
|
|
370
364
|
"function isContractWhitelisted(address wallet, address target) external view returns (bool)",
|
|
371
365
|
]);
|
|
372
|
-
const peContract = new
|
|
366
|
+
const peContract = new ethers.Contract(policyAddress, peExtIface, provider);
|
|
373
367
|
const defiEnabled = await peContract.defiAccessEnabled(walletAddress).catch(() => false);
|
|
374
368
|
if (!defiEnabled) {
|
|
375
369
|
await sendTx({ to: policyAddress, data: peExtIface.encodeFunctionData("enableDefiAccess", [walletAddress]), value: "0x0" }, "enableDefiAccess on PolicyEngine");
|
|
376
|
-
|
|
370
|
+
saveConfig(config);
|
|
377
371
|
}
|
|
378
372
|
else {
|
|
379
|
-
console.log(" " +
|
|
373
|
+
console.log(" " + c.success + c.dim(" enableDefiAccess — already done"));
|
|
380
374
|
}
|
|
381
375
|
// 5b) whitelistContract for AgentRegistry (check first)
|
|
382
376
|
const whitelisted = await peContract.isContractWhitelisted(walletAddress, agentRegistryAddress).catch(() => false);
|
|
383
377
|
if (!whitelisted) {
|
|
384
378
|
await sendTx({ to: policyAddress, data: peExtIface.encodeFunctionData("whitelistContract", [walletAddress, agentRegistryAddress]), value: "0x0" }, "whitelistContract: AgentRegistry on PolicyEngine");
|
|
385
|
-
|
|
379
|
+
saveConfig(config);
|
|
386
380
|
}
|
|
387
381
|
else {
|
|
388
|
-
console.log(" " +
|
|
382
|
+
console.log(" " + c.success + c.dim(" whitelistContract(AgentRegistry) — already done"));
|
|
389
383
|
}
|
|
390
384
|
// 5c+d) executeContractCall → register
|
|
391
|
-
const regIface = new
|
|
392
|
-
const execIface = new
|
|
385
|
+
const regIface = new ethers.Interface(AGENT_REGISTRY_ABI);
|
|
386
|
+
const execIface = new ethers.Interface(ARC402_WALLET_EXECUTE_ABI);
|
|
393
387
|
const regData = regIface.encodeFunctionData("register", [agentName, capabilities, agentServiceType, agentEndpoint, ""]);
|
|
394
388
|
const execData = execIface.encodeFunctionData("executeContractCall", [{
|
|
395
389
|
target: agentRegistryAddress,
|
|
@@ -397,103 +391,103 @@ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config
|
|
|
397
391
|
value: 0n,
|
|
398
392
|
minReturnValue: 0n,
|
|
399
393
|
maxApprovalAmount: 0n,
|
|
400
|
-
approvalToken:
|
|
394
|
+
approvalToken: ethers.ZeroAddress,
|
|
401
395
|
}]);
|
|
402
396
|
await sendTx({ to: walletAddress, data: execData, value: "0x0" }, `register agent: ${agentName}`);
|
|
403
|
-
console.log(" " +
|
|
397
|
+
console.log(" " + c.success + " Agent registered: " + agentName);
|
|
404
398
|
}
|
|
405
399
|
}
|
|
406
400
|
else {
|
|
407
|
-
console.log(" " +
|
|
401
|
+
console.log(" " + c.warning + " AgentRegistry address not configured — skipping");
|
|
408
402
|
}
|
|
409
403
|
// Save progress after agent step, then clear on ceremony complete
|
|
410
404
|
config.onboardingProgress = { walletAddress, step: 5, completedSteps: ["machineKey", "passkey", "policy", "agent"] };
|
|
411
|
-
|
|
405
|
+
saveConfig(config);
|
|
412
406
|
// ── Step 7: Workroom Init ─────────────────────────────────────────────────
|
|
413
|
-
console.log("\n" +
|
|
407
|
+
console.log("\n" + c.dim("── Step 7: Workroom ────────────────────────────────────────────"));
|
|
414
408
|
let workroomInitialized = false;
|
|
415
409
|
let dockerFound = false;
|
|
416
410
|
try {
|
|
417
|
-
const dockerCheck =
|
|
411
|
+
const dockerCheck = spawnSync("docker", ["--version"], { encoding: "utf-8", timeout: 5000 });
|
|
418
412
|
dockerFound = dockerCheck.status === 0;
|
|
419
413
|
}
|
|
420
414
|
catch {
|
|
421
415
|
dockerFound = false;
|
|
422
416
|
}
|
|
423
417
|
if (dockerFound) {
|
|
424
|
-
console.log(" " +
|
|
418
|
+
console.log(" " + c.dim("Docker found — initializing workroom..."));
|
|
425
419
|
try {
|
|
426
|
-
const initResult =
|
|
420
|
+
const initResult = spawnSync(process.execPath, [process.argv[1], "workroom", "init"], {
|
|
427
421
|
encoding: "utf-8",
|
|
428
422
|
timeout: 120000,
|
|
429
423
|
env: { ...process.env },
|
|
430
424
|
});
|
|
431
425
|
workroomInitialized = initResult.status === 0;
|
|
432
426
|
if (workroomInitialized) {
|
|
433
|
-
console.log(" " +
|
|
427
|
+
console.log(" " + c.success + " Workroom initialized");
|
|
434
428
|
}
|
|
435
429
|
else {
|
|
436
|
-
console.log(" " +
|
|
430
|
+
console.log(" " + c.warning + " Workroom init incomplete — run: arc402 workroom init");
|
|
437
431
|
}
|
|
438
432
|
}
|
|
439
433
|
catch {
|
|
440
|
-
console.log(" " +
|
|
434
|
+
console.log(" " + c.warning + " Workroom init failed — run: arc402 workroom init");
|
|
441
435
|
}
|
|
442
436
|
}
|
|
443
437
|
else {
|
|
444
|
-
console.log(" " +
|
|
438
|
+
console.log(" " + c.warning + " Docker not found");
|
|
445
439
|
console.log(" Install Docker: https://docs.docker.com/get-docker/");
|
|
446
|
-
console.log(" Or run daemon in host mode: " +
|
|
440
|
+
console.log(" Or run daemon in host mode: " + c.white("arc402 daemon start --host"));
|
|
447
441
|
}
|
|
448
442
|
// ── Step 8: Daemon Start ──────────────────────────────────────────────────
|
|
449
|
-
console.log("\n" +
|
|
443
|
+
console.log("\n" + c.dim("── Step 8: Daemon ──────────────────────────────────────────────"));
|
|
450
444
|
let daemonRunning = false;
|
|
451
445
|
const relayPort = 4402;
|
|
452
446
|
if (workroomInitialized) {
|
|
453
447
|
try {
|
|
454
|
-
const startResult =
|
|
448
|
+
const startResult = spawnSync(process.execPath, [process.argv[1], "workroom", "start"], {
|
|
455
449
|
encoding: "utf-8",
|
|
456
450
|
timeout: 30000,
|
|
457
451
|
env: { ...process.env },
|
|
458
452
|
});
|
|
459
453
|
daemonRunning = startResult.status === 0;
|
|
460
454
|
if (daemonRunning) {
|
|
461
|
-
console.log(" " +
|
|
455
|
+
console.log(" " + c.success + " Daemon online (port " + relayPort + ")");
|
|
462
456
|
}
|
|
463
457
|
else {
|
|
464
|
-
console.log(" " +
|
|
458
|
+
console.log(" " + c.warning + " Daemon start failed — run: arc402 workroom start");
|
|
465
459
|
}
|
|
466
460
|
}
|
|
467
461
|
catch {
|
|
468
|
-
console.log(" " +
|
|
462
|
+
console.log(" " + c.warning + " Daemon start failed — run: arc402 workroom start");
|
|
469
463
|
}
|
|
470
464
|
}
|
|
471
465
|
else if (!dockerFound) {
|
|
472
466
|
try {
|
|
473
|
-
const startResult =
|
|
467
|
+
const startResult = spawnSync(process.execPath, [process.argv[1], "daemon", "start", "--host"], {
|
|
474
468
|
encoding: "utf-8",
|
|
475
469
|
timeout: 30000,
|
|
476
470
|
env: { ...process.env },
|
|
477
471
|
});
|
|
478
472
|
daemonRunning = startResult.status === 0;
|
|
479
473
|
if (daemonRunning) {
|
|
480
|
-
console.log(" " +
|
|
474
|
+
console.log(" " + c.success + " Daemon online — host mode (port " + relayPort + ")");
|
|
481
475
|
}
|
|
482
476
|
else {
|
|
483
|
-
console.log(" " +
|
|
477
|
+
console.log(" " + c.warning + " Daemon not started — run: arc402 daemon start --host");
|
|
484
478
|
}
|
|
485
479
|
}
|
|
486
480
|
catch {
|
|
487
|
-
console.log(" " +
|
|
481
|
+
console.log(" " + c.warning + " Daemon not started — run: arc402 daemon start --host");
|
|
488
482
|
}
|
|
489
483
|
}
|
|
490
484
|
else {
|
|
491
|
-
console.log(" " +
|
|
485
|
+
console.log(" " + c.warning + " Daemon not started — run: arc402 workroom init first");
|
|
492
486
|
}
|
|
493
487
|
// ── Step 6: Summary ───────────────────────────────────────────────────────
|
|
494
488
|
const trustScore = await (async () => {
|
|
495
489
|
try {
|
|
496
|
-
const trust = new
|
|
490
|
+
const trust = new ethers.Contract(config.trustRegistryAddress, TRUST_REGISTRY_ABI, provider);
|
|
497
491
|
const s = await trust.getScore(walletAddress);
|
|
498
492
|
return s.toString();
|
|
499
493
|
}
|
|
@@ -502,37 +496,37 @@ async function runCompleteOnboardingCeremony(walletAddress, ownerAddress, config
|
|
|
502
496
|
}
|
|
503
497
|
})();
|
|
504
498
|
const workroomLabel = dockerFound
|
|
505
|
-
? (workroomInitialized ? (daemonRunning ?
|
|
506
|
-
:
|
|
499
|
+
? (workroomInitialized ? (daemonRunning ? c.green("✓ Running") : c.yellow("✓ Initialized")) : c.yellow("⚠ Init needed"))
|
|
500
|
+
: c.yellow("⚠ No Docker");
|
|
507
501
|
const daemonLabel = daemonRunning
|
|
508
|
-
?
|
|
509
|
-
:
|
|
502
|
+
? c.green("✓ Online (port " + relayPort + ")")
|
|
503
|
+
: c.dim("not started");
|
|
510
504
|
const endpointLabel = agentEndpoint
|
|
511
|
-
?
|
|
512
|
-
:
|
|
505
|
+
? c.white(agentEndpoint) + c.dim(` → localhost:${relayPort}`)
|
|
506
|
+
: c.dim("—");
|
|
513
507
|
// Clear onboarding progress — ceremony complete
|
|
514
508
|
delete config.onboardingProgress;
|
|
515
|
-
|
|
516
|
-
console.log("\n " +
|
|
517
|
-
|
|
518
|
-
{ label: "Wallet", value:
|
|
519
|
-
{ label: "Owner", value:
|
|
520
|
-
{ label: "Machine", value:
|
|
521
|
-
{ label: "Passkey", value: passkeyActive ?
|
|
522
|
-
{ label: "Velocity", value:
|
|
523
|
-
{ label: "Guardian", value: guardianAddress ?
|
|
524
|
-
{ label: "Hire limit", value:
|
|
525
|
-
{ label: "Agent", value: agentName ?
|
|
526
|
-
{ label: "Service", value: agentServiceType ?
|
|
509
|
+
saveConfig(config);
|
|
510
|
+
console.log("\n " + c.success + c.white(" Onboarding complete"));
|
|
511
|
+
renderTree([
|
|
512
|
+
{ label: "Wallet", value: c.white(walletAddress) },
|
|
513
|
+
{ label: "Owner", value: c.white(ownerAddress) },
|
|
514
|
+
{ label: "Machine", value: c.white(machineKeyAddress) + c.dim(" (authorized)") },
|
|
515
|
+
{ label: "Passkey", value: passkeyActive ? c.green("✓ set") : c.yellow("⚠ skipped") },
|
|
516
|
+
{ label: "Velocity", value: c.white(velocityLimitEth + " ETH") },
|
|
517
|
+
{ label: "Guardian", value: guardianAddress ? c.white(guardianAddress) : c.dim("none") },
|
|
518
|
+
{ label: "Hire limit", value: c.white(hireLimit + " ETH") },
|
|
519
|
+
{ label: "Agent", value: agentName ? c.white(agentName) : c.dim("not registered") },
|
|
520
|
+
{ label: "Service", value: agentServiceType ? c.white(agentServiceType) : c.dim("—") },
|
|
527
521
|
{ label: "Workroom", value: workroomLabel },
|
|
528
522
|
{ label: "Daemon", value: daemonLabel },
|
|
529
523
|
{ label: "Endpoint", value: endpointLabel },
|
|
530
|
-
{ label: "Trust", value:
|
|
524
|
+
{ label: "Trust", value: c.white(`${trustScore}`), last: true },
|
|
531
525
|
]);
|
|
532
|
-
console.log("\n " +
|
|
526
|
+
console.log("\n " + c.dim("Next: fund your wallet with 0.002 ETH on Base"));
|
|
533
527
|
}
|
|
534
528
|
function printOpenShellHint() {
|
|
535
|
-
const r =
|
|
529
|
+
const r = spawnSync("which", ["openshell"], { encoding: "utf-8" });
|
|
536
530
|
if (r.status === 0 && r.stdout.trim()) {
|
|
537
531
|
console.log("\nOpenShell detected. Run: arc402 openshell init");
|
|
538
532
|
}
|
|
@@ -540,17 +534,17 @@ function printOpenShellHint() {
|
|
|
540
534
|
console.log("\nOptional: install OpenShell for sandboxed execution: arc402 openshell install");
|
|
541
535
|
}
|
|
542
536
|
}
|
|
543
|
-
function registerWalletCommands(program) {
|
|
537
|
+
export function registerWalletCommands(program) {
|
|
544
538
|
const wallet = program.command("wallet").description("Wallet utilities");
|
|
545
539
|
// ─── status ────────────────────────────────────────────────────────────────
|
|
546
540
|
wallet.command("status").description("Show address, balances, contract wallet, guardian, and frozen status").option("--json").action(async (opts) => {
|
|
547
|
-
const config =
|
|
548
|
-
const { provider, address } = await
|
|
541
|
+
const config = loadConfig();
|
|
542
|
+
const { provider, address } = await getClient(config);
|
|
549
543
|
if (!address)
|
|
550
544
|
throw new Error("No wallet configured");
|
|
551
|
-
const usdcAddress =
|
|
552
|
-
const usdc = new
|
|
553
|
-
const trust = new
|
|
545
|
+
const usdcAddress = getUsdcAddress(config);
|
|
546
|
+
const usdc = new ethers.Contract(usdcAddress, ["function balanceOf(address owner) external view returns (uint256)"], provider);
|
|
547
|
+
const trust = new TrustClient(config.trustRegistryAddress, provider);
|
|
554
548
|
const [ethBalance, usdcBalance, score] = await Promise.all([
|
|
555
549
|
provider.getBalance(address),
|
|
556
550
|
usdc.balanceOf(address),
|
|
@@ -561,7 +555,7 @@ function registerWalletCommands(program) {
|
|
|
561
555
|
let contractGuardian = null;
|
|
562
556
|
if (config.walletContractAddress) {
|
|
563
557
|
try {
|
|
564
|
-
const walletContract = new
|
|
558
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_GUARDIAN_ABI, provider);
|
|
565
559
|
[contractFrozen, contractGuardian] = await Promise.all([
|
|
566
560
|
walletContract.frozen(),
|
|
567
561
|
walletContract.guardian(),
|
|
@@ -572,10 +566,10 @@ function registerWalletCommands(program) {
|
|
|
572
566
|
const payload = {
|
|
573
567
|
address,
|
|
574
568
|
network: config.network,
|
|
575
|
-
ethBalance:
|
|
569
|
+
ethBalance: ethers.formatEther(ethBalance),
|
|
576
570
|
usdcBalance: (Number(usdcBalance) / 1e6).toFixed(2),
|
|
577
571
|
trustScore: score.score,
|
|
578
|
-
trustTier:
|
|
572
|
+
trustTier: getTrustTier(score.score),
|
|
579
573
|
walletContractAddress: config.walletContractAddress ?? null,
|
|
580
574
|
frozen: contractFrozen,
|
|
581
575
|
guardian: contractGuardian,
|
|
@@ -595,11 +589,11 @@ function registerWalletCommands(program) {
|
|
|
595
589
|
if (payload.walletContractAddress)
|
|
596
590
|
treeItems.push({ label: "Contract", value: payload.walletContractAddress });
|
|
597
591
|
if (contractFrozen !== null)
|
|
598
|
-
treeItems.push({ label: "Frozen", value: contractFrozen ?
|
|
599
|
-
if (contractGuardian && contractGuardian !==
|
|
592
|
+
treeItems.push({ label: "Frozen", value: contractFrozen ? c.red("YES") : c.green("no") });
|
|
593
|
+
if (contractGuardian && contractGuardian !== ethers.ZeroAddress)
|
|
600
594
|
treeItems.push({ label: "Guardian", value: contractGuardian });
|
|
601
595
|
treeItems[treeItems.length - 1].last = true;
|
|
602
|
-
|
|
596
|
+
renderTree(treeItems);
|
|
603
597
|
}
|
|
604
598
|
});
|
|
605
599
|
// ─── wc-reset ──────────────────────────────────────────────────────────────
|
|
@@ -612,16 +606,16 @@ function registerWalletCommands(program) {
|
|
|
612
606
|
.description("Clear stale WalletConnect session — forces a fresh QR pairing on next connection")
|
|
613
607
|
.option("--json")
|
|
614
608
|
.action(async (opts) => {
|
|
615
|
-
const config =
|
|
609
|
+
const config = loadConfig();
|
|
616
610
|
const hadSession = !!config.wcSession;
|
|
617
611
|
// 1. Clear from config
|
|
618
|
-
|
|
612
|
+
clearWCSession(config);
|
|
619
613
|
// 2. Wipe WC SDK storage (may be a file or a directory depending on SDK version)
|
|
620
|
-
const wcStoragePath =
|
|
614
|
+
const wcStoragePath = path.join(os.homedir(), ".arc402", "wc-storage.json");
|
|
621
615
|
let storageWiped = false;
|
|
622
616
|
try {
|
|
623
|
-
if (
|
|
624
|
-
|
|
617
|
+
if (fs.existsSync(wcStoragePath)) {
|
|
618
|
+
fs.rmSync(wcStoragePath, { recursive: true, force: true });
|
|
625
619
|
storageWiped = true;
|
|
626
620
|
}
|
|
627
621
|
}
|
|
@@ -631,7 +625,7 @@ function registerWalletCommands(program) {
|
|
|
631
625
|
console.log(JSON.stringify({ ok: false, error: `Could not delete ${wcStoragePath}: ${msg}` }));
|
|
632
626
|
}
|
|
633
627
|
else {
|
|
634
|
-
console.warn(" " +
|
|
628
|
+
console.warn(" " + c.warning + " " + c.yellow(`Could not delete ${wcStoragePath}: ${msg}`));
|
|
635
629
|
console.warn(" You may need to delete it manually.");
|
|
636
630
|
}
|
|
637
631
|
return;
|
|
@@ -640,12 +634,12 @@ function registerWalletCommands(program) {
|
|
|
640
634
|
console.log(JSON.stringify({ ok: true, hadSession, storageWiped }));
|
|
641
635
|
}
|
|
642
636
|
else {
|
|
643
|
-
console.log(" " +
|
|
637
|
+
console.log(" " + c.success + c.white(" WalletConnect session cleared"));
|
|
644
638
|
if (storageWiped)
|
|
645
|
-
console.log(" " +
|
|
639
|
+
console.log(" " + c.dim("Storage wiped:") + " " + c.white(wcStoragePath));
|
|
646
640
|
else
|
|
647
|
-
console.log(" " +
|
|
648
|
-
console.log("\n" +
|
|
641
|
+
console.log(" " + c.dim("(No storage file found — already clean)"));
|
|
642
|
+
console.log("\n" + c.dim("Next: run any wallet command and scan the fresh QR code."));
|
|
649
643
|
}
|
|
650
644
|
});
|
|
651
645
|
// ─── new ───────────────────────────────────────────────────────────────────
|
|
@@ -654,12 +648,12 @@ function registerWalletCommands(program) {
|
|
|
654
648
|
.option("--network <network>", "Network (base-mainnet or base-sepolia)", "base-sepolia")
|
|
655
649
|
.action(async (opts) => {
|
|
656
650
|
const network = opts.network;
|
|
657
|
-
const defaults =
|
|
651
|
+
const defaults = NETWORK_DEFAULTS[network];
|
|
658
652
|
if (!defaults) {
|
|
659
653
|
console.error(`Unknown network: ${network}. Use base-mainnet or base-sepolia.`);
|
|
660
654
|
process.exit(1);
|
|
661
655
|
}
|
|
662
|
-
const generated =
|
|
656
|
+
const generated = ethers.Wallet.createRandom();
|
|
663
657
|
const config = {
|
|
664
658
|
network,
|
|
665
659
|
rpcUrl: defaults.rpcUrl,
|
|
@@ -667,12 +661,12 @@ function registerWalletCommands(program) {
|
|
|
667
661
|
trustRegistryAddress: defaults.trustRegistryAddress,
|
|
668
662
|
walletFactoryAddress: defaults.walletFactoryAddress,
|
|
669
663
|
};
|
|
670
|
-
|
|
671
|
-
|
|
664
|
+
saveConfig(config);
|
|
665
|
+
renderTree([
|
|
672
666
|
{ label: "Address", value: generated.address },
|
|
673
|
-
{ label: "Config", value:
|
|
667
|
+
{ label: "Config", value: getConfigPath(), last: true },
|
|
674
668
|
]);
|
|
675
|
-
console.log(
|
|
669
|
+
console.log(c.dim("Next: fund your wallet with ETH, then run: arc402 wallet deploy"));
|
|
676
670
|
});
|
|
677
671
|
// ─── import ────────────────────────────────────────────────────────────────
|
|
678
672
|
wallet.command("import")
|
|
@@ -681,7 +675,7 @@ function registerWalletCommands(program) {
|
|
|
681
675
|
.option("--key-file <path>", "Read private key from file instead of prompting")
|
|
682
676
|
.action(async (opts) => {
|
|
683
677
|
const network = opts.network;
|
|
684
|
-
const defaults =
|
|
678
|
+
const defaults = NETWORK_DEFAULTS[network];
|
|
685
679
|
if (!defaults) {
|
|
686
680
|
console.error(`Unknown network: ${network}. Use base-mainnet or base-sepolia.`);
|
|
687
681
|
process.exit(1);
|
|
@@ -689,7 +683,7 @@ function registerWalletCommands(program) {
|
|
|
689
683
|
let privateKey;
|
|
690
684
|
if (opts.keyFile) {
|
|
691
685
|
try {
|
|
692
|
-
privateKey =
|
|
686
|
+
privateKey = fs.readFileSync(opts.keyFile, "utf-8").trim();
|
|
693
687
|
}
|
|
694
688
|
catch (e) {
|
|
695
689
|
console.error(`Cannot read key file: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -698,7 +692,7 @@ function registerWalletCommands(program) {
|
|
|
698
692
|
}
|
|
699
693
|
else {
|
|
700
694
|
// Interactive prompt — hidden input avoids shell history
|
|
701
|
-
const answer = await (
|
|
695
|
+
const answer = await prompts({
|
|
702
696
|
type: "password",
|
|
703
697
|
name: "key",
|
|
704
698
|
message: "Paste private key (hidden):",
|
|
@@ -711,7 +705,7 @@ function registerWalletCommands(program) {
|
|
|
711
705
|
}
|
|
712
706
|
let imported;
|
|
713
707
|
try {
|
|
714
|
-
imported = new
|
|
708
|
+
imported = new ethers.Wallet(privateKey);
|
|
715
709
|
}
|
|
716
710
|
catch {
|
|
717
711
|
console.error("Invalid private key. Must be a 0x-prefixed hex string.");
|
|
@@ -724,24 +718,24 @@ function registerWalletCommands(program) {
|
|
|
724
718
|
trustRegistryAddress: defaults.trustRegistryAddress,
|
|
725
719
|
walletFactoryAddress: defaults.walletFactoryAddress,
|
|
726
720
|
};
|
|
727
|
-
|
|
728
|
-
|
|
721
|
+
saveConfig(config);
|
|
722
|
+
renderTree([
|
|
729
723
|
{ label: "Address", value: imported.address },
|
|
730
|
-
{ label: "Config", value:
|
|
724
|
+
{ label: "Config", value: getConfigPath(), last: true },
|
|
731
725
|
]);
|
|
732
|
-
console.warn(" " +
|
|
726
|
+
console.warn(" " + c.warning + " " + c.yellow("Store your private key safely — anyone with it controls your wallet"));
|
|
733
727
|
});
|
|
734
728
|
// ─── fund ──────────────────────────────────────────────────────────────────
|
|
735
729
|
wallet.command("fund")
|
|
736
730
|
.description("Show how to get ETH onto your wallet")
|
|
737
731
|
.action(async () => {
|
|
738
|
-
const config =
|
|
739
|
-
const { provider, address } = await
|
|
732
|
+
const config = loadConfig();
|
|
733
|
+
const { provider, address } = await getClient(config);
|
|
740
734
|
if (!address)
|
|
741
735
|
throw new Error("No wallet configured");
|
|
742
736
|
const ethBalance = await provider.getBalance(address);
|
|
743
737
|
console.log(`\nYour wallet address:\n ${address}`);
|
|
744
|
-
console.log(`\nCurrent balance: ${
|
|
738
|
+
console.log(`\nCurrent balance: ${ethers.formatEther(ethBalance)} ETH`);
|
|
745
739
|
console.log(`\nFunding options:`);
|
|
746
740
|
console.log(` Bridge (Base mainnet): https://bridge.base.org`);
|
|
747
741
|
console.log(` Coinbase: If you use Coinbase, you can withdraw directly to Base mainnet`);
|
|
@@ -754,17 +748,17 @@ function registerWalletCommands(program) {
|
|
|
754
748
|
.description("Check ETH balance on Base")
|
|
755
749
|
.option("--json")
|
|
756
750
|
.action(async (opts) => {
|
|
757
|
-
const config =
|
|
758
|
-
const { provider, address } = await
|
|
751
|
+
const config = loadConfig();
|
|
752
|
+
const { provider, address } = await getClient(config);
|
|
759
753
|
if (!address)
|
|
760
754
|
throw new Error("No wallet configured");
|
|
761
755
|
const ethBalance = await provider.getBalance(address);
|
|
762
|
-
const formatted =
|
|
756
|
+
const formatted = ethers.formatEther(ethBalance);
|
|
763
757
|
if (opts.json) {
|
|
764
758
|
console.log(JSON.stringify({ address, balance: formatted, balanceWei: ethBalance.toString() }));
|
|
765
759
|
}
|
|
766
760
|
else {
|
|
767
|
-
|
|
761
|
+
renderTree([
|
|
768
762
|
{ label: "Address", value: address },
|
|
769
763
|
{ label: "Balance", value: `${formatted} ETH`, last: true },
|
|
770
764
|
]);
|
|
@@ -776,8 +770,8 @@ function registerWalletCommands(program) {
|
|
|
776
770
|
.option("--owner <address>", "Master key address to query (defaults to config.ownerAddress)")
|
|
777
771
|
.option("--json")
|
|
778
772
|
.action(async (opts) => {
|
|
779
|
-
const config =
|
|
780
|
-
const factoryAddress = config.walletFactoryAddress ??
|
|
773
|
+
const config = loadConfig();
|
|
774
|
+
const factoryAddress = config.walletFactoryAddress ?? NETWORK_DEFAULTS[config.network]?.walletFactoryAddress;
|
|
781
775
|
if (!factoryAddress) {
|
|
782
776
|
console.error("walletFactoryAddress not found in config or NETWORK_DEFAULTS.");
|
|
783
777
|
process.exit(1);
|
|
@@ -787,12 +781,12 @@ function registerWalletCommands(program) {
|
|
|
787
781
|
console.error("No owner address. Pass --owner <address> or set ownerAddress in config (run `arc402 wallet deploy` first).");
|
|
788
782
|
process.exit(1);
|
|
789
783
|
}
|
|
790
|
-
const provider = new
|
|
791
|
-
const factory = new
|
|
784
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
785
|
+
const factory = new ethers.Contract(factoryAddress, WALLET_FACTORY_ABI, provider);
|
|
792
786
|
const wallets = await factory.getWallets(ownerAddress);
|
|
793
787
|
const results = await Promise.all(wallets.map(async (addr) => {
|
|
794
|
-
const walletContract = new
|
|
795
|
-
const trustContract = new
|
|
788
|
+
const walletContract = new ethers.Contract(addr, ARC402_WALLET_GUARDIAN_ABI, provider);
|
|
789
|
+
const trustContract = new ethers.Contract(config.trustRegistryAddress, TRUST_REGISTRY_ABI, provider);
|
|
796
790
|
const [frozen, score] = await Promise.all([
|
|
797
791
|
walletContract.frozen().catch(() => null),
|
|
798
792
|
trustContract.getScore(addr).catch(() => BigInt(0)),
|
|
@@ -825,18 +819,18 @@ function registerWalletCommands(program) {
|
|
|
825
819
|
.action(async (address) => {
|
|
826
820
|
let checksumAddress;
|
|
827
821
|
try {
|
|
828
|
-
checksumAddress =
|
|
822
|
+
checksumAddress = ethers.getAddress(address);
|
|
829
823
|
}
|
|
830
824
|
catch {
|
|
831
825
|
console.error(`Invalid address: ${address}`);
|
|
832
826
|
process.exit(1);
|
|
833
827
|
}
|
|
834
|
-
const config =
|
|
835
|
-
const factoryAddress = config.walletFactoryAddress ??
|
|
828
|
+
const config = loadConfig();
|
|
829
|
+
const factoryAddress = config.walletFactoryAddress ?? NETWORK_DEFAULTS[config.network]?.walletFactoryAddress;
|
|
836
830
|
if (factoryAddress && config.ownerAddress) {
|
|
837
831
|
try {
|
|
838
|
-
const provider = new
|
|
839
|
-
const factory = new
|
|
832
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
833
|
+
const factory = new ethers.Contract(factoryAddress, WALLET_FACTORY_ABI, provider);
|
|
840
834
|
const wallets = await factory.getWallets(config.ownerAddress);
|
|
841
835
|
const found = wallets.some((w) => w.toLowerCase() === checksumAddress.toLowerCase());
|
|
842
836
|
if (!found) {
|
|
@@ -847,7 +841,7 @@ function registerWalletCommands(program) {
|
|
|
847
841
|
catch { /* allow override if factory call fails */ }
|
|
848
842
|
}
|
|
849
843
|
config.walletContractAddress = checksumAddress;
|
|
850
|
-
|
|
844
|
+
saveConfig(config);
|
|
851
845
|
console.log(`Active wallet set to ${checksumAddress}`);
|
|
852
846
|
});
|
|
853
847
|
// ─── deploy ────────────────────────────────────────────────────────────────
|
|
@@ -858,60 +852,60 @@ function registerWalletCommands(program) {
|
|
|
858
852
|
.option("--sponsored", "Use CDP paymaster for gas sponsorship (requires paymasterUrl + cdpKeyName + CDP_PRIVATE_KEY env)")
|
|
859
853
|
.option("--dry-run", "Simulate the deployment ceremony without sending transactions")
|
|
860
854
|
.action(async (opts) => {
|
|
861
|
-
const config =
|
|
855
|
+
const config = loadConfig();
|
|
862
856
|
if (opts.dryRun) {
|
|
863
|
-
const factoryAddr = config.walletFactoryAddress ??
|
|
857
|
+
const factoryAddr = config.walletFactoryAddress ?? NETWORK_DEFAULTS[config.network]?.walletFactoryAddress ?? "(not configured)";
|
|
864
858
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
865
859
|
console.log();
|
|
866
|
-
console.log(" " +
|
|
867
|
-
console.log(" " +
|
|
868
|
-
console.log(" " +
|
|
869
|
-
console.log(" " +
|
|
870
|
-
console.log(" " +
|
|
871
|
-
console.log(" " +
|
|
872
|
-
console.log(" " +
|
|
860
|
+
console.log(" " + c.dim("── Dry run: wallet deploy ──────────────────────────────────────"));
|
|
861
|
+
console.log(" " + c.dim("Network: ") + c.white(config.network));
|
|
862
|
+
console.log(" " + c.dim("Chain ID: ") + c.white(String(chainId)));
|
|
863
|
+
console.log(" " + c.dim("RPC: ") + c.white(config.rpcUrl));
|
|
864
|
+
console.log(" " + c.dim("WalletFactory: ") + c.white(factoryAddr));
|
|
865
|
+
console.log(" " + c.dim("Signing method: ") + c.white(opts.smartWallet ? "Base Smart Wallet" : opts.hardware ? "Hardware (WC URI)" : "WalletConnect"));
|
|
866
|
+
console.log(" " + c.dim("Sponsored: ") + c.white(opts.sponsored ? "yes" : "no"));
|
|
873
867
|
console.log();
|
|
874
|
-
console.log(" " +
|
|
868
|
+
console.log(" " + c.dim("Steps that would run:"));
|
|
875
869
|
console.log(" 1. Connect " + (opts.smartWallet ? "Coinbase Smart Wallet" : "WalletConnect") + " session");
|
|
876
870
|
console.log(" 2. Call WalletFactory.createWallet() → deploy ARC402Wallet");
|
|
877
871
|
console.log(" 3. Save walletContractAddress to config");
|
|
878
872
|
console.log(" 4. Run onboarding ceremony (PolicyEngine, machine key, agent registration)");
|
|
879
873
|
console.log();
|
|
880
|
-
console.log(" " +
|
|
874
|
+
console.log(" " + c.dim("No transactions sent (--dry-run mode)."));
|
|
881
875
|
console.log();
|
|
882
876
|
return;
|
|
883
877
|
}
|
|
884
|
-
const factoryAddress = config.walletFactoryAddress ??
|
|
878
|
+
const factoryAddress = config.walletFactoryAddress ?? NETWORK_DEFAULTS[config.network]?.walletFactoryAddress;
|
|
885
879
|
if (!factoryAddress) {
|
|
886
880
|
console.error("walletFactoryAddress not found in config or NETWORK_DEFAULTS. Add walletFactoryAddress to your config.");
|
|
887
881
|
process.exit(1);
|
|
888
882
|
}
|
|
889
883
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
890
|
-
const provider = new
|
|
891
|
-
const factoryInterface = new
|
|
884
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
885
|
+
const factoryInterface = new ethers.Interface(WALLET_FACTORY_ABI);
|
|
892
886
|
if (opts.sponsored) {
|
|
893
887
|
// ── Sponsored deploy via CDP paymaster + ERC-4337 bundler ─────────────
|
|
894
888
|
// Note: WalletFactoryV3/V4 use msg.sender as wallet owner. In ERC-4337
|
|
895
889
|
// context msg.sender = EntryPoint. A factory upgrade with explicit owner
|
|
896
890
|
// param is needed for fully correct sponsored deployment. Until then,
|
|
897
891
|
// this path is available for testing and future-proofing.
|
|
898
|
-
const paymasterUrl = config.paymasterUrl ??
|
|
892
|
+
const paymasterUrl = config.paymasterUrl ?? NETWORK_DEFAULTS[config.network]?.paymasterUrl;
|
|
899
893
|
const cdpKeyName = config.cdpKeyName ?? process.env.CDP_KEY_NAME;
|
|
900
894
|
const cdpPrivateKey = config.cdpPrivateKey ?? process.env.CDP_PRIVATE_KEY;
|
|
901
895
|
if (!paymasterUrl) {
|
|
902
896
|
console.error("paymasterUrl not configured. Add it to config or set NEXT_PUBLIC_PAYMASTER_URL.");
|
|
903
897
|
process.exit(1);
|
|
904
898
|
}
|
|
905
|
-
const { signer, address: ownerAddress } = await
|
|
899
|
+
const { signer, address: ownerAddress } = await requireSigner(config);
|
|
906
900
|
const bundlerUrl = process.env.BUNDLER_URL ?? "https://api.pimlico.io/v2/base/rpc";
|
|
907
|
-
const pm = new
|
|
908
|
-
const bundler = new
|
|
901
|
+
const pm = new PaymasterClient(paymasterUrl, cdpKeyName, cdpPrivateKey);
|
|
902
|
+
const bundler = new BundlerClient(bundlerUrl, DEFAULT_ENTRY_POINT, chainId);
|
|
909
903
|
console.log(`Sponsoring deploy via ${paymasterUrl}...`);
|
|
910
|
-
const factoryIface = new
|
|
911
|
-
const factoryData = factoryIface.encodeFunctionData("createWallet", [
|
|
904
|
+
const factoryIface = new ethers.Interface(WALLET_FACTORY_ABI);
|
|
905
|
+
const factoryData = factoryIface.encodeFunctionData("createWallet", [DEFAULT_ENTRY_POINT]);
|
|
912
906
|
// Predict counterfactual sender address using EntryPoint.getSenderAddress
|
|
913
|
-
const entryPoint = new
|
|
914
|
-
const initCodePacked =
|
|
907
|
+
const entryPoint = new ethers.Contract(DEFAULT_ENTRY_POINT, ["function getSenderAddress(bytes calldata initCode) external"], provider);
|
|
908
|
+
const initCodePacked = ethers.concat([factoryAddress, factoryData]);
|
|
915
909
|
let senderAddress;
|
|
916
910
|
try {
|
|
917
911
|
// getSenderAddress always reverts with SenderAddressResult(address)
|
|
@@ -925,7 +919,7 @@ function registerWalletCommands(program) {
|
|
|
925
919
|
console.error("Could not predict wallet address:", msg);
|
|
926
920
|
process.exit(1);
|
|
927
921
|
}
|
|
928
|
-
senderAddress =
|
|
922
|
+
senderAddress = ethers.getAddress("0x" + match[1].slice(24));
|
|
929
923
|
}
|
|
930
924
|
console.log(`Predicted wallet address: ${senderAddress}`);
|
|
931
925
|
const userOp = await pm.sponsorUserOperation({
|
|
@@ -934,22 +928,22 @@ function registerWalletCommands(program) {
|
|
|
934
928
|
callData: "0x",
|
|
935
929
|
factory: factoryAddress,
|
|
936
930
|
factoryData,
|
|
937
|
-
callGasLimit:
|
|
938
|
-
verificationGasLimit:
|
|
939
|
-
preVerificationGas:
|
|
940
|
-
maxFeePerGas:
|
|
941
|
-
maxPriorityFeePerGas:
|
|
931
|
+
callGasLimit: ethers.toBeHex(300_000),
|
|
932
|
+
verificationGasLimit: ethers.toBeHex(400_000),
|
|
933
|
+
preVerificationGas: ethers.toBeHex(60_000),
|
|
934
|
+
maxFeePerGas: ethers.toBeHex((await provider.getFeeData()).maxFeePerGas ?? BigInt(1_000_000_000)),
|
|
935
|
+
maxPriorityFeePerGas: ethers.toBeHex((await provider.getFeeData()).maxPriorityFeePerGas ?? BigInt(100_000_000)),
|
|
942
936
|
signature: "0x",
|
|
943
|
-
},
|
|
937
|
+
}, DEFAULT_ENTRY_POINT);
|
|
944
938
|
// Sign UserOp with owner key
|
|
945
|
-
const userOpHash =
|
|
939
|
+
const userOpHash = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(["address", "uint256", "bytes32", "bytes32", "bytes32", "uint256", "bytes32", "bytes32"], [
|
|
946
940
|
userOp.sender, BigInt(userOp.nonce),
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
BigInt(chainId),
|
|
941
|
+
ethers.keccak256(userOp.factory ? ethers.concat([userOp.factory, userOp.factoryData ?? "0x"]) : "0x"),
|
|
942
|
+
ethers.keccak256(userOp.callData),
|
|
943
|
+
ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256", "uint256", "uint256", "uint256", "address", "bytes"], [userOp.verificationGasLimit, userOp.callGasLimit, userOp.preVerificationGas, userOp.maxFeePerGas, userOp.maxPriorityFeePerGas, userOp.paymaster ?? ethers.ZeroAddress, userOp.paymasterData ?? "0x"])),
|
|
944
|
+
BigInt(chainId), DEFAULT_ENTRY_POINT, ethers.ZeroHash,
|
|
951
945
|
]));
|
|
952
|
-
userOp.signature = await signer.signMessage(
|
|
946
|
+
userOp.signature = await signer.signMessage(ethers.getBytes(userOpHash));
|
|
953
947
|
const userOpHash2 = await bundler.sendUserOperation(userOp);
|
|
954
948
|
console.log(`UserOp submitted: ${userOpHash2}`);
|
|
955
949
|
console.log("Waiting for confirmation...");
|
|
@@ -960,24 +954,24 @@ function registerWalletCommands(program) {
|
|
|
960
954
|
}
|
|
961
955
|
config.walletContractAddress = senderAddress;
|
|
962
956
|
config.ownerAddress = ownerAddress;
|
|
963
|
-
|
|
964
|
-
console.log("\n" +
|
|
965
|
-
|
|
957
|
+
saveConfig(config);
|
|
958
|
+
console.log("\n" + c.success + c.white(" ARC402Wallet deployed (sponsored)"));
|
|
959
|
+
renderTree([
|
|
966
960
|
{ label: "Wallet", value: senderAddress },
|
|
967
961
|
{ label: "Owner", value: ownerAddress },
|
|
968
962
|
{ label: "Gas", value: "Sponsorship active — initial setup ops are free", last: true },
|
|
969
963
|
]);
|
|
970
|
-
console.log(" " +
|
|
971
|
-
console.log(
|
|
972
|
-
console.log(
|
|
973
|
-
console.log(
|
|
974
|
-
console.log("\n" +
|
|
975
|
-
console.log("\n" +
|
|
976
|
-
console.log("\n" +
|
|
964
|
+
console.log(" " + c.warning + " " + c.yellow("IMPORTANT: Onboarding ceremony was not run on this wallet."));
|
|
965
|
+
console.log(c.dim(" Category spend limits have NOT been configured. All executeSpend and"));
|
|
966
|
+
console.log(c.dim(` executeTokenSpend calls will fail with "PolicyEngine: category not configured"`));
|
|
967
|
+
console.log(c.dim(" until you run governance setup manually via WalletConnect:"));
|
|
968
|
+
console.log("\n" + c.dim(" arc402 wallet governance setup"));
|
|
969
|
+
console.log("\n" + c.dim(" This must be done before making any spend from this wallet."));
|
|
970
|
+
console.log("\n" + c.dim("Next: arc402 wallet set-passkey <x> <y> --sponsored"));
|
|
977
971
|
printOpenShellHint();
|
|
978
972
|
}
|
|
979
973
|
else if (opts.smartWallet) {
|
|
980
|
-
const { txHash, account } = await
|
|
974
|
+
const { txHash, account } = await requestCoinbaseSmartWalletSignature(chainId, (ownerAccount) => ({
|
|
981
975
|
to: factoryAddress,
|
|
982
976
|
data: factoryInterface.encodeFunctionData("createWallet", ["0x0000000071727De22E5E9d8BAf0edAc6f37da032"]),
|
|
983
977
|
value: "0x0",
|
|
@@ -990,7 +984,7 @@ function registerWalletCommands(program) {
|
|
|
990
984
|
process.exit(1);
|
|
991
985
|
}
|
|
992
986
|
let walletAddress = null;
|
|
993
|
-
const factoryContract = new
|
|
987
|
+
const factoryContract = new ethers.Contract(factoryAddress, WALLET_FACTORY_ABI, provider);
|
|
994
988
|
for (const log of receipt.logs) {
|
|
995
989
|
try {
|
|
996
990
|
const parsed = factoryContract.interface.parseLog(log);
|
|
@@ -1007,14 +1001,14 @@ function registerWalletCommands(program) {
|
|
|
1007
1001
|
}
|
|
1008
1002
|
config.walletContractAddress = walletAddress;
|
|
1009
1003
|
config.ownerAddress = account;
|
|
1010
|
-
|
|
1011
|
-
console.log("\n" +
|
|
1012
|
-
|
|
1004
|
+
saveConfig(config);
|
|
1005
|
+
console.log("\n" + c.success + c.white(" ARC402Wallet deployed"));
|
|
1006
|
+
renderTree([
|
|
1013
1007
|
{ label: "Wallet", value: walletAddress },
|
|
1014
|
-
{ label: "Owner", value: account +
|
|
1008
|
+
{ label: "Owner", value: account + c.dim(" (Base Smart Wallet)"), last: true },
|
|
1015
1009
|
]);
|
|
1016
|
-
console.log(
|
|
1017
|
-
console.log(
|
|
1010
|
+
console.log(c.dim("Your wallet contract is ready for policy enforcement"));
|
|
1011
|
+
console.log(c.dim("\nNext: run 'arc402 wallet set-guardian' to configure the emergency guardian key."));
|
|
1018
1012
|
printOpenShellHint();
|
|
1019
1013
|
}
|
|
1020
1014
|
else if (config.walletConnectProjectId) {
|
|
@@ -1031,37 +1025,37 @@ function registerWalletCommands(program) {
|
|
|
1031
1025
|
2: "machine key", 3: "passkey", 4: "policy setup", 5: "agent registration",
|
|
1032
1026
|
};
|
|
1033
1027
|
const nextStep = (resumeProgress.step ?? 1) + 1;
|
|
1034
|
-
console.log(" " +
|
|
1028
|
+
console.log(" " + c.dim(`◈ Resuming onboarding from step ${nextStep} (${stepNames[nextStep] ?? "ceremony"})...`));
|
|
1035
1029
|
}
|
|
1036
1030
|
// ── Gas estimation ─────────────────────────────────────────────────────
|
|
1037
1031
|
if (!isResuming) {
|
|
1038
1032
|
let gasMsg = "~0.003 ETH (6 transactions on Base)";
|
|
1039
1033
|
try {
|
|
1040
1034
|
const feeData = await provider.getFeeData();
|
|
1041
|
-
const gasPrice = feeData.maxFeePerGas ?? feeData.gasPrice ?? BigInt(
|
|
1035
|
+
const gasPrice = feeData.maxFeePerGas ?? feeData.gasPrice ?? BigInt(1_500_000_000);
|
|
1042
1036
|
const deployGas = await provider.estimateGas({
|
|
1043
1037
|
to: factoryAddress,
|
|
1044
1038
|
data: factoryInterface.encodeFunctionData("createWallet", ["0x0000000071727De22E5E9d8BAf0edAc6f37da032"]),
|
|
1045
|
-
}).catch(() => BigInt(
|
|
1046
|
-
const ceremonyGas = BigInt(
|
|
1047
|
-
const totalGasEth = parseFloat(
|
|
1039
|
+
}).catch(() => BigInt(280_000));
|
|
1040
|
+
const ceremonyGas = BigInt(700_000); // ~5 ceremony txs × ~140k each
|
|
1041
|
+
const totalGasEth = parseFloat(ethers.formatEther((deployGas + ceremonyGas) * gasPrice));
|
|
1048
1042
|
gasMsg = `~${totalGasEth.toFixed(4)} ETH (6 transactions on Base)`;
|
|
1049
1043
|
}
|
|
1050
1044
|
catch { /* use default */ }
|
|
1051
|
-
console.log(" " +
|
|
1045
|
+
console.log(" " + c.dim(`◈ Estimated gas: ${gasMsg}`));
|
|
1052
1046
|
}
|
|
1053
1047
|
// ── Step 1: Connect ────────────────────────────────────────────────────
|
|
1054
1048
|
const connectPrompt = isResuming
|
|
1055
1049
|
? "Connect wallet to resume onboarding"
|
|
1056
1050
|
: "Approve ARC402Wallet deployment — you will be set as owner";
|
|
1057
|
-
const { client, session, account } = await
|
|
1051
|
+
const { client, session, account } = await connectPhoneWallet(config.walletConnectProjectId, chainId, config, { telegramOpts, prompt: connectPrompt, hardware: !!opts.hardware });
|
|
1058
1052
|
const networkName = chainId === 8453 ? "Base" : "Base Sepolia";
|
|
1059
1053
|
const shortAddr = `${account.slice(0, 6)}...${account.slice(-5)}`;
|
|
1060
|
-
console.log("\n" +
|
|
1054
|
+
console.log("\n" + c.success + c.white(` Connected: ${shortAddr} on ${networkName}`));
|
|
1061
1055
|
if (telegramOpts && !isResuming) {
|
|
1062
1056
|
// Send "connected" message with a deploy confirmation button.
|
|
1063
1057
|
// TODO: wire up full callback_data round-trip when a persistent bot process is available.
|
|
1064
|
-
await
|
|
1058
|
+
await sendTelegramMessage({
|
|
1065
1059
|
botToken: telegramOpts.botToken,
|
|
1066
1060
|
chatId: telegramOpts.chatId,
|
|
1067
1061
|
threadId: telegramOpts.threadId,
|
|
@@ -1073,13 +1067,13 @@ function registerWalletCommands(program) {
|
|
|
1073
1067
|
if (isResuming) {
|
|
1074
1068
|
// Resume: skip deploy, use existing wallet
|
|
1075
1069
|
walletAddress = config.walletContractAddress;
|
|
1076
|
-
console.log(" " +
|
|
1070
|
+
console.log(" " + c.dim(`◈ Using existing wallet: ${walletAddress}`));
|
|
1077
1071
|
}
|
|
1078
1072
|
else {
|
|
1079
1073
|
// ── Step 2: Confirm & Deploy ─────────────────────────────────────────
|
|
1080
1074
|
// WalletConnect approval already confirmed intent — sending automatically
|
|
1081
1075
|
console.log("Deploying...");
|
|
1082
|
-
const txHash = await
|
|
1076
|
+
const txHash = await sendTransactionWithSession(client, session, account, chainId, {
|
|
1083
1077
|
to: factoryAddress,
|
|
1084
1078
|
data: factoryInterface.encodeFunctionData("createWallet", ["0x0000000071727De22E5E9d8BAf0edAc6f37da032"]),
|
|
1085
1079
|
value: "0x0",
|
|
@@ -1092,7 +1086,7 @@ function registerWalletCommands(program) {
|
|
|
1092
1086
|
process.exit(1);
|
|
1093
1087
|
}
|
|
1094
1088
|
let deployedWallet = null;
|
|
1095
|
-
const factoryContract = new
|
|
1089
|
+
const factoryContract = new ethers.Contract(factoryAddress, WALLET_FACTORY_ABI, provider);
|
|
1096
1090
|
for (const log of receipt.logs) {
|
|
1097
1091
|
try {
|
|
1098
1092
|
const parsed = factoryContract.interface.parseLog(log);
|
|
@@ -1111,24 +1105,24 @@ function registerWalletCommands(program) {
|
|
|
1111
1105
|
// ── Step 1 complete: save wallet + owner immediately ─────────────────
|
|
1112
1106
|
config.walletContractAddress = walletAddress;
|
|
1113
1107
|
config.ownerAddress = account;
|
|
1114
|
-
|
|
1108
|
+
saveConfig(config);
|
|
1115
1109
|
try {
|
|
1116
|
-
|
|
1110
|
+
fs.chmodSync(getConfigPath(), 0o600);
|
|
1117
1111
|
}
|
|
1118
1112
|
catch { /* best-effort */ }
|
|
1119
|
-
console.log("\n " +
|
|
1120
|
-
|
|
1113
|
+
console.log("\n " + c.success + c.white(" Wallet deployed"));
|
|
1114
|
+
renderTree([
|
|
1121
1115
|
{ label: "Wallet", value: walletAddress },
|
|
1122
1116
|
{ label: "Owner", value: account, last: true },
|
|
1123
1117
|
]);
|
|
1124
1118
|
}
|
|
1125
1119
|
// ── Steps 2–6: Complete onboarding ceremony (same WalletConnect session)
|
|
1126
1120
|
const sendTxCeremony = async (call, description) => {
|
|
1127
|
-
console.log(" " +
|
|
1128
|
-
const hash = await
|
|
1129
|
-
console.log(" " +
|
|
1121
|
+
console.log(" " + c.dim(`◈ ${description}`));
|
|
1122
|
+
const hash = await sendTransactionWithSession(client, session, account, chainId, call);
|
|
1123
|
+
console.log(" " + c.dim(" waiting for confirmation..."));
|
|
1130
1124
|
await provider.waitForTransaction(hash, 1);
|
|
1131
|
-
console.log(" " +
|
|
1125
|
+
console.log(" " + c.success + " " + c.white(description));
|
|
1132
1126
|
return hash;
|
|
1133
1127
|
};
|
|
1134
1128
|
await runCompleteOnboardingCeremony(walletAddress, account, config, provider, sendTxCeremony);
|
|
@@ -1137,9 +1131,9 @@ function registerWalletCommands(program) {
|
|
|
1137
1131
|
else {
|
|
1138
1132
|
console.warn("⚠ WalletConnect not configured. Using stored private key (insecure).");
|
|
1139
1133
|
console.warn(" Run `arc402 config set walletConnectProjectId <id>` to enable phone wallet signing.");
|
|
1140
|
-
const { signer, address } = await
|
|
1141
|
-
const factory = new
|
|
1142
|
-
const deploySpinner =
|
|
1134
|
+
const { signer, address } = await requireSigner(config);
|
|
1135
|
+
const factory = new ethers.Contract(factoryAddress, WALLET_FACTORY_ABI, signer);
|
|
1136
|
+
const deploySpinner = startSpinner(`Deploying ARC402Wallet via factory at ${factoryAddress}...`);
|
|
1143
1137
|
const tx = await factory.createWallet("0x0000000071727De22E5E9d8BAf0edAc6f37da032");
|
|
1144
1138
|
const receipt = await tx.wait();
|
|
1145
1139
|
let walletAddress = null;
|
|
@@ -1159,7 +1153,7 @@ function registerWalletCommands(program) {
|
|
|
1159
1153
|
}
|
|
1160
1154
|
deploySpinner.succeed("Wallet deployed");
|
|
1161
1155
|
// Generate guardian key (separate from hot key) and call setGuardian
|
|
1162
|
-
const guardianWallet =
|
|
1156
|
+
const guardianWallet = ethers.Wallet.createRandom();
|
|
1163
1157
|
config.walletContractAddress = walletAddress;
|
|
1164
1158
|
config.ownerAddress = address;
|
|
1165
1159
|
config.guardianAddress = guardianWallet.address;
|
|
@@ -1167,23 +1161,23 @@ function registerWalletCommands(program) {
|
|
|
1167
1161
|
saveGuardianKey(guardianWallet.privateKey);
|
|
1168
1162
|
if (config.guardianPrivateKey)
|
|
1169
1163
|
delete config.guardianPrivateKey;
|
|
1170
|
-
|
|
1164
|
+
saveConfig(config);
|
|
1171
1165
|
// Call setGuardian on the deployed wallet
|
|
1172
|
-
const walletContract = new
|
|
1166
|
+
const walletContract = new ethers.Contract(walletAddress, ARC402_WALLET_GUARDIAN_ABI, signer);
|
|
1173
1167
|
const setGuardianTx = await walletContract.setGuardian(guardianWallet.address);
|
|
1174
1168
|
await setGuardianTx.wait();
|
|
1175
1169
|
// ── Mandatory onboarding ceremony (private key path) ──────────────────
|
|
1176
1170
|
console.log("\nRunning mandatory onboarding ceremony...");
|
|
1177
|
-
const provider2 = new
|
|
1171
|
+
const provider2 = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1178
1172
|
await runWalletOnboardingCeremony(walletAddress, address, config, provider2, async (call, description) => {
|
|
1179
|
-
console.log(" " +
|
|
1173
|
+
console.log(" " + c.dim(`Sending: ${description}`));
|
|
1180
1174
|
const tx2 = await signer.sendTransaction({ to: call.to, data: call.data, value: call.value === "0x0" ? 0n : BigInt(call.value) });
|
|
1181
1175
|
await tx2.wait(1);
|
|
1182
|
-
console.log(" " +
|
|
1176
|
+
console.log(" " + c.success + " " + c.dim(description) + " " + c.dim(tx2.hash));
|
|
1183
1177
|
return tx2.hash;
|
|
1184
1178
|
});
|
|
1185
|
-
console.log(` ${
|
|
1186
|
-
|
|
1179
|
+
console.log(` ${c.success} ARC402Wallet deployed`);
|
|
1180
|
+
renderTree([
|
|
1187
1181
|
{ label: "Wallet", value: walletAddress },
|
|
1188
1182
|
{ label: "Guardian", value: guardianWallet.address, last: true },
|
|
1189
1183
|
]);
|
|
@@ -1197,12 +1191,12 @@ function registerWalletCommands(program) {
|
|
|
1197
1191
|
.description("Send ETH from configured wallet (amount: '0.001eth' or wei)")
|
|
1198
1192
|
.option("--json")
|
|
1199
1193
|
.action(async (to, amountRaw, opts) => {
|
|
1200
|
-
const config =
|
|
1201
|
-
const { signer } = await
|
|
1194
|
+
const config = loadConfig();
|
|
1195
|
+
const { signer } = await requireSigner(config);
|
|
1202
1196
|
const value = parseAmount(amountRaw);
|
|
1203
1197
|
const tx = await signer.sendTransaction({ to, value });
|
|
1204
1198
|
if (opts.json) {
|
|
1205
|
-
console.log(JSON.stringify({ txHash: tx.hash, to, amount:
|
|
1199
|
+
console.log(JSON.stringify({ txHash: tx.hash, to, amount: ethers.formatEther(value) }));
|
|
1206
1200
|
}
|
|
1207
1201
|
else {
|
|
1208
1202
|
console.log(`Tx hash: ${tx.hash}`);
|
|
@@ -1214,22 +1208,22 @@ function registerWalletCommands(program) {
|
|
|
1214
1208
|
.description("Show per-tx and daily spending limits for a category")
|
|
1215
1209
|
.requiredOption("--category <cat>", "Category name (e.g. code.review)")
|
|
1216
1210
|
.action(async (opts) => {
|
|
1217
|
-
const config =
|
|
1211
|
+
const config = loadConfig();
|
|
1218
1212
|
const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
|
|
1219
1213
|
const walletAddr = config.walletContractAddress;
|
|
1220
1214
|
if (!walletAddr) {
|
|
1221
1215
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1222
1216
|
process.exit(1);
|
|
1223
1217
|
}
|
|
1224
|
-
const provider = new
|
|
1225
|
-
const contract = new
|
|
1218
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1219
|
+
const contract = new ethers.Contract(policyAddress, POLICY_ENGINE_LIMITS_ABI, provider);
|
|
1226
1220
|
const [perTxLimit, dailyLimit] = await Promise.all([
|
|
1227
1221
|
contract.categoryLimits(walletAddr, opts.category),
|
|
1228
1222
|
contract.dailyCategoryLimit(walletAddr, opts.category),
|
|
1229
1223
|
]);
|
|
1230
1224
|
console.log(`Category: ${opts.category}`);
|
|
1231
|
-
console.log(`Per-tx: ${perTxLimit === 0n ? "(not set)" :
|
|
1232
|
-
console.log(`Daily: ${dailyLimit === 0n ? "(not set)" :
|
|
1225
|
+
console.log(`Per-tx: ${perTxLimit === 0n ? "(not set)" : ethers.formatEther(perTxLimit) + " ETH"}`);
|
|
1226
|
+
console.log(`Daily: ${dailyLimit === 0n ? "(not set)" : ethers.formatEther(dailyLimit) + " ETH"}`);
|
|
1233
1227
|
if (dailyLimit > 0n) {
|
|
1234
1228
|
console.log(`\nNote: Daily limits use two 12-hour buckets (current + previous window).`);
|
|
1235
1229
|
console.log(` The effective limit applies across a rolling 12-24 hour period, not a strict calendar day.`);
|
|
@@ -1240,19 +1234,19 @@ function registerWalletCommands(program) {
|
|
|
1240
1234
|
.requiredOption("--category <cat>", "Category name (e.g. code.review)")
|
|
1241
1235
|
.requiredOption("--amount <eth>", "Limit in ETH (e.g. 0.1)")
|
|
1242
1236
|
.action(async (opts) => {
|
|
1243
|
-
const config =
|
|
1237
|
+
const config = loadConfig();
|
|
1244
1238
|
const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
|
|
1245
1239
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1246
|
-
const amount =
|
|
1247
|
-
const policyInterface = new
|
|
1240
|
+
const amount = ethers.parseEther(opts.amount);
|
|
1241
|
+
const policyInterface = new ethers.Interface(POLICY_ENGINE_LIMITS_ABI);
|
|
1248
1242
|
if (config.walletConnectProjectId) {
|
|
1249
1243
|
const walletAddr = config.walletContractAddress;
|
|
1250
1244
|
if (!walletAddr) {
|
|
1251
1245
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1252
1246
|
process.exit(1);
|
|
1253
1247
|
}
|
|
1254
|
-
const provider = new
|
|
1255
|
-
const { txHash } = await
|
|
1248
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1249
|
+
const { txHash } = await requestPhoneWalletSignature(config.walletConnectProjectId, chainId, (account) => ({
|
|
1256
1250
|
to: policyAddress,
|
|
1257
1251
|
data: policyInterface.encodeFunctionData("setCategoryLimitFor", [walletAddr, opts.category, amount]),
|
|
1258
1252
|
value: "0x0",
|
|
@@ -1268,8 +1262,8 @@ function registerWalletCommands(program) {
|
|
|
1268
1262
|
else {
|
|
1269
1263
|
console.warn("⚠ WalletConnect not configured. Using stored private key (insecure).");
|
|
1270
1264
|
console.warn(" Run `arc402 config set walletConnectProjectId <id>` to enable phone wallet signing.");
|
|
1271
|
-
const { signer, address } = await
|
|
1272
|
-
const contract = new
|
|
1265
|
+
const { signer, address } = await requireSigner(config);
|
|
1266
|
+
const contract = new ethers.Contract(policyAddress, POLICY_ENGINE_LIMITS_ABI, signer);
|
|
1273
1267
|
await (await contract.setCategoryLimitFor(address, opts.category, amount)).wait();
|
|
1274
1268
|
console.log(`Spend limit for ${opts.category} set to ${opts.amount} ETH`);
|
|
1275
1269
|
}
|
|
@@ -1284,7 +1278,7 @@ function registerWalletCommands(program) {
|
|
|
1284
1278
|
.requiredOption("--category <cat>", "Category name (e.g. compute)")
|
|
1285
1279
|
.requiredOption("--amount <eth>", "Daily limit in ETH (e.g. 0.5)")
|
|
1286
1280
|
.action(async (opts) => {
|
|
1287
|
-
const config =
|
|
1281
|
+
const config = loadConfig();
|
|
1288
1282
|
console.log(`\nNote: ARC-402 has two independent velocity limit layers:`);
|
|
1289
1283
|
console.log(` 1. Wallet-level (arc402 wallet set-velocity-limit): ETH cap per rolling hour, enforced by ARC402Wallet contract. Breach auto-freezes wallet.`);
|
|
1290
1284
|
console.log(` 2. PolicyEngine-level (arc402 wallet policy set-daily-limit): Per-category daily cap, enforced by PolicyEngine. Breach returns a soft error without freezing.`);
|
|
@@ -1296,14 +1290,14 @@ function registerWalletCommands(program) {
|
|
|
1296
1290
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1297
1291
|
process.exit(1);
|
|
1298
1292
|
}
|
|
1299
|
-
const amount =
|
|
1293
|
+
const amount = ethers.parseEther(opts.amount);
|
|
1300
1294
|
console.log(`\nNote: Daily limits use two 12-hour buckets (current + previous window).`);
|
|
1301
1295
|
console.log(` The effective limit applies across a rolling 12-24 hour period, not a strict calendar day.`);
|
|
1302
1296
|
console.log(` Setting daily limit for category "${opts.category}" to ${opts.amount} ETH.\n`);
|
|
1303
|
-
const policyInterface = new
|
|
1297
|
+
const policyInterface = new ethers.Interface(POLICY_ENGINE_LIMITS_ABI);
|
|
1304
1298
|
if (config.walletConnectProjectId) {
|
|
1305
|
-
const provider = new
|
|
1306
|
-
const { txHash } = await
|
|
1299
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1300
|
+
const { txHash } = await requestPhoneWalletSignature(config.walletConnectProjectId, chainId, () => ({
|
|
1307
1301
|
to: policyAddress,
|
|
1308
1302
|
data: policyInterface.encodeFunctionData("setDailyLimitFor", [walletAddr, opts.category, amount]),
|
|
1309
1303
|
value: "0x0",
|
|
@@ -1317,8 +1311,8 @@ function registerWalletCommands(program) {
|
|
|
1317
1311
|
}
|
|
1318
1312
|
else {
|
|
1319
1313
|
console.warn("⚠ WalletConnect not configured. Using stored private key (insecure).");
|
|
1320
|
-
const { signer, address } = await
|
|
1321
|
-
const contract = new
|
|
1314
|
+
const { signer, address } = await requireSigner(config);
|
|
1315
|
+
const contract = new ethers.Contract(policyAddress, POLICY_ENGINE_LIMITS_ABI, signer);
|
|
1322
1316
|
await (await contract.setDailyLimitFor(address, opts.category, amount)).wait();
|
|
1323
1317
|
console.log(`Daily limit for ${opts.category} set to ${opts.amount} ETH (12/24h rolling window)`);
|
|
1324
1318
|
}
|
|
@@ -1326,7 +1320,7 @@ function registerWalletCommands(program) {
|
|
|
1326
1320
|
walletPolicy.command("set <policyId>")
|
|
1327
1321
|
.description("Set the active policy ID on ARC402Wallet (phone wallet signs via WalletConnect)")
|
|
1328
1322
|
.action(async (policyId) => {
|
|
1329
|
-
const config =
|
|
1323
|
+
const config = loadConfig();
|
|
1330
1324
|
if (!config.walletContractAddress) {
|
|
1331
1325
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1332
1326
|
process.exit(1);
|
|
@@ -1338,18 +1332,18 @@ function registerWalletCommands(program) {
|
|
|
1338
1332
|
// Normalise policyId to bytes32 hex
|
|
1339
1333
|
let policyIdHex;
|
|
1340
1334
|
try {
|
|
1341
|
-
policyIdHex =
|
|
1335
|
+
policyIdHex = ethers.zeroPadValue(ethers.hexlify(policyId.startsWith("0x") ? policyId : ethers.toUtf8Bytes(policyId)), 32);
|
|
1342
1336
|
}
|
|
1343
1337
|
catch {
|
|
1344
1338
|
console.error("Invalid policyId — must be a hex string (0x…) or UTF-8 label.");
|
|
1345
1339
|
process.exit(1);
|
|
1346
1340
|
}
|
|
1347
1341
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1348
|
-
const provider = new
|
|
1349
|
-
const ownerInterface = new
|
|
1342
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1343
|
+
const ownerInterface = new ethers.Interface(ARC402_WALLET_OWNER_ABI);
|
|
1350
1344
|
let currentPolicy = "(unknown)";
|
|
1351
1345
|
try {
|
|
1352
|
-
const walletContract = new
|
|
1346
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_OWNER_ABI, provider);
|
|
1353
1347
|
currentPolicy = await walletContract.activePolicyId();
|
|
1354
1348
|
}
|
|
1355
1349
|
catch { /* contract may not be deployed yet */ }
|
|
@@ -1359,15 +1353,15 @@ function registerWalletCommands(program) {
|
|
|
1359
1353
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1360
1354
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1361
1355
|
: undefined;
|
|
1362
|
-
const { txHash } = await
|
|
1356
|
+
const { txHash } = await requestPhoneWalletSignature(config.walletConnectProjectId, chainId, () => ({
|
|
1363
1357
|
to: config.walletContractAddress,
|
|
1364
1358
|
data: ownerInterface.encodeFunctionData("updatePolicy", [policyIdHex]),
|
|
1365
1359
|
value: "0x0",
|
|
1366
1360
|
}), `Approve: update policy to ${policyIdHex}`, telegramOpts, config);
|
|
1367
1361
|
await provider.waitForTransaction(txHash);
|
|
1368
|
-
console.log("\n" +
|
|
1369
|
-
console.log(" " +
|
|
1370
|
-
console.log(" " +
|
|
1362
|
+
console.log("\n" + c.success + c.white(" Active policy updated"));
|
|
1363
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1364
|
+
console.log(" " + c.dim("Policy:") + " " + c.white(policyIdHex));
|
|
1371
1365
|
});
|
|
1372
1366
|
// ─── freeze (guardian key — emergency wallet freeze) ──────────────────────
|
|
1373
1367
|
//
|
|
@@ -1379,7 +1373,7 @@ function registerWalletCommands(program) {
|
|
|
1379
1373
|
.option("--drain", "Also drain all ETH to owner address (use when machine compromise is suspected)")
|
|
1380
1374
|
.option("--json")
|
|
1381
1375
|
.action(async (opts) => {
|
|
1382
|
-
const config =
|
|
1376
|
+
const config = loadConfig();
|
|
1383
1377
|
if (!config.walletContractAddress) {
|
|
1384
1378
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1385
1379
|
process.exit(1);
|
|
@@ -1389,9 +1383,9 @@ function registerWalletCommands(program) {
|
|
|
1389
1383
|
console.error(`Guardian key not found. Expected at ~/.arc402/guardian.key (or guardianPrivateKey in config for legacy setups).`);
|
|
1390
1384
|
process.exit(1);
|
|
1391
1385
|
}
|
|
1392
|
-
const provider = new
|
|
1393
|
-
const guardianSigner = new
|
|
1394
|
-
const walletContract = new
|
|
1386
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1387
|
+
const guardianSigner = new ethers.Wallet(guardianKey, provider);
|
|
1388
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_GUARDIAN_ABI, guardianSigner);
|
|
1395
1389
|
let tx;
|
|
1396
1390
|
if (opts.drain) {
|
|
1397
1391
|
console.log("Triggering freeze-and-drain via guardian key...");
|
|
@@ -1422,7 +1416,7 @@ function registerWalletCommands(program) {
|
|
|
1422
1416
|
.option("--hardware", "Hardware wallet mode: show raw wc: URI only")
|
|
1423
1417
|
.option("--json")
|
|
1424
1418
|
.action(async (opts) => {
|
|
1425
|
-
const config =
|
|
1419
|
+
const config = loadConfig();
|
|
1426
1420
|
if (!config.walletContractAddress) {
|
|
1427
1421
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1428
1422
|
process.exit(1);
|
|
@@ -1432,19 +1426,19 @@ function registerWalletCommands(program) {
|
|
|
1432
1426
|
process.exit(1);
|
|
1433
1427
|
}
|
|
1434
1428
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1435
|
-
const provider = new
|
|
1436
|
-
const walletInterface = new
|
|
1429
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1430
|
+
const walletInterface = new ethers.Interface(ARC402_WALLET_GUARDIAN_ABI);
|
|
1437
1431
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1438
1432
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1439
1433
|
: undefined;
|
|
1440
|
-
const { client, session, account } = await
|
|
1434
|
+
const { client, session, account } = await connectPhoneWallet(config.walletConnectProjectId, chainId, config, { telegramOpts, prompt: "Approve: unfreeze ARC402Wallet", hardware: !!opts.hardware });
|
|
1441
1435
|
const networkName = chainId === 8453 ? "Base" : "Base Sepolia";
|
|
1442
1436
|
const shortAddr = `${account.slice(0, 6)}...${account.slice(-5)}`;
|
|
1443
|
-
console.log("\n" +
|
|
1444
|
-
console.log("\n" +
|
|
1437
|
+
console.log("\n" + c.success + c.white(` Connected: ${shortAddr} on ${networkName}`));
|
|
1438
|
+
console.log("\n" + c.dim("Wallet to unfreeze:") + " " + c.white(config.walletContractAddress ?? ""));
|
|
1445
1439
|
// WalletConnect approval already confirmed intent — sending automatically
|
|
1446
|
-
console.log(
|
|
1447
|
-
const txHash = await
|
|
1440
|
+
console.log(c.dim("Sending transaction..."));
|
|
1441
|
+
const txHash = await sendTransactionWithSession(client, session, account, chainId, {
|
|
1448
1442
|
to: config.walletContractAddress,
|
|
1449
1443
|
data: walletInterface.encodeFunctionData("unfreeze", []),
|
|
1450
1444
|
value: "0x0",
|
|
@@ -1454,8 +1448,8 @@ function registerWalletCommands(program) {
|
|
|
1454
1448
|
console.log(JSON.stringify({ txHash, walletAddress: config.walletContractAddress }));
|
|
1455
1449
|
}
|
|
1456
1450
|
else {
|
|
1457
|
-
console.log("\n" +
|
|
1458
|
-
console.log(" " +
|
|
1451
|
+
console.log("\n" + c.success + c.white(` Wallet ${config.walletContractAddress} unfrozen`));
|
|
1452
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1459
1453
|
}
|
|
1460
1454
|
});
|
|
1461
1455
|
// ─── set-guardian ──────────────────────────────────────────────────────────
|
|
@@ -1467,7 +1461,7 @@ function registerWalletCommands(program) {
|
|
|
1467
1461
|
.option("--guardian-key <key>", "Use an existing private key as the guardian (optional)")
|
|
1468
1462
|
.option("--hardware", "Hardware wallet mode: show raw wc: URI only")
|
|
1469
1463
|
.action(async (opts) => {
|
|
1470
|
-
const config =
|
|
1464
|
+
const config = loadConfig();
|
|
1471
1465
|
if (!config.walletContractAddress) {
|
|
1472
1466
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1473
1467
|
process.exit(1);
|
|
@@ -1479,7 +1473,7 @@ function registerWalletCommands(program) {
|
|
|
1479
1473
|
let guardianWallet;
|
|
1480
1474
|
if (opts.guardianKey) {
|
|
1481
1475
|
try {
|
|
1482
|
-
guardianWallet = new
|
|
1476
|
+
guardianWallet = new ethers.Wallet(opts.guardianKey);
|
|
1483
1477
|
}
|
|
1484
1478
|
catch {
|
|
1485
1479
|
console.error("Invalid guardian key. Must be a 0x-prefixed hex string.");
|
|
@@ -1487,24 +1481,24 @@ function registerWalletCommands(program) {
|
|
|
1487
1481
|
}
|
|
1488
1482
|
}
|
|
1489
1483
|
else {
|
|
1490
|
-
guardianWallet = new
|
|
1484
|
+
guardianWallet = new ethers.Wallet(ethers.Wallet.createRandom().privateKey);
|
|
1491
1485
|
console.log("Generated new guardian key.");
|
|
1492
1486
|
}
|
|
1493
1487
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1494
|
-
const provider = new
|
|
1495
|
-
const walletInterface = new
|
|
1488
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1489
|
+
const walletInterface = new ethers.Interface(ARC402_WALLET_GUARDIAN_ABI);
|
|
1496
1490
|
console.log(`\nGuardian address: ${guardianWallet.address}`);
|
|
1497
1491
|
console.log(`Wallet contract: ${config.walletContractAddress}`);
|
|
1498
1492
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1499
1493
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1500
1494
|
: undefined;
|
|
1501
|
-
const { client, session, account } = await
|
|
1495
|
+
const { client, session, account } = await connectPhoneWallet(config.walletConnectProjectId, chainId, config, { telegramOpts, prompt: `Approve: set guardian to ${guardianWallet.address}`, hardware: !!opts.hardware });
|
|
1502
1496
|
const networkName = chainId === 8453 ? "Base" : "Base Sepolia";
|
|
1503
1497
|
const shortAddr = `${account.slice(0, 6)}...${account.slice(-5)}`;
|
|
1504
|
-
console.log("\n" +
|
|
1498
|
+
console.log("\n" + c.success + c.white(` Connected: ${shortAddr} on ${networkName}`));
|
|
1505
1499
|
// WalletConnect approval already confirmed intent — sending automatically
|
|
1506
|
-
console.log(
|
|
1507
|
-
const txHash = await
|
|
1500
|
+
console.log(c.dim("Sending transaction..."));
|
|
1501
|
+
const txHash = await sendTransactionWithSession(client, session, account, chainId, {
|
|
1508
1502
|
to: config.walletContractAddress,
|
|
1509
1503
|
data: walletInterface.encodeFunctionData("setGuardian", [guardianWallet.address]),
|
|
1510
1504
|
value: "0x0",
|
|
@@ -1514,32 +1508,32 @@ function registerWalletCommands(program) {
|
|
|
1514
1508
|
if (config.guardianPrivateKey)
|
|
1515
1509
|
delete config.guardianPrivateKey;
|
|
1516
1510
|
config.guardianAddress = guardianWallet.address;
|
|
1517
|
-
|
|
1518
|
-
console.log("\n" +
|
|
1519
|
-
console.log(" " +
|
|
1520
|
-
console.log(" " +
|
|
1521
|
-
console.log(" " +
|
|
1511
|
+
saveConfig(config);
|
|
1512
|
+
console.log("\n" + c.success + c.white(` Guardian set to: ${guardianWallet.address}`));
|
|
1513
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1514
|
+
console.log(" " + c.dim("Guardian private key saved to ~/.arc402/guardian.key (chmod 400)."));
|
|
1515
|
+
console.log(" " + c.warning + " " + c.yellow("The guardian key can freeze your wallet. Store it separately from your hot key."));
|
|
1522
1516
|
});
|
|
1523
1517
|
// ─── policy-engine freeze / unfreeze (legacy — for PolicyEngine-level freeze) ──
|
|
1524
1518
|
wallet.command("freeze-policy <walletAddress>")
|
|
1525
1519
|
.description("Freeze PolicyEngine spend for a wallet address (authorized freeze agents only)")
|
|
1526
1520
|
.action(async (walletAddress) => {
|
|
1527
|
-
const config =
|
|
1521
|
+
const config = loadConfig();
|
|
1528
1522
|
if (!config.policyEngineAddress)
|
|
1529
1523
|
throw new Error("policyEngineAddress missing in config");
|
|
1530
|
-
const { signer } = await
|
|
1531
|
-
const client = new
|
|
1524
|
+
const { signer } = await requireSigner(config);
|
|
1525
|
+
const client = new PolicyClient(config.policyEngineAddress, signer);
|
|
1532
1526
|
await client.freezeSpend(walletAddress);
|
|
1533
1527
|
console.log(`wallet ${walletAddress} spend frozen (PolicyEngine)`);
|
|
1534
1528
|
});
|
|
1535
1529
|
wallet.command("unfreeze-policy <walletAddress>")
|
|
1536
1530
|
.description("Unfreeze PolicyEngine spend for a wallet. Only callable by the wallet or its registered owner.")
|
|
1537
1531
|
.action(async (walletAddress) => {
|
|
1538
|
-
const config =
|
|
1532
|
+
const config = loadConfig();
|
|
1539
1533
|
if (!config.policyEngineAddress)
|
|
1540
1534
|
throw new Error("policyEngineAddress missing in config");
|
|
1541
|
-
const { signer } = await
|
|
1542
|
-
const client = new
|
|
1535
|
+
const { signer } = await requireSigner(config);
|
|
1536
|
+
const client = new PolicyClient(config.policyEngineAddress, signer);
|
|
1543
1537
|
await client.unfreeze(walletAddress);
|
|
1544
1538
|
console.log(`wallet ${walletAddress} spend unfrozen (PolicyEngine)`);
|
|
1545
1539
|
});
|
|
@@ -1549,7 +1543,7 @@ function registerWalletCommands(program) {
|
|
|
1549
1543
|
.option("--dry-run", "Show calldata without connecting to wallet")
|
|
1550
1544
|
.option("--hardware", "Hardware wallet mode: show raw wc: URI only")
|
|
1551
1545
|
.action(async (newRegistryAddress, opts) => {
|
|
1552
|
-
const config =
|
|
1546
|
+
const config = loadConfig();
|
|
1553
1547
|
if (!config.walletContractAddress) {
|
|
1554
1548
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1555
1549
|
process.exit(1);
|
|
@@ -1560,18 +1554,18 @@ function registerWalletCommands(program) {
|
|
|
1560
1554
|
}
|
|
1561
1555
|
let checksumAddress;
|
|
1562
1556
|
try {
|
|
1563
|
-
checksumAddress =
|
|
1557
|
+
checksumAddress = ethers.getAddress(newRegistryAddress);
|
|
1564
1558
|
}
|
|
1565
1559
|
catch {
|
|
1566
1560
|
console.error(`Invalid address: ${newRegistryAddress}`);
|
|
1567
1561
|
process.exit(1);
|
|
1568
1562
|
}
|
|
1569
1563
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1570
|
-
const provider = new
|
|
1571
|
-
const walletInterface = new
|
|
1564
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1565
|
+
const walletInterface = new ethers.Interface(ARC402_WALLET_REGISTRY_ABI);
|
|
1572
1566
|
let currentRegistry = "(unknown)";
|
|
1573
1567
|
try {
|
|
1574
|
-
const walletContract = new
|
|
1568
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_REGISTRY_ABI, provider);
|
|
1575
1569
|
currentRegistry = await walletContract.registry();
|
|
1576
1570
|
}
|
|
1577
1571
|
catch { /* contract may not expose registry() */ }
|
|
@@ -1597,21 +1591,21 @@ function registerWalletCommands(program) {
|
|
|
1597
1591
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1598
1592
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1599
1593
|
: undefined;
|
|
1600
|
-
const { client, session, account } = await
|
|
1594
|
+
const { client, session, account } = await connectPhoneWallet(config.walletConnectProjectId, chainId, config, { telegramOpts, prompt: "Approve registry upgrade proposal on ARC402Wallet", hardware: !!opts.hardware });
|
|
1601
1595
|
const networkName = chainId === 8453 ? "Base" : "Base Sepolia";
|
|
1602
1596
|
const shortAddr = `${account.slice(0, 6)}...${account.slice(-5)}`;
|
|
1603
|
-
console.log("\n" +
|
|
1597
|
+
console.log("\n" + c.success + c.white(` Connected: ${shortAddr} on ${networkName}`));
|
|
1604
1598
|
// WalletConnect approval already confirmed intent — sending automatically
|
|
1605
|
-
console.log(
|
|
1606
|
-
const txHash = await
|
|
1599
|
+
console.log(c.dim("Sending transaction..."));
|
|
1600
|
+
const txHash = await sendTransactionWithSession(client, session, account, chainId, {
|
|
1607
1601
|
to: config.walletContractAddress,
|
|
1608
1602
|
data: calldata,
|
|
1609
1603
|
value: "0x0",
|
|
1610
1604
|
});
|
|
1611
1605
|
const unlockAt = new Date(Date.now() + 2 * 24 * 60 * 60 * 1000);
|
|
1612
|
-
console.log("\n" +
|
|
1613
|
-
console.log(" " +
|
|
1614
|
-
console.log(" " +
|
|
1606
|
+
console.log("\n" + c.success + c.white(" Registry upgrade proposed"));
|
|
1607
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1608
|
+
console.log(" " + c.dim("Unlock at:") + " " + c.white(unlockAt.toISOString()) + c.dim(" (approximately)"));
|
|
1615
1609
|
console.log(`\nNext steps:`);
|
|
1616
1610
|
console.log(` Wait 2 days, then run:`);
|
|
1617
1611
|
console.log(` arc402 wallet execute-registry-upgrade`);
|
|
@@ -1622,7 +1616,7 @@ function registerWalletCommands(program) {
|
|
|
1622
1616
|
wallet.command("execute-registry-upgrade")
|
|
1623
1617
|
.description("Execute a pending registry upgrade after the 2-day timelock (phone wallet signs via WalletConnect)")
|
|
1624
1618
|
.action(async () => {
|
|
1625
|
-
const config =
|
|
1619
|
+
const config = loadConfig();
|
|
1626
1620
|
if (!config.walletContractAddress) {
|
|
1627
1621
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1628
1622
|
process.exit(1);
|
|
@@ -1632,8 +1626,8 @@ function registerWalletCommands(program) {
|
|
|
1632
1626
|
process.exit(1);
|
|
1633
1627
|
}
|
|
1634
1628
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1635
|
-
const provider = new
|
|
1636
|
-
const walletContract = new
|
|
1629
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1630
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_REGISTRY_ABI, provider);
|
|
1637
1631
|
let pendingRegistry;
|
|
1638
1632
|
let unlockAt;
|
|
1639
1633
|
try {
|
|
@@ -1646,7 +1640,7 @@ function registerWalletCommands(program) {
|
|
|
1646
1640
|
console.error("Failed to read pending registry from contract:", e);
|
|
1647
1641
|
process.exit(1);
|
|
1648
1642
|
}
|
|
1649
|
-
if (pendingRegistry ===
|
|
1643
|
+
if (pendingRegistry === ethers.ZeroAddress) {
|
|
1650
1644
|
console.log("No pending registry upgrade.");
|
|
1651
1645
|
return;
|
|
1652
1646
|
}
|
|
@@ -1666,8 +1660,8 @@ function registerWalletCommands(program) {
|
|
|
1666
1660
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1667
1661
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1668
1662
|
: undefined;
|
|
1669
|
-
const walletInterface = new
|
|
1670
|
-
const { txHash } = await
|
|
1663
|
+
const walletInterface = new ethers.Interface(ARC402_WALLET_REGISTRY_ABI);
|
|
1664
|
+
const { txHash } = await requestPhoneWalletSignature(config.walletConnectProjectId, chainId, () => ({
|
|
1671
1665
|
to: config.walletContractAddress,
|
|
1672
1666
|
data: walletInterface.encodeFunctionData("executeRegistryUpdate", []),
|
|
1673
1667
|
value: "0x0",
|
|
@@ -1679,9 +1673,9 @@ function registerWalletCommands(program) {
|
|
|
1679
1673
|
confirmedRegistry = await walletContract.registry();
|
|
1680
1674
|
}
|
|
1681
1675
|
catch { /* use pendingRegistry as fallback */ }
|
|
1682
|
-
console.log("\n" +
|
|
1683
|
-
console.log(" " +
|
|
1684
|
-
console.log(" " +
|
|
1676
|
+
console.log("\n" + c.success + c.white(" Registry upgrade executed"));
|
|
1677
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1678
|
+
console.log(" " + c.dim("New registry:") + " " + c.white(confirmedRegistry));
|
|
1685
1679
|
if (confirmedRegistry.toLowerCase() === pendingRegistry.toLowerCase()) {
|
|
1686
1680
|
console.log(` Registry updated successfully — addresses now resolve through new registry.`);
|
|
1687
1681
|
}
|
|
@@ -1700,7 +1694,7 @@ function registerWalletCommands(program) {
|
|
|
1700
1694
|
.option("--hardware", "Hardware wallet mode: show raw wc: URI only")
|
|
1701
1695
|
.option("--json")
|
|
1702
1696
|
.action(async (target, opts) => {
|
|
1703
|
-
const config =
|
|
1697
|
+
const config = loadConfig();
|
|
1704
1698
|
if (!config.walletContractAddress) {
|
|
1705
1699
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1706
1700
|
process.exit(1);
|
|
@@ -1711,7 +1705,7 @@ function registerWalletCommands(program) {
|
|
|
1711
1705
|
}
|
|
1712
1706
|
let checksumTarget;
|
|
1713
1707
|
try {
|
|
1714
|
-
checksumTarget =
|
|
1708
|
+
checksumTarget = ethers.getAddress(target);
|
|
1715
1709
|
}
|
|
1716
1710
|
catch {
|
|
1717
1711
|
console.error(`Invalid address: ${target}`);
|
|
@@ -1719,20 +1713,20 @@ function registerWalletCommands(program) {
|
|
|
1719
1713
|
}
|
|
1720
1714
|
const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
|
|
1721
1715
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1722
|
-
const provider = new
|
|
1716
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1723
1717
|
// Check if already whitelisted
|
|
1724
1718
|
const peAbi = [
|
|
1725
1719
|
"function whitelistContract(address wallet, address target) external",
|
|
1726
1720
|
"function isContractWhitelisted(address wallet, address target) external view returns (bool)",
|
|
1727
1721
|
];
|
|
1728
|
-
const pe = new
|
|
1722
|
+
const pe = new ethers.Contract(policyAddress, peAbi, provider);
|
|
1729
1723
|
let alreadyWhitelisted = false;
|
|
1730
1724
|
try {
|
|
1731
1725
|
alreadyWhitelisted = await pe.isContractWhitelisted(config.walletContractAddress, checksumTarget);
|
|
1732
1726
|
}
|
|
1733
1727
|
catch { /* ignore */ }
|
|
1734
1728
|
if (alreadyWhitelisted) {
|
|
1735
|
-
console.log(" " +
|
|
1729
|
+
console.log(" " + c.success + " " + c.white(checksumTarget) + c.dim(` is already whitelisted for ${config.walletContractAddress}`));
|
|
1736
1730
|
process.exit(0);
|
|
1737
1731
|
}
|
|
1738
1732
|
console.log(`\nWallet: ${config.walletContractAddress}`);
|
|
@@ -1741,8 +1735,8 @@ function registerWalletCommands(program) {
|
|
|
1741
1735
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1742
1736
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1743
1737
|
: undefined;
|
|
1744
|
-
const policyIface = new
|
|
1745
|
-
const { txHash } = await
|
|
1738
|
+
const policyIface = new ethers.Interface(peAbi);
|
|
1739
|
+
const { txHash } = await requestPhoneWalletSignature(config.walletConnectProjectId, chainId, () => ({
|
|
1746
1740
|
to: policyAddress,
|
|
1747
1741
|
data: policyIface.encodeFunctionData("whitelistContract", [
|
|
1748
1742
|
config.walletContractAddress,
|
|
@@ -1755,8 +1749,8 @@ function registerWalletCommands(program) {
|
|
|
1755
1749
|
console.log(JSON.stringify({ ok: true, txHash, wallet: config.walletContractAddress, target: checksumTarget }));
|
|
1756
1750
|
}
|
|
1757
1751
|
else {
|
|
1758
|
-
console.log("\n" +
|
|
1759
|
-
|
|
1752
|
+
console.log("\n" + c.success + c.white(" Contract whitelisted"));
|
|
1753
|
+
renderTree([
|
|
1760
1754
|
{ label: "Tx", value: txHash },
|
|
1761
1755
|
{ label: "Wallet", value: config.walletContractAddress ?? "" },
|
|
1762
1756
|
{ label: "Target", value: checksumTarget, last: true },
|
|
@@ -1767,7 +1761,7 @@ function registerWalletCommands(program) {
|
|
|
1767
1761
|
wallet.command("set-interceptor <address>")
|
|
1768
1762
|
.description("Set the authorized X402 interceptor address on ARC402Wallet (phone wallet signs via WalletConnect)")
|
|
1769
1763
|
.action(async (interceptorAddress) => {
|
|
1770
|
-
const config =
|
|
1764
|
+
const config = loadConfig();
|
|
1771
1765
|
if (!config.walletContractAddress) {
|
|
1772
1766
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1773
1767
|
process.exit(1);
|
|
@@ -1778,18 +1772,18 @@ function registerWalletCommands(program) {
|
|
|
1778
1772
|
}
|
|
1779
1773
|
let checksumAddress;
|
|
1780
1774
|
try {
|
|
1781
|
-
checksumAddress =
|
|
1775
|
+
checksumAddress = ethers.getAddress(interceptorAddress);
|
|
1782
1776
|
}
|
|
1783
1777
|
catch {
|
|
1784
1778
|
console.error(`Invalid address: ${interceptorAddress}`);
|
|
1785
1779
|
process.exit(1);
|
|
1786
1780
|
}
|
|
1787
1781
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1788
|
-
const provider = new
|
|
1789
|
-
const ownerInterface = new
|
|
1782
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1783
|
+
const ownerInterface = new ethers.Interface(ARC402_WALLET_OWNER_ABI);
|
|
1790
1784
|
let currentInterceptor = "(unknown)";
|
|
1791
1785
|
try {
|
|
1792
|
-
const walletContract = new
|
|
1786
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_OWNER_ABI, provider);
|
|
1793
1787
|
currentInterceptor = await walletContract.authorizedInterceptor();
|
|
1794
1788
|
}
|
|
1795
1789
|
catch { /* contract may not be deployed yet */ }
|
|
@@ -1799,21 +1793,21 @@ function registerWalletCommands(program) {
|
|
|
1799
1793
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1800
1794
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1801
1795
|
: undefined;
|
|
1802
|
-
const { txHash } = await
|
|
1796
|
+
const { txHash } = await requestPhoneWalletSignature(config.walletConnectProjectId, chainId, () => ({
|
|
1803
1797
|
to: config.walletContractAddress,
|
|
1804
1798
|
data: ownerInterface.encodeFunctionData("setAuthorizedInterceptor", [checksumAddress]),
|
|
1805
1799
|
value: "0x0",
|
|
1806
1800
|
}), `Approve: set X402 interceptor to ${checksumAddress}`, telegramOpts, config);
|
|
1807
1801
|
await provider.waitForTransaction(txHash);
|
|
1808
|
-
console.log("\n" +
|
|
1809
|
-
console.log(" " +
|
|
1810
|
-
console.log(" " +
|
|
1802
|
+
console.log("\n" + c.success + c.white(" X402 interceptor updated"));
|
|
1803
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1804
|
+
console.log(" " + c.dim("Interceptor:") + " " + c.white(checksumAddress));
|
|
1811
1805
|
});
|
|
1812
1806
|
// ─── set-velocity-limit ────────────────────────────────────────────────────
|
|
1813
1807
|
wallet.command("set-velocity-limit <limit>")
|
|
1814
1808
|
.description("Set the per-rolling-window ETH velocity limit on ARC402Wallet (limit in ETH, phone wallet signs via WalletConnect)")
|
|
1815
1809
|
.action(async (limitEth) => {
|
|
1816
|
-
const config =
|
|
1810
|
+
const config = loadConfig();
|
|
1817
1811
|
console.log(`\nNote: ARC-402 has two independent velocity limit layers:`);
|
|
1818
1812
|
console.log(` 1. Wallet-level (arc402 wallet set-velocity-limit): ETH cap per rolling hour, enforced by ARC402Wallet contract. Breach auto-freezes wallet.`);
|
|
1819
1813
|
console.log(` 2. PolicyEngine-level (arc402 wallet policy set-daily-limit): Per-category daily cap, enforced by PolicyEngine. Breach returns a soft error without freezing.`);
|
|
@@ -1828,20 +1822,20 @@ function registerWalletCommands(program) {
|
|
|
1828
1822
|
}
|
|
1829
1823
|
let limitWei;
|
|
1830
1824
|
try {
|
|
1831
|
-
limitWei =
|
|
1825
|
+
limitWei = ethers.parseEther(limitEth);
|
|
1832
1826
|
}
|
|
1833
1827
|
catch {
|
|
1834
1828
|
console.error(`Invalid limit: ${limitEth}. Provide a value in ETH (e.g. 0.5)`);
|
|
1835
1829
|
process.exit(1);
|
|
1836
1830
|
}
|
|
1837
1831
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1838
|
-
const provider = new
|
|
1839
|
-
const ownerInterface = new
|
|
1832
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1833
|
+
const ownerInterface = new ethers.Interface(ARC402_WALLET_OWNER_ABI);
|
|
1840
1834
|
let currentLimit = "(unknown)";
|
|
1841
1835
|
try {
|
|
1842
|
-
const walletContract = new
|
|
1836
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_OWNER_ABI, provider);
|
|
1843
1837
|
const raw = await walletContract.velocityLimit();
|
|
1844
|
-
currentLimit = raw === 0n ? "disabled" : `${
|
|
1838
|
+
currentLimit = raw === 0n ? "disabled" : `${ethers.formatEther(raw)} ETH`;
|
|
1845
1839
|
}
|
|
1846
1840
|
catch { /* contract may not be deployed yet */ }
|
|
1847
1841
|
console.log(`\nWallet: ${config.walletContractAddress}`);
|
|
@@ -1850,15 +1844,15 @@ function registerWalletCommands(program) {
|
|
|
1850
1844
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1851
1845
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1852
1846
|
: undefined;
|
|
1853
|
-
const { txHash } = await
|
|
1847
|
+
const { txHash } = await requestPhoneWalletSignature(config.walletConnectProjectId, chainId, () => ({
|
|
1854
1848
|
to: config.walletContractAddress,
|
|
1855
1849
|
data: ownerInterface.encodeFunctionData("setVelocityLimit", [limitWei]),
|
|
1856
1850
|
value: "0x0",
|
|
1857
1851
|
}), `Approve: set velocity limit to ${limitEth} ETH`, telegramOpts, config);
|
|
1858
1852
|
await provider.waitForTransaction(txHash);
|
|
1859
|
-
console.log("\n" +
|
|
1860
|
-
console.log(" " +
|
|
1861
|
-
console.log(" " +
|
|
1853
|
+
console.log("\n" + c.success + c.white(" Velocity limit updated"));
|
|
1854
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1855
|
+
console.log(" " + c.dim("New limit:") + " " + c.white(`${limitEth} ETH per rolling window`));
|
|
1862
1856
|
});
|
|
1863
1857
|
// ─── register-policy ───────────────────────────────────────────────────────
|
|
1864
1858
|
//
|
|
@@ -1869,7 +1863,7 @@ function registerWalletCommands(program) {
|
|
|
1869
1863
|
.description("Register this wallet on PolicyEngine (required before spend limits can be set)")
|
|
1870
1864
|
.option("--hardware", "Hardware wallet mode: show raw wc: URI only")
|
|
1871
1865
|
.action(async (opts) => {
|
|
1872
|
-
const config =
|
|
1866
|
+
const config = loadConfig();
|
|
1873
1867
|
if (!config.walletContractAddress) {
|
|
1874
1868
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1875
1869
|
process.exit(1);
|
|
@@ -1880,20 +1874,20 @@ function registerWalletCommands(program) {
|
|
|
1880
1874
|
}
|
|
1881
1875
|
const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
|
|
1882
1876
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1883
|
-
const provider = new
|
|
1877
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1884
1878
|
let ownerAddress = config.ownerAddress;
|
|
1885
1879
|
if (!ownerAddress) {
|
|
1886
1880
|
// Fallback 1: WalletConnect session account
|
|
1887
1881
|
if (config.wcSession?.account) {
|
|
1888
1882
|
ownerAddress = config.wcSession.account;
|
|
1889
|
-
console.log(
|
|
1883
|
+
console.log(c.dim(`Owner resolved from WalletConnect session: ${ownerAddress}`));
|
|
1890
1884
|
}
|
|
1891
1885
|
else {
|
|
1892
1886
|
// Fallback 2: call owner() on the wallet contract
|
|
1893
1887
|
try {
|
|
1894
|
-
const walletOwnerContract = new
|
|
1888
|
+
const walletOwnerContract = new ethers.Contract(config.walletContractAddress, ["function owner() external view returns (address)"], provider);
|
|
1895
1889
|
ownerAddress = await walletOwnerContract.owner();
|
|
1896
|
-
console.log(
|
|
1890
|
+
console.log(c.dim(`Owner resolved from contract: ${ownerAddress}`));
|
|
1897
1891
|
}
|
|
1898
1892
|
catch {
|
|
1899
1893
|
console.error("ownerAddress not set in config and could not be resolved from contract or WalletConnect session.");
|
|
@@ -1902,21 +1896,21 @@ function registerWalletCommands(program) {
|
|
|
1902
1896
|
}
|
|
1903
1897
|
}
|
|
1904
1898
|
// Encode registerWallet(wallet, owner) calldata — called on PolicyEngine
|
|
1905
|
-
const policyInterface = new
|
|
1899
|
+
const policyInterface = new ethers.Interface([
|
|
1906
1900
|
"function registerWallet(address wallet, address owner) external",
|
|
1907
1901
|
]);
|
|
1908
1902
|
const registerCalldata = policyInterface.encodeFunctionData("registerWallet", [
|
|
1909
1903
|
config.walletContractAddress,
|
|
1910
1904
|
ownerAddress,
|
|
1911
1905
|
]);
|
|
1912
|
-
const executeInterface = new
|
|
1906
|
+
const executeInterface = new ethers.Interface(ARC402_WALLET_EXECUTE_ABI);
|
|
1913
1907
|
console.log(`\nWallet: ${config.walletContractAddress}`);
|
|
1914
1908
|
console.log(`PolicyEngine: ${policyAddress}`);
|
|
1915
1909
|
console.log(`Owner: ${ownerAddress}`);
|
|
1916
1910
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1917
1911
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1918
1912
|
: undefined;
|
|
1919
|
-
const { txHash } = await
|
|
1913
|
+
const { txHash } = await requestPhoneWalletSignature(config.walletConnectProjectId, chainId, () => ({
|
|
1920
1914
|
to: config.walletContractAddress,
|
|
1921
1915
|
data: executeInterface.encodeFunctionData("executeContractCall", [{
|
|
1922
1916
|
target: policyAddress,
|
|
@@ -1924,20 +1918,20 @@ function registerWalletCommands(program) {
|
|
|
1924
1918
|
value: 0n,
|
|
1925
1919
|
minReturnValue: 0n,
|
|
1926
1920
|
maxApprovalAmount: 0n,
|
|
1927
|
-
approvalToken:
|
|
1921
|
+
approvalToken: ethers.ZeroAddress,
|
|
1928
1922
|
}]),
|
|
1929
1923
|
value: "0x0",
|
|
1930
1924
|
}), `Approve: register wallet on PolicyEngine`, telegramOpts, config);
|
|
1931
1925
|
await provider.waitForTransaction(txHash);
|
|
1932
|
-
console.log("\n" +
|
|
1933
|
-
console.log(" " +
|
|
1934
|
-
console.log(
|
|
1926
|
+
console.log("\n" + c.success + c.white(" Wallet registered on PolicyEngine"));
|
|
1927
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1928
|
+
console.log(c.dim("\nNext: run 'arc402 wallet policy set-limit' to configure spending limits."));
|
|
1935
1929
|
});
|
|
1936
1930
|
// ─── cancel-registry-upgrade ───────────────────────────────────────────────
|
|
1937
1931
|
wallet.command("cancel-registry-upgrade")
|
|
1938
1932
|
.description("Cancel a pending registry upgrade before it executes (phone wallet signs via WalletConnect)")
|
|
1939
1933
|
.action(async () => {
|
|
1940
|
-
const config =
|
|
1934
|
+
const config = loadConfig();
|
|
1941
1935
|
if (!config.walletContractAddress) {
|
|
1942
1936
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
1943
1937
|
process.exit(1);
|
|
@@ -1947,8 +1941,8 @@ function registerWalletCommands(program) {
|
|
|
1947
1941
|
process.exit(1);
|
|
1948
1942
|
}
|
|
1949
1943
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
1950
|
-
const provider = new
|
|
1951
|
-
const walletContract = new
|
|
1944
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
1945
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_REGISTRY_ABI, provider);
|
|
1952
1946
|
let pendingRegistry;
|
|
1953
1947
|
let unlockAtCancel = 0n;
|
|
1954
1948
|
try {
|
|
@@ -1961,7 +1955,7 @@ function registerWalletCommands(program) {
|
|
|
1961
1955
|
console.error("Failed to read pending registry from contract:", e);
|
|
1962
1956
|
process.exit(1);
|
|
1963
1957
|
}
|
|
1964
|
-
if (pendingRegistry ===
|
|
1958
|
+
if (pendingRegistry === ethers.ZeroAddress) {
|
|
1965
1959
|
console.log("No pending registry upgrade to cancel.");
|
|
1966
1960
|
return;
|
|
1967
1961
|
}
|
|
@@ -1979,14 +1973,14 @@ function registerWalletCommands(program) {
|
|
|
1979
1973
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
1980
1974
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
1981
1975
|
: undefined;
|
|
1982
|
-
const walletInterface = new
|
|
1983
|
-
const { txHash } = await
|
|
1976
|
+
const walletInterface = new ethers.Interface(ARC402_WALLET_REGISTRY_ABI);
|
|
1977
|
+
const { txHash } = await requestPhoneWalletSignature(config.walletConnectProjectId, chainId, () => ({
|
|
1984
1978
|
to: config.walletContractAddress,
|
|
1985
1979
|
data: walletInterface.encodeFunctionData("cancelRegistryUpdate", []),
|
|
1986
1980
|
value: "0x0",
|
|
1987
1981
|
}), "Approve registry upgrade cancellation on ARC402Wallet", telegramOpts, config);
|
|
1988
|
-
console.log("\n" +
|
|
1989
|
-
console.log(" " +
|
|
1982
|
+
console.log("\n" + c.success + c.white(" Registry upgrade cancelled"));
|
|
1983
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(txHash));
|
|
1990
1984
|
});
|
|
1991
1985
|
// ─── governance setup ──────────────────────────────────────────────────────
|
|
1992
1986
|
//
|
|
@@ -1997,7 +1991,7 @@ function registerWalletCommands(program) {
|
|
|
1997
1991
|
governance.command("setup")
|
|
1998
1992
|
.description("Interactive governance setup — velocity limit, guardian key, and spending limits in one WalletConnect session")
|
|
1999
1993
|
.action(async () => {
|
|
2000
|
-
const config =
|
|
1994
|
+
const config = loadConfig();
|
|
2001
1995
|
if (!config.walletContractAddress) {
|
|
2002
1996
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
2003
1997
|
process.exit(1);
|
|
@@ -2009,14 +2003,14 @@ function registerWalletCommands(program) {
|
|
|
2009
2003
|
const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
|
|
2010
2004
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
2011
2005
|
// ── Step 1: velocity limit ────────────────────────────────────────────
|
|
2012
|
-
const { velocityEth } = await (
|
|
2006
|
+
const { velocityEth } = await prompts({
|
|
2013
2007
|
type: "text",
|
|
2014
2008
|
name: "velocityEth",
|
|
2015
2009
|
message: "Velocity limit (max ETH per rolling window)",
|
|
2016
2010
|
initial: "0.05",
|
|
2017
2011
|
validate: (v) => {
|
|
2018
2012
|
try {
|
|
2019
|
-
|
|
2013
|
+
ethers.parseEther(v);
|
|
2020
2014
|
return true;
|
|
2021
2015
|
}
|
|
2022
2016
|
catch {
|
|
@@ -2029,7 +2023,7 @@ function registerWalletCommands(program) {
|
|
|
2029
2023
|
return;
|
|
2030
2024
|
}
|
|
2031
2025
|
// ── Step 2: guardian key ──────────────────────────────────────────────
|
|
2032
|
-
const { wantGuardian } = await (
|
|
2026
|
+
const { wantGuardian } = await prompts({
|
|
2033
2027
|
type: "confirm",
|
|
2034
2028
|
name: "wantGuardian",
|
|
2035
2029
|
message: "Set guardian key?",
|
|
@@ -2041,7 +2035,7 @@ function registerWalletCommands(program) {
|
|
|
2041
2035
|
}
|
|
2042
2036
|
let guardianWallet = null;
|
|
2043
2037
|
if (wantGuardian) {
|
|
2044
|
-
guardianWallet = new
|
|
2038
|
+
guardianWallet = new ethers.Wallet(ethers.Wallet.createRandom().privateKey);
|
|
2045
2039
|
console.log(` Generated guardian address: ${guardianWallet.address}`);
|
|
2046
2040
|
}
|
|
2047
2041
|
const categories = [];
|
|
@@ -2052,7 +2046,7 @@ function registerWalletCommands(program) {
|
|
|
2052
2046
|
];
|
|
2053
2047
|
console.log("\nSpending categories — press Enter to skip any:");
|
|
2054
2048
|
for (const { label, default: def } of defaultCategories) {
|
|
2055
|
-
const { amountRaw } = await (
|
|
2049
|
+
const { amountRaw } = await prompts({
|
|
2056
2050
|
type: "text",
|
|
2057
2051
|
name: "amountRaw",
|
|
2058
2052
|
message: ` ${label} limit in ETH`,
|
|
@@ -2068,7 +2062,7 @@ function registerWalletCommands(program) {
|
|
|
2068
2062
|
}
|
|
2069
2063
|
// Custom categories loop
|
|
2070
2064
|
while (true) {
|
|
2071
|
-
const { customName } = await (
|
|
2065
|
+
const { customName } = await prompts({
|
|
2072
2066
|
type: "text",
|
|
2073
2067
|
name: "customName",
|
|
2074
2068
|
message: " Add custom category? [name or Enter to skip]",
|
|
@@ -2076,14 +2070,14 @@ function registerWalletCommands(program) {
|
|
|
2076
2070
|
});
|
|
2077
2071
|
if (customName === undefined || customName.trim() === "")
|
|
2078
2072
|
break;
|
|
2079
|
-
const { customAmount } = await (
|
|
2073
|
+
const { customAmount } = await prompts({
|
|
2080
2074
|
type: "text",
|
|
2081
2075
|
name: "customAmount",
|
|
2082
2076
|
message: ` ${customName.trim()} limit in ETH`,
|
|
2083
2077
|
initial: "0.05",
|
|
2084
2078
|
validate: (v) => {
|
|
2085
2079
|
try {
|
|
2086
|
-
|
|
2080
|
+
ethers.parseEther(v);
|
|
2087
2081
|
return true;
|
|
2088
2082
|
}
|
|
2089
2083
|
catch {
|
|
@@ -2114,7 +2108,7 @@ function registerWalletCommands(program) {
|
|
|
2114
2108
|
console.log(` Transactions: ${1 + (guardianWallet ? 1 : 0) + categories.length} + onboarding (registerWallet, enableDefiAccess) total`);
|
|
2115
2109
|
console.log("─────────────────────────────────────────────────────");
|
|
2116
2110
|
// ── Step 5: confirm ───────────────────────────────────────────────────
|
|
2117
|
-
const { confirmed } = await (
|
|
2111
|
+
const { confirmed } = await prompts({
|
|
2118
2112
|
type: "confirm",
|
|
2119
2113
|
name: "confirmed",
|
|
2120
2114
|
message: "Confirm and sign with your wallet?",
|
|
@@ -2129,14 +2123,14 @@ function registerWalletCommands(program) {
|
|
|
2129
2123
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
2130
2124
|
: undefined;
|
|
2131
2125
|
console.log("\nConnecting wallet...");
|
|
2132
|
-
const { client, session, account } = await
|
|
2133
|
-
const provider = new
|
|
2134
|
-
const ownerInterface = new
|
|
2135
|
-
const guardianInterface = new
|
|
2136
|
-
const policyInterface = new
|
|
2137
|
-
const executeInterface = new
|
|
2138
|
-
const govInterface = new
|
|
2139
|
-
const policyGovContract = new
|
|
2126
|
+
const { client, session, account } = await connectPhoneWallet(config.walletConnectProjectId, chainId, config, { telegramOpts, prompt: "Approve governance setup transactions on ARC402Wallet" });
|
|
2127
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2128
|
+
const ownerInterface = new ethers.Interface(ARC402_WALLET_OWNER_ABI);
|
|
2129
|
+
const guardianInterface = new ethers.Interface(ARC402_WALLET_GUARDIAN_ABI);
|
|
2130
|
+
const policyInterface = new ethers.Interface(POLICY_ENGINE_LIMITS_ABI);
|
|
2131
|
+
const executeInterface = new ethers.Interface(ARC402_WALLET_EXECUTE_ABI);
|
|
2132
|
+
const govInterface = new ethers.Interface(POLICY_ENGINE_GOVERNANCE_ABI);
|
|
2133
|
+
const policyGovContract = new ethers.Contract(policyAddress, POLICY_ENGINE_GOVERNANCE_ABI, provider);
|
|
2140
2134
|
const calls = [];
|
|
2141
2135
|
// ── P0: mandatory onboarding calls (registerWallet + enableDefiAccess) ──
|
|
2142
2136
|
// Check what the constructor already did to avoid double-registration reverts
|
|
@@ -2144,7 +2138,7 @@ function registerWalletCommands(program) {
|
|
|
2144
2138
|
let govAlreadyDefiEnabled = false;
|
|
2145
2139
|
try {
|
|
2146
2140
|
const registeredOwner = await policyGovContract.walletOwners(config.walletContractAddress);
|
|
2147
|
-
govAlreadyRegistered = registeredOwner !==
|
|
2141
|
+
govAlreadyRegistered = registeredOwner !== ethers.ZeroAddress;
|
|
2148
2142
|
}
|
|
2149
2143
|
catch { /* assume not registered */ }
|
|
2150
2144
|
try {
|
|
@@ -2161,7 +2155,7 @@ function registerWalletCommands(program) {
|
|
|
2161
2155
|
value: 0n,
|
|
2162
2156
|
minReturnValue: 0n,
|
|
2163
2157
|
maxApprovalAmount: 0n,
|
|
2164
|
-
approvalToken:
|
|
2158
|
+
approvalToken: ethers.ZeroAddress,
|
|
2165
2159
|
}]),
|
|
2166
2160
|
value: "0x0",
|
|
2167
2161
|
});
|
|
@@ -2176,7 +2170,7 @@ function registerWalletCommands(program) {
|
|
|
2176
2170
|
// velocity limit
|
|
2177
2171
|
calls.push({
|
|
2178
2172
|
to: config.walletContractAddress,
|
|
2179
|
-
data: ownerInterface.encodeFunctionData("setVelocityLimit", [
|
|
2173
|
+
data: ownerInterface.encodeFunctionData("setVelocityLimit", [ethers.parseEther(velocityEth)]),
|
|
2180
2174
|
value: "0x0",
|
|
2181
2175
|
});
|
|
2182
2176
|
// guardian
|
|
@@ -2194,7 +2188,7 @@ function registerWalletCommands(program) {
|
|
|
2194
2188
|
data: policyInterface.encodeFunctionData("setCategoryLimitFor", [
|
|
2195
2189
|
config.walletContractAddress,
|
|
2196
2190
|
category,
|
|
2197
|
-
|
|
2191
|
+
ethers.parseEther(amountEth),
|
|
2198
2192
|
]),
|
|
2199
2193
|
value: "0x0",
|
|
2200
2194
|
});
|
|
@@ -2224,7 +2218,7 @@ function registerWalletCommands(program) {
|
|
|
2224
2218
|
console.log(" (wallet_sendCalls not supported — sending sequentially)");
|
|
2225
2219
|
for (let i = 0; i < calls.length; i++) {
|
|
2226
2220
|
console.log(` Sending transaction ${i + 1}/${calls.length}...`);
|
|
2227
|
-
const txHash = await
|
|
2221
|
+
const txHash = await sendTransactionWithSession(client, session, account, chainId, calls[i]);
|
|
2228
2222
|
txHashes.push(txHash);
|
|
2229
2223
|
}
|
|
2230
2224
|
}
|
|
@@ -2234,26 +2228,26 @@ function registerWalletCommands(program) {
|
|
|
2234
2228
|
if (config.guardianPrivateKey)
|
|
2235
2229
|
delete config.guardianPrivateKey;
|
|
2236
2230
|
config.guardianAddress = guardianWallet.address;
|
|
2237
|
-
|
|
2231
|
+
saveConfig(config);
|
|
2238
2232
|
}
|
|
2239
|
-
console.log("\n" +
|
|
2233
|
+
console.log("\n" + c.success + c.white(" Governance setup complete"));
|
|
2240
2234
|
if (usedBatch) {
|
|
2241
|
-
console.log(" " +
|
|
2235
|
+
console.log(" " + c.dim("Batch tx:") + " " + c.white(txHashes[0]));
|
|
2242
2236
|
}
|
|
2243
2237
|
else {
|
|
2244
|
-
txHashes.forEach((h, i) => console.log(" " +
|
|
2238
|
+
txHashes.forEach((h, i) => console.log(" " + c.dim(`Tx ${i + 1}:`) + " " + c.white(h)));
|
|
2245
2239
|
}
|
|
2246
2240
|
if (guardianWallet) {
|
|
2247
|
-
console.log(" " +
|
|
2248
|
-
console.log(" " +
|
|
2241
|
+
console.log(" " + c.success + c.dim(` Guardian key saved to ~/.arc402/guardian.key — address: ${guardianWallet.address}`));
|
|
2242
|
+
console.log(" " + c.warning + " " + c.yellow("Store the guardian private key separately from your hot key."));
|
|
2249
2243
|
}
|
|
2250
|
-
console.log(
|
|
2244
|
+
console.log(c.dim("\nVerify with: arc402 wallet status && arc402 wallet policy show"));
|
|
2251
2245
|
});
|
|
2252
2246
|
// ─── authorize-machine-key ─────────────────────────────────────────────────
|
|
2253
2247
|
wallet.command("authorize-machine-key <key>")
|
|
2254
2248
|
.description("Authorize a machine key (hot key) on your ARC402Wallet (phone wallet signs via WalletConnect)")
|
|
2255
2249
|
.action(async (keyAddress) => {
|
|
2256
|
-
const config =
|
|
2250
|
+
const config = loadConfig();
|
|
2257
2251
|
if (!config.walletContractAddress) {
|
|
2258
2252
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
2259
2253
|
process.exit(1);
|
|
@@ -2264,23 +2258,23 @@ function registerWalletCommands(program) {
|
|
|
2264
2258
|
}
|
|
2265
2259
|
let checksumKey;
|
|
2266
2260
|
try {
|
|
2267
|
-
checksumKey =
|
|
2261
|
+
checksumKey = ethers.getAddress(keyAddress);
|
|
2268
2262
|
}
|
|
2269
2263
|
catch {
|
|
2270
2264
|
console.error(`Invalid address: ${keyAddress}`);
|
|
2271
2265
|
process.exit(1);
|
|
2272
2266
|
}
|
|
2273
2267
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
2274
|
-
const provider = new
|
|
2268
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2275
2269
|
const machineKeyAbi = ["function authorizeMachineKey(address key) external", "function authorizedMachineKeys(address) external view returns (bool)"];
|
|
2276
|
-
const walletContract = new
|
|
2270
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, machineKeyAbi, provider);
|
|
2277
2271
|
let alreadyAuthorized = false;
|
|
2278
2272
|
try {
|
|
2279
2273
|
alreadyAuthorized = await walletContract.authorizedMachineKeys(checksumKey);
|
|
2280
2274
|
}
|
|
2281
2275
|
catch { /* ignore */ }
|
|
2282
2276
|
if (alreadyAuthorized) {
|
|
2283
|
-
console.log("\n" +
|
|
2277
|
+
console.log("\n" + c.success + " " + c.white(checksumKey) + c.dim(` is already authorized as a machine key on ${config.walletContractAddress}`));
|
|
2284
2278
|
process.exit(0);
|
|
2285
2279
|
}
|
|
2286
2280
|
console.log(`\nWallet: ${config.walletContractAddress}`);
|
|
@@ -2288,29 +2282,29 @@ function registerWalletCommands(program) {
|
|
|
2288
2282
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
2289
2283
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
2290
2284
|
: undefined;
|
|
2291
|
-
const walletInterface = new
|
|
2285
|
+
const walletInterface = new ethers.Interface(machineKeyAbi);
|
|
2292
2286
|
const txData = {
|
|
2293
2287
|
to: config.walletContractAddress,
|
|
2294
2288
|
data: walletInterface.encodeFunctionData("authorizeMachineKey", [checksumKey]),
|
|
2295
2289
|
value: "0x0",
|
|
2296
2290
|
};
|
|
2297
|
-
const { client, session, account } = await
|
|
2291
|
+
const { client, session, account } = await connectPhoneWallet(config.walletConnectProjectId, chainId, config, {
|
|
2298
2292
|
telegramOpts,
|
|
2299
2293
|
prompt: `Authorize machine key ${checksumKey} on ARC402Wallet — allows autonomous protocol ops`,
|
|
2300
2294
|
});
|
|
2301
|
-
console.log("\n" +
|
|
2302
|
-
console.log(
|
|
2303
|
-
const hash = await
|
|
2304
|
-
console.log("\n" +
|
|
2305
|
-
console.log(
|
|
2295
|
+
console.log("\n" + c.success + c.white(` Connected: ${account}`));
|
|
2296
|
+
console.log(c.dim("Sending authorizeMachineKey transaction..."));
|
|
2297
|
+
const hash = await sendTransactionWithSession(client, session, account, chainId, txData);
|
|
2298
|
+
console.log("\n" + c.dim("Transaction submitted:") + " " + c.white(hash));
|
|
2299
|
+
console.log(c.dim("Waiting for confirmation..."));
|
|
2306
2300
|
const receipt = await provider.waitForTransaction(hash, 1, 60000);
|
|
2307
2301
|
if (!receipt || receipt.status !== 1) {
|
|
2308
|
-
console.error(
|
|
2302
|
+
console.error(c.failure + " " + c.red("Transaction failed."));
|
|
2309
2303
|
process.exit(1);
|
|
2310
2304
|
}
|
|
2311
2305
|
const confirmed = await walletContract.authorizedMachineKeys(checksumKey);
|
|
2312
|
-
console.log("\n" +
|
|
2313
|
-
|
|
2306
|
+
console.log("\n" + c.success + c.white(` Machine key authorized: ${confirmed ? "YES" : "NO"}`));
|
|
2307
|
+
renderTree([
|
|
2314
2308
|
{ label: "Wallet", value: config.walletContractAddress ?? "" },
|
|
2315
2309
|
{ label: "Machine key", value: checksumKey },
|
|
2316
2310
|
{ label: "Tx", value: hash, last: true },
|
|
@@ -2325,7 +2319,7 @@ function registerWalletCommands(program) {
|
|
|
2325
2319
|
wallet.command("revoke-machine-key <address>")
|
|
2326
2320
|
.description("Revoke an authorized machine key on ARC402Wallet (phone wallet signs via WalletConnect)")
|
|
2327
2321
|
.action(async (keyAddress) => {
|
|
2328
|
-
const config =
|
|
2322
|
+
const config = loadConfig();
|
|
2329
2323
|
if (!config.walletContractAddress) {
|
|
2330
2324
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
2331
2325
|
process.exit(1);
|
|
@@ -2336,15 +2330,15 @@ function registerWalletCommands(program) {
|
|
|
2336
2330
|
}
|
|
2337
2331
|
let checksumKey;
|
|
2338
2332
|
try {
|
|
2339
|
-
checksumKey =
|
|
2333
|
+
checksumKey = ethers.getAddress(keyAddress);
|
|
2340
2334
|
}
|
|
2341
2335
|
catch {
|
|
2342
2336
|
console.error(`Invalid address: ${keyAddress}`);
|
|
2343
2337
|
process.exit(1);
|
|
2344
2338
|
}
|
|
2345
2339
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
2346
|
-
const provider = new
|
|
2347
|
-
const walletContract = new
|
|
2340
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2341
|
+
const walletContract = new ethers.Contract(config.walletContractAddress, ARC402_WALLET_MACHINE_KEY_ABI, provider);
|
|
2348
2342
|
// Pre-check: verify the key IS currently authorized
|
|
2349
2343
|
let isAuthorized = false;
|
|
2350
2344
|
try {
|
|
@@ -2361,25 +2355,25 @@ function registerWalletCommands(program) {
|
|
|
2361
2355
|
const telegramOpts = config.telegramBotToken && config.telegramChatId
|
|
2362
2356
|
? { botToken: config.telegramBotToken, chatId: config.telegramChatId, threadId: config.telegramThreadId }
|
|
2363
2357
|
: undefined;
|
|
2364
|
-
const walletInterface = new
|
|
2365
|
-
const { client, session, account } = await
|
|
2366
|
-
console.log("\n" +
|
|
2367
|
-
console.log(
|
|
2368
|
-
const hash = await
|
|
2358
|
+
const walletInterface = new ethers.Interface(ARC402_WALLET_MACHINE_KEY_ABI);
|
|
2359
|
+
const { client, session, account } = await connectPhoneWallet(config.walletConnectProjectId, chainId, config, { telegramOpts, prompt: `Revoke machine key ${checksumKey} on ARC402Wallet` });
|
|
2360
|
+
console.log("\n" + c.success + c.white(` Connected: ${account}`));
|
|
2361
|
+
console.log(c.dim("Sending revokeMachineKey transaction..."));
|
|
2362
|
+
const hash = await sendTransactionWithSession(client, session, account, chainId, {
|
|
2369
2363
|
to: config.walletContractAddress,
|
|
2370
2364
|
data: walletInterface.encodeFunctionData("revokeMachineKey", [checksumKey]),
|
|
2371
2365
|
value: "0x0",
|
|
2372
2366
|
});
|
|
2373
|
-
console.log("\n" +
|
|
2374
|
-
console.log(
|
|
2367
|
+
console.log("\n" + c.dim("Transaction submitted:") + " " + c.white(hash));
|
|
2368
|
+
console.log(c.dim("Waiting for confirmation..."));
|
|
2375
2369
|
const receipt = await provider.waitForTransaction(hash, 1, 60000);
|
|
2376
2370
|
if (!receipt || receipt.status !== 1) {
|
|
2377
|
-
console.error(
|
|
2371
|
+
console.error(c.failure + " " + c.red("Transaction failed."));
|
|
2378
2372
|
process.exit(1);
|
|
2379
2373
|
}
|
|
2380
2374
|
const stillAuthorized = await walletContract.authorizedMachineKeys(checksumKey);
|
|
2381
|
-
console.log("\n" +
|
|
2382
|
-
|
|
2375
|
+
console.log("\n" + c.success + c.white(` Machine key revoked: ${stillAuthorized ? "NO (still authorized — check tx)" : "YES"}`));
|
|
2376
|
+
renderTree([
|
|
2383
2377
|
{ label: "Wallet", value: config.walletContractAddress ?? "" },
|
|
2384
2378
|
{ label: "Machine key", value: checksumKey },
|
|
2385
2379
|
{ label: "Tx", value: hash, last: true },
|
|
@@ -2395,17 +2389,17 @@ function registerWalletCommands(program) {
|
|
|
2395
2389
|
.description("List authorized machine keys by scanning contract events")
|
|
2396
2390
|
.option("--json")
|
|
2397
2391
|
.action(async (opts) => {
|
|
2398
|
-
const config =
|
|
2392
|
+
const config = loadConfig();
|
|
2399
2393
|
const walletAddr = config.walletContractAddress;
|
|
2400
2394
|
if (!walletAddr) {
|
|
2401
2395
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
2402
2396
|
process.exit(1);
|
|
2403
2397
|
}
|
|
2404
|
-
const provider = new
|
|
2405
|
-
const walletContract = new
|
|
2398
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2399
|
+
const walletContract = new ethers.Contract(walletAddr, ARC402_WALLET_MACHINE_KEY_ABI, provider);
|
|
2406
2400
|
// Scan for MachineKeyAuthorized and MachineKeyRevoked events
|
|
2407
|
-
const authorizedTopic =
|
|
2408
|
-
const revokedTopic =
|
|
2401
|
+
const authorizedTopic = ethers.id("MachineKeyAuthorized(address)");
|
|
2402
|
+
const revokedTopic = ethers.id("MachineKeyRevoked(address)");
|
|
2409
2403
|
const authorizedKeys = new Set();
|
|
2410
2404
|
const revokedKeys = new Set();
|
|
2411
2405
|
try {
|
|
@@ -2414,11 +2408,11 @@ function registerWalletCommands(program) {
|
|
|
2414
2408
|
provider.getLogs({ address: walletAddr, topics: [revokedTopic], fromBlock: 0 }),
|
|
2415
2409
|
]);
|
|
2416
2410
|
for (const log of authLogs) {
|
|
2417
|
-
const key =
|
|
2411
|
+
const key = ethers.getAddress("0x" + log.topics[1].slice(26));
|
|
2418
2412
|
authorizedKeys.add(key);
|
|
2419
2413
|
}
|
|
2420
2414
|
for (const log of revokeLogs) {
|
|
2421
|
-
const key =
|
|
2415
|
+
const key = ethers.getAddress("0x" + log.topics[1].slice(26));
|
|
2422
2416
|
revokedKeys.add(key);
|
|
2423
2417
|
}
|
|
2424
2418
|
}
|
|
@@ -2426,7 +2420,7 @@ function registerWalletCommands(program) {
|
|
|
2426
2420
|
// Build active key list: authorized but not revoked
|
|
2427
2421
|
const activeFromEvents = [...authorizedKeys].filter((k) => !revokedKeys.has(k));
|
|
2428
2422
|
// Also check configured machine key
|
|
2429
|
-
const configMachineKey = config.privateKey ? new
|
|
2423
|
+
const configMachineKey = config.privateKey ? new ethers.Wallet(config.privateKey).address : null;
|
|
2430
2424
|
// Verify each candidate against chain
|
|
2431
2425
|
const candidates = new Set(activeFromEvents);
|
|
2432
2426
|
if (configMachineKey)
|
|
@@ -2468,8 +2462,8 @@ function registerWalletCommands(program) {
|
|
|
2468
2462
|
.option("--task-type <type>", "Task type string for the context", "general")
|
|
2469
2463
|
.option("--json")
|
|
2470
2464
|
.action(async (opts) => {
|
|
2471
|
-
const config =
|
|
2472
|
-
|
|
2465
|
+
const config = loadConfig();
|
|
2466
|
+
warnIfPublicRpc(config);
|
|
2473
2467
|
const walletAddr = config.walletContractAddress;
|
|
2474
2468
|
if (!walletAddr) {
|
|
2475
2469
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
@@ -2479,9 +2473,9 @@ function registerWalletCommands(program) {
|
|
|
2479
2473
|
console.error("privateKey not set in config — machine key required for open-context.");
|
|
2480
2474
|
process.exit(1);
|
|
2481
2475
|
}
|
|
2482
|
-
const provider = new
|
|
2483
|
-
const machineKey = new
|
|
2484
|
-
const walletContract = new
|
|
2476
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2477
|
+
const machineKey = new ethers.Wallet(config.privateKey, provider);
|
|
2478
|
+
const walletContract = new ethers.Contract(walletAddr, ARC402_WALLET_PROTOCOL_ABI, machineKey);
|
|
2485
2479
|
// Check context isn't already open
|
|
2486
2480
|
const isOpen = await walletContract.contextOpen();
|
|
2487
2481
|
if (isOpen) {
|
|
@@ -2489,7 +2483,7 @@ function registerWalletCommands(program) {
|
|
|
2489
2483
|
console.error("Close it first: arc402 wallet close-context");
|
|
2490
2484
|
process.exit(1);
|
|
2491
2485
|
}
|
|
2492
|
-
const contextId =
|
|
2486
|
+
const contextId = ethers.hexlify(ethers.randomBytes(32));
|
|
2493
2487
|
console.log(`Opening context (taskType: ${opts.taskType})...`);
|
|
2494
2488
|
const tx = await walletContract.openContext(contextId, opts.taskType);
|
|
2495
2489
|
const receipt = await tx.wait(1);
|
|
@@ -2497,13 +2491,13 @@ function registerWalletCommands(program) {
|
|
|
2497
2491
|
console.log(JSON.stringify({ walletAddress: walletAddr, contextId, taskType: opts.taskType, txHash: receipt?.hash }));
|
|
2498
2492
|
}
|
|
2499
2493
|
else {
|
|
2500
|
-
console.log(" " +
|
|
2501
|
-
|
|
2494
|
+
console.log(" " + c.success + c.white(" Context opened"));
|
|
2495
|
+
renderTree([
|
|
2502
2496
|
{ label: "contextId", value: contextId },
|
|
2503
2497
|
{ label: "taskType", value: opts.taskType },
|
|
2504
2498
|
{ label: "Tx", value: receipt?.hash ?? "", last: true },
|
|
2505
2499
|
]);
|
|
2506
|
-
console.log(
|
|
2500
|
+
console.log(c.dim("\nNote: Each context allows only one spend. Call `arc402 wallet attest` then `arc402 wallet drain` (or executeSpend directly)."));
|
|
2507
2501
|
}
|
|
2508
2502
|
});
|
|
2509
2503
|
// ─── attest (J1-03) ────────────────────────────────────────────────────────
|
|
@@ -2519,8 +2513,8 @@ function registerWalletCommands(program) {
|
|
|
2519
2513
|
.option("--ttl <seconds>", "Attestation TTL in seconds (default: 600)", "600")
|
|
2520
2514
|
.option("--json")
|
|
2521
2515
|
.action(async (opts) => {
|
|
2522
|
-
const config =
|
|
2523
|
-
|
|
2516
|
+
const config = loadConfig();
|
|
2517
|
+
warnIfPublicRpc(config);
|
|
2524
2518
|
const walletAddr = config.walletContractAddress;
|
|
2525
2519
|
if (!walletAddr) {
|
|
2526
2520
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
@@ -2532,19 +2526,19 @@ function registerWalletCommands(program) {
|
|
|
2532
2526
|
}
|
|
2533
2527
|
let checksumRecipient;
|
|
2534
2528
|
try {
|
|
2535
|
-
checksumRecipient =
|
|
2529
|
+
checksumRecipient = ethers.getAddress(opts.recipient);
|
|
2536
2530
|
}
|
|
2537
2531
|
catch {
|
|
2538
2532
|
console.error(`Invalid recipient address: ${opts.recipient}`);
|
|
2539
2533
|
process.exit(1);
|
|
2540
2534
|
}
|
|
2541
|
-
const tokenAddress = opts.token ?
|
|
2542
|
-
const amount =
|
|
2535
|
+
const tokenAddress = opts.token ? ethers.getAddress(opts.token) : ethers.ZeroAddress;
|
|
2536
|
+
const amount = ethers.parseEther(opts.amount);
|
|
2543
2537
|
const expiresAt = Math.floor(Date.now() / 1000) + parseInt(opts.ttl, 10);
|
|
2544
|
-
const attestationId =
|
|
2545
|
-
const provider = new
|
|
2546
|
-
const machineKey = new
|
|
2547
|
-
const walletContract = new
|
|
2538
|
+
const attestationId = ethers.hexlify(ethers.randomBytes(32));
|
|
2539
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2540
|
+
const machineKey = new ethers.Wallet(config.privateKey, provider);
|
|
2541
|
+
const walletContract = new ethers.Contract(walletAddr, ARC402_WALLET_PROTOCOL_ABI, machineKey);
|
|
2548
2542
|
console.log(`Creating attestation...`);
|
|
2549
2543
|
const tx = await walletContract.attest(attestationId, opts.category, `cli attest: ${opts.category} to ${checksumRecipient}`, checksumRecipient, amount, tokenAddress, expiresAt);
|
|
2550
2544
|
const receipt = await tx.wait(1);
|
|
@@ -2561,16 +2555,16 @@ function registerWalletCommands(program) {
|
|
|
2561
2555
|
}));
|
|
2562
2556
|
}
|
|
2563
2557
|
else {
|
|
2564
|
-
console.log(" " +
|
|
2565
|
-
|
|
2558
|
+
console.log(" " + c.success + c.white(" Attestation created"));
|
|
2559
|
+
renderTree([
|
|
2566
2560
|
{ label: "attestationId", value: attestationId },
|
|
2567
2561
|
{ label: "recipient", value: checksumRecipient },
|
|
2568
2562
|
{ label: "amount", value: opts.amount + " ETH" },
|
|
2569
|
-
{ label: "token", value: tokenAddress ===
|
|
2563
|
+
{ label: "token", value: tokenAddress === ethers.ZeroAddress ? "ETH" : tokenAddress },
|
|
2570
2564
|
{ label: "expiresAt", value: new Date(expiresAt * 1000).toISOString() },
|
|
2571
2565
|
{ label: "Tx", value: receipt?.hash ?? "", last: true },
|
|
2572
2566
|
]);
|
|
2573
|
-
console.log(
|
|
2567
|
+
console.log(c.dim("\nUse this attestationId in `arc402 wallet drain` or your spend flow."));
|
|
2574
2568
|
}
|
|
2575
2569
|
});
|
|
2576
2570
|
// ─── velocity-status (J8-03) ───────────────────────────────────────────────
|
|
@@ -2580,14 +2574,14 @@ function registerWalletCommands(program) {
|
|
|
2580
2574
|
.description("Show wallet-level velocity limit, current window spend, and remaining budget")
|
|
2581
2575
|
.option("--json")
|
|
2582
2576
|
.action(async (opts) => {
|
|
2583
|
-
const config =
|
|
2577
|
+
const config = loadConfig();
|
|
2584
2578
|
const walletAddr = config.walletContractAddress;
|
|
2585
2579
|
if (!walletAddr) {
|
|
2586
2580
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
2587
2581
|
process.exit(1);
|
|
2588
2582
|
}
|
|
2589
|
-
const provider = new
|
|
2590
|
-
const walletContract = new
|
|
2583
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2584
|
+
const walletContract = new ethers.Contract(walletAddr, ARC402_WALLET_OWNER_ABI, provider);
|
|
2591
2585
|
let velocityLimit = 0n;
|
|
2592
2586
|
let velocityWindowStart = 0n;
|
|
2593
2587
|
let cumulativeSpend = 0n;
|
|
@@ -2607,11 +2601,11 @@ function registerWalletCommands(program) {
|
|
|
2607
2601
|
if (opts.json) {
|
|
2608
2602
|
console.log(JSON.stringify({
|
|
2609
2603
|
walletAddress: walletAddr,
|
|
2610
|
-
velocityLimit:
|
|
2604
|
+
velocityLimit: ethers.formatEther(velocityLimit),
|
|
2611
2605
|
velocityLimitEnabled: velocityLimit > 0n,
|
|
2612
2606
|
velocityWindowStart: windowStartDate?.toISOString() ?? null,
|
|
2613
|
-
cumulativeSpend:
|
|
2614
|
-
remaining: remaining !== null ?
|
|
2607
|
+
cumulativeSpend: ethers.formatEther(cumulativeSpend),
|
|
2608
|
+
remaining: remaining !== null ? ethers.formatEther(remaining) : null,
|
|
2615
2609
|
}, null, 2));
|
|
2616
2610
|
}
|
|
2617
2611
|
else {
|
|
@@ -2620,10 +2614,10 @@ function registerWalletCommands(program) {
|
|
|
2620
2614
|
console.log(` Velocity limit: disabled (set with \`arc402 wallet set-velocity-limit <eth>\`)`);
|
|
2621
2615
|
}
|
|
2622
2616
|
else {
|
|
2623
|
-
console.log(` Limit: ${
|
|
2617
|
+
console.log(` Limit: ${ethers.formatEther(velocityLimit)} ETH per rolling window`);
|
|
2624
2618
|
console.log(` Window start: ${windowStartDate?.toISOString() ?? "(no window yet)"}`);
|
|
2625
|
-
console.log(` Spent: ${
|
|
2626
|
-
console.log(` Remaining: ${remaining !== null ?
|
|
2619
|
+
console.log(` Spent: ${ethers.formatEther(cumulativeSpend)} ETH`);
|
|
2620
|
+
console.log(` Remaining: ${remaining !== null ? ethers.formatEther(remaining) + " ETH" : "N/A"}`);
|
|
2627
2621
|
}
|
|
2628
2622
|
}
|
|
2629
2623
|
});
|
|
@@ -2634,15 +2628,15 @@ function registerWalletCommands(program) {
|
|
|
2634
2628
|
.description("Check whether the wallet's spend context is currently open (uses Alchemy RPC)")
|
|
2635
2629
|
.option("--json")
|
|
2636
2630
|
.action(async (opts) => {
|
|
2637
|
-
const config =
|
|
2638
|
-
|
|
2631
|
+
const config = loadConfig();
|
|
2632
|
+
warnIfPublicRpc(config);
|
|
2639
2633
|
const walletAddr = config.walletContractAddress;
|
|
2640
2634
|
if (!walletAddr) {
|
|
2641
2635
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
2642
2636
|
process.exit(1);
|
|
2643
2637
|
}
|
|
2644
|
-
const provider = new
|
|
2645
|
-
const walletContract = new
|
|
2638
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2639
|
+
const walletContract = new ethers.Contract(walletAddr, ARC402_WALLET_PROTOCOL_ABI, provider);
|
|
2646
2640
|
const isOpen = await walletContract.contextOpen();
|
|
2647
2641
|
if (opts.json) {
|
|
2648
2642
|
console.log(JSON.stringify({ walletAddress: walletAddr, contextOpen: isOpen }));
|
|
@@ -2660,8 +2654,8 @@ function registerWalletCommands(program) {
|
|
|
2660
2654
|
.description("Force-close a stale open context on the wallet (machine key signs — onlyOwnerOrMachineKey)")
|
|
2661
2655
|
.option("--json")
|
|
2662
2656
|
.action(async (opts) => {
|
|
2663
|
-
const config =
|
|
2664
|
-
|
|
2657
|
+
const config = loadConfig();
|
|
2658
|
+
warnIfPublicRpc(config);
|
|
2665
2659
|
const walletAddr = config.walletContractAddress;
|
|
2666
2660
|
if (!walletAddr) {
|
|
2667
2661
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
@@ -2671,9 +2665,9 @@ function registerWalletCommands(program) {
|
|
|
2671
2665
|
console.error("privateKey not set in config — machine key required for close-context.");
|
|
2672
2666
|
process.exit(1);
|
|
2673
2667
|
}
|
|
2674
|
-
const provider = new
|
|
2675
|
-
const machineKey = new
|
|
2676
|
-
const walletContract = new
|
|
2668
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2669
|
+
const machineKey = new ethers.Wallet(config.privateKey, provider);
|
|
2670
|
+
const walletContract = new ethers.Contract(walletAddr, ARC402_WALLET_PROTOCOL_ABI, machineKey);
|
|
2677
2671
|
const isOpen = await walletContract.contextOpen();
|
|
2678
2672
|
if (!isOpen) {
|
|
2679
2673
|
if (opts.json) {
|
|
@@ -2691,9 +2685,9 @@ function registerWalletCommands(program) {
|
|
|
2691
2685
|
console.log(JSON.stringify({ walletAddress: walletAddr, txHash: receipt?.hash, contextOpen: false }));
|
|
2692
2686
|
}
|
|
2693
2687
|
else {
|
|
2694
|
-
console.log(" " +
|
|
2695
|
-
console.log(" " +
|
|
2696
|
-
console.log(" " +
|
|
2688
|
+
console.log(" " + c.success + c.white(" Context closed"));
|
|
2689
|
+
console.log(" " + c.dim("Tx:") + " " + c.white(receipt?.hash ?? ""));
|
|
2690
|
+
console.log(" " + c.dim("Wallet:") + " " + c.white(walletAddr));
|
|
2697
2691
|
}
|
|
2698
2692
|
});
|
|
2699
2693
|
// ─── drain ─────────────────────────────────────────────────────────────────
|
|
@@ -2708,8 +2702,8 @@ function registerWalletCommands(program) {
|
|
|
2708
2702
|
.option("--category <cat>", "Spend category (default: general)", "general")
|
|
2709
2703
|
.option("--json")
|
|
2710
2704
|
.action(async (recipientArg, opts) => {
|
|
2711
|
-
const config =
|
|
2712
|
-
|
|
2705
|
+
const config = loadConfig();
|
|
2706
|
+
warnIfPublicRpc(config);
|
|
2713
2707
|
const walletAddr = config.walletContractAddress;
|
|
2714
2708
|
if (!walletAddr) {
|
|
2715
2709
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
@@ -2726,26 +2720,26 @@ function registerWalletCommands(program) {
|
|
|
2726
2720
|
}
|
|
2727
2721
|
let checksumRecipient;
|
|
2728
2722
|
try {
|
|
2729
|
-
checksumRecipient =
|
|
2723
|
+
checksumRecipient = ethers.getAddress(recipient);
|
|
2730
2724
|
}
|
|
2731
2725
|
catch {
|
|
2732
2726
|
console.error(`Invalid recipient address: ${recipient}`);
|
|
2733
2727
|
process.exit(1);
|
|
2734
2728
|
}
|
|
2735
|
-
const GAS_RESERVE =
|
|
2736
|
-
const provider = new
|
|
2737
|
-
const machineKey = new
|
|
2738
|
-
const walletContract = new
|
|
2729
|
+
const GAS_RESERVE = ethers.parseEther("0.00005");
|
|
2730
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2731
|
+
const machineKey = new ethers.Wallet(config.privateKey, provider);
|
|
2732
|
+
const walletContract = new ethers.Contract(walletAddr, ARC402_WALLET_PROTOCOL_ABI, machineKey);
|
|
2739
2733
|
// ── Pre-flight checks ──────────────────────────────────────────────────
|
|
2740
2734
|
const balance = await provider.getBalance(walletAddr);
|
|
2741
|
-
console.log(`Wallet balance: ${
|
|
2735
|
+
console.log(`Wallet balance: ${ethers.formatEther(balance)} ETH`);
|
|
2742
2736
|
if (balance <= GAS_RESERVE) {
|
|
2743
|
-
console.error(`Insufficient balance: ${
|
|
2737
|
+
console.error(`Insufficient balance: ${ethers.formatEther(balance)} ETH — need more than ${ethers.formatEther(GAS_RESERVE)} ETH reserve`);
|
|
2744
2738
|
process.exit(1);
|
|
2745
2739
|
}
|
|
2746
2740
|
// Check category is configured on PolicyEngine
|
|
2747
2741
|
const policyAddress = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
|
|
2748
|
-
const policyContract = new
|
|
2742
|
+
const policyContract = new ethers.Contract(policyAddress, POLICY_ENGINE_LIMITS_ABI, provider);
|
|
2749
2743
|
const categoryLimit = await policyContract.categoryLimits(walletAddr, opts.category);
|
|
2750
2744
|
if (categoryLimit === 0n) {
|
|
2751
2745
|
console.error(`Category "${opts.category}" is not configured on PolicyEngine for this wallet.`);
|
|
@@ -2754,7 +2748,7 @@ function registerWalletCommands(program) {
|
|
|
2754
2748
|
}
|
|
2755
2749
|
// Verify machine key is authorized
|
|
2756
2750
|
const machineKeyAbi = ["function authorizedMachineKeys(address) external view returns (bool)"];
|
|
2757
|
-
const walletCheck = new
|
|
2751
|
+
const walletCheck = new ethers.Contract(walletAddr, machineKeyAbi, provider);
|
|
2758
2752
|
let isAuthorized = false;
|
|
2759
2753
|
try {
|
|
2760
2754
|
isAuthorized = await walletCheck.authorizedMachineKeys(machineKey.address);
|
|
@@ -2770,46 +2764,46 @@ function registerWalletCommands(program) {
|
|
|
2770
2764
|
// Compute drain amount
|
|
2771
2765
|
let drainAmount;
|
|
2772
2766
|
if (opts.amount) {
|
|
2773
|
-
drainAmount =
|
|
2767
|
+
drainAmount = ethers.parseEther(opts.amount);
|
|
2774
2768
|
}
|
|
2775
2769
|
else {
|
|
2776
2770
|
drainAmount = balance - GAS_RESERVE;
|
|
2777
2771
|
}
|
|
2778
2772
|
if (drainAmount > categoryLimit) {
|
|
2779
|
-
console.warn(`WARN: drainAmount (${
|
|
2773
|
+
console.warn(`WARN: drainAmount (${ethers.formatEther(drainAmount)} ETH) exceeds category limit (${ethers.formatEther(categoryLimit)} ETH)`);
|
|
2780
2774
|
console.warn(` Capping at category limit.`);
|
|
2781
2775
|
drainAmount = categoryLimit;
|
|
2782
2776
|
}
|
|
2783
2777
|
console.log(`\nDrain plan:`);
|
|
2784
2778
|
console.log(` Wallet: ${walletAddr}`);
|
|
2785
2779
|
console.log(` Recipient: ${checksumRecipient}`);
|
|
2786
|
-
console.log(` Amount: ${
|
|
2780
|
+
console.log(` Amount: ${ethers.formatEther(drainAmount)} ETH`);
|
|
2787
2781
|
console.log(` Category: ${opts.category}`);
|
|
2788
2782
|
console.log(` MachineKey: ${machineKey.address}`);
|
|
2789
2783
|
console.log(`\nNote: Each context allows exactly one spend. A new context is opened for each drain call.\n`);
|
|
2790
2784
|
// ── Step 1: context cleanup ────────────────────────────────────────────
|
|
2791
2785
|
const isOpen = await walletContract.contextOpen();
|
|
2792
2786
|
if (isOpen) {
|
|
2793
|
-
console.log(
|
|
2787
|
+
console.log(c.dim("Stale context found — closing it first..."));
|
|
2794
2788
|
const closeTx = await walletContract.closeContext();
|
|
2795
2789
|
await closeTx.wait(2);
|
|
2796
|
-
console.log(" " +
|
|
2790
|
+
console.log(" " + c.success + c.dim(` Closed: ${closeTx.hash}`));
|
|
2797
2791
|
}
|
|
2798
2792
|
// ── Step 2: openContext ────────────────────────────────────────────────
|
|
2799
|
-
const contextId =
|
|
2800
|
-
console.log(
|
|
2793
|
+
const contextId = ethers.keccak256(ethers.toUtf8Bytes(`drain-${Date.now()}`));
|
|
2794
|
+
console.log(c.dim("Opening context..."));
|
|
2801
2795
|
const openTx = await walletContract.openContext(contextId, "drain");
|
|
2802
2796
|
const openReceipt = await openTx.wait(1);
|
|
2803
|
-
console.log(" " +
|
|
2797
|
+
console.log(" " + c.success + c.dim(` openContext: ${openReceipt?.hash}`));
|
|
2804
2798
|
// ── Step 3: attest (direct on wallet — onlyOwnerOrMachineKey, NOT via executeContractCall)
|
|
2805
|
-
const attestationId =
|
|
2799
|
+
const attestationId = ethers.hexlify(ethers.randomBytes(32));
|
|
2806
2800
|
const expiry = Math.floor(Date.now() / 1000) + 600; // 10 min TTL
|
|
2807
|
-
console.log(
|
|
2808
|
-
const attestTx = await walletContract.attest(attestationId, "spend", `drain to ${checksumRecipient}`, checksumRecipient, drainAmount,
|
|
2801
|
+
console.log(c.dim("Creating attestation (direct on wallet)..."));
|
|
2802
|
+
const attestTx = await walletContract.attest(attestationId, "spend", `drain to ${checksumRecipient}`, checksumRecipient, drainAmount, ethers.ZeroAddress, expiry);
|
|
2809
2803
|
const attestReceipt = await attestTx.wait(1);
|
|
2810
|
-
console.log(" " +
|
|
2804
|
+
console.log(" " + c.success + c.dim(` attest: ${attestReceipt?.hash}`));
|
|
2811
2805
|
// ── Step 4: executeSpend ───────────────────────────────────────────────
|
|
2812
|
-
console.log(
|
|
2806
|
+
console.log(c.dim("Executing spend..."));
|
|
2813
2807
|
let spendReceiptHash;
|
|
2814
2808
|
try {
|
|
2815
2809
|
const spendTx = await walletContract.executeSpend(checksumRecipient, drainAmount, opts.category, attestationId);
|
|
@@ -2817,21 +2811,21 @@ function registerWalletCommands(program) {
|
|
|
2817
2811
|
spendReceiptHash = spendReceipt?.hash;
|
|
2818
2812
|
}
|
|
2819
2813
|
catch (e) {
|
|
2820
|
-
|
|
2814
|
+
handleWalletError(e);
|
|
2821
2815
|
}
|
|
2822
|
-
console.log(" " +
|
|
2816
|
+
console.log(" " + c.success + c.dim(` executeSpend: ${spendReceiptHash}`));
|
|
2823
2817
|
// ── Step 5: closeContext ───────────────────────────────────────────────
|
|
2824
|
-
console.log(
|
|
2818
|
+
console.log(c.dim("Closing context..."));
|
|
2825
2819
|
const closeTx2 = await walletContract.closeContext();
|
|
2826
2820
|
const closeReceipt = await closeTx2.wait(1);
|
|
2827
|
-
console.log(" " +
|
|
2821
|
+
console.log(" " + c.success + c.dim(` closeContext: ${closeReceipt?.hash}`));
|
|
2828
2822
|
const newBalance = await provider.getBalance(walletAddr);
|
|
2829
2823
|
if (opts.json) {
|
|
2830
2824
|
console.log(JSON.stringify({
|
|
2831
2825
|
ok: true,
|
|
2832
2826
|
walletAddress: walletAddr,
|
|
2833
2827
|
recipient: checksumRecipient,
|
|
2834
|
-
amount:
|
|
2828
|
+
amount: ethers.formatEther(drainAmount),
|
|
2835
2829
|
category: opts.category,
|
|
2836
2830
|
txHashes: {
|
|
2837
2831
|
openContext: openReceipt?.hash,
|
|
@@ -2839,14 +2833,14 @@ function registerWalletCommands(program) {
|
|
|
2839
2833
|
executeSpend: spendReceiptHash,
|
|
2840
2834
|
closeContext: closeReceipt?.hash,
|
|
2841
2835
|
},
|
|
2842
|
-
remainingBalance:
|
|
2836
|
+
remainingBalance: ethers.formatEther(newBalance),
|
|
2843
2837
|
}));
|
|
2844
2838
|
}
|
|
2845
2839
|
else {
|
|
2846
|
-
console.log("\n" +
|
|
2847
|
-
|
|
2848
|
-
{ label: "Sent", value: `${
|
|
2849
|
-
{ label: "Remaining", value: `${
|
|
2840
|
+
console.log("\n" + c.success + c.white(" Drain complete"));
|
|
2841
|
+
renderTree([
|
|
2842
|
+
{ label: "Sent", value: `${ethers.formatEther(drainAmount)} ETH → ${checksumRecipient}` },
|
|
2843
|
+
{ label: "Remaining", value: `${ethers.formatEther(newBalance)} ETH`, last: true },
|
|
2850
2844
|
]);
|
|
2851
2845
|
}
|
|
2852
2846
|
});
|
|
@@ -2866,8 +2860,8 @@ function registerWalletCommands(program) {
|
|
|
2866
2860
|
.option("--decimals <n>", "Token decimals override (default: auto-detect from contract)", "auto")
|
|
2867
2861
|
.option("--json")
|
|
2868
2862
|
.action(async (recipientArg, amountArg, opts) => {
|
|
2869
|
-
const config =
|
|
2870
|
-
|
|
2863
|
+
const config = loadConfig();
|
|
2864
|
+
warnIfPublicRpc(config);
|
|
2871
2865
|
const walletAddr = config.walletContractAddress;
|
|
2872
2866
|
if (!walletAddr) {
|
|
2873
2867
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
@@ -2880,11 +2874,11 @@ function registerWalletCommands(program) {
|
|
|
2880
2874
|
// Resolve token address
|
|
2881
2875
|
let tokenAddress;
|
|
2882
2876
|
if (opts.token.toLowerCase() === "usdc") {
|
|
2883
|
-
tokenAddress =
|
|
2877
|
+
tokenAddress = getUsdcAddress(config);
|
|
2884
2878
|
}
|
|
2885
2879
|
else {
|
|
2886
2880
|
try {
|
|
2887
|
-
tokenAddress =
|
|
2881
|
+
tokenAddress = ethers.getAddress(opts.token);
|
|
2888
2882
|
}
|
|
2889
2883
|
catch {
|
|
2890
2884
|
console.error(`Invalid token address: ${opts.token}`);
|
|
@@ -2893,20 +2887,20 @@ function registerWalletCommands(program) {
|
|
|
2893
2887
|
}
|
|
2894
2888
|
let checksumRecipient;
|
|
2895
2889
|
try {
|
|
2896
|
-
checksumRecipient =
|
|
2890
|
+
checksumRecipient = ethers.getAddress(recipientArg);
|
|
2897
2891
|
}
|
|
2898
2892
|
catch {
|
|
2899
2893
|
console.error(`Invalid recipient address: ${recipientArg}`);
|
|
2900
2894
|
process.exit(1);
|
|
2901
2895
|
}
|
|
2902
|
-
const provider = new
|
|
2903
|
-
const machineKey = new
|
|
2896
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
2897
|
+
const machineKey = new ethers.Wallet(config.privateKey, provider);
|
|
2904
2898
|
// Determine token decimals
|
|
2905
2899
|
const erc20Abi = [
|
|
2906
2900
|
"function decimals() external view returns (uint8)",
|
|
2907
2901
|
"function balanceOf(address owner) external view returns (uint256)",
|
|
2908
2902
|
];
|
|
2909
|
-
const erc20 = new
|
|
2903
|
+
const erc20 = new ethers.Contract(tokenAddress, erc20Abi, provider);
|
|
2910
2904
|
let decimals;
|
|
2911
2905
|
if (opts.decimals !== "auto") {
|
|
2912
2906
|
decimals = parseInt(opts.decimals, 10);
|
|
@@ -2921,7 +2915,7 @@ function registerWalletCommands(program) {
|
|
|
2921
2915
|
}
|
|
2922
2916
|
let tokenAmount;
|
|
2923
2917
|
try {
|
|
2924
|
-
tokenAmount =
|
|
2918
|
+
tokenAmount = ethers.parseUnits(amountArg, decimals);
|
|
2925
2919
|
}
|
|
2926
2920
|
catch {
|
|
2927
2921
|
console.error(`Invalid amount: ${amountArg}. Provide a decimal value (e.g. 1.5).`);
|
|
@@ -2930,12 +2924,12 @@ function registerWalletCommands(program) {
|
|
|
2930
2924
|
// Check token balance
|
|
2931
2925
|
const tokenBalance = await erc20.balanceOf(walletAddr);
|
|
2932
2926
|
if (tokenBalance < tokenAmount) {
|
|
2933
|
-
console.error(`Insufficient token balance: ${
|
|
2927
|
+
console.error(`Insufficient token balance: ${ethers.formatUnits(tokenBalance, decimals)} < ${amountArg}`);
|
|
2934
2928
|
process.exit(1);
|
|
2935
2929
|
}
|
|
2936
2930
|
// Check category is configured on PolicyEngine
|
|
2937
2931
|
const policyAddressT = config.policyEngineAddress ?? POLICY_ENGINE_DEFAULT;
|
|
2938
|
-
const policyContractT = new
|
|
2932
|
+
const policyContractT = new ethers.Contract(policyAddressT, POLICY_ENGINE_LIMITS_ABI, provider);
|
|
2939
2933
|
const categoryLimitT = await policyContractT.categoryLimits(walletAddr, opts.category);
|
|
2940
2934
|
if (categoryLimitT === 0n) {
|
|
2941
2935
|
console.error(`Category "${opts.category}" is not configured on PolicyEngine for this wallet.`);
|
|
@@ -2944,7 +2938,7 @@ function registerWalletCommands(program) {
|
|
|
2944
2938
|
}
|
|
2945
2939
|
// Verify machine key is authorized
|
|
2946
2940
|
const mkAbi = ["function authorizedMachineKeys(address) external view returns (bool)"];
|
|
2947
|
-
const walletCheckT = new
|
|
2941
|
+
const walletCheckT = new ethers.Contract(walletAddr, mkAbi, provider);
|
|
2948
2942
|
let isAuthorizedT = false;
|
|
2949
2943
|
try {
|
|
2950
2944
|
isAuthorizedT = await walletCheckT.authorizedMachineKeys(machineKey.address);
|
|
@@ -2957,7 +2951,7 @@ function registerWalletCommands(program) {
|
|
|
2957
2951
|
console.error(`Fix: arc402 wallet authorize-machine-key ${machineKey.address}`);
|
|
2958
2952
|
process.exit(1);
|
|
2959
2953
|
}
|
|
2960
|
-
const walletContractT = new
|
|
2954
|
+
const walletContractT = new ethers.Contract(walletAddr, ARC402_WALLET_PROTOCOL_ABI, machineKey);
|
|
2961
2955
|
console.log(`\nDrain token plan:`);
|
|
2962
2956
|
console.log(` Wallet: ${walletAddr}`);
|
|
2963
2957
|
console.log(` Recipient: ${checksumRecipient}`);
|
|
@@ -2969,34 +2963,34 @@ function registerWalletCommands(program) {
|
|
|
2969
2963
|
// ── Step 1: context cleanup ──────────────────────────────────────────────
|
|
2970
2964
|
const isOpenT = await walletContractT.contextOpen();
|
|
2971
2965
|
if (isOpenT) {
|
|
2972
|
-
console.log(
|
|
2966
|
+
console.log(c.dim("Stale context found — closing it first..."));
|
|
2973
2967
|
const closeTxT = await walletContractT.closeContext();
|
|
2974
2968
|
await closeTxT.wait(2);
|
|
2975
|
-
console.log(" " +
|
|
2969
|
+
console.log(" " + c.success + c.dim(` Closed: ${closeTxT.hash}`));
|
|
2976
2970
|
}
|
|
2977
2971
|
// ── Step 2: openContext ──────────────────────────────────────────────────
|
|
2978
|
-
const contextIdT =
|
|
2979
|
-
console.log(
|
|
2972
|
+
const contextIdT = ethers.keccak256(ethers.toUtf8Bytes(`drain-token-${Date.now()}`));
|
|
2973
|
+
console.log(c.dim("Opening context..."));
|
|
2980
2974
|
const openTxT = await walletContractT.openContext(contextIdT, "drain");
|
|
2981
2975
|
const openReceiptT = await openTxT.wait(1);
|
|
2982
|
-
console.log(" " +
|
|
2976
|
+
console.log(" " + c.success + c.dim(` openContext: ${openReceiptT?.hash}`));
|
|
2983
2977
|
// ── Step 3: attest with token address ────────────────────────────────────
|
|
2984
|
-
const attestationIdT =
|
|
2978
|
+
const attestationIdT = ethers.hexlify(ethers.randomBytes(32));
|
|
2985
2979
|
const expiryT = Math.floor(Date.now() / 1000) + 600; // 10 min TTL
|
|
2986
|
-
console.log(
|
|
2980
|
+
console.log(c.dim("Creating attestation (with token address)..."));
|
|
2987
2981
|
const attestTxT = await walletContractT.attest(attestationIdT, "spend", `token drain to ${checksumRecipient}`, checksumRecipient, tokenAmount, tokenAddress, expiryT);
|
|
2988
2982
|
const attestReceiptT = await attestTxT.wait(1);
|
|
2989
|
-
console.log(" " +
|
|
2983
|
+
console.log(" " + c.success + c.dim(` attest: ${attestReceiptT?.hash}`));
|
|
2990
2984
|
// ── Step 4: executeTokenSpend ────────────────────────────────────────────
|
|
2991
|
-
console.log(
|
|
2985
|
+
console.log(c.dim("Executing token spend..."));
|
|
2992
2986
|
const spendTxT = await walletContractT.executeTokenSpend(checksumRecipient, tokenAmount, tokenAddress, opts.category, attestationIdT);
|
|
2993
2987
|
const spendReceiptT = await spendTxT.wait(1);
|
|
2994
|
-
console.log(" " +
|
|
2988
|
+
console.log(" " + c.success + c.dim(` executeTokenSpend: ${spendReceiptT?.hash}`));
|
|
2995
2989
|
// ── Step 5: closeContext ─────────────────────────────────────────────────
|
|
2996
|
-
console.log(
|
|
2990
|
+
console.log(c.dim("Closing context..."));
|
|
2997
2991
|
const closeTxT2 = await walletContractT.closeContext();
|
|
2998
2992
|
const closeReceiptT = await closeTxT2.wait(1);
|
|
2999
|
-
console.log(" " +
|
|
2993
|
+
console.log(" " + c.success + c.dim(` closeContext: ${closeReceiptT?.hash}`));
|
|
3000
2994
|
const newTokenBalance = await erc20.balanceOf(walletAddr);
|
|
3001
2995
|
if (opts.json) {
|
|
3002
2996
|
console.log(JSON.stringify({
|
|
@@ -3012,15 +3006,15 @@ function registerWalletCommands(program) {
|
|
|
3012
3006
|
executeTokenSpend: spendReceiptT?.hash,
|
|
3013
3007
|
closeContext: closeReceiptT?.hash,
|
|
3014
3008
|
},
|
|
3015
|
-
remainingTokenBalance:
|
|
3009
|
+
remainingTokenBalance: ethers.formatUnits(newTokenBalance, decimals),
|
|
3016
3010
|
}));
|
|
3017
3011
|
}
|
|
3018
3012
|
else {
|
|
3019
|
-
console.log("\n" +
|
|
3020
|
-
|
|
3013
|
+
console.log("\n" + c.success + c.white(" Token drain complete"));
|
|
3014
|
+
renderTree([
|
|
3021
3015
|
{ label: "Sent", value: `${amountArg} → ${checksumRecipient}` },
|
|
3022
3016
|
{ label: "Token", value: tokenAddress },
|
|
3023
|
-
{ label: "Remaining", value:
|
|
3017
|
+
{ label: "Remaining", value: ethers.formatUnits(newTokenBalance, decimals), last: true },
|
|
3024
3018
|
]);
|
|
3025
3019
|
}
|
|
3026
3020
|
});
|
|
@@ -3033,7 +3027,7 @@ function registerWalletCommands(program) {
|
|
|
3033
3027
|
wallet.command("set-passkey <pubKeyX> <pubKeyY>")
|
|
3034
3028
|
.description("Activate passkey (Face ID) on ARC402Wallet — takes P256 x/y coords from passkey setup (phone wallet signs via WalletConnect)")
|
|
3035
3029
|
.action(async (pubKeyX, pubKeyY) => {
|
|
3036
|
-
const config =
|
|
3030
|
+
const config = loadConfig();
|
|
3037
3031
|
if (!config.walletContractAddress) {
|
|
3038
3032
|
console.error("walletContractAddress not set in config. Run `arc402 wallet deploy` first.");
|
|
3039
3033
|
process.exit(1);
|
|
@@ -3053,8 +3047,8 @@ function registerWalletCommands(program) {
|
|
|
3053
3047
|
process.exit(1);
|
|
3054
3048
|
}
|
|
3055
3049
|
const chainId = config.network === "base-mainnet" ? 8453 : 84532;
|
|
3056
|
-
const provider = new
|
|
3057
|
-
const walletInterface = new
|
|
3050
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
3051
|
+
const walletInterface = new ethers.Interface(ARC402_WALLET_PASSKEY_ABI);
|
|
3058
3052
|
console.log(`\nWallet: ${config.walletContractAddress}`);
|
|
3059
3053
|
console.log(`pubKeyX: ${pubKeyX}`);
|
|
3060
3054
|
console.log(`pubKeyY: ${pubKeyY}`);
|
|
@@ -3066,28 +3060,28 @@ function registerWalletCommands(program) {
|
|
|
3066
3060
|
data: walletInterface.encodeFunctionData("setPasskey", [pubKeyX, pubKeyY]),
|
|
3067
3061
|
value: "0x0",
|
|
3068
3062
|
};
|
|
3069
|
-
const { client, session, account } = await
|
|
3063
|
+
const { client, session, account } = await connectPhoneWallet(config.walletConnectProjectId, chainId, config, {
|
|
3070
3064
|
telegramOpts,
|
|
3071
3065
|
prompt: `Activate passkey (Face ID) on ARC402Wallet — enables P256 governance signing`,
|
|
3072
3066
|
});
|
|
3073
|
-
console.log("\n" +
|
|
3074
|
-
console.log(
|
|
3075
|
-
const hash = await
|
|
3076
|
-
console.log("\n" +
|
|
3077
|
-
console.log(
|
|
3067
|
+
console.log("\n" + c.success + c.white(` Connected: ${account}`));
|
|
3068
|
+
console.log(c.dim("Sending setPasskey transaction..."));
|
|
3069
|
+
const hash = await sendTransactionWithSession(client, session, account, chainId, txData);
|
|
3070
|
+
console.log("\n" + c.dim("Transaction submitted:") + " " + c.white(hash));
|
|
3071
|
+
console.log(c.dim("Waiting for confirmation..."));
|
|
3078
3072
|
const receipt = await provider.waitForTransaction(hash, 1, 60000);
|
|
3079
3073
|
if (!receipt || receipt.status !== 1) {
|
|
3080
|
-
console.error(
|
|
3074
|
+
console.error(c.failure + " " + c.red("Transaction failed."));
|
|
3081
3075
|
process.exit(1);
|
|
3082
3076
|
}
|
|
3083
|
-
console.log("\n" +
|
|
3084
|
-
|
|
3077
|
+
console.log("\n" + c.success + c.white(" Passkey activated on ARC402Wallet"));
|
|
3078
|
+
renderTree([
|
|
3085
3079
|
{ label: "Wallet", value: config.walletContractAddress ?? "" },
|
|
3086
3080
|
{ label: "pubKeyX", value: pubKeyX },
|
|
3087
3081
|
{ label: "pubKeyY", value: pubKeyY },
|
|
3088
3082
|
{ label: "Tx", value: hash, last: true },
|
|
3089
3083
|
]);
|
|
3090
|
-
console.log(
|
|
3084
|
+
console.log(c.dim("\nGovernance ops now require Face ID instead of MetaMask."));
|
|
3091
3085
|
await client.disconnect({ topic: session.topic, reason: { code: 6000, message: "done" } });
|
|
3092
3086
|
process.exit(0);
|
|
3093
3087
|
});
|