arc402-cli 1.0.0-rc.1 → 1.0.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/README.md +41 -2
- package/dist/abis.d.ts +1 -0
- package/dist/abis.d.ts.map +1 -1
- package/dist/abis.js +29 -1
- package/dist/abis.js.map +1 -1
- package/dist/commands/backup.d.ts +3 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +106 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/compute.d.ts +14 -0
- package/dist/commands/compute.d.ts.map +1 -0
- package/dist/commands/compute.js +466 -0
- package/dist/commands/compute.js.map +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +11 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +67 -0
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/discover.d.ts.map +1 -1
- package/dist/commands/discover.js +60 -15
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +205 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +299 -65
- package/dist/commands/wallet.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +146 -9
- package/dist/commands/watch.js.map +1 -1
- package/dist/commands/workroom.d.ts.map +1 -1
- package/dist/commands/workroom.js +112 -6
- package/dist/commands/workroom.js.map +1 -1
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -4
- package/dist/config.js.map +1 -1
- package/dist/daemon/compute-metering.d.ts +61 -0
- package/dist/daemon/compute-metering.d.ts.map +1 -0
- package/dist/daemon/compute-metering.js +299 -0
- package/dist/daemon/compute-metering.js.map +1 -0
- package/dist/daemon/compute-session.d.ts +100 -0
- package/dist/daemon/compute-session.d.ts.map +1 -0
- package/dist/daemon/compute-session.js +231 -0
- package/dist/daemon/compute-session.js.map +1 -0
- package/dist/daemon/config.d.ts +33 -1
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +69 -0
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/credentials.d.ts +24 -0
- package/dist/daemon/credentials.d.ts.map +1 -0
- package/dist/daemon/credentials.js +80 -0
- package/dist/daemon/credentials.js.map +1 -0
- package/dist/daemon/delivery-client.d.ts +35 -0
- package/dist/daemon/delivery-client.d.ts.map +1 -0
- package/dist/daemon/delivery-client.js +231 -0
- package/dist/daemon/delivery-client.js.map +1 -0
- package/dist/daemon/file-delivery.d.ts +98 -0
- package/dist/daemon/file-delivery.d.ts.map +1 -0
- package/dist/daemon/file-delivery.js +461 -0
- package/dist/daemon/file-delivery.js.map +1 -0
- package/dist/daemon/index.d.ts +1 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +793 -227
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/notify.d.ts +35 -6
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +176 -48
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/worker-executor.d.ts +71 -0
- package/dist/daemon/worker-executor.d.ts.map +1 -0
- package/dist/daemon/worker-executor.js +382 -0
- package/dist/daemon/worker-executor.js.map +1 -0
- package/dist/drain-v4.js +2 -2
- package/dist/drain-v4.js.map +1 -1
- package/dist/endpoint-notify.d.ts +9 -1
- package/dist/endpoint-notify.d.ts.map +1 -1
- package/dist/endpoint-notify.js +116 -3
- package/dist/endpoint-notify.js.map +1 -1
- package/dist/index.js +81 -1
- package/dist/index.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +6 -0
- package/dist/program.js.map +1 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +69 -486
- package/dist/repl.js.map +1 -1
- package/dist/tui/App.d.ts +12 -0
- package/dist/tui/App.d.ts.map +1 -0
- package/dist/tui/App.js +154 -0
- package/dist/tui/App.js.map +1 -0
- package/dist/tui/Footer.d.ts +11 -0
- package/dist/tui/Footer.d.ts.map +1 -0
- package/dist/tui/Footer.js +13 -0
- package/dist/tui/Footer.js.map +1 -0
- package/dist/tui/Header.d.ts +14 -0
- package/dist/tui/Header.d.ts.map +1 -0
- package/dist/tui/Header.js +19 -0
- package/dist/tui/Header.js.map +1 -0
- package/dist/tui/InputLine.d.ts +11 -0
- package/dist/tui/InputLine.d.ts.map +1 -0
- package/dist/tui/InputLine.js +145 -0
- package/dist/tui/InputLine.js.map +1 -0
- package/dist/tui/Viewport.d.ts +14 -0
- package/dist/tui/Viewport.d.ts.map +1 -0
- package/dist/tui/Viewport.js +48 -0
- package/dist/tui/Viewport.js.map +1 -0
- package/dist/tui/WalletConnectPairing.d.ts +23 -0
- package/dist/tui/WalletConnectPairing.d.ts.map +1 -0
- package/dist/tui/WalletConnectPairing.js +61 -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 +21 -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 +10 -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 +23 -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 +10 -0
- package/dist/tui/components/ConfirmPrompt.js.map +1 -0
- package/dist/tui/components/CustomTextInput.d.ts +15 -0
- package/dist/tui/components/CustomTextInput.d.ts.map +1 -0
- package/dist/tui/components/CustomTextInput.js +99 -0
- package/dist/tui/components/CustomTextInput.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 +61 -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 +32 -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 +29 -0
- package/dist/tui/components/Toast.js.map +1 -0
- package/dist/tui/index.d.ts +2 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +55 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/useChat.d.ts +11 -0
- package/dist/tui/useChat.d.ts.map +1 -0
- package/dist/tui/useChat.js +91 -0
- package/dist/tui/useChat.js.map +1 -0
- package/dist/tui/useCommand.d.ts +12 -0
- package/dist/tui/useCommand.d.ts.map +1 -0
- package/dist/tui/useCommand.js +137 -0
- package/dist/tui/useCommand.js.map +1 -0
- package/dist/tui/useNotifications.d.ts +9 -0
- package/dist/tui/useNotifications.d.ts.map +1 -0
- package/dist/tui/useNotifications.js +17 -0
- package/dist/tui/useNotifications.js.map +1 -0
- package/dist/tui/useScroll.d.ts +17 -0
- package/dist/tui/useScroll.d.ts.map +1 -0
- package/dist/tui/useScroll.js +46 -0
- package/dist/tui/useScroll.js.map +1 -0
- package/dist/ui/format.d.ts.map +1 -1
- package/dist/ui/format.js +2 -0
- package/dist/ui/format.js.map +1 -1
- package/dist/ui/qr-render.d.ts +25 -0
- package/dist/ui/qr-render.d.ts.map +1 -0
- package/dist/ui/qr-render.js +90 -0
- package/dist/ui/qr-render.js.map +1 -0
- package/dist/ui/rpc-fallback.d.ts +11 -0
- package/dist/ui/rpc-fallback.d.ts.map +1 -0
- package/dist/ui/rpc-fallback.js +58 -0
- package/dist/ui/rpc-fallback.js.map +1 -0
- package/dist/walletconnect.d.ts +4 -0
- package/dist/walletconnect.d.ts.map +1 -1
- package/dist/walletconnect.js.map +1 -1
- package/package.json +10 -2
- package/scripts/authorize-machine-key.ts +0 -43
- package/scripts/drain-wallet.ts +0 -149
- package/scripts/execute-spend-only.ts +0 -81
- package/scripts/register-agent-userop.ts +0 -186
- package/src/abis.ts +0 -187
- package/src/bundler.ts +0 -235
- package/src/client.ts +0 -36
- package/src/coinbase-smart-wallet.ts +0 -51
- package/src/commands/accept.ts +0 -64
- package/src/commands/agent-handshake.ts +0 -72
- package/src/commands/agent.ts +0 -691
- package/src/commands/agreements.ts +0 -350
- package/src/commands/arbitrator.ts +0 -180
- package/src/commands/arena-handshake.ts +0 -257
- package/src/commands/arena.ts +0 -122
- package/src/commands/cancel.ts +0 -35
- package/src/commands/channel.ts +0 -218
- package/src/commands/coldstart.ts +0 -165
- package/src/commands/config.ts +0 -58
- package/src/commands/contract-interaction.ts +0 -166
- package/src/commands/daemon.ts +0 -978
- package/src/commands/deliver.ts +0 -148
- package/src/commands/discover.ts +0 -297
- package/src/commands/dispute.ts +0 -375
- package/src/commands/endpoint.ts +0 -620
- package/src/commands/feed.ts +0 -229
- package/src/commands/hire.ts +0 -245
- package/src/commands/migrate.ts +0 -177
- package/src/commands/negotiate.ts +0 -271
- package/src/commands/openshell.ts +0 -1055
- package/src/commands/owner.ts +0 -35
- package/src/commands/policy.ts +0 -263
- package/src/commands/relay.ts +0 -273
- package/src/commands/remediate.ts +0 -24
- package/src/commands/reputation.ts +0 -79
- package/src/commands/setup.ts +0 -343
- package/src/commands/trust.ts +0 -27
- package/src/commands/verify.ts +0 -91
- package/src/commands/wallet.ts +0 -3280
- package/src/commands/watch.ts +0 -23
- package/src/commands/watchtower.ts +0 -248
- package/src/commands/workroom.ts +0 -959
- package/src/config.ts +0 -174
- package/src/daemon/config.ts +0 -308
- package/src/daemon/hire-listener.ts +0 -226
- package/src/daemon/index.ts +0 -955
- package/src/daemon/job-lifecycle.ts +0 -215
- package/src/daemon/notify.ts +0 -157
- package/src/daemon/token-metering.ts +0 -183
- package/src/daemon/userops.ts +0 -119
- package/src/daemon/wallet-monitor.ts +0 -90
- package/src/drain-v4.ts +0 -159
- package/src/endpoint-config.ts +0 -83
- package/src/endpoint-notify.ts +0 -46
- package/src/index.ts +0 -26
- package/src/openshell-runtime.ts +0 -277
- package/src/program.ts +0 -83
- package/src/repl.ts +0 -680
- package/src/signing.ts +0 -28
- package/src/telegram-notify.ts +0 -88
- package/src/ui/banner.ts +0 -51
- package/src/ui/colors.ts +0 -30
- package/src/ui/format.ts +0 -77
- package/src/ui/spinner.ts +0 -56
- package/src/ui/tree.ts +0 -16
- package/src/utils/format.ts +0 -48
- package/src/utils/hash.ts +0 -5
- package/src/utils/time.ts +0 -15
- package/src/wallet-router.ts +0 -178
- package/src/walletconnect-session.ts +0 -27
- package/src/walletconnect.ts +0 -294
- package/test/time.test.js +0 -11
- package/tsconfig.json +0 -19
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { ethers } from "ethers";
|
|
3
|
-
import { loadConfig, getUsdcAddress } from "../config";
|
|
4
|
-
import { requireSigner } from "../client";
|
|
5
|
-
import { AGENT_REGISTRY_ABI } from "../abis";
|
|
6
|
-
import { startSpinner } from "../ui/spinner";
|
|
7
|
-
import { renderTree } from "../ui/tree";
|
|
8
|
-
import { c } from "../ui/colors";
|
|
9
|
-
|
|
10
|
-
const DEFAULT_REGISTRY_ADDRESS = "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865";
|
|
11
|
-
|
|
12
|
-
async function pingHandshakeEndpoint(
|
|
13
|
-
agentAddress: string,
|
|
14
|
-
payload: Record<string, unknown>,
|
|
15
|
-
registryAddress: string,
|
|
16
|
-
provider: ethers.Provider
|
|
17
|
-
): Promise<void> {
|
|
18
|
-
const registry = new ethers.Contract(registryAddress, AGENT_REGISTRY_ABI, provider);
|
|
19
|
-
const agentData = await registry.getAgent(agentAddress);
|
|
20
|
-
const endpoint = agentData.endpoint as string;
|
|
21
|
-
if (!endpoint) return;
|
|
22
|
-
await fetch(`${endpoint}/handshake`, {
|
|
23
|
-
method: "POST",
|
|
24
|
-
headers: { "Content-Type": "application/json" },
|
|
25
|
-
body: JSON.stringify(payload),
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ─── Handshake Contract ABI (from Handshake.sol) ─────────────────────────────
|
|
30
|
-
|
|
31
|
-
const HANDSHAKE_ABI = [
|
|
32
|
-
"function sendHandshake(address to, uint8 hsType, string note) payable",
|
|
33
|
-
"function sendHandshakeWithToken(address to, uint8 hsType, string note, address token, uint256 tokenAmount)",
|
|
34
|
-
"function sendBatch(address[] recipients, uint8[] hsTypes, string[] notes)",
|
|
35
|
-
"function hasConnection(address from, address to) view returns (bool)",
|
|
36
|
-
"function isMutual(address a, address b) view returns (bool)",
|
|
37
|
-
"function getStats(address agent) view returns (uint256 sent, uint256 received, uint256 uniqueInbound)",
|
|
38
|
-
"function sentCount(address) view returns (uint256)",
|
|
39
|
-
"function receivedCount(address) view returns (uint256)",
|
|
40
|
-
"function uniqueSenders(address) view returns (uint256)",
|
|
41
|
-
"function totalHandshakes() view returns (uint256)",
|
|
42
|
-
"function allowedTokens(address) view returns (bool)",
|
|
43
|
-
"event HandshakeSent(uint256 indexed handshakeId, address indexed from, address indexed to, uint8 hsType, address token, uint256 amount, string note, uint256 timestamp)",
|
|
44
|
-
"event NewConnection(address indexed from, address indexed to, uint256 handshakeId)",
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
const POLICY_ENGINE_ABI = [
|
|
48
|
-
"function isContractWhitelisted(address wallet, address target) view returns (bool)",
|
|
49
|
-
"function whitelistContract(address wallet, address target)",
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
const HANDSHAKE_TYPES: Record<string, number> = {
|
|
53
|
-
respect: 0,
|
|
54
|
-
curiosity: 1,
|
|
55
|
-
endorsement: 2,
|
|
56
|
-
thanks: 3,
|
|
57
|
-
collaboration: 4,
|
|
58
|
-
challenge: 5,
|
|
59
|
-
referral: 6,
|
|
60
|
-
hello: 7,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// ─── Auto-Whitelist ──────────────────────────────────────────────────────────
|
|
64
|
-
|
|
65
|
-
async function ensureWhitelisted(
|
|
66
|
-
signer: ethers.Signer,
|
|
67
|
-
provider: ethers.Provider,
|
|
68
|
-
walletAddress: string,
|
|
69
|
-
policyEngineAddress: string,
|
|
70
|
-
handshakeAddress: string
|
|
71
|
-
): Promise<void> {
|
|
72
|
-
const pe = new ethers.Contract(policyEngineAddress, POLICY_ENGINE_ABI, provider);
|
|
73
|
-
const isWhitelisted = await pe.isContractWhitelisted(walletAddress, handshakeAddress);
|
|
74
|
-
|
|
75
|
-
if (!isWhitelisted) {
|
|
76
|
-
console.log("Handshake contract not yet whitelisted on your wallet.");
|
|
77
|
-
console.log("Whitelisting now (one-time setup)...");
|
|
78
|
-
|
|
79
|
-
const peSigner = new ethers.Contract(policyEngineAddress, POLICY_ENGINE_ABI, signer);
|
|
80
|
-
const tx = await peSigner.whitelistContract(walletAddress, handshakeAddress);
|
|
81
|
-
console.log(` tx: ${tx.hash}`);
|
|
82
|
-
await tx.wait();
|
|
83
|
-
console.log(" ✓ Handshake contract whitelisted\n");
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ─── Commands ────────────────────────────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
export function registerArenaHandshakeCommands(program: Command): void {
|
|
90
|
-
const hs = program
|
|
91
|
-
.command("shake")
|
|
92
|
-
.description("ARC Arena social handshake — send a typed trust signal to another agent.");
|
|
93
|
-
|
|
94
|
-
// ── send ──────────────────────────────────────────────────────────────────
|
|
95
|
-
hs.command("send <agentAddress>")
|
|
96
|
-
.description("Send a handshake to another agent.")
|
|
97
|
-
.option("--type <type>", "Handshake type: respect, curiosity, endorsement, thanks, collaboration, challenge, referral, hello", "hello")
|
|
98
|
-
.option("--note <note>", "Short message (max 280 chars)", "")
|
|
99
|
-
.option("--tip <amount>", "ETH tip to attach (e.g. 0.01)")
|
|
100
|
-
.option("--usdc <amount>", "USDC tip to attach (e.g. 5.00)")
|
|
101
|
-
.option("--json", "Output as JSON")
|
|
102
|
-
.action(async (agentAddress: string, opts) => {
|
|
103
|
-
const config = loadConfig();
|
|
104
|
-
const { signer, provider } = await requireSigner(config);
|
|
105
|
-
const myAddress = await signer.getAddress();
|
|
106
|
-
|
|
107
|
-
if (!config.handshakeAddress) {
|
|
108
|
-
console.error("handshakeAddress not configured. Run: arc402 config set handshakeAddress <address>");
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
if (!config.policyEngineAddress) {
|
|
112
|
-
console.error("policyEngineAddress not configured.");
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Auto-whitelist check
|
|
117
|
-
await ensureWhitelisted(signer, provider, myAddress, config.policyEngineAddress, config.handshakeAddress);
|
|
118
|
-
|
|
119
|
-
const hsType = HANDSHAKE_TYPES[opts.type.toLowerCase()];
|
|
120
|
-
if (hsType === undefined) {
|
|
121
|
-
console.error(`Unknown handshake type: ${opts.type}`);
|
|
122
|
-
console.error(`Valid types: ${Object.keys(HANDSHAKE_TYPES).join(", ")}`);
|
|
123
|
-
process.exit(1);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const handshake = new ethers.Contract(config.handshakeAddress, HANDSHAKE_ABI, signer);
|
|
127
|
-
|
|
128
|
-
const hsSpinner = startSpinner(`Sending ${opts.type} handshake...`);
|
|
129
|
-
let tx;
|
|
130
|
-
if (opts.usdc) {
|
|
131
|
-
// USDC handshake
|
|
132
|
-
const usdcAddress = getUsdcAddress(config);
|
|
133
|
-
const amount = ethers.parseUnits(opts.usdc, 6);
|
|
134
|
-
tx = await handshake.sendHandshakeWithToken(agentAddress, hsType, opts.note, usdcAddress, amount);
|
|
135
|
-
} else {
|
|
136
|
-
// ETH handshake (with optional tip)
|
|
137
|
-
const value = opts.tip ? ethers.parseEther(opts.tip) : 0n;
|
|
138
|
-
tx = await handshake.sendHandshake(agentAddress, hsType, opts.note, { value });
|
|
139
|
-
}
|
|
140
|
-
hsSpinner.succeed("Handshake sent");
|
|
141
|
-
|
|
142
|
-
// Notify recipient's HTTP endpoint (non-blocking)
|
|
143
|
-
const registryAddress = config.agentRegistryV2Address ?? config.agentRegistryAddress ?? DEFAULT_REGISTRY_ADDRESS;
|
|
144
|
-
try {
|
|
145
|
-
await pingHandshakeEndpoint(
|
|
146
|
-
agentAddress,
|
|
147
|
-
{ from: myAddress, type: opts.type, note: opts.note, txHash: tx.hash },
|
|
148
|
-
registryAddress,
|
|
149
|
-
provider
|
|
150
|
-
);
|
|
151
|
-
} catch (err) {
|
|
152
|
-
console.warn(`Warning: could not notify recipient endpoint: ${err instanceof Error ? err.message : String(err)}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (opts.json) {
|
|
156
|
-
console.log(JSON.stringify({ tx: tx.hash, from: myAddress, to: agentAddress, type: opts.type, note: opts.note }));
|
|
157
|
-
} else {
|
|
158
|
-
const treeItems = [
|
|
159
|
-
{ label: "From", value: myAddress },
|
|
160
|
-
{ label: "To", value: agentAddress },
|
|
161
|
-
{ label: "Type", value: opts.type },
|
|
162
|
-
...(opts.note ? [{ label: "Note", value: opts.note as string }] : []),
|
|
163
|
-
...(opts.tip ? [{ label: "Tip", value: `${opts.tip as string} ETH` }] : []),
|
|
164
|
-
...(opts.usdc ? [{ label: "Tip", value: `${opts.usdc as string} USDC` }] : []),
|
|
165
|
-
{ label: "Tx", value: tx.hash as string, last: true },
|
|
166
|
-
];
|
|
167
|
-
renderTree(treeItems);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// ── batch ─────────────────────────────────────────────────────────────────
|
|
172
|
-
hs.command("batch")
|
|
173
|
-
.description("Send handshakes to multiple agents at once (onboarding ritual).")
|
|
174
|
-
.argument("<agents...>", "Agent addresses to handshake (up to 10)")
|
|
175
|
-
.option("--type <type>", "Handshake type for all", "hello")
|
|
176
|
-
.option("--note <note>", "Note for all", "")
|
|
177
|
-
.action(async (agents: string[], opts) => {
|
|
178
|
-
const config = loadConfig();
|
|
179
|
-
const { signer, provider } = await requireSigner(config);
|
|
180
|
-
const myAddress = await signer.getAddress();
|
|
181
|
-
|
|
182
|
-
if (!config.handshakeAddress || !config.policyEngineAddress) {
|
|
183
|
-
console.error("handshakeAddress or policyEngineAddress not configured.");
|
|
184
|
-
process.exit(1);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
await ensureWhitelisted(signer, provider, myAddress, config.policyEngineAddress, config.handshakeAddress);
|
|
188
|
-
|
|
189
|
-
const hsType = HANDSHAKE_TYPES[opts.type.toLowerCase()];
|
|
190
|
-
if (hsType === undefined) {
|
|
191
|
-
console.error(`Unknown type: ${opts.type}. Valid: ${Object.keys(HANDSHAKE_TYPES).join(", ")}`);
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (agents.length > 10) {
|
|
196
|
-
console.error("Max 10 agents per batch.");
|
|
197
|
-
process.exit(1);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const handshake = new ethers.Contract(config.handshakeAddress, HANDSHAKE_ABI, signer);
|
|
201
|
-
const types = agents.map(() => hsType);
|
|
202
|
-
const notes = agents.map(() => opts.note);
|
|
203
|
-
|
|
204
|
-
const tx = await handshake.sendBatch(agents, types, notes);
|
|
205
|
-
console.log(`✓ Batch handshake sent to ${agents.length} agents`);
|
|
206
|
-
agents.forEach(a => console.log(` → ${a}`));
|
|
207
|
-
console.log(` tx: ${tx.hash}`);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// ── stats ─────────────────────────────────────────────────────────────────
|
|
211
|
-
hs.command("stats [address]")
|
|
212
|
-
.description("View handshake stats for an agent.")
|
|
213
|
-
.action(async (address?: string) => {
|
|
214
|
-
const config = loadConfig();
|
|
215
|
-
const { signer, provider } = await requireSigner(config);
|
|
216
|
-
const target = address || await signer.getAddress();
|
|
217
|
-
|
|
218
|
-
if (!config.handshakeAddress) {
|
|
219
|
-
console.error("handshakeAddress not configured.");
|
|
220
|
-
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const handshake = new ethers.Contract(config.handshakeAddress, HANDSHAKE_ABI, provider);
|
|
224
|
-
const [sent, received, unique] = await handshake.getStats(target);
|
|
225
|
-
const total = await handshake.totalHandshakes();
|
|
226
|
-
|
|
227
|
-
console.log(`Handshake Stats: ${target}`);
|
|
228
|
-
console.log(` Sent: ${sent}`);
|
|
229
|
-
console.log(` Received: ${received}`);
|
|
230
|
-
console.log(` Unique senders: ${unique}`);
|
|
231
|
-
console.log(` Network total: ${total}`);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// ── check ─────────────────────────────────────────────────────────────────
|
|
235
|
-
hs.command("check <agentAddress>")
|
|
236
|
-
.description("Check if a connection or mutual handshake exists with an agent.")
|
|
237
|
-
.action(async (agentAddress: string) => {
|
|
238
|
-
const config = loadConfig();
|
|
239
|
-
const { signer, provider } = await requireSigner(config);
|
|
240
|
-
const myAddress = await signer.getAddress();
|
|
241
|
-
|
|
242
|
-
if (!config.handshakeAddress) {
|
|
243
|
-
console.error("handshakeAddress not configured.");
|
|
244
|
-
process.exit(1);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const handshake = new ethers.Contract(config.handshakeAddress, HANDSHAKE_ABI, provider);
|
|
248
|
-
const iSent = await handshake.hasConnection(myAddress, agentAddress);
|
|
249
|
-
const theySent = await handshake.hasConnection(agentAddress, myAddress);
|
|
250
|
-
const mutual = await handshake.isMutual(myAddress, agentAddress);
|
|
251
|
-
|
|
252
|
-
console.log(`Connection: ${myAddress} ↔ ${agentAddress}`);
|
|
253
|
-
console.log(` You → them: ${iSent ? "✓ handshaked" : "✗ no handshake"}`);
|
|
254
|
-
console.log(` Them → you: ${theySent ? "✓ handshaked" : "✗ no handshake"}`);
|
|
255
|
-
console.log(` Mutual: ${mutual ? "✓ yes" : "✗ no"}`);
|
|
256
|
-
});
|
|
257
|
-
}
|
package/src/commands/arena.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import { runFeed, FeedOptions } from "./feed";
|
|
4
|
-
import { c } from "../ui/colors";
|
|
5
|
-
|
|
6
|
-
const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/1744310/arc-402/v0.2.0";
|
|
7
|
-
|
|
8
|
-
async function gql(query: string): Promise<Record<string, unknown>> {
|
|
9
|
-
const res = await fetch(SUBGRAPH_URL, {
|
|
10
|
-
method: "POST",
|
|
11
|
-
headers: { "Content-Type": "application/json" },
|
|
12
|
-
body: JSON.stringify({ query }),
|
|
13
|
-
});
|
|
14
|
-
if (!res.ok) throw new Error(`Subgraph HTTP ${res.status}`);
|
|
15
|
-
const json = (await res.json()) as { data?: Record<string, unknown>; errors?: unknown[] };
|
|
16
|
-
if (json.errors?.length) throw new Error(`Subgraph error: ${JSON.stringify(json.errors[0])}`);
|
|
17
|
-
return json.data ?? {};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function registerArenaCommands(program: Command): void {
|
|
21
|
-
const arena = program.command("arena").description("Arena network commands");
|
|
22
|
-
|
|
23
|
-
// ─── arena stats ───────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
arena
|
|
26
|
-
.command("stats")
|
|
27
|
-
.description("Show Arena network statistics")
|
|
28
|
-
.option("--json", "Output as JSON")
|
|
29
|
-
.action(async (opts) => {
|
|
30
|
-
try {
|
|
31
|
-
const data = await gql(`{
|
|
32
|
-
protocolStats(id: "global") {
|
|
33
|
-
totalAgents
|
|
34
|
-
totalWallets
|
|
35
|
-
totalAgreements
|
|
36
|
-
totalHandshakes
|
|
37
|
-
totalConnections
|
|
38
|
-
totalVouches
|
|
39
|
-
totalCapabilityClaims
|
|
40
|
-
}
|
|
41
|
-
}`);
|
|
42
|
-
|
|
43
|
-
const stats = data["protocolStats"] as Record<string, unknown> | null;
|
|
44
|
-
if (!stats) {
|
|
45
|
-
const msg = "No stats available — subgraph may still be syncing.";
|
|
46
|
-
if (opts.json) {
|
|
47
|
-
console.log(JSON.stringify({ error: msg }));
|
|
48
|
-
} else {
|
|
49
|
-
console.error(' ' + c.warning + c.white(` ${msg}`));
|
|
50
|
-
}
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Try to count active agreements separately — non-fatal if it fails
|
|
55
|
-
let activeAgreements = 0;
|
|
56
|
-
try {
|
|
57
|
-
const agData = await gql(`{
|
|
58
|
-
proposals: agreements(where: { state: 0 }, first: 1000) { id }
|
|
59
|
-
accepted: agreements(where: { state: 1 }, first: 1000) { id }
|
|
60
|
-
}`);
|
|
61
|
-
activeAgreements =
|
|
62
|
-
((agData["proposals"] as unknown[]) ?? []).length +
|
|
63
|
-
((agData["accepted"] as unknown[]) ?? []).length;
|
|
64
|
-
} catch {
|
|
65
|
-
// ignore — active count just stays 0
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (opts.json) {
|
|
69
|
-
console.log(JSON.stringify({ ...stats, activeAgreements }, null, 2));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const pad = (v: unknown) => String(v ?? 0).padStart(6);
|
|
74
|
-
console.log(chalk.bold("╔══════════════════════════════════════╗"));
|
|
75
|
-
console.log(chalk.bold("║ ARC Arena — Network Stats ║"));
|
|
76
|
-
console.log(chalk.bold("╚══════════════════════════════════════╝"));
|
|
77
|
-
console.log();
|
|
78
|
-
console.log(` Agents ${pad(stats["totalAgents"])} registered`);
|
|
79
|
-
console.log(` Wallets ${pad(stats["totalWallets"])} deployed`);
|
|
80
|
-
console.log(
|
|
81
|
-
` Agreements ${pad(stats["totalAgreements"])} total (${activeAgreements} active)`,
|
|
82
|
-
);
|
|
83
|
-
console.log(` Handshakes ${pad(stats["totalHandshakes"])} sent`);
|
|
84
|
-
console.log(` Connections ${pad(stats["totalConnections"])} unique pairs`);
|
|
85
|
-
console.log(` Vouches ${pad(stats["totalVouches"])} active`);
|
|
86
|
-
console.log(` Capabilities ${pad(stats["totalCapabilityClaims"])} claimed`);
|
|
87
|
-
console.log();
|
|
88
|
-
console.log(chalk.dim(" Subgraph: v0.2.0 · synced"));
|
|
89
|
-
} catch (err) {
|
|
90
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
91
|
-
if (opts.json) {
|
|
92
|
-
console.log(JSON.stringify({ error: "Subgraph unavailable", details: msg }));
|
|
93
|
-
} else {
|
|
94
|
-
console.error(' ' + c.failure + c.white(` Subgraph unavailable: ${msg}`));
|
|
95
|
-
}
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// ─── arena feed (alias) ─────────────────────────────────────────────────────
|
|
101
|
-
|
|
102
|
-
arena
|
|
103
|
-
.command("feed")
|
|
104
|
-
.description("Live terminal feed of recent Arena events (alias for arc402 feed)")
|
|
105
|
-
.option("--limit <n>", "Number of events to show", "20")
|
|
106
|
-
.option("--live", "Poll every 30s for new events")
|
|
107
|
-
.option("--type <type>", "Filter by event type: handshake|hire|fulfill|vouch")
|
|
108
|
-
.option("--json", "Output as JSON")
|
|
109
|
-
.action(async (opts) => {
|
|
110
|
-
try {
|
|
111
|
-
await runFeed(opts as FeedOptions);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
114
|
-
if ((opts as FeedOptions).json) {
|
|
115
|
-
console.log(JSON.stringify({ error: "Subgraph unavailable", details: msg }));
|
|
116
|
-
} else {
|
|
117
|
-
console.error(' ' + c.failure + c.white(` Subgraph unavailable: ${msg}`));
|
|
118
|
-
}
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
}
|
package/src/commands/cancel.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { ServiceAgreementClient } from "@arc402/sdk";
|
|
3
|
-
import { loadConfig } from "../config";
|
|
4
|
-
import { requireSigner } from "../client";
|
|
5
|
-
import { printSenderInfo, executeContractWriteViaWallet } from "../wallet-router";
|
|
6
|
-
import { SERVICE_AGREEMENT_ABI } from "../abis";
|
|
7
|
-
import { c } from "../ui/colors";
|
|
8
|
-
import { startSpinner } from "../ui/spinner";
|
|
9
|
-
|
|
10
|
-
export function registerCancelCommand(program: Command): void {
|
|
11
|
-
program.command("cancel <id>").description("Cancel a proposed agreement; use --expired for post-deadline recovery paths").option("--expired", "Call expiredCancel()", false).action(async (id, opts) => {
|
|
12
|
-
const config = loadConfig();
|
|
13
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
14
|
-
const { signer } = await requireSigner(config);
|
|
15
|
-
printSenderInfo(config);
|
|
16
|
-
const spinner = startSpinner("Submitting transaction...");
|
|
17
|
-
try {
|
|
18
|
-
if (config.walletContractAddress) {
|
|
19
|
-
const fn = opts.expired ? "expiredCancel" : "cancel";
|
|
20
|
-
await executeContractWriteViaWallet(
|
|
21
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
22
|
-
SERVICE_AGREEMENT_ABI, fn, [BigInt(id)],
|
|
23
|
-
);
|
|
24
|
-
} else {
|
|
25
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
26
|
-
if (opts.expired) await client.expiredCancel(BigInt(id)); else await client.cancel(BigInt(id));
|
|
27
|
-
}
|
|
28
|
-
spinner.succeed();
|
|
29
|
-
} catch (err) {
|
|
30
|
-
spinner.fail();
|
|
31
|
-
throw err;
|
|
32
|
-
}
|
|
33
|
-
console.log(' ' + c.success + c.white(` Cancelled — agreement #${id}`));
|
|
34
|
-
});
|
|
35
|
-
}
|
package/src/commands/channel.ts
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { loadConfig } from "../config";
|
|
3
|
-
import { requireSigner, getClient } from "../client";
|
|
4
|
-
import { ChannelClient } from "@arc402/sdk";
|
|
5
|
-
import type { ChannelState } from "@arc402/sdk";
|
|
6
|
-
import * as fs from "fs";
|
|
7
|
-
import * as path from "path";
|
|
8
|
-
import * as os from "os";
|
|
9
|
-
import { c } from '../ui/colors';
|
|
10
|
-
import { startSpinner } from '../ui/spinner';
|
|
11
|
-
import { renderTree } from '../ui/tree';
|
|
12
|
-
import { formatAddress } from '../ui/format';
|
|
13
|
-
|
|
14
|
-
const CHANNEL_STATES_DIR = path.join(os.homedir(), ".arc402", "channel-states");
|
|
15
|
-
|
|
16
|
-
function loadStateFile(path: string): ChannelState {
|
|
17
|
-
const raw = fs.readFileSync(path, "utf-8");
|
|
18
|
-
const obj = JSON.parse(raw);
|
|
19
|
-
return {
|
|
20
|
-
...obj,
|
|
21
|
-
cumulativePayment: BigInt(obj.cumulativePayment),
|
|
22
|
-
} as ChannelState;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function registerChannelCommands(program: Command): void {
|
|
26
|
-
const channel = program.command("channel").description("Session channel management — open, close, challenge, reclaim");
|
|
27
|
-
|
|
28
|
-
channel.command("open <provider>")
|
|
29
|
-
.description("Open a session channel with a provider")
|
|
30
|
-
.requiredOption("--token <address>", "Token address (0x0 for ETH)")
|
|
31
|
-
.requiredOption("--max <amount>", "Max deposit amount in wei")
|
|
32
|
-
.requiredOption("--rate <amount>", "Expected rate per call in wei (informational)")
|
|
33
|
-
.requiredOption("--deadline <timestamp>", "Channel expiry unix timestamp")
|
|
34
|
-
.option("--json", "JSON output")
|
|
35
|
-
.action(async (provider, opts) => {
|
|
36
|
-
const config = loadConfig();
|
|
37
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
38
|
-
const { signer } = await requireSigner(config);
|
|
39
|
-
const client = new ChannelClient(config.serviceAgreementAddress, signer);
|
|
40
|
-
const spinner = startSpinner('Opening session channel…');
|
|
41
|
-
const result = await client.openSessionChannel(
|
|
42
|
-
provider,
|
|
43
|
-
opts.token,
|
|
44
|
-
BigInt(opts.max),
|
|
45
|
-
BigInt(opts.rate),
|
|
46
|
-
Number(opts.deadline)
|
|
47
|
-
);
|
|
48
|
-
if (opts.json || program.opts().json) {
|
|
49
|
-
spinner.stop();
|
|
50
|
-
console.log(JSON.stringify(result, null, 2));
|
|
51
|
-
} else {
|
|
52
|
-
spinner.succeed(' Opened — channel ' + result.channelId);
|
|
53
|
-
renderTree([
|
|
54
|
-
{ label: 'Channel', value: result.channelId },
|
|
55
|
-
{ label: 'Tx', value: result.txHash, last: true },
|
|
56
|
-
]);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
channel.command("status <channelId>")
|
|
61
|
-
.description("Get channel status")
|
|
62
|
-
.option("--json", "JSON output")
|
|
63
|
-
.action(async (channelId, opts) => {
|
|
64
|
-
const config = loadConfig();
|
|
65
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
66
|
-
const { provider } = await getClient(config);
|
|
67
|
-
const client = new ChannelClient(config.serviceAgreementAddress, provider);
|
|
68
|
-
const ch = await client.getChannelStatus(channelId);
|
|
69
|
-
if (opts.json || program.opts().json) {
|
|
70
|
-
console.log(JSON.stringify(ch, (_k, v) => typeof v === "bigint" ? v.toString() : v, 2));
|
|
71
|
-
} else {
|
|
72
|
-
console.log('\n ' + c.mark + c.white(` Channel ${channelId}`));
|
|
73
|
-
renderTree([
|
|
74
|
-
{ label: 'Status', value: ch.status },
|
|
75
|
-
{ label: 'Client', value: formatAddress(ch.client) },
|
|
76
|
-
{ label: 'Provider', value: formatAddress(ch.provider) },
|
|
77
|
-
{ label: 'Deposit', value: ch.depositAmount.toString() },
|
|
78
|
-
{ label: 'Settled', value: ch.settledAmount.toString() },
|
|
79
|
-
{ label: 'Seq', value: ch.lastSequenceNumber.toString(), last: true },
|
|
80
|
-
]);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
channel.command("list <wallet>")
|
|
85
|
-
.description("List open channels for a wallet (client or provider)")
|
|
86
|
-
.option("--json", "JSON output")
|
|
87
|
-
.action(async (wallet, opts) => {
|
|
88
|
-
const config = loadConfig();
|
|
89
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
90
|
-
const { provider } = await getClient(config);
|
|
91
|
-
const client = new ChannelClient(config.serviceAgreementAddress, provider);
|
|
92
|
-
const channels = await client.getOpenChannels(wallet);
|
|
93
|
-
if (opts.json || program.opts().json) {
|
|
94
|
-
console.log(JSON.stringify(channels, (_k, v) => typeof v === "bigint" ? v.toString() : v, 2));
|
|
95
|
-
} else {
|
|
96
|
-
if (channels.length === 0) {
|
|
97
|
-
console.log("no open channels");
|
|
98
|
-
} else {
|
|
99
|
-
for (const ch of channels) {
|
|
100
|
-
console.log(` ${ch.status} deposit=${ch.depositAmount} seq=${ch.lastSequenceNumber} deadline=${ch.deadline}`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
channel.command("close <channelId>")
|
|
107
|
-
.description("Close a channel cooperatively with signed final state")
|
|
108
|
-
.requiredOption("--state <path>", "Path to signed state JSON file")
|
|
109
|
-
.option("--json", "JSON output")
|
|
110
|
-
.action(async (channelId, opts) => {
|
|
111
|
-
const config = loadConfig();
|
|
112
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
113
|
-
const { signer } = await requireSigner(config);
|
|
114
|
-
const client = new ChannelClient(config.serviceAgreementAddress, signer);
|
|
115
|
-
const state = loadStateFile(opts.state);
|
|
116
|
-
const result = await client.closeChannel(channelId, state);
|
|
117
|
-
if (opts.json || program.opts().json) {
|
|
118
|
-
console.log(JSON.stringify(result, null, 2));
|
|
119
|
-
} else {
|
|
120
|
-
console.log(' ' + c.success + c.white(` Close submitted — ${result.txHash}`));
|
|
121
|
-
console.log(' ' + c.dim('Challenge window open (24h)'));
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
channel.command("challenge <channelId>")
|
|
126
|
-
.description("Challenge a stale close with a higher sequence state")
|
|
127
|
-
.requiredOption("--state <path>", "Path to signed state JSON file with higher sequenceNumber")
|
|
128
|
-
.option("--json", "JSON output")
|
|
129
|
-
.action(async (channelId, opts) => {
|
|
130
|
-
const config = loadConfig();
|
|
131
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
132
|
-
const { signer } = await requireSigner(config);
|
|
133
|
-
const client = new ChannelClient(config.serviceAgreementAddress, signer);
|
|
134
|
-
const state = loadStateFile(opts.state);
|
|
135
|
-
const result = await client.challengeChannel(channelId, state);
|
|
136
|
-
if (opts.json || program.opts().json) {
|
|
137
|
-
console.log(JSON.stringify(result, null, 2));
|
|
138
|
-
} else {
|
|
139
|
-
console.log(' ' + c.success + c.white(` Challenge submitted — ${result.txHash}`));
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
channel.command("finalise <channelId>")
|
|
144
|
-
.description("Finalise a close after the challenge window expires")
|
|
145
|
-
.option("--json", "JSON output")
|
|
146
|
-
.action(async (channelId, opts) => {
|
|
147
|
-
const config = loadConfig();
|
|
148
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
149
|
-
const { signer } = await requireSigner(config);
|
|
150
|
-
const client = new ChannelClient(config.serviceAgreementAddress, signer);
|
|
151
|
-
const result = await client.finaliseChallenge(channelId);
|
|
152
|
-
if (opts.json || program.opts().json) {
|
|
153
|
-
console.log(JSON.stringify(result, null, 2));
|
|
154
|
-
} else {
|
|
155
|
-
console.log(' ' + c.success + c.white(` Finalised — ${result.txHash}`));
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
channel.command("reclaim <channelId>")
|
|
160
|
-
.description("Client: reclaim deposit from expired channel")
|
|
161
|
-
.option("--json", "JSON output")
|
|
162
|
-
.action(async (channelId, opts) => {
|
|
163
|
-
const config = loadConfig();
|
|
164
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
165
|
-
const { signer } = await requireSigner(config);
|
|
166
|
-
const client = new ChannelClient(config.serviceAgreementAddress, signer);
|
|
167
|
-
const result = await client.reclaimExpiredChannel(channelId);
|
|
168
|
-
if (opts.json || program.opts().json) {
|
|
169
|
-
console.log(JSON.stringify(result, null, 2));
|
|
170
|
-
} else {
|
|
171
|
-
console.log(' ' + c.success + c.white(` Reclaimed — ${result.txHash}`));
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
channel.command("store-state <channelId> <statePath>")
|
|
176
|
-
.description(
|
|
177
|
-
"Save a signed channel state to local storage (~/.arc402/channel-states/<channelId>.json) " +
|
|
178
|
-
"for the `arc402 daemon channel-watch` to use when auto-challenging stale closes."
|
|
179
|
-
)
|
|
180
|
-
.option("--json", "JSON output")
|
|
181
|
-
.action((channelId, statePath, opts) => {
|
|
182
|
-
let raw: string;
|
|
183
|
-
try {
|
|
184
|
-
raw = fs.readFileSync(statePath, "utf-8");
|
|
185
|
-
} catch (err) {
|
|
186
|
-
console.error(`Cannot read state file: ${statePath}\n${err}`);
|
|
187
|
-
process.exit(1);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
let state: Record<string, unknown>;
|
|
191
|
-
try {
|
|
192
|
-
state = JSON.parse(raw) as Record<string, unknown>;
|
|
193
|
-
} catch {
|
|
194
|
-
console.error("State file is not valid JSON.");
|
|
195
|
-
process.exit(1);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const required = ["sequenceNumber", "cumulativePayment", "callCount", "token", "timestamp", "clientSig", "providerSig"];
|
|
199
|
-
for (const field of required) {
|
|
200
|
-
if (!(field in state)) {
|
|
201
|
-
console.error(`State file missing required field: ${field}`);
|
|
202
|
-
process.exit(1);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
fs.mkdirSync(CHANNEL_STATES_DIR, { recursive: true, mode: 0o700 });
|
|
207
|
-
const dest = path.join(CHANNEL_STATES_DIR, `${channelId}.json`);
|
|
208
|
-
const stored = { ...state, channelId };
|
|
209
|
-
fs.writeFileSync(dest, JSON.stringify(stored, null, 2), { mode: 0o600 });
|
|
210
|
-
|
|
211
|
-
if (opts.json || program.opts().json) {
|
|
212
|
-
console.log(JSON.stringify({ stored: true, channelId, path: dest }));
|
|
213
|
-
} else {
|
|
214
|
-
console.log(' ' + c.success + c.white(` State stored — ${dest}`));
|
|
215
|
-
console.log(' ' + c.dim(`seq: ${state.sequenceNumber}`));
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
}
|