arc402-cli 0.9.18 → 0.10.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 +45 -14
- 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 +61 -27
- 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 +9 -5
- package/dist/client.js.map +1 -1
- package/dist/coinbase-smart-wallet.js +4 -1
- package/dist/coinbase-smart-wallet.js.map +1 -1
- package/dist/commands/accept.js +28 -25
- package/dist/commands/accept.js.map +1 -1
- package/dist/commands/agent-handshake.js +18 -15
- package/dist/commands/agent-handshake.js.map +1 -1
- package/dist/commands/agent.js +104 -98
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/agreements.js +98 -62
- package/dist/commands/agreements.js.map +1 -1
- package/dist/commands/arbitrator.js +81 -45
- package/dist/commands/arbitrator.js.map +1 -1
- package/dist/commands/arena-handshake.d.ts.map +1 -1
- package/dist/commands/arena-handshake.js +35 -53
- package/dist/commands/arena-handshake.js.map +1 -1
- package/dist/commands/arena.js +18 -12
- package/dist/commands/arena.js.map +1 -1
- package/dist/commands/backup.js +36 -30
- package/dist/commands/backup.js.map +1 -1
- package/dist/commands/cancel.js +18 -15
- package/dist/commands/cancel.js.map +1 -1
- package/dist/commands/channel.js +81 -45
- package/dist/commands/channel.js.map +1 -1
- package/dist/commands/coldstart.js +34 -31
- package/dist/commands/coldstart.js.map +1 -1
- 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.js +30 -24
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/contract-interaction.js +15 -12
- package/dist/commands/contract-interaction.js.map +1 -1
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +135 -98
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/deliver.js +76 -37
- package/dist/commands/deliver.js.map +1 -1
- package/dist/commands/discover.js +27 -24
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/dispute.js +110 -104
- package/dist/commands/dispute.js.map +1 -1
- package/dist/commands/doctor.js +55 -16
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/endpoint.js +95 -56
- package/dist/commands/endpoint.js.map +1 -1
- package/dist/commands/feed.js +18 -11
- package/dist/commands/feed.js.map +1 -1
- package/dist/commands/hire.js +40 -37
- package/dist/commands/hire.js.map +1 -1
- package/dist/commands/migrate.js +33 -30
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/negotiate.d.ts.map +1 -1
- package/dist/commands/negotiate.js +36 -34
- package/dist/commands/negotiate.js.map +1 -1
- package/dist/commands/openshell.js +104 -68
- package/dist/commands/openshell.js.map +1 -1
- package/dist/commands/owner.js +20 -17
- package/dist/commands/owner.js.map +1 -1
- package/dist/commands/policy.js +43 -41
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/relay.d.ts.map +1 -1
- package/dist/commands/relay.js +51 -18
- package/dist/commands/relay.js.map +1 -1
- package/dist/commands/remediate.js +23 -20
- package/dist/commands/remediate.js.map +1 -1
- package/dist/commands/reputation.js +27 -25
- package/dist/commands/reputation.js.map +1 -1
- package/dist/commands/setup.js +104 -65
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/trust.js +20 -17
- package/dist/commands/trust.js.map +1 -1
- package/dist/commands/verify.js +21 -18
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +645 -679
- package/dist/commands/wallet.js.map +1 -1
- package/dist/commands/watch.js +36 -33
- package/dist/commands/watch.js.map +1 -1
- package/dist/commands/watchtower.js +73 -37
- package/dist/commands/watchtower.js.map +1 -1
- package/dist/commands/workroom.d.ts.map +1 -1
- package/dist/commands/workroom.js +282 -143
- package/dist/commands/workroom.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +71 -22
- 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 +19 -1
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +90 -16
- 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/hire-listener.d.ts +3 -3
- package/dist/daemon/hire-listener.d.ts.map +1 -1
- package/dist/daemon/hire-listener.js +47 -13
- package/dist/daemon/hire-listener.js.map +1 -1
- package/dist/daemon/index.d.ts +2 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +526 -53
- 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 +51 -11
- 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 +53 -19
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/token-metering.js +47 -8
- 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 +27 -23
- 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 +12 -8
- package/dist/daemon/wallet-monitor.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 +64 -26
- package/dist/drain-v4.js.map +1 -1
- package/dist/endpoint-config.js +63 -20
- package/dist/endpoint-config.js.map +1 -1
- package/dist/endpoint-notify.js +48 -9
- package/dist/endpoint-notify.js.map +1 -1
- package/dist/index.js +50 -18
- package/dist/index.js.map +1 -1
- package/dist/openshell-runtime.d.ts.map +1 -1
- package/dist/openshell-runtime.js +82 -38
- package/dist/openshell-runtime.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +85 -78
- package/dist/program.js.map +1 -1
- package/dist/repl.js +31 -25
- package/dist/repl.js.map +1 -1
- package/dist/signing.js +6 -3
- package/dist/signing.js.map +1 -1
- package/dist/telegram-notify.js +40 -3
- package/dist/telegram-notify.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +56 -89
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Footer.js +7 -4
- package/dist/tui/Footer.js.map +1 -1
- package/dist/tui/Header.d.ts +1 -1
- package/dist/tui/Header.d.ts.map +1 -1
- package/dist/tui/Header.js +14 -9
- package/dist/tui/Header.js.map +1 -1
- package/dist/tui/InputLine.d.ts +2 -1
- package/dist/tui/InputLine.d.ts.map +1 -1
- package/dist/tui/InputLine.js +47 -97
- package/dist/tui/InputLine.js.map +1 -1
- package/dist/tui/Viewport.d.ts +1 -2
- package/dist/tui/Viewport.d.ts.map +1 -1
- package/dist/tui/Viewport.js +26 -6
- package/dist/tui/Viewport.js.map +1 -1
- package/dist/tui/WalletConnectPairing.js +19 -16
- package/dist/tui/WalletConnectPairing.js.map +1 -1
- package/dist/tui/components/Button.js +9 -6
- package/dist/tui/components/Button.js.map +1 -1
- package/dist/tui/components/CeremonyView.js +8 -5
- package/dist/tui/components/CeremonyView.js.map +1 -1
- package/dist/tui/components/CompletionDropdown.js +9 -6
- package/dist/tui/components/CompletionDropdown.js.map +1 -1
- package/dist/tui/components/ConfirmPrompt.js +8 -5
- package/dist/tui/components/ConfirmPrompt.js.map +1 -1
- package/dist/tui/components/CustomTextInput.js +14 -11
- package/dist/tui/components/CustomTextInput.js.map +1 -1
- package/dist/tui/components/InteractiveTable.js +12 -9
- package/dist/tui/components/InteractiveTable.js.map +1 -1
- package/dist/tui/components/StepSpinner.js +13 -10
- package/dist/tui/components/StepSpinner.js.map +1 -1
- package/dist/tui/components/Toast.js +12 -8
- package/dist/tui/components/Toast.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +21 -28
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/useChat.js +19 -13
- package/dist/tui/useChat.js.map +1 -1
- package/dist/tui/useCommand.d.ts +2 -3
- package/dist/tui/useCommand.d.ts.map +1 -1
- package/dist/tui/useCommand.js +24 -100
- package/dist/tui/useCommand.js.map +1 -1
- package/dist/tui/useNotifications.js +8 -5
- package/dist/tui/useNotifications.js.map +1 -1
- package/dist/tui/useScroll.d.ts.map +1 -1
- package/dist/tui/useScroll.js +12 -15
- package/dist/tui/useScroll.js.map +1 -1
- package/dist/ui/banner.d.ts +0 -12
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +19 -35
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/colors.js +19 -13
- package/dist/ui/colors.js.map +1 -1
- package/dist/ui/format.js +14 -6
- package/dist/ui/format.js.map +1 -1
- package/dist/ui/qr-render.js +11 -4
- package/dist/ui/qr-render.js.map +1 -1
- package/dist/ui/rpc-fallback.js +11 -6
- package/dist/ui/rpc-fallback.js.map +1 -1
- package/dist/ui/spinner.js +12 -6
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/tree.js +6 -3
- package/dist/ui/tree.js.map +1 -1
- package/dist/utils/format.js +41 -27
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/hash.js +42 -4
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/time.js +6 -2
- 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 +19 -12
- 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 +11 -6
- package/dist/walletconnect-session.js.map +1 -1
- package/dist/walletconnect.d.ts +5 -6
- package/dist/walletconnect.d.ts.map +1 -1
- package/dist/walletconnect.js +35 -32
- package/dist/walletconnect.js.map +1 -1
- package/package.json +11 -10
- package/INK6-UX-SPEC.md +0 -446
- package/MIGRATION-SPEC.md +0 -108
- package/TUI-SPEC.md +0 -214
- 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 -274
- package/src/commands/arena.ts +0 -122
- package/src/commands/backup.ts +0 -117
- 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 -68
- package/src/commands/contract-interaction.ts +0 -166
- package/src/commands/daemon.ts +0 -1054
- package/src/commands/deliver.ts +0 -148
- package/src/commands/discover.ts +0 -350
- package/src/commands/dispute.ts +0 -375
- package/src/commands/doctor.ts +0 -172
- 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 -272
- 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 -277
- 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 -3548
- package/src/commands/watch.ts +0 -220
- package/src/commands/watchtower.ts +0 -248
- package/src/commands/workroom.ts +0 -963
- package/src/config.ts +0 -220
- package/src/daemon/config.ts +0 -344
- package/src/daemon/hire-listener.ts +0 -226
- package/src/daemon/index.ts +0 -1089
- package/src/daemon/job-lifecycle.ts +0 -215
- package/src/daemon/notify.ts +0 -297
- 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 -129
- package/src/index.ts +0 -74
- package/src/openshell-runtime.ts +0 -281
- package/src/program.ts +0 -88
- package/src/repl.ts +0 -178
- package/src/signing.ts +0 -28
- package/src/telegram-notify.ts +0 -88
- package/src/tui/App.tsx +0 -263
- package/src/tui/Footer.tsx +0 -18
- package/src/tui/Header.tsx +0 -45
- package/src/tui/InputLine.tsx +0 -243
- package/src/tui/Viewport.tsx +0 -51
- package/src/tui/WalletConnectPairing.tsx +0 -114
- package/src/tui/components/Button.tsx +0 -38
- package/src/tui/components/CeremonyView.tsx +0 -39
- package/src/tui/components/CompletionDropdown.tsx +0 -56
- package/src/tui/components/ConfirmPrompt.tsx +0 -36
- package/src/tui/components/CustomTextInput.tsx +0 -132
- package/src/tui/components/InteractiveTable.tsx +0 -106
- package/src/tui/components/StepSpinner.tsx +0 -84
- package/src/tui/components/Toast.tsx +0 -59
- package/src/tui/index.tsx +0 -90
- package/src/tui/useChat.ts +0 -103
- package/src/tui/useCommand.ts +0 -238
- package/src/tui/useNotifications.ts +0 -28
- package/src/tui/useScroll.ts +0 -69
- package/src/ui/banner.ts +0 -78
- package/src/ui/colors.ts +0 -30
- package/src/ui/format.ts +0 -78
- package/src/ui/qr-render.ts +0 -92
- package/src/ui/rpc-fallback.ts +0 -59
- 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 -309
- package/test/time.test.js +0 -11
- package/tsconfig.json +0 -33
package/src/commands/dispute.ts
DELETED
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { ethers } from "ethers";
|
|
3
|
-
import prompts from "prompts";
|
|
4
|
-
import { ArbitrationVote, DirectDisputeReason, DisputeClass, DisputeMode, DisputeOutcome, EvidenceType, ServiceAgreementClient, DisputeArbitrationClient } from "@arc402/sdk";
|
|
5
|
-
import { loadConfig } from "../config.js";
|
|
6
|
-
import { getClient, requireSigner } from "../client.js";
|
|
7
|
-
import { hashFile, hashString } from "../utils/hash.js";
|
|
8
|
-
import { printSenderInfo, executeContractWriteViaWallet } from "../wallet-router.js";
|
|
9
|
-
import { SERVICE_AGREEMENT_ABI } from "../abis.js";
|
|
10
|
-
import { c } from '../ui/colors.js';
|
|
11
|
-
import { startSpinner } from '../ui/spinner.js';
|
|
12
|
-
|
|
13
|
-
export function registerDisputeCommand(program: Command): void {
|
|
14
|
-
const dispute = program.command("dispute").description("Formal dispute workflow; remediation-first by default, with narrow hard-fail direct-dispute exceptions");
|
|
15
|
-
|
|
16
|
-
// Fee quote (requires DisputeArbitration configured) — read-only, no wallet routing needed
|
|
17
|
-
dispute.command("fee-quote <agreementId>")
|
|
18
|
-
.description("Get dispute fee quote for an agreement")
|
|
19
|
-
.requiredOption("--price <price>", "Agreement price in wei/token units")
|
|
20
|
-
.requiredOption("--token <token>", "Token address (0x0 for ETH)")
|
|
21
|
-
.requiredOption("--mode <mode>", "unilateral|mutual")
|
|
22
|
-
.requiredOption("--class <class>", "hard-failure|ambiguity|high-sensitivity")
|
|
23
|
-
.action(async (agreementId, opts) => {
|
|
24
|
-
const config = loadConfig();
|
|
25
|
-
if (!config.disputeArbitrationAddress) throw new Error("disputeArbitrationAddress missing in config");
|
|
26
|
-
const { provider } = await getClient(config);
|
|
27
|
-
const client = new DisputeArbitrationClient(config.disputeArbitrationAddress, provider);
|
|
28
|
-
const modeMap: Record<string, DisputeMode> = { unilateral: DisputeMode.UNILATERAL, mutual: DisputeMode.MUTUAL };
|
|
29
|
-
const classMap: Record<string, DisputeClass> = {
|
|
30
|
-
'hard-failure': DisputeClass.HARD_FAILURE,
|
|
31
|
-
'ambiguity': DisputeClass.AMBIGUITY_QUALITY,
|
|
32
|
-
'high-sensitivity': DisputeClass.HIGH_SENSITIVITY,
|
|
33
|
-
};
|
|
34
|
-
const mode = modeMap[String(opts.mode).toLowerCase()];
|
|
35
|
-
const disputeClass = classMap[String(opts.class).toLowerCase()];
|
|
36
|
-
if (!mode || !disputeClass) throw new Error("Invalid --mode or --class");
|
|
37
|
-
const feeInTokens = await client.getFeeQuote(BigInt(opts.price), opts.token, mode, disputeClass);
|
|
38
|
-
console.log(' ' + c.mark + c.white(` Fee quote — agreement #${agreementId}: ${feeInTokens.toString()} tokens`));
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Open with explicit mode/class
|
|
42
|
-
dispute.command("open-with-mode <agreementId>")
|
|
43
|
-
.description("Open dispute with specific mode and class (requires fee in msg.value for ETH)")
|
|
44
|
-
.requiredOption("--mode <mode>", "unilateral|mutual")
|
|
45
|
-
.requiredOption("--class <class>", "hard-failure|ambiguity|high-sensitivity")
|
|
46
|
-
.requiredOption("--reason <reason>")
|
|
47
|
-
.option("--fee <fee>", "Fee in wei (for ETH agreements)", "0")
|
|
48
|
-
.action(async (agreementId, opts) => {
|
|
49
|
-
const config = loadConfig();
|
|
50
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
51
|
-
const { signer } = await requireSigner(config);
|
|
52
|
-
const modeMap: Record<string, DisputeMode> = { unilateral: DisputeMode.UNILATERAL, mutual: DisputeMode.MUTUAL };
|
|
53
|
-
const classMap: Record<string, DisputeClass> = {
|
|
54
|
-
'hard-failure': DisputeClass.HARD_FAILURE,
|
|
55
|
-
'ambiguity': DisputeClass.AMBIGUITY_QUALITY,
|
|
56
|
-
'high-sensitivity': DisputeClass.HIGH_SENSITIVITY,
|
|
57
|
-
};
|
|
58
|
-
const mode = modeMap[String(opts.mode).toLowerCase()];
|
|
59
|
-
const disputeClass = classMap[String(opts.class).toLowerCase()];
|
|
60
|
-
if (!mode || !disputeClass) throw new Error("Invalid --mode or --class");
|
|
61
|
-
printSenderInfo(config);
|
|
62
|
-
if (config.walletContractAddress) {
|
|
63
|
-
await executeContractWriteViaWallet(
|
|
64
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
65
|
-
SERVICE_AGREEMENT_ABI, "openDisputeWithMode",
|
|
66
|
-
[BigInt(agreementId), mode, disputeClass, opts.reason],
|
|
67
|
-
BigInt(opts.fee),
|
|
68
|
-
);
|
|
69
|
-
} else {
|
|
70
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
71
|
-
await client.openDisputeWithMode(BigInt(agreementId), mode, disputeClass, opts.reason, BigInt(opts.fee));
|
|
72
|
-
}
|
|
73
|
-
console.log(' ' + c.success + c.white(` Dispute opened — agreement #${agreementId}`));
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Join mutual dispute (respondent pays their half) — DisputeArbitration contract, no wallet routing
|
|
77
|
-
dispute.command("join <agreementId>")
|
|
78
|
-
.description("Join a mutual dispute as respondent (pays half the fee)")
|
|
79
|
-
.option("--fee <fee>", "Half-fee in wei (for ETH agreements)", "0")
|
|
80
|
-
.action(async (agreementId, opts) => {
|
|
81
|
-
const config = loadConfig();
|
|
82
|
-
if (!config.disputeArbitrationAddress) throw new Error("disputeArbitrationAddress missing in config");
|
|
83
|
-
const { signer } = await requireSigner(config);
|
|
84
|
-
const client = new DisputeArbitrationClient(config.disputeArbitrationAddress, signer);
|
|
85
|
-
await client.joinMutualDispute(BigInt(agreementId), BigInt(opts.fee));
|
|
86
|
-
console.log(' ' + c.success + c.white(` Joined mutual dispute — agreement #${agreementId}`));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
dispute.command("open <id>")
|
|
90
|
-
.requiredOption("--reason <reason>")
|
|
91
|
-
.option("--escalated", "Use escalateToDispute after remediation", false)
|
|
92
|
-
.option("--direct <type>", "Direct-dispute hard-fail reason: no-delivery|deadline-breach|invalid-deliverable|safety-critical")
|
|
93
|
-
.action(async (id, opts) => {
|
|
94
|
-
const config = loadConfig();
|
|
95
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
96
|
-
|
|
97
|
-
// Pre-flight: check disputeModule is configured (J4-01)
|
|
98
|
-
{
|
|
99
|
-
const { provider: dpProvider } = await getClient(config);
|
|
100
|
-
const saCheck = new ethers.Contract(
|
|
101
|
-
config.serviceAgreementAddress,
|
|
102
|
-
["function disputeModule() external view returns (address)"],
|
|
103
|
-
dpProvider,
|
|
104
|
-
);
|
|
105
|
-
let disputeModuleAddr: string = ethers.ZeroAddress;
|
|
106
|
-
try {
|
|
107
|
-
disputeModuleAddr = await saCheck.disputeModule();
|
|
108
|
-
} catch { /* assume not configured */ }
|
|
109
|
-
if (disputeModuleAddr === ethers.ZeroAddress) {
|
|
110
|
-
console.error(`No dispute module configured on this ServiceAgreement.`);
|
|
111
|
-
console.error(`Disputes require a DisputeModule to be set by the SA owner.`);
|
|
112
|
-
console.error(`This protocol deployment may not support formal disputes.`);
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Pre-flight: read dispute fee and prompt user (J4-02)
|
|
118
|
-
if (config.disputeArbitrationAddress) {
|
|
119
|
-
const { provider: feeProvider } = await getClient(config);
|
|
120
|
-
const daCheck = new ethers.Contract(
|
|
121
|
-
config.disputeArbitrationAddress,
|
|
122
|
-
["function getDisputeFee() external view returns (uint256)"],
|
|
123
|
-
feeProvider,
|
|
124
|
-
);
|
|
125
|
-
let feeWei = 0n;
|
|
126
|
-
try {
|
|
127
|
-
feeWei = await daCheck.getDisputeFee();
|
|
128
|
-
} catch { /* fee getter may not exist — assume 0 */ }
|
|
129
|
-
if (feeWei > 0n) {
|
|
130
|
-
const feeEth = ethers.formatEther(feeWei);
|
|
131
|
-
console.log(`\nDispute fee: ${feeEth} ETH. This will be deducted from your wallet.`);
|
|
132
|
-
const { proceed } = await prompts({
|
|
133
|
-
type: "confirm",
|
|
134
|
-
name: "proceed",
|
|
135
|
-
message: "Continue?",
|
|
136
|
-
initial: true,
|
|
137
|
-
});
|
|
138
|
-
if (!proceed) { console.log("Aborted."); process.exit(0); }
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const { signer } = await requireSigner(config);
|
|
143
|
-
const directMap: Record<string, DirectDisputeReason> = {
|
|
144
|
-
'no-delivery': DirectDisputeReason.NO_DELIVERY,
|
|
145
|
-
'deadline-breach': DirectDisputeReason.HARD_DEADLINE_BREACH,
|
|
146
|
-
'invalid-deliverable': DirectDisputeReason.INVALID_OR_FRAUDULENT_DELIVERABLE,
|
|
147
|
-
'safety-critical': DirectDisputeReason.SAFETY_CRITICAL_VIOLATION,
|
|
148
|
-
};
|
|
149
|
-
if (opts.escalated && opts.direct) throw new Error('Choose either --escalated or --direct, not both');
|
|
150
|
-
printSenderInfo(config);
|
|
151
|
-
if (config.walletContractAddress) {
|
|
152
|
-
if (opts.direct) {
|
|
153
|
-
const directReason = directMap[String(opts.direct).toLowerCase()];
|
|
154
|
-
if (directReason === undefined) throw new Error('Unsupported --direct reason');
|
|
155
|
-
await executeContractWriteViaWallet(
|
|
156
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
157
|
-
SERVICE_AGREEMENT_ABI, "directDispute", [BigInt(id), directReason, opts.reason],
|
|
158
|
-
);
|
|
159
|
-
} else if (opts.escalated) {
|
|
160
|
-
await executeContractWriteViaWallet(
|
|
161
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
162
|
-
SERVICE_AGREEMENT_ABI, "escalateToDispute", [BigInt(id), opts.reason],
|
|
163
|
-
);
|
|
164
|
-
} else {
|
|
165
|
-
await executeContractWriteViaWallet(
|
|
166
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
167
|
-
SERVICE_AGREEMENT_ABI, "dispute", [BigInt(id), opts.reason],
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
172
|
-
if (opts.direct) {
|
|
173
|
-
const directReason = directMap[String(opts.direct).toLowerCase()];
|
|
174
|
-
if (directReason === undefined) throw new Error('Unsupported --direct reason');
|
|
175
|
-
await client.directDispute(BigInt(id), directReason, opts.reason);
|
|
176
|
-
} else if (opts.escalated) {
|
|
177
|
-
await client.escalateToDispute(BigInt(id), opts.reason);
|
|
178
|
-
} else {
|
|
179
|
-
await client.dispute(BigInt(id), opts.reason);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
console.log(' ' + c.success + c.white(` Dispute opened — agreement #${id}`));
|
|
183
|
-
|
|
184
|
-
// J4-04: Display arbitration selection window deadline
|
|
185
|
-
try {
|
|
186
|
-
const { provider: dpAW } = await getClient(config);
|
|
187
|
-
const saAW = new ethers.Contract(
|
|
188
|
-
config.serviceAgreementAddress,
|
|
189
|
-
["function ARBITRATION_SELECTION_WINDOW() external view returns (uint256)"],
|
|
190
|
-
dpAW,
|
|
191
|
-
);
|
|
192
|
-
const selectionWindow: bigint = await saAW.ARBITRATION_SELECTION_WINDOW();
|
|
193
|
-
const deadlineDate = new Date(Date.now() + Number(selectionWindow) * 1000);
|
|
194
|
-
console.log(`Arbitration selection window closes: ${deadlineDate.toLocaleString()}. An arbitrator must be assigned before then.`);
|
|
195
|
-
} catch { /* not available on this deployment */ }
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
dispute.command("evidence <id>").requiredOption("--type <type>", "transcript|deliverable|acceptance|communication|external|other").option("--file <path>").option("--text <text>").option("--uri <uri>", "External evidence URI", "").action(async (id, opts) => {
|
|
199
|
-
const config = loadConfig();
|
|
200
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
201
|
-
const { signer } = await requireSigner(config);
|
|
202
|
-
const mapping: Record<string, EvidenceType> = { transcript: EvidenceType.TRANSCRIPT, deliverable: EvidenceType.DELIVERABLE, acceptance: EvidenceType.ACCEPTANCE_CRITERIA, communication: EvidenceType.COMMUNICATION, external: EvidenceType.EXTERNAL_REFERENCE, other: EvidenceType.OTHER };
|
|
203
|
-
const hash = opts.file ? hashFile(opts.file) : hashString(opts.text ?? opts.uri ?? `evidence:${id}`);
|
|
204
|
-
const evidenceType = mapping[String(opts.type).toLowerCase()] ?? EvidenceType.OTHER;
|
|
205
|
-
printSenderInfo(config);
|
|
206
|
-
if (config.walletContractAddress) {
|
|
207
|
-
await executeContractWriteViaWallet(
|
|
208
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
209
|
-
SERVICE_AGREEMENT_ABI, "submitDisputeEvidence", [BigInt(id), evidenceType, hash, opts.uri],
|
|
210
|
-
);
|
|
211
|
-
} else {
|
|
212
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
213
|
-
await client.submitDisputeEvidence(BigInt(id), evidenceType, hash, opts.uri);
|
|
214
|
-
}
|
|
215
|
-
console.log(' ' + c.success + c.white(` Evidence submitted — agreement #${id}`));
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// status — read-only, no wallet routing needed
|
|
219
|
-
dispute.command("status <id>").option("--json").action(async (id, opts) => {
|
|
220
|
-
const config = loadConfig();
|
|
221
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
222
|
-
const { provider } = await getClient(config); const client = new ServiceAgreementClient(config.serviceAgreementAddress, provider);
|
|
223
|
-
const result = {
|
|
224
|
-
case: await client.getDisputeCase(BigInt(id)),
|
|
225
|
-
arbitration: await client.getArbitrationCase(BigInt(id)),
|
|
226
|
-
evidence: await client.getDisputeEvidenceAll(BigInt(id)),
|
|
227
|
-
};
|
|
228
|
-
console.log(JSON.stringify(result, (_k, value) => typeof value === 'bigint' ? value.toString() : value, opts.json ? 2 : 2));
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
dispute.command("nominate <id>")
|
|
232
|
-
.description("Nominate an arbitrator during the on-chain arbitration phase")
|
|
233
|
-
.requiredOption("--arbitrator <address>")
|
|
234
|
-
.action(async (id, opts) => {
|
|
235
|
-
const config = loadConfig();
|
|
236
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
237
|
-
|
|
238
|
-
// Pre-flight: check arbitrator is approved (J4-03)
|
|
239
|
-
if (config.disputeArbitrationAddress) {
|
|
240
|
-
const { provider: arbProvider } = await getClient(config);
|
|
241
|
-
const daCheck = new ethers.Contract(
|
|
242
|
-
config.disputeArbitrationAddress,
|
|
243
|
-
["function isApprovedArbitrator(address arbitrator) external view returns (bool)"],
|
|
244
|
-
arbProvider,
|
|
245
|
-
);
|
|
246
|
-
let isApproved = true;
|
|
247
|
-
try {
|
|
248
|
-
isApproved = await daCheck.isApprovedArbitrator(opts.arbitrator);
|
|
249
|
-
} catch { /* assume approved if read fails */ }
|
|
250
|
-
if (!isApproved) {
|
|
251
|
-
console.error(`Arbitrator ${opts.arbitrator} is not approved.`);
|
|
252
|
-
console.error(`Use \`arc402 dispute list-arbitrators\` to see approved arbitrators.`);
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const { signer } = await requireSigner(config);
|
|
258
|
-
printSenderInfo(config);
|
|
259
|
-
if (config.walletContractAddress) {
|
|
260
|
-
await executeContractWriteViaWallet(
|
|
261
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
262
|
-
SERVICE_AGREEMENT_ABI, "nominateArbitrator", [BigInt(id), opts.arbitrator],
|
|
263
|
-
);
|
|
264
|
-
} else {
|
|
265
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
266
|
-
await client.nominateArbitrator(BigInt(id), opts.arbitrator);
|
|
267
|
-
}
|
|
268
|
-
console.log(' ' + c.success + c.white(` Arbitrator nominated — agreement #${id}`));
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
dispute.command("vote <id>")
|
|
272
|
-
.description("Cast an arbitration vote on-chain")
|
|
273
|
-
.requiredOption("--vote <vote>", "provider|refund|split|human-review")
|
|
274
|
-
.option("--provider-award <amount>", "Wei/token units", "0")
|
|
275
|
-
.option("--client-award <amount>", "Wei/token units", "0")
|
|
276
|
-
.action(async (id, opts) => {
|
|
277
|
-
const config = loadConfig();
|
|
278
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
279
|
-
const { signer, address: voterAddress, provider: voteProvider } = await requireSigner(config);
|
|
280
|
-
|
|
281
|
-
// J4-05: Pre-flight — verify caller is on the arbitration panel
|
|
282
|
-
try {
|
|
283
|
-
const saClient = new ServiceAgreementClient(config.serviceAgreementAddress, voteProvider);
|
|
284
|
-
const arbCase = await saClient.getArbitrationCase(BigInt(id));
|
|
285
|
-
const onPanel = arbCase.arbitrators.map((a: string) => a.toLowerCase()).includes(voterAddress.toLowerCase());
|
|
286
|
-
if (!onPanel) {
|
|
287
|
-
console.error(`You are not on the arbitration panel for agreement ${id}. Only assigned arbitrators can vote.`);
|
|
288
|
-
process.exit(1);
|
|
289
|
-
}
|
|
290
|
-
} catch (e) {
|
|
291
|
-
if ((e as NodeJS.ErrnoException)?.code === 'ERR_USE_AFTER_CLOSE' || String(e).includes('process.exit')) throw e;
|
|
292
|
-
// If read fails, skip the check and let the transaction reveal the error
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const mapping: Record<string, ArbitrationVote> = {
|
|
296
|
-
provider: ArbitrationVote.PROVIDER_WINS,
|
|
297
|
-
refund: ArbitrationVote.CLIENT_REFUND,
|
|
298
|
-
split: ArbitrationVote.SPLIT,
|
|
299
|
-
'human-review': ArbitrationVote.HUMAN_REVIEW_REQUIRED,
|
|
300
|
-
};
|
|
301
|
-
const vote = mapping[String(opts.vote).toLowerCase()];
|
|
302
|
-
if (vote === undefined) throw new Error('Unsupported --vote value');
|
|
303
|
-
printSenderInfo(config);
|
|
304
|
-
if (config.walletContractAddress) {
|
|
305
|
-
await executeContractWriteViaWallet(
|
|
306
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
307
|
-
SERVICE_AGREEMENT_ABI, "castArbitrationVote",
|
|
308
|
-
[BigInt(id), vote, BigInt(opts.providerAward), BigInt(opts.clientAward)],
|
|
309
|
-
);
|
|
310
|
-
} else {
|
|
311
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
312
|
-
await client.castArbitrationVote(BigInt(id), vote, BigInt(opts.providerAward), BigInt(opts.clientAward));
|
|
313
|
-
}
|
|
314
|
-
console.log(' ' + c.success + c.white(` Vote recorded — agreement #${id}`));
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
dispute.command("human <id>")
|
|
318
|
-
.description("Request human escalation when arbitration stalls or requires human backstop")
|
|
319
|
-
.requiredOption("--reason <reason>")
|
|
320
|
-
.action(async (id, opts) => {
|
|
321
|
-
const config = loadConfig();
|
|
322
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
323
|
-
const { signer } = await requireSigner(config);
|
|
324
|
-
printSenderInfo(config);
|
|
325
|
-
if (config.walletContractAddress) {
|
|
326
|
-
await executeContractWriteViaWallet(
|
|
327
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
328
|
-
SERVICE_AGREEMENT_ABI, "requestHumanEscalation", [BigInt(id), opts.reason],
|
|
329
|
-
);
|
|
330
|
-
} else {
|
|
331
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
332
|
-
await client.requestHumanEscalation(BigInt(id), opts.reason);
|
|
333
|
-
}
|
|
334
|
-
console.log(' ' + c.success + c.white(` Escalated to human — agreement #${id}`));
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
dispute.command("resolve <id>").description("Owner-only admin path if you are operating the dispute contract").requiredOption("--outcome <outcome>", "provider|refund|partial-provider|partial-client|mutual-cancel|human-review").option("--provider-award <amount>", "Wei/token units", "0").option("--client-award <amount>", "Wei/token units", "0").action(async (id, opts) => {
|
|
338
|
-
const config = loadConfig();
|
|
339
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
340
|
-
const { signer } = await requireSigner(config);
|
|
341
|
-
const mapping: Record<string, DisputeOutcome> = { provider: DisputeOutcome.PROVIDER_WINS, refund: DisputeOutcome.CLIENT_REFUND, 'partial-provider': DisputeOutcome.PARTIAL_PROVIDER, 'partial-client': DisputeOutcome.PARTIAL_CLIENT, 'mutual-cancel': DisputeOutcome.MUTUAL_CANCEL, 'human-review': DisputeOutcome.HUMAN_REVIEW_REQUIRED };
|
|
342
|
-
printSenderInfo(config);
|
|
343
|
-
if (config.walletContractAddress) {
|
|
344
|
-
await executeContractWriteViaWallet(
|
|
345
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
346
|
-
SERVICE_AGREEMENT_ABI, "resolveDisputeDetailed",
|
|
347
|
-
[BigInt(id), mapping[String(opts.outcome)], BigInt(opts.providerAward), BigInt(opts.clientAward)],
|
|
348
|
-
);
|
|
349
|
-
} else {
|
|
350
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
351
|
-
await client.resolveDisputeDetailed(BigInt(id), mapping[String(opts.outcome)], BigInt(opts.providerAward), BigInt(opts.clientAward));
|
|
352
|
-
}
|
|
353
|
-
console.log(' ' + c.success + c.white(` Resolved — agreement #${id}`));
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
dispute.command("owner-resolve <agreementId>")
|
|
357
|
-
.description("Owner-only: resolve a dispute directly in favor of provider or client. Requires DISPUTED or ESCALATED_TO_HUMAN status.")
|
|
358
|
-
.option("--favor-provider", "Resolve in favor of the provider (default: false = favor client)", false)
|
|
359
|
-
.action(async (agreementId, opts) => {
|
|
360
|
-
const config = loadConfig();
|
|
361
|
-
if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
|
|
362
|
-
const { signer } = await requireSigner(config);
|
|
363
|
-
printSenderInfo(config);
|
|
364
|
-
if (config.walletContractAddress) {
|
|
365
|
-
await executeContractWriteViaWallet(
|
|
366
|
-
config.walletContractAddress, signer, config.serviceAgreementAddress,
|
|
367
|
-
SERVICE_AGREEMENT_ABI, "ownerResolveDispute", [BigInt(agreementId), !!opts.favorProvider],
|
|
368
|
-
);
|
|
369
|
-
} else {
|
|
370
|
-
const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
|
|
371
|
-
await client.ownerResolveDispute(BigInt(agreementId), !!opts.favorProvider);
|
|
372
|
-
}
|
|
373
|
-
console.log(' ' + c.success + c.white(` Owner resolved — agreement #${agreementId}`));
|
|
374
|
-
});
|
|
375
|
-
}
|
package/src/commands/doctor.ts
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import * as fs from "fs";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import * as os from "os";
|
|
6
|
-
import { spawnSync } from "child_process";
|
|
7
|
-
import { configExists, loadConfig } from "../config.js";
|
|
8
|
-
import { c } from "../ui/colors.js";
|
|
9
|
-
|
|
10
|
-
const ARC402_DIR = path.join(os.homedir(), ".arc402");
|
|
11
|
-
const CONFIG_PATH = path.join(ARC402_DIR, "config.json");
|
|
12
|
-
const DAEMON_PID_PATH = path.join(ARC402_DIR, "daemon.pid");
|
|
13
|
-
|
|
14
|
-
function ok(label: string, detail?: string): void {
|
|
15
|
-
process.stdout.write(
|
|
16
|
-
" " + chalk.green("✓") + " " + chalk.white(label) + (detail ? chalk.dim(" " + detail) : "") + "\n"
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function fail(label: string, detail?: string): void {
|
|
21
|
-
process.stdout.write(
|
|
22
|
-
" " + chalk.red("✗") + " " + chalk.white(label) + (detail ? chalk.dim(" " + detail) : "") + "\n"
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function warn(label: string, detail?: string): void {
|
|
27
|
-
process.stdout.write(
|
|
28
|
-
" " + chalk.yellow("⚠") + " " + chalk.white(label) + (detail ? chalk.dim(" " + detail) : "") + "\n"
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function fetchJson(url: string, timeoutMs = 4000): Promise<unknown> {
|
|
33
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
34
|
-
return res.json();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function registerDoctorCommand(program: Command): void {
|
|
38
|
-
program
|
|
39
|
-
.command("doctor")
|
|
40
|
-
.description("Check ARC-402 environment health")
|
|
41
|
-
.action(async () => {
|
|
42
|
-
process.stdout.write("\n" + c.cyan("◈ ") + chalk.white("arc402 doctor") + "\n\n");
|
|
43
|
-
|
|
44
|
-
// ── 1. Config exists ───────────────────────────────────────────────────
|
|
45
|
-
if (configExists()) {
|
|
46
|
-
ok("Config found", CONFIG_PATH);
|
|
47
|
-
} else {
|
|
48
|
-
fail("Config not found", "Run: arc402 config init");
|
|
49
|
-
process.stdout.write("\n");
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const config = loadConfig();
|
|
54
|
-
|
|
55
|
-
// ── 2. RPC reachable ───────────────────────────────────────────────────
|
|
56
|
-
try {
|
|
57
|
-
const body = JSON.stringify({ jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1 });
|
|
58
|
-
const res = await fetch(config.rpcUrl, {
|
|
59
|
-
method: "POST",
|
|
60
|
-
headers: { "Content-Type": "application/json" },
|
|
61
|
-
body,
|
|
62
|
-
signal: AbortSignal.timeout(5000),
|
|
63
|
-
});
|
|
64
|
-
const json = await res.json() as { result?: string };
|
|
65
|
-
if (json.result) {
|
|
66
|
-
const block = parseInt(json.result, 16);
|
|
67
|
-
ok("RPC reachable", `block #${block.toLocaleString()}`);
|
|
68
|
-
} else {
|
|
69
|
-
fail("RPC returned no block", config.rpcUrl);
|
|
70
|
-
}
|
|
71
|
-
} catch (err) {
|
|
72
|
-
fail("RPC unreachable", config.rpcUrl + " — " + (err instanceof Error ? err.message : String(err)));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ── 3. Wallet deployed ─────────────────────────────────────────────────
|
|
76
|
-
if (config.walletContractAddress) {
|
|
77
|
-
try {
|
|
78
|
-
const body = JSON.stringify({
|
|
79
|
-
jsonrpc: "2.0",
|
|
80
|
-
method: "eth_getCode",
|
|
81
|
-
params: [config.walletContractAddress, "latest"],
|
|
82
|
-
id: 1,
|
|
83
|
-
});
|
|
84
|
-
const res = await fetch(config.rpcUrl, {
|
|
85
|
-
method: "POST",
|
|
86
|
-
headers: { "Content-Type": "application/json" },
|
|
87
|
-
body,
|
|
88
|
-
signal: AbortSignal.timeout(5000),
|
|
89
|
-
});
|
|
90
|
-
const json = await res.json() as { result?: string };
|
|
91
|
-
if (json.result && json.result !== "0x" && json.result.length > 2) {
|
|
92
|
-
ok("Wallet deployed", config.walletContractAddress);
|
|
93
|
-
} else {
|
|
94
|
-
fail("Wallet not deployed", config.walletContractAddress + " — no code at address");
|
|
95
|
-
}
|
|
96
|
-
} catch (err) {
|
|
97
|
-
warn("Wallet check failed", err instanceof Error ? err.message : String(err));
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
warn("Wallet not configured", "Run: arc402 wallet deploy");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ── 4. Machine key authorized ──────────────────────────────────────────
|
|
104
|
-
if (config.privateKey && config.walletContractAddress) {
|
|
105
|
-
try {
|
|
106
|
-
const { ethers } = await import("ethers");
|
|
107
|
-
const machineKey = new ethers.Wallet(config.privateKey);
|
|
108
|
-
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
109
|
-
const iface = new ethers.Interface(["function authorizedMachineKeys(address) external view returns (bool)"]);
|
|
110
|
-
const data = iface.encodeFunctionData("authorizedMachineKeys", [machineKey.address]);
|
|
111
|
-
const result = await Promise.race([
|
|
112
|
-
provider.call({ to: config.walletContractAddress, data }),
|
|
113
|
-
new Promise<never>((_, r) => setTimeout(() => r(new Error("timeout")), 4000)),
|
|
114
|
-
]);
|
|
115
|
-
const authorized = iface.decodeFunctionResult("authorizedMachineKeys", result)[0] as boolean;
|
|
116
|
-
if (authorized) {
|
|
117
|
-
ok("Machine key authorized", machineKey.address);
|
|
118
|
-
} else {
|
|
119
|
-
fail("Machine key not authorized", machineKey.address + " — run: arc402 wallet onboard");
|
|
120
|
-
}
|
|
121
|
-
} catch (err) {
|
|
122
|
-
warn("Machine key check failed", err instanceof Error ? err.message : String(err));
|
|
123
|
-
}
|
|
124
|
-
} else if (!config.privateKey) {
|
|
125
|
-
warn("Machine key not configured", "No privateKey in config");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ── 5. Daemon running ──────────────────────────────────────────────────
|
|
129
|
-
let daemonRunning = false;
|
|
130
|
-
if (fs.existsSync(DAEMON_PID_PATH)) {
|
|
131
|
-
try {
|
|
132
|
-
const pid = parseInt(fs.readFileSync(DAEMON_PID_PATH, "utf-8").trim(), 10);
|
|
133
|
-
// Check if process is alive
|
|
134
|
-
process.kill(pid, 0);
|
|
135
|
-
daemonRunning = true;
|
|
136
|
-
ok("Daemon running", `PID ${pid}`);
|
|
137
|
-
} catch {
|
|
138
|
-
fail("Daemon PID file stale", "Run: arc402 daemon start");
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
fail("Daemon not running", "Run: arc402 daemon start");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ── 6. Docker available ────────────────────────────────────────────────
|
|
145
|
-
try {
|
|
146
|
-
const docker = spawnSync("docker", ["--version"], { encoding: "utf-8", timeout: 3000 });
|
|
147
|
-
if (docker.status === 0) {
|
|
148
|
-
ok("Docker available", docker.stdout.trim().split("\n")[0]);
|
|
149
|
-
} else {
|
|
150
|
-
warn("Docker not found", "Install from https://docs.docker.com/get-docker/");
|
|
151
|
-
}
|
|
152
|
-
} catch {
|
|
153
|
-
warn("Docker not found", "Install from https://docs.docker.com/get-docker/");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ── 7. HTTP relay reachable (if daemon is running) ─────────────────────
|
|
157
|
-
if (daemonRunning) {
|
|
158
|
-
try {
|
|
159
|
-
const health = await fetchJson("http://localhost:4402/health") as { status?: string };
|
|
160
|
-
if (health.status === "online") {
|
|
161
|
-
ok("HTTP relay reachable", "http://localhost:4402");
|
|
162
|
-
} else {
|
|
163
|
-
warn("HTTP relay responded", JSON.stringify(health));
|
|
164
|
-
}
|
|
165
|
-
} catch {
|
|
166
|
-
fail("HTTP relay not reachable", "http://localhost:4402 — daemon may still be starting");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
process.stdout.write("\n");
|
|
171
|
-
});
|
|
172
|
-
}
|