arc402-cli 1.0.0-rc.1 → 1.1.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 +43 -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/tunnel.d.ts +3 -0
- package/dist/commands/tunnel.d.ts.map +1 -0
- package/dist/commands/tunnel.js +281 -0
- package/dist/commands/tunnel.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 +13 -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 +8 -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 +11 -3
- 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
package/src/commands/feed.ts
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import { c } from '../ui/colors';
|
|
4
|
-
|
|
5
|
-
const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/1744310/arc-402/v0.2.0";
|
|
6
|
-
|
|
7
|
-
const HS_TYPE_LABELS: Record<number, string> = {
|
|
8
|
-
0: "Respected",
|
|
9
|
-
1: "Curious",
|
|
10
|
-
2: "Endorsed",
|
|
11
|
-
3: "Thanked",
|
|
12
|
-
4: "Collaborated",
|
|
13
|
-
5: "Challenged",
|
|
14
|
-
6: "Referred",
|
|
15
|
-
7: "Hello",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
async function gql(query: string): Promise<Record<string, unknown>> {
|
|
19
|
-
const res = await fetch(SUBGRAPH_URL, {
|
|
20
|
-
method: "POST",
|
|
21
|
-
headers: { "Content-Type": "application/json" },
|
|
22
|
-
body: JSON.stringify({ query }),
|
|
23
|
-
});
|
|
24
|
-
if (!res.ok) throw new Error(`Subgraph HTTP ${res.status}`);
|
|
25
|
-
const json = (await res.json()) as { data?: Record<string, unknown>; errors?: unknown[] };
|
|
26
|
-
if (json.errors?.length) throw new Error(`Subgraph error: ${JSON.stringify(json.errors[0])}`);
|
|
27
|
-
return json.data ?? {};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function shortAddr(addr: string): string {
|
|
31
|
-
return addr.length > 12 ? `${addr.slice(0, 6)}...${addr.slice(-4)}` : addr;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function utcTime(ts: number): string {
|
|
35
|
-
const d = new Date(ts * 1000);
|
|
36
|
-
const h = d.getUTCHours().toString().padStart(2, "0");
|
|
37
|
-
const m = d.getUTCMinutes().toString().padStart(2, "0");
|
|
38
|
-
return `${h}:${m} UTC`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function weiToEth(wei: string): string {
|
|
42
|
-
return (Number(BigInt(wei)) / 1e18).toFixed(4);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function weiToUsdc(wei: string): string {
|
|
46
|
-
return (Number(BigInt(wei)) / 1e6).toFixed(2);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
type FeedEvent = {
|
|
50
|
-
type: "handshake" | "hire" | "fulfill" | "vouch";
|
|
51
|
-
timestamp: number;
|
|
52
|
-
raw: Record<string, unknown>;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
async function fetchFeedEvents(limit: number, typeFilter?: string, sinceTs?: number): Promise<FeedEvent[]> {
|
|
56
|
-
const includeHandshakes = !typeFilter || typeFilter === "handshake";
|
|
57
|
-
const includeHire = !typeFilter || typeFilter === "hire";
|
|
58
|
-
const includeFulfill = !typeFilter || typeFilter === "fulfill";
|
|
59
|
-
const includeVouch = !typeFilter || typeFilter === "vouch";
|
|
60
|
-
|
|
61
|
-
const tsFilt = sinceTs ? `, where: { timestamp_gt: "${sinceTs}" }` : "";
|
|
62
|
-
const agTsFilt = sinceTs ? `, where: { proposedAt_gt: "${sinceTs}" }` : "";
|
|
63
|
-
|
|
64
|
-
const parts: string[] = [];
|
|
65
|
-
|
|
66
|
-
if (includeHandshakes) {
|
|
67
|
-
parts.push(`
|
|
68
|
-
handshakes(orderBy: timestamp, orderDirection: desc, first: ${limit}${tsFilt}) {
|
|
69
|
-
id from { id name } to { id name } hsType note timestamp isNewConnection
|
|
70
|
-
}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (includeHire || includeFulfill) {
|
|
74
|
-
parts.push(`
|
|
75
|
-
agreements(orderBy: proposedAt, orderDirection: desc, first: ${limit}${agTsFilt}) {
|
|
76
|
-
id client provider serviceType price state proposedAt updatedAt
|
|
77
|
-
}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (includeVouch) {
|
|
81
|
-
parts.push(`
|
|
82
|
-
vouches(orderBy: id, orderDirection: desc, first: ${limit}) {
|
|
83
|
-
id voucher { id name } newAgent { id name } stakeAmount active
|
|
84
|
-
}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const data = await gql(`{ ${parts.join("\n")} }`);
|
|
88
|
-
const events: FeedEvent[] = [];
|
|
89
|
-
|
|
90
|
-
if (includeHandshakes) {
|
|
91
|
-
for (const h of (data.handshakes as Record<string, unknown>[]) ?? []) {
|
|
92
|
-
events.push({ type: "handshake", timestamp: Number(h["timestamp"]), raw: h });
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (data["agreements"]) {
|
|
97
|
-
for (const a of data["agreements"] as Record<string, unknown>[]) {
|
|
98
|
-
const state = Number(a["state"]);
|
|
99
|
-
if (includeHire && state === 0) {
|
|
100
|
-
events.push({ type: "hire", timestamp: Number(a["proposedAt"]), raw: a });
|
|
101
|
-
}
|
|
102
|
-
if (includeFulfill && state === 2) {
|
|
103
|
-
events.push({ type: "fulfill", timestamp: Number(a["updatedAt"]), raw: a });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (includeVouch) {
|
|
109
|
-
for (const v of (data["vouches"] as Record<string, unknown>[]) ?? []) {
|
|
110
|
-
events.push({ type: "vouch", timestamp: 0, raw: v });
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
115
|
-
return events.slice(0, limit);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function renderFeedEvent(ev: FeedEvent): string {
|
|
119
|
-
const ts = ev.timestamp > 0 ? `[${utcTime(ev.timestamp)}]` : "[ ]";
|
|
120
|
-
|
|
121
|
-
switch (ev.type) {
|
|
122
|
-
case "handshake": {
|
|
123
|
-
const h = ev.raw;
|
|
124
|
-
const from = h["from"] as Record<string, string>;
|
|
125
|
-
const to = h["to"] as Record<string, string>;
|
|
126
|
-
const fromName = from["name"] || shortAddr(from["id"]);
|
|
127
|
-
const toName = to["name"] || shortAddr(to["id"]);
|
|
128
|
-
const label = HS_TYPE_LABELS[Number(h["hsType"])] ?? `Type${h["hsType"]}`;
|
|
129
|
-
const note = h["note"] ? ` "${h["note"]}"` : "";
|
|
130
|
-
return chalk.cyan(`${ts} 🤝 ${fromName} → ${toName} ${label}${note}`);
|
|
131
|
-
}
|
|
132
|
-
case "hire": {
|
|
133
|
-
const a = ev.raw;
|
|
134
|
-
const price = a["price"] ? ` ${weiToEth(a["price"] as string)} ETH` : "";
|
|
135
|
-
return chalk.yellow(
|
|
136
|
-
`${ts} 📋 ${shortAddr(a["client"] as string)} hired ${shortAddr(a["provider"] as string)} ${a["serviceType"]}${price}`,
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
case "fulfill": {
|
|
140
|
-
const a = ev.raw;
|
|
141
|
-
const price = a["price"] ? ` ${weiToEth(a["price"] as string)} ETH released` : "";
|
|
142
|
-
return chalk.green(`${ts} ✅ Agreement #${(a["id"] as string).slice(0, 8)} fulfilled${price}`);
|
|
143
|
-
}
|
|
144
|
-
case "vouch": {
|
|
145
|
-
const v = ev.raw;
|
|
146
|
-
const voucher = v["voucher"] as Record<string, string>;
|
|
147
|
-
const newAgent = v["newAgent"] as Record<string, string>;
|
|
148
|
-
const vName = voucher["name"] || shortAddr(voucher["id"]);
|
|
149
|
-
const nName = newAgent["name"] || shortAddr(newAgent["id"]);
|
|
150
|
-
const stake = v["stakeAmount"] ? ` ${weiToUsdc(v["stakeAmount"] as string)} USDC staked` : "";
|
|
151
|
-
return chalk.magenta(`${ts} 🔗 ${vName} vouched for ${nName}${stake}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export interface FeedOptions {
|
|
157
|
-
limit?: string;
|
|
158
|
-
live?: boolean;
|
|
159
|
-
type?: string;
|
|
160
|
-
json?: boolean;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export async function runFeed(opts: FeedOptions): Promise<void> {
|
|
164
|
-
const limit = parseInt(opts.limit ?? "20", 10);
|
|
165
|
-
const typeFilter = opts.type;
|
|
166
|
-
|
|
167
|
-
const events = await fetchFeedEvents(limit, typeFilter);
|
|
168
|
-
|
|
169
|
-
if (opts.json) {
|
|
170
|
-
console.log(JSON.stringify(events, null, 2));
|
|
171
|
-
if (!opts.live) return;
|
|
172
|
-
} else {
|
|
173
|
-
for (const ev of [...events].reverse()) {
|
|
174
|
-
console.log(renderFeedEvent(ev));
|
|
175
|
-
}
|
|
176
|
-
if (!opts.live) return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Live mode — keep process alive and poll every 30s
|
|
180
|
-
process.stdin.resume();
|
|
181
|
-
let lastTs = events.length > 0 ? events[0].timestamp : Math.floor(Date.now() / 1000);
|
|
182
|
-
|
|
183
|
-
const interval = setInterval(async () => {
|
|
184
|
-
try {
|
|
185
|
-
const newEvents = await fetchFeedEvents(limit, typeFilter, lastTs);
|
|
186
|
-
if (newEvents.length > 0) {
|
|
187
|
-
if (opts.json) {
|
|
188
|
-
console.log(JSON.stringify(newEvents, null, 2));
|
|
189
|
-
} else {
|
|
190
|
-
console.log(chalk.dim("─".repeat(50)));
|
|
191
|
-
for (const ev of [...newEvents].reverse()) {
|
|
192
|
-
console.log(renderFeedEvent(ev));
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
lastTs = newEvents[0].timestamp;
|
|
196
|
-
}
|
|
197
|
-
} catch {
|
|
198
|
-
if (!opts.json) console.log(chalk.dim(" (subgraph unavailable)"));
|
|
199
|
-
}
|
|
200
|
-
}, 30_000);
|
|
201
|
-
|
|
202
|
-
process.on("SIGINT", () => {
|
|
203
|
-
clearInterval(interval);
|
|
204
|
-
process.exit(0);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export function registerFeedCommand(program: Command): void {
|
|
209
|
-
program
|
|
210
|
-
.command("feed")
|
|
211
|
-
.description("Live terminal feed of recent Arena events")
|
|
212
|
-
.option("--limit <n>", "Number of events to show", "20")
|
|
213
|
-
.option("--live", "Poll every 30s for new events")
|
|
214
|
-
.option("--type <type>", "Filter by event type: handshake|hire|fulfill|vouch")
|
|
215
|
-
.option("--json", "Output as JSON")
|
|
216
|
-
.action(async (opts) => {
|
|
217
|
-
try {
|
|
218
|
-
await runFeed(opts as FeedOptions);
|
|
219
|
-
} catch (err) {
|
|
220
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
221
|
-
if ((opts as FeedOptions).json) {
|
|
222
|
-
console.log(JSON.stringify({ error: "Subgraph unavailable", details: msg }));
|
|
223
|
-
} else {
|
|
224
|
-
console.error(chalk.red(`Subgraph unavailable: ${msg}`));
|
|
225
|
-
}
|
|
226
|
-
process.exit(1);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
}
|
package/src/commands/hire.ts
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { ServiceAgreementClient, SessionManager } from "@arc402/sdk";
|
|
3
|
-
import { ethers } from "ethers";
|
|
4
|
-
import { getUsdcAddress, loadConfig } from "../config";
|
|
5
|
-
import { requireSigner } from "../client";
|
|
6
|
-
import { hashFile, hashString } from "../utils/hash";
|
|
7
|
-
import { parseDuration } from "../utils/time";
|
|
8
|
-
import { printSenderInfo, executeContractWriteViaWallet } from "../wallet-router";
|
|
9
|
-
import { AGENT_REGISTRY_ABI, SERVICE_AGREEMENT_ABI } from "../abis";
|
|
10
|
-
import { c } from '../ui/colors';
|
|
11
|
-
import { startSpinner } from '../ui/spinner';
|
|
12
|
-
import { renderTree, TreeItem } from '../ui/tree';
|
|
13
|
-
import { formatAddress } from '../ui/format';
|
|
14
|
-
|
|
15
|
-
const DEFAULT_REGISTRY_ADDRESS = "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865";
|
|
16
|
-
|
|
17
|
-
const sessionManager = new SessionManager();
|
|
18
|
-
|
|
19
|
-
export function registerHireCommand(program: Command): void {
|
|
20
|
-
program
|
|
21
|
-
.command("hire")
|
|
22
|
-
.description("Create the on-chain commitment after off-chain negotiation")
|
|
23
|
-
.requiredOption("--agent <address>")
|
|
24
|
-
.requiredOption("--task <description>")
|
|
25
|
-
.requiredOption("--service-type <type>")
|
|
26
|
-
.option("--max <amount>", "Max price in wei (e.g. 1000000000000000) or ETH (e.g. 0.001eth) or USDC (e.g. 1USDC). Required unless --session is provided.")
|
|
27
|
-
.option("--deadline <duration>", "Deadline as duration (1h, 30m, 7d) or absolute ISO date (2026-04-01). Required unless --session is provided.")
|
|
28
|
-
.option("--token <token>", "eth or usdc", "eth")
|
|
29
|
-
.option("--deliverable-spec <filepath>")
|
|
30
|
-
.option("--session <sessionId>", "Load agreed price and deadline from a completed negotiation session")
|
|
31
|
-
.option("--json")
|
|
32
|
-
.action(async (opts) => {
|
|
33
|
-
const config = loadConfig();
|
|
34
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
35
|
-
const { signer, address } = await requireSigner(config);
|
|
36
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
37
|
-
|
|
38
|
-
let maxAmount: string;
|
|
39
|
-
let deadlineArg: string;
|
|
40
|
-
let transcriptHash: string | undefined;
|
|
41
|
-
|
|
42
|
-
if (opts.session) {
|
|
43
|
-
const session = sessionManager.load(opts.session);
|
|
44
|
-
if (session.state !== "ACCEPTED") throw new Error(`Session ${opts.session} is not in ACCEPTED state (state: ${session.state})`);
|
|
45
|
-
if (!session.agreedPrice || !session.agreedDeadline) throw new Error(`Session ${opts.session} is missing agreedPrice or agreedDeadline`);
|
|
46
|
-
maxAmount = session.agreedPrice;
|
|
47
|
-
deadlineArg = session.agreedDeadline;
|
|
48
|
-
transcriptHash = session.transcriptHash;
|
|
49
|
-
} else {
|
|
50
|
-
if (!opts.max) throw new Error("--max is required when --session is not provided. Examples: 0.001eth, 1000000000000000 (wei), 1USDC");
|
|
51
|
-
if (!opts.deadline) throw new Error("--deadline is required when --session is not provided. Examples: 1h, 30m, 7d, 2026-04-01");
|
|
52
|
-
maxAmount = opts.max;
|
|
53
|
-
deadlineArg = opts.deadline;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Normalise --max: strip trailing 'eth' or 'USDC' suffix and convert to correct unit
|
|
57
|
-
const useUsdc = String(opts.token).toLowerCase() === "usdc";
|
|
58
|
-
const ethSuffix = /^(\d+(?:\.\d+)?)eth$/i.exec(maxAmount);
|
|
59
|
-
const usdcSuffix = /^(\d+(?:\.\d+)?)usdc$/i.exec(maxAmount);
|
|
60
|
-
if (ethSuffix) maxAmount = String(BigInt(Math.round(parseFloat(ethSuffix[1]) * 1e18)));
|
|
61
|
-
else if (usdcSuffix) maxAmount = usdcSuffix[1]; // keep decimal for USDC path
|
|
62
|
-
const token = useUsdc ? getUsdcAddress(config) : ethers.ZeroAddress;
|
|
63
|
-
let price: bigint;
|
|
64
|
-
try {
|
|
65
|
-
price = useUsdc ? BigInt(Math.round(Number(maxAmount) * 1_000_000)) : BigInt(maxAmount);
|
|
66
|
-
} catch {
|
|
67
|
-
throw new Error(`Invalid --max value "${opts.max}". Use wei (1000000000000000), ETH (0.001eth), or USDC (1USDC)`);
|
|
68
|
-
}
|
|
69
|
-
if (price <= 0n) throw new Error(`--max must be greater than zero`);
|
|
70
|
-
|
|
71
|
-
// Pre-flight: check client !== provider (J2-03)
|
|
72
|
-
if (address.toLowerCase() === opts.agent.toLowerCase()) {
|
|
73
|
-
console.error("Cannot hire yourself: client and provider addresses are the same.");
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Pre-flight: check provider is registered in AgentRegistry (J2-02)
|
|
78
|
-
const agentRegistryAddress = config.agentRegistryV2Address ?? config.agentRegistryAddress;
|
|
79
|
-
if (agentRegistryAddress) {
|
|
80
|
-
const arProvider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
81
|
-
const arCheck = new ethers.Contract(
|
|
82
|
-
agentRegistryAddress,
|
|
83
|
-
["function isRegistered(address wallet) external view returns (bool)"],
|
|
84
|
-
arProvider,
|
|
85
|
-
);
|
|
86
|
-
let isRegistered = true;
|
|
87
|
-
try {
|
|
88
|
-
isRegistered = await arCheck.isRegistered(opts.agent);
|
|
89
|
-
} catch { /* assume registered if read fails */ }
|
|
90
|
-
if (!isRegistered) {
|
|
91
|
-
console.error(`Provider ${opts.agent} is not registered in AgentRegistry.`);
|
|
92
|
-
console.error(`Verify the agent address is correct, or check the registry at ${agentRegistryAddress}.`);
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Pre-flight: check token is allowed on this ServiceAgreement (J2-01)
|
|
98
|
-
if (useUsdc) {
|
|
99
|
-
const saProvider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
100
|
-
const saCheck = new ethers.Contract(
|
|
101
|
-
config.serviceAgreementAddress,
|
|
102
|
-
["function allowedTokens(address) external view returns (bool)"],
|
|
103
|
-
saProvider,
|
|
104
|
-
);
|
|
105
|
-
let isAllowed = false;
|
|
106
|
-
try {
|
|
107
|
-
isAllowed = await saCheck.allowedTokens(token);
|
|
108
|
-
} catch { isAllowed = true; /* assume allowed if read fails */ }
|
|
109
|
-
if (!isAllowed) {
|
|
110
|
-
console.error(`Token ${token} is not allowed on this ServiceAgreement.`);
|
|
111
|
-
console.error(`Only the SA owner can allowlist tokens via:`);
|
|
112
|
-
console.error(` cast send ${config.serviceAgreementAddress} "allowToken(address)" ${token}`);
|
|
113
|
-
console.error(`For ETH payments, use --token eth`);
|
|
114
|
-
process.exit(1);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Use spec hash as deliverables hash; if transcript exists, incorporate it
|
|
119
|
-
const baseHash = opts.deliverableSpec ? hashFile(opts.deliverableSpec) : hashString(opts.task);
|
|
120
|
-
const deliverablesHash = transcriptHash
|
|
121
|
-
? (ethers.keccak256(ethers.toUtf8Bytes(baseHash + transcriptHash)) as `0x${string}`)
|
|
122
|
-
: baseHash;
|
|
123
|
-
|
|
124
|
-
// Parse deadline: if it looks like an ISO date, convert to seconds from now
|
|
125
|
-
let deadlineSeconds: number;
|
|
126
|
-
const isoMatch = deadlineArg.match(/^\d{4}-\d{2}-\d{2}/);
|
|
127
|
-
if (isoMatch) {
|
|
128
|
-
const target = Math.floor(new Date(deadlineArg).getTime() / 1000);
|
|
129
|
-
deadlineSeconds = target - Math.floor(Date.now() / 1000);
|
|
130
|
-
if (deadlineSeconds <= 0) throw new Error(`Deadline ${deadlineArg} is in the past`);
|
|
131
|
-
} else {
|
|
132
|
-
deadlineSeconds = parseDuration(deadlineArg);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
printSenderInfo(config);
|
|
136
|
-
|
|
137
|
-
let agreementId: bigint;
|
|
138
|
-
|
|
139
|
-
const hireSpinner = startSpinner('Submitting agreement...');
|
|
140
|
-
|
|
141
|
-
if (config.walletContractAddress) {
|
|
142
|
-
// Smart wallet path — wallet handles per-tx USDC approval via maxApprovalAmount
|
|
143
|
-
const tx = await executeContractWriteViaWallet(
|
|
144
|
-
config.walletContractAddress,
|
|
145
|
-
signer,
|
|
146
|
-
config.serviceAgreementAddress,
|
|
147
|
-
SERVICE_AGREEMENT_ABI,
|
|
148
|
-
"propose",
|
|
149
|
-
[opts.agent, opts.serviceType, opts.task, price, token, deadlineSeconds, deliverablesHash],
|
|
150
|
-
useUsdc ? 0n : price, // ETH value forwarded to SA; 0 for USDC agreements
|
|
151
|
-
useUsdc ? token : ethers.ZeroAddress, // approvalToken for USDC
|
|
152
|
-
useUsdc ? price : 0n, // maxApprovalAmount for USDC
|
|
153
|
-
);
|
|
154
|
-
const receipt = await tx.wait();
|
|
155
|
-
const saInterface = new ethers.Interface(SERVICE_AGREEMENT_ABI);
|
|
156
|
-
let found = false;
|
|
157
|
-
for (const log of receipt!.logs) {
|
|
158
|
-
if (log.address.toLowerCase() === config.serviceAgreementAddress.toLowerCase()) {
|
|
159
|
-
try {
|
|
160
|
-
const parsed = saInterface.parseLog(log);
|
|
161
|
-
if (parsed?.name === "AgreementProposed") {
|
|
162
|
-
agreementId = parsed.args[0] as bigint;
|
|
163
|
-
found = true;
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
} catch { /* skip unparseable logs */ }
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if (!found) throw new Error("AgreementProposed event not found in transaction receipt");
|
|
170
|
-
} else {
|
|
171
|
-
// EOA path — existing behaviour
|
|
172
|
-
if (useUsdc) {
|
|
173
|
-
const usdc = new ethers.Contract(
|
|
174
|
-
token,
|
|
175
|
-
["function approve(address spender,uint256 amount) external returns (bool)", "function allowance(address owner,address spender) external view returns (uint256)"],
|
|
176
|
-
signer
|
|
177
|
-
);
|
|
178
|
-
const allowance = await usdc.allowance(address, config.serviceAgreementAddress);
|
|
179
|
-
if (allowance < price) await (await usdc.approve(config.serviceAgreementAddress, price)).wait();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const result = await client.propose({
|
|
183
|
-
provider: opts.agent,
|
|
184
|
-
serviceType: opts.serviceType,
|
|
185
|
-
description: opts.task,
|
|
186
|
-
price,
|
|
187
|
-
token,
|
|
188
|
-
deadline: deadlineSeconds,
|
|
189
|
-
deliverablesHash,
|
|
190
|
-
});
|
|
191
|
-
agreementId = result.agreementId;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
hireSpinner.succeed('Agreement proposed');
|
|
195
|
-
|
|
196
|
-
// Notify provider's HTTP endpoint (non-blocking)
|
|
197
|
-
const hireRegistryAddress = config.agentRegistryV2Address ?? config.agentRegistryAddress ?? DEFAULT_REGISTRY_ADDRESS;
|
|
198
|
-
try {
|
|
199
|
-
const hireProvider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
200
|
-
const hireRegistry = new ethers.Contract(hireRegistryAddress, AGENT_REGISTRY_ABI, hireProvider);
|
|
201
|
-
const agentData = await hireRegistry.getAgent(opts.agent);
|
|
202
|
-
const endpoint = agentData.endpoint as string;
|
|
203
|
-
if (endpoint) {
|
|
204
|
-
await fetch(`${endpoint}/hire`, {
|
|
205
|
-
method: "POST",
|
|
206
|
-
headers: { "Content-Type": "application/json" },
|
|
207
|
-
body: JSON.stringify({
|
|
208
|
-
agreementId: agreementId!.toString(),
|
|
209
|
-
from: address,
|
|
210
|
-
provider: opts.agent,
|
|
211
|
-
serviceType: opts.serviceType,
|
|
212
|
-
task: opts.task,
|
|
213
|
-
price: price.toString(),
|
|
214
|
-
token,
|
|
215
|
-
deadline: deadlineSeconds,
|
|
216
|
-
deliverablesHash,
|
|
217
|
-
}),
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
} catch (err) {
|
|
221
|
-
console.warn(`Warning: could not notify provider endpoint: ${err instanceof Error ? err.message : String(err)}`);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (opts.session) {
|
|
225
|
-
sessionManager.setOnChainId(opts.session, agreementId!.toString());
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (opts.json) {
|
|
229
|
-
const output: Record<string, unknown> = { agreementId: agreementId!.toString(), deliverablesHash };
|
|
230
|
-
if (transcriptHash) output.transcriptHash = transcriptHash;
|
|
231
|
-
if (opts.session) output.sessionId = opts.session;
|
|
232
|
-
return console.log(JSON.stringify(output, null, 2));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
console.log(' ' + c.success + c.white(` Agreement #${agreementId!} proposed`));
|
|
236
|
-
const hireTreeItems: TreeItem[] = [
|
|
237
|
-
{ label: 'Agent', value: formatAddress(opts.agent) },
|
|
238
|
-
{ label: 'Task', value: opts.task.slice(0, 60) + (opts.task.length > 60 ? '...' : '') },
|
|
239
|
-
{ label: 'Service', value: opts.serviceType },
|
|
240
|
-
{ label: 'Hash', value: String(deliverablesHash), last: !transcriptHash },
|
|
241
|
-
];
|
|
242
|
-
if (transcriptHash) hireTreeItems.push({ label: 'Transcript', value: transcriptHash, last: true });
|
|
243
|
-
renderTree(hireTreeItems);
|
|
244
|
-
});
|
|
245
|
-
}
|
package/src/commands/migrate.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { ethers } from "ethers";
|
|
3
|
-
import { loadConfig } from "../config";
|
|
4
|
-
import { getClient, requireSigner } from "../client";
|
|
5
|
-
import { c } from '../ui/colors';
|
|
6
|
-
import { startSpinner } from '../ui/spinner';
|
|
7
|
-
import { renderTree } from '../ui/tree';
|
|
8
|
-
import { formatAddress } from '../ui/format';
|
|
9
|
-
|
|
10
|
-
const MIGRATION_REGISTRY_ABI = [
|
|
11
|
-
"function registerMigration(address oldWallet, address newWallet) external",
|
|
12
|
-
"function resolveActiveWallet(address wallet) external view returns (address)",
|
|
13
|
-
"function getLineage(address wallet) external view returns (address[])",
|
|
14
|
-
"function migratedTo(address wallet) external view returns (address)",
|
|
15
|
-
"function migratedFrom(address wallet) external view returns (address)",
|
|
16
|
-
"event MigrationRegistered(address indexed oldWallet, address indexed newWallet, address indexed owner, uint256 migratedAt, uint256 scoreAtMigration, uint256 appliedDecay)",
|
|
17
|
-
] as const;
|
|
18
|
-
|
|
19
|
-
export function registerMigrateCommands(program: Command): void {
|
|
20
|
-
const migrate = program
|
|
21
|
-
.command("migrate")
|
|
22
|
-
.description("Wallet migration — register, query status, or print lineage history")
|
|
23
|
-
.argument("[oldWallet]", "old wallet address (required for registration)")
|
|
24
|
-
.argument("[newWallet]", "new wallet address (required for registration)")
|
|
25
|
-
.option("--json")
|
|
26
|
-
.action(async (oldWallet, newWallet, opts) => {
|
|
27
|
-
if (!oldWallet || !newWallet) {
|
|
28
|
-
console.error("Usage: arc402 migrate <oldWallet> <newWallet>");
|
|
29
|
-
console.error("Both wallets must share the same registered owner address.");
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const config = loadConfig();
|
|
34
|
-
if (!config.migrationRegistryAddress) {
|
|
35
|
-
console.error("migrationRegistryAddress not configured. Run `arc402 config set migrationRegistryAddress <address>`.");
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const { signer } = await requireSigner(config);
|
|
40
|
-
const contract = new ethers.Contract(config.migrationRegistryAddress, MIGRATION_REGISTRY_ABI, signer);
|
|
41
|
-
|
|
42
|
-
const migrateSpinner = startSpinner('Registering migration...');
|
|
43
|
-
const tx = await contract.registerMigration(oldWallet, newWallet);
|
|
44
|
-
const receipt = await tx.wait();
|
|
45
|
-
migrateSpinner.succeed('Migration registered');
|
|
46
|
-
|
|
47
|
-
const payload = {
|
|
48
|
-
oldWallet,
|
|
49
|
-
newWallet,
|
|
50
|
-
txHash: receipt.hash,
|
|
51
|
-
};
|
|
52
|
-
if (opts.json) return console.log(JSON.stringify(payload, null, 2));
|
|
53
|
-
renderTree([
|
|
54
|
-
{ label: 'Old', value: formatAddress(oldWallet) },
|
|
55
|
-
{ label: 'New', value: formatAddress(newWallet) },
|
|
56
|
-
{ label: 'Note', value: '10% trust score decay applied', last: true },
|
|
57
|
-
]);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// ─── migrate status <address> ──────────────────────────────────────────────
|
|
61
|
-
|
|
62
|
-
migrate
|
|
63
|
-
.command("status <address>")
|
|
64
|
-
.description("Show whether a wallet is in a migration lineage and its current active wallet")
|
|
65
|
-
.option("--json")
|
|
66
|
-
.action(async (address, opts) => {
|
|
67
|
-
const config = loadConfig();
|
|
68
|
-
if (!config.migrationRegistryAddress) {
|
|
69
|
-
console.error("migrationRegistryAddress not configured. Run `arc402 config set migrationRegistryAddress <address>`.");
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const { provider } = await getClient(config);
|
|
74
|
-
const contract = new ethers.Contract(config.migrationRegistryAddress, MIGRATION_REGISTRY_ABI, provider);
|
|
75
|
-
|
|
76
|
-
const [activeWallet, migratedTo, migratedFrom] = await Promise.all([
|
|
77
|
-
contract.resolveActiveWallet(address),
|
|
78
|
-
contract.migratedTo(address),
|
|
79
|
-
contract.migratedFrom(address),
|
|
80
|
-
]);
|
|
81
|
-
|
|
82
|
-
const isCurrent = activeWallet.toLowerCase() === address.toLowerCase();
|
|
83
|
-
const hasMigrated = migratedTo !== ethers.ZeroAddress;
|
|
84
|
-
const wasSource = migratedFrom !== ethers.ZeroAddress;
|
|
85
|
-
|
|
86
|
-
const payload = {
|
|
87
|
-
address,
|
|
88
|
-
activeWallet,
|
|
89
|
-
isCurrent,
|
|
90
|
-
migratedTo: hasMigrated ? migratedTo : null,
|
|
91
|
-
migratedFrom: wasSource ? migratedFrom : null,
|
|
92
|
-
};
|
|
93
|
-
if (opts.json) return console.log(JSON.stringify(payload, null, 2));
|
|
94
|
-
const statusItems: import('../ui/tree').TreeItem[] = [
|
|
95
|
-
{ label: 'Address', value: formatAddress(address) },
|
|
96
|
-
{ label: 'Active', value: formatAddress(activeWallet) },
|
|
97
|
-
{ label: 'Status', value: isCurrent ? 'current (no further migration)' : `migrated — resolves to ${formatAddress(activeWallet)}` },
|
|
98
|
-
];
|
|
99
|
-
if (hasMigrated) statusItems.push({ label: 'Migrated to', value: formatAddress(migratedTo) });
|
|
100
|
-
if (wasSource) statusItems.push({ label: 'Migrated from', value: formatAddress(migratedFrom) });
|
|
101
|
-
statusItems[statusItems.length - 1].last = true;
|
|
102
|
-
renderTree(statusItems);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// ─── migrate lineage <address> ────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
migrate
|
|
108
|
-
.command("lineage <address>")
|
|
109
|
-
.description("Print full migration lineage history with timestamps")
|
|
110
|
-
.option("--json")
|
|
111
|
-
.action(async (address, opts) => {
|
|
112
|
-
const config = loadConfig();
|
|
113
|
-
if (!config.migrationRegistryAddress) {
|
|
114
|
-
console.error("migrationRegistryAddress not configured. Run `arc402 config set migrationRegistryAddress <address>`.");
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const { provider } = await getClient(config);
|
|
119
|
-
const contract = new ethers.Contract(config.migrationRegistryAddress, MIGRATION_REGISTRY_ABI, provider);
|
|
120
|
-
|
|
121
|
-
const lineage: string[] = await contract.getLineage(address);
|
|
122
|
-
|
|
123
|
-
if (lineage.length === 0) {
|
|
124
|
-
const payload = { address, lineage: [], migrations: 0 };
|
|
125
|
-
if (opts.json) return console.log(JSON.stringify(payload, null, 2));
|
|
126
|
-
console.log(`address=${address}`);
|
|
127
|
-
console.log(` no migration history`);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Fetch MigrationRegistered events for timestamps and decay info
|
|
132
|
-
interface MigrationEntry { step: number; from: string; to: string; timestamp: string | null; scoreAtMigration: string | null; decayBps: string | null }
|
|
133
|
-
const entries: MigrationEntry[] = [];
|
|
134
|
-
|
|
135
|
-
for (let i = 0; i < lineage.length - 1; i++) {
|
|
136
|
-
const from = lineage[i];
|
|
137
|
-
const to = lineage[i + 1];
|
|
138
|
-
let timestamp: string | null = null;
|
|
139
|
-
let scoreAtMigration: string | null = null;
|
|
140
|
-
let decayBps: string | null = null;
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const filter = contract.filters.MigrationRegistered(from, to);
|
|
144
|
-
const events = await contract.queryFilter(filter);
|
|
145
|
-
if (events.length > 0) {
|
|
146
|
-
const ev = events[0] as ethers.EventLog;
|
|
147
|
-
const block = await provider.getBlock(ev.blockNumber);
|
|
148
|
-
timestamp = block ? new Date(block.timestamp * 1000).toISOString() : null;
|
|
149
|
-
scoreAtMigration = ev.args[3]?.toString() ?? null;
|
|
150
|
-
decayBps = ev.args[4]?.toString() ?? null;
|
|
151
|
-
}
|
|
152
|
-
} catch {
|
|
153
|
-
// event query not critical — continue without timestamps
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
entries.push({ step: i + 1, from, to, timestamp, scoreAtMigration, decayBps });
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const payload = {
|
|
160
|
-
address,
|
|
161
|
-
lineage,
|
|
162
|
-
migrations: entries.length,
|
|
163
|
-
history: entries,
|
|
164
|
-
};
|
|
165
|
-
if (opts.json) return console.log(JSON.stringify(payload, null, 2));
|
|
166
|
-
|
|
167
|
-
const lineageItems = lineage.map((addr, i) => {
|
|
168
|
-
const roleLabel = i === 0 ? ' (origin)' : i === lineage.length - 1 ? ' (current)' : '';
|
|
169
|
-
const e = i < entries.length ? entries[i] : null;
|
|
170
|
-
let value = formatAddress(addr) + roleLabel;
|
|
171
|
-
if (e?.timestamp) value += ` · ${e.timestamp}`;
|
|
172
|
-
if (e?.scoreAtMigration) value += ` · score: ${e.scoreAtMigration} (decay: ${Number(e.decayBps) / 100}%)`;
|
|
173
|
-
return { label: `[${i}]`, value, last: i === lineage.length - 1 };
|
|
174
|
-
});
|
|
175
|
-
renderTree(lineageItems);
|
|
176
|
-
});
|
|
177
|
-
}
|