@arc402/sdk 0.6.5 → 0.6.6
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/CHANGELOG.md +11 -0
- package/README.md +35 -3
- package/dist/daemon.d.ts +118 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +174 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/package.json +1 -1
- package/src/daemon.ts +280 -0
- package/src/index.ts +2 -0
- package/test/daemon-client.test.js +82 -0
- package/test/daemon.test.js +103 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.6] — 2026-04-02
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `DaemonClient` / `DaemonNodeClient` for the split ARC-402 node API (`/health`, `/auth/*`, `/wallet/status`, `/workroom/status`, `/agreements`).
|
|
7
|
+
- Local daemon URL helpers that infer `:4402` / `:4403` from `~/.arc402/daemon.toml`.
|
|
8
|
+
- Local daemon token loading from `~/.arc402/daemon.token` for authenticated node reads.
|
|
9
|
+
|
|
10
|
+
### Docs
|
|
11
|
+
- Clarified the split between the delivery plane (`DeliveryClient` on `:4402`) and the authenticated node API plane (`DaemonClient` on `:4403`).
|
|
12
|
+
- Synced the README to the current official node/daemon architecture and package release lane.
|
|
13
|
+
|
|
3
14
|
## [0.6.3] — 2026-03-25
|
|
4
15
|
|
|
5
16
|
### Synced
|
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@arc402/sdk)
|
|
4
4
|
|
|
5
|
-
Typed TypeScript SDK for the current ARC-402 network workflow: discovery, off-chain negotiation payloads, escrowed hiring, delivery verification, remediation, disputes, reputation signals, sponsorship, canonical capability taxonomy, governance reads, and
|
|
5
|
+
Typed TypeScript SDK for the current ARC-402 network workflow: discovery, off-chain negotiation payloads, escrowed hiring, delivery verification, remediation, disputes, reputation signals, sponsorship, canonical capability taxonomy, governance reads, and operator/node runtime reads.
|
|
6
6
|
|
|
7
|
-
Typed TypeScript SDK for the ARC-402 protocol on Base mainnet
|
|
7
|
+
Typed TypeScript SDK for the ARC-402 protocol on Base mainnet and the current node/daemon architecture.
|
|
8
8
|
|
|
9
9
|
> Launch-scope note: experimental ZK/privacy extensions are intentionally not part of the default SDK happy path. Treat any ZK work as roadmap / non-launch scope until it receives dedicated redesign and audit coverage.
|
|
10
10
|
|
|
@@ -23,7 +23,7 @@ openclaw install arc402-agent
|
|
|
23
23
|
|
|
24
24
|
The SDK is the programmatic surface. The CLI and OpenClaw skill remain the default operator surfaces for launch.
|
|
25
25
|
|
|
26
|
-
## What v0.
|
|
26
|
+
## What v0.6.6 adds
|
|
27
27
|
|
|
28
28
|
- typed remediation + dispute models aligned to `ServiceAgreement`
|
|
29
29
|
- `ReputationOracleClient`
|
|
@@ -32,6 +32,8 @@ The SDK is the programmatic surface. The CLI and OpenClaw skill remain the defau
|
|
|
32
32
|
- `GovernanceClient`
|
|
33
33
|
- heartbeat / operational trust reads on `AgentRegistry` (informational today, not strong ranking-grade truth)
|
|
34
34
|
- negotiation message helpers for Spec 14 payloads
|
|
35
|
+
- `DaemonClient` / `DaemonNodeClient` for the official split node runtime (`/health`, `/auth/*`, `/wallet/status`, `/workroom/status`, `/agreements`)
|
|
36
|
+
- daemon URL/token helpers that follow the host-node layout (`~/.arc402/daemon.toml`, `~/.arc402/daemon.token`)
|
|
35
37
|
|
|
36
38
|
## Operator model
|
|
37
39
|
|
|
@@ -42,6 +44,8 @@ The launch mental model is **operator-first**:
|
|
|
42
44
|
|
|
43
45
|
If you want that naming directly in code, the package now exports `ARC402OperatorClient` as an alias of `ARC402WalletClient`.
|
|
44
46
|
|
|
47
|
+
The active package publish lane is still the `packages/arc402-cli` + `packages/arc402-daemon` pair. This SDK stays in `reference/sdk`, but it now exposes the same local node API shape so programmatic integrations can harden against the official node/daemon runtime instead of shelling out to the CLI.
|
|
48
|
+
|
|
45
49
|
## Quick start
|
|
46
50
|
|
|
47
51
|
```ts
|
|
@@ -189,6 +193,34 @@ const outPath = await delivery.downloadDeliverable(42n, "report.pdf", "./downloa
|
|
|
189
193
|
|
|
190
194
|
The CLI shortcut: `arc402 deliver <id> --output <file>` uploads files to the delivery layer and submits the bundle hash on-chain in one step.
|
|
191
195
|
|
|
196
|
+
Use `DeliveryClient` for the delivery plane on `:4402`. Use `DaemonClient` / `DaemonNodeClient` for the authenticated split node API on `:4403`.
|
|
197
|
+
|
|
198
|
+
## Node / daemon runtime
|
|
199
|
+
|
|
200
|
+
For operator-facing local node reads and auth, use `DaemonClient`. This targets the split daemon API on `http://127.0.0.1:4403` by default, infers the API port from `~/.arc402/daemon.toml` when available, and reads `~/.arc402/daemon.token` automatically for authenticated endpoints.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { DaemonClient } from "@arc402/sdk";
|
|
204
|
+
|
|
205
|
+
const daemon = new DaemonClient();
|
|
206
|
+
|
|
207
|
+
const health = await daemon.getHealth();
|
|
208
|
+
const wallet = await daemon.getWalletStatus();
|
|
209
|
+
const workroom = await daemon.getWorkroomStatus();
|
|
210
|
+
const agreements = await daemon.listAgreements();
|
|
211
|
+
|
|
212
|
+
console.log({ health, wallet, workroom, agreements: agreements.agreements.length });
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
To create a remote owner session explicitly:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
const daemon = new DaemonClient({ baseUrl: "https://node.example.com" });
|
|
219
|
+
const challenge = await daemon.requestAuthChallenge("0xYourWallet");
|
|
220
|
+
const signature = await ownerSigner.signMessage(challenge.challenge);
|
|
221
|
+
const session = await daemon.createSession(challenge.challengeId, signature);
|
|
222
|
+
```
|
|
223
|
+
|
|
192
224
|
## Sponsorship + governance + operational context
|
|
193
225
|
|
|
194
226
|
```ts
|
package/dist/daemon.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export declare const DEFAULT_DAEMON_HTTP_URL = "http://127.0.0.1:4402";
|
|
2
|
+
export declare const DEFAULT_DAEMON_API_URL = "http://127.0.0.1:4403";
|
|
3
|
+
export declare const DEFAULT_DAEMON_TOKEN_PATH: string;
|
|
4
|
+
export declare const DEFAULT_DAEMON_CONFIG_PATH: string;
|
|
5
|
+
export interface DaemonHealthStatus {
|
|
6
|
+
ok: boolean;
|
|
7
|
+
wallet: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DaemonWalletStatus {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
wallet: string;
|
|
12
|
+
daemonId: string;
|
|
13
|
+
chainId: number;
|
|
14
|
+
rpcUrl: string;
|
|
15
|
+
policyEngineAddress: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DaemonWorkroomStatus {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
status: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DaemonAgreementRecord {
|
|
22
|
+
id: string;
|
|
23
|
+
agreement_id: string | null;
|
|
24
|
+
hirer_address: string;
|
|
25
|
+
capability: string;
|
|
26
|
+
price_eth: string;
|
|
27
|
+
deadline_unix: number;
|
|
28
|
+
spec_hash: string;
|
|
29
|
+
task_description: string | null;
|
|
30
|
+
status: string;
|
|
31
|
+
created_at: number;
|
|
32
|
+
updated_at: number;
|
|
33
|
+
reject_reason: string | null;
|
|
34
|
+
}
|
|
35
|
+
export interface DaemonAgreementsResponse {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
agreements: DaemonAgreementRecord[];
|
|
38
|
+
}
|
|
39
|
+
export interface AuthChallengeResponse {
|
|
40
|
+
challengeId: string;
|
|
41
|
+
challenge: string;
|
|
42
|
+
daemonId: string;
|
|
43
|
+
wallet: string;
|
|
44
|
+
chainId: number;
|
|
45
|
+
scope: string;
|
|
46
|
+
expiresAt: number;
|
|
47
|
+
issuedAt: number;
|
|
48
|
+
}
|
|
49
|
+
export interface AuthSessionResponse {
|
|
50
|
+
ok: true;
|
|
51
|
+
token: string;
|
|
52
|
+
wallets: string[];
|
|
53
|
+
wallet: string;
|
|
54
|
+
scope: string;
|
|
55
|
+
expiresAt: number;
|
|
56
|
+
}
|
|
57
|
+
export interface RevokeSessionsResponse {
|
|
58
|
+
ok: boolean;
|
|
59
|
+
revoked: string;
|
|
60
|
+
}
|
|
61
|
+
export interface DaemonNodeClientOptions {
|
|
62
|
+
/** Split daemon API base URL. Defaults to http://127.0.0.1:4403. */
|
|
63
|
+
apiUrl?: string;
|
|
64
|
+
/** Alias for apiUrl to match older docs/examples. */
|
|
65
|
+
baseUrl?: string;
|
|
66
|
+
/** Local daemon token. Defaults to ~/.arc402/daemon.token when present. */
|
|
67
|
+
token?: string;
|
|
68
|
+
/** Explicit path to daemon.token if you don't want the default. */
|
|
69
|
+
tokenPath?: string;
|
|
70
|
+
/** Explicit path to daemon.toml if you want port inference from a non-default location. */
|
|
71
|
+
configPath?: string;
|
|
72
|
+
/** Custom fetch implementation for testing or non-standard runtimes. */
|
|
73
|
+
fetchImpl?: typeof fetch;
|
|
74
|
+
}
|
|
75
|
+
export declare class DaemonClientError extends Error {
|
|
76
|
+
readonly statusCode?: number;
|
|
77
|
+
readonly details?: unknown;
|
|
78
|
+
constructor(message: string, statusCode?: number, details?: unknown);
|
|
79
|
+
}
|
|
80
|
+
export declare function loadLocalDaemonToken(tokenPath?: string): string | undefined;
|
|
81
|
+
export declare function resolveDaemonHttpBaseUrl(configPath?: string): string;
|
|
82
|
+
export declare function resolveDaemonApiBaseUrl(options?: Pick<DaemonNodeClientOptions, "apiUrl" | "baseUrl" | "configPath">): string;
|
|
83
|
+
/**
|
|
84
|
+
* Thin typed client for the split ARC-402 node API (`arc402-api`).
|
|
85
|
+
*
|
|
86
|
+
* This surface is for operator-side reads and auth against the host daemon/node process:
|
|
87
|
+
* - `/health`
|
|
88
|
+
* - `/auth/challenge`
|
|
89
|
+
* - `/auth/session`
|
|
90
|
+
* - `/auth/revoke`
|
|
91
|
+
* - `/wallet/status`
|
|
92
|
+
* - `/workroom/status`
|
|
93
|
+
* - `/agreements`
|
|
94
|
+
*
|
|
95
|
+
* The delivery plane still lives on the host daemon HTTP port (usually `:4402`)
|
|
96
|
+
* and is wrapped separately by `DeliveryClient`.
|
|
97
|
+
*/
|
|
98
|
+
export declare class DaemonNodeClient {
|
|
99
|
+
readonly apiUrl: string;
|
|
100
|
+
private readonly options;
|
|
101
|
+
private readonly fetchImpl;
|
|
102
|
+
constructor(options?: DaemonNodeClientOptions);
|
|
103
|
+
private request;
|
|
104
|
+
getHealth(): Promise<DaemonHealthStatus>;
|
|
105
|
+
requestAuthChallenge(wallet: string, requestedScope?: string): Promise<AuthChallengeResponse>;
|
|
106
|
+
health(): Promise<DaemonHealthStatus>;
|
|
107
|
+
createSession(challengeId: string, signature: string): Promise<AuthSessionResponse>;
|
|
108
|
+
revokeSessions(): Promise<RevokeSessionsResponse>;
|
|
109
|
+
revokeSession(): Promise<RevokeSessionsResponse>;
|
|
110
|
+
getWalletStatus(): Promise<DaemonWalletStatus>;
|
|
111
|
+
walletStatus(): Promise<DaemonWalletStatus>;
|
|
112
|
+
getWorkroomStatus(): Promise<DaemonWorkroomStatus>;
|
|
113
|
+
workroomStatus(): Promise<DaemonWorkroomStatus>;
|
|
114
|
+
listAgreements(): Promise<DaemonAgreementsResponse>;
|
|
115
|
+
agreements(): Promise<DaemonAgreementsResponse>;
|
|
116
|
+
}
|
|
117
|
+
export { DaemonNodeClient as DaemonClient };
|
|
118
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DaemonClient = exports.DaemonNodeClient = exports.DaemonClientError = exports.DEFAULT_DAEMON_CONFIG_PATH = exports.DEFAULT_DAEMON_TOKEN_PATH = exports.DEFAULT_DAEMON_API_URL = exports.DEFAULT_DAEMON_HTTP_URL = void 0;
|
|
7
|
+
exports.loadLocalDaemonToken = loadLocalDaemonToken;
|
|
8
|
+
exports.resolveDaemonHttpBaseUrl = resolveDaemonHttpBaseUrl;
|
|
9
|
+
exports.resolveDaemonApiBaseUrl = resolveDaemonApiBaseUrl;
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
exports.DEFAULT_DAEMON_HTTP_URL = "http://127.0.0.1:4402";
|
|
14
|
+
exports.DEFAULT_DAEMON_API_URL = "http://127.0.0.1:4403";
|
|
15
|
+
exports.DEFAULT_DAEMON_TOKEN_PATH = path_1.default.join(os_1.default.homedir(), ".arc402", "daemon.token");
|
|
16
|
+
exports.DEFAULT_DAEMON_CONFIG_PATH = path_1.default.join(os_1.default.homedir(), ".arc402", "daemon.toml");
|
|
17
|
+
class DaemonClientError extends Error {
|
|
18
|
+
constructor(message, statusCode, details) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.statusCode = statusCode;
|
|
21
|
+
this.details = details;
|
|
22
|
+
this.name = "DaemonClientError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.DaemonClientError = DaemonClientError;
|
|
26
|
+
function trimTrailingSlash(input) {
|
|
27
|
+
return input.replace(/\/$/, "");
|
|
28
|
+
}
|
|
29
|
+
function readTomlInteger(contents, key) {
|
|
30
|
+
const match = contents.match(new RegExp(`^\\s*${key}\\s*=\\s*(\\d+)\\s*$`, "m"));
|
|
31
|
+
if (!match)
|
|
32
|
+
return undefined;
|
|
33
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
34
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
35
|
+
}
|
|
36
|
+
function loadLocalDaemonToken(tokenPath = exports.DEFAULT_DAEMON_TOKEN_PATH) {
|
|
37
|
+
try {
|
|
38
|
+
const token = (0, fs_1.readFileSync)(tokenPath, "utf-8").trim();
|
|
39
|
+
return token.length > 0 ? token : undefined;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function resolveDaemonHttpBaseUrl(configPath = exports.DEFAULT_DAEMON_CONFIG_PATH) {
|
|
46
|
+
try {
|
|
47
|
+
if ((0, fs_1.existsSync)(configPath)) {
|
|
48
|
+
const config = (0, fs_1.readFileSync)(configPath, "utf-8");
|
|
49
|
+
const port = readTomlInteger(config, "listen_port") ?? 4402;
|
|
50
|
+
return `http://127.0.0.1:${port}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Fall through to default.
|
|
55
|
+
}
|
|
56
|
+
return exports.DEFAULT_DAEMON_HTTP_URL;
|
|
57
|
+
}
|
|
58
|
+
function resolveDaemonApiBaseUrl(options = {}) {
|
|
59
|
+
const explicit = options.apiUrl ?? options.baseUrl;
|
|
60
|
+
if (explicit && explicit.trim().length > 0) {
|
|
61
|
+
return trimTrailingSlash(explicit.trim());
|
|
62
|
+
}
|
|
63
|
+
const httpBase = resolveDaemonHttpBaseUrl(options.configPath ?? exports.DEFAULT_DAEMON_CONFIG_PATH);
|
|
64
|
+
try {
|
|
65
|
+
const url = new URL(httpBase);
|
|
66
|
+
const httpPort = url.port ? Number.parseInt(url.port, 10) : (url.protocol === "https:" ? 443 : 80);
|
|
67
|
+
if (Number.isFinite(httpPort)) {
|
|
68
|
+
url.port = String(httpPort + 1);
|
|
69
|
+
return trimTrailingSlash(url.toString());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Fall through to default.
|
|
74
|
+
}
|
|
75
|
+
return exports.DEFAULT_DAEMON_API_URL;
|
|
76
|
+
}
|
|
77
|
+
function safeJsonParse(value) {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(value);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Thin typed client for the split ARC-402 node API (`arc402-api`).
|
|
87
|
+
*
|
|
88
|
+
* This surface is for operator-side reads and auth against the host daemon/node process:
|
|
89
|
+
* - `/health`
|
|
90
|
+
* - `/auth/challenge`
|
|
91
|
+
* - `/auth/session`
|
|
92
|
+
* - `/auth/revoke`
|
|
93
|
+
* - `/wallet/status`
|
|
94
|
+
* - `/workroom/status`
|
|
95
|
+
* - `/agreements`
|
|
96
|
+
*
|
|
97
|
+
* The delivery plane still lives on the host daemon HTTP port (usually `:4402`)
|
|
98
|
+
* and is wrapped separately by `DeliveryClient`.
|
|
99
|
+
*/
|
|
100
|
+
class DaemonNodeClient {
|
|
101
|
+
constructor(options = {}) {
|
|
102
|
+
this.options = options;
|
|
103
|
+
this.apiUrl = resolveDaemonApiBaseUrl(options);
|
|
104
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
105
|
+
}
|
|
106
|
+
async request(method, urlPath, body, useSession = false) {
|
|
107
|
+
const token = this.options.token ?? loadLocalDaemonToken(this.options.tokenPath ?? exports.DEFAULT_DAEMON_TOKEN_PATH);
|
|
108
|
+
const headers = {};
|
|
109
|
+
if (body !== undefined) {
|
|
110
|
+
headers["Content-Type"] = "application/json";
|
|
111
|
+
}
|
|
112
|
+
if (useSession) {
|
|
113
|
+
if (!token) {
|
|
114
|
+
throw new DaemonClientError("No daemon session token available");
|
|
115
|
+
}
|
|
116
|
+
headers.Authorization = `Bearer ${token}`;
|
|
117
|
+
}
|
|
118
|
+
const response = await this.fetchImpl(`${this.apiUrl}${urlPath}`, {
|
|
119
|
+
method,
|
|
120
|
+
headers,
|
|
121
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
122
|
+
});
|
|
123
|
+
const text = await response.text();
|
|
124
|
+
const payload = text.length > 0 ? safeJsonParse(text) : undefined;
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
const message = typeof payload === "object" &&
|
|
127
|
+
payload !== null &&
|
|
128
|
+
"error" in payload &&
|
|
129
|
+
typeof payload.error === "string"
|
|
130
|
+
? payload.error
|
|
131
|
+
: text || `Daemon request failed (${response.status})`;
|
|
132
|
+
throw new DaemonClientError(message, response.status, payload ?? text);
|
|
133
|
+
}
|
|
134
|
+
return (payload ?? {});
|
|
135
|
+
}
|
|
136
|
+
async getHealth() {
|
|
137
|
+
return this.request("GET", "/health");
|
|
138
|
+
}
|
|
139
|
+
async requestAuthChallenge(wallet, requestedScope = "operator") {
|
|
140
|
+
return this.request("POST", "/auth/challenge", { wallet, requestedScope });
|
|
141
|
+
}
|
|
142
|
+
async health() {
|
|
143
|
+
return this.getHealth();
|
|
144
|
+
}
|
|
145
|
+
async createSession(challengeId, signature) {
|
|
146
|
+
return this.request("POST", "/auth/session", { challengeId, signature });
|
|
147
|
+
}
|
|
148
|
+
async revokeSessions() {
|
|
149
|
+
return this.request("POST", "/auth/revoke", undefined, true);
|
|
150
|
+
}
|
|
151
|
+
async revokeSession() {
|
|
152
|
+
return this.revokeSessions();
|
|
153
|
+
}
|
|
154
|
+
async getWalletStatus() {
|
|
155
|
+
return this.request("GET", "/wallet/status", undefined, true);
|
|
156
|
+
}
|
|
157
|
+
async walletStatus() {
|
|
158
|
+
return this.getWalletStatus();
|
|
159
|
+
}
|
|
160
|
+
async getWorkroomStatus() {
|
|
161
|
+
return this.request("GET", "/workroom/status", undefined, true);
|
|
162
|
+
}
|
|
163
|
+
async workroomStatus() {
|
|
164
|
+
return this.getWorkroomStatus();
|
|
165
|
+
}
|
|
166
|
+
async listAgreements() {
|
|
167
|
+
return this.request("GET", "/agreements", undefined, true);
|
|
168
|
+
}
|
|
169
|
+
async agreements() {
|
|
170
|
+
return this.listAgreements();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.DaemonNodeClient = DaemonNodeClient;
|
|
174
|
+
exports.DaemonClient = DaemonNodeClient;
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export { resolveEndpoint, notifyEndpoint, notifyHire, notifyHandshake, notifyHir
|
|
|
37
37
|
export type { EndpointNotifyResult } from "./endpoint";
|
|
38
38
|
export { DeliveryClient, DEFAULT_DAEMON_URL } from "./delivery";
|
|
39
39
|
export type { DeliveryFile, DeliveryManifest, DeliveryClientOptions } from "./delivery";
|
|
40
|
+
export { DaemonClient, DaemonNodeClient, DaemonClientError, DEFAULT_DAEMON_HTTP_URL, DEFAULT_DAEMON_API_URL, DEFAULT_DAEMON_TOKEN_PATH, DEFAULT_DAEMON_CONFIG_PATH, resolveDaemonHttpBaseUrl, resolveDaemonApiBaseUrl, loadLocalDaemonToken } from "./daemon";
|
|
41
|
+
export type { DaemonNodeClientOptions, DaemonHealthStatus, DaemonWalletStatus, DaemonWorkroomStatus, DaemonAgreementRecord, DaemonAgreementsResponse, AuthChallengeResponse, AuthSessionResponse, RevokeSessionsResponse } from "./daemon";
|
|
40
42
|
export declare const COMPUTE_AGREEMENT_ADDRESS = "0xf898A8A2cF9900A588B174d9f96349BBA95e57F3";
|
|
41
43
|
export declare const SUBSCRIPTION_AGREEMENT_ADDRESS = "0x809c1D997Eab3531Eb2d01FCD5120Ac786D850D6";
|
|
42
44
|
export declare const ARC402_REGISTRY_V2_ADDRESS = "0xcc0D8731ccCf6CFfF4e66F6d68cA86330Ea8B622";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.AGENT_METADATA_SCHEMA = exports.uploadMetadata = exports.decodeMetadata = exports.encodeMetadata = exports.validateMetadata = exports.buildMetadata = exports.uploadEncryptedIPFS = exports.decryptDeliverable = exports.encryptDeliverable = exports.uploadToIPFS = exports.hashDeliverableFile = exports.hashDeliverable = exports.DeliverableType = exports.DeliverableClient = exports.WatchtowerClient = exports.AgreementTreeClient = exports.ChannelClient = exports.SessionManager = exports.NegotiationGuard = exports.parseNegotiationMessage = exports.createNegotiationReject = exports.createNegotiationAccept = exports.createNegotiationCounter = exports.createNegotiationProposal = exports.createSignedReject = exports.createSignedAccept = exports.createSignedCounter = exports.createSignedProposal = exports.signNegotiationMessage = exports.GovernanceClient = exports.CapabilityRegistryClient = exports.SponsorshipAttestationClient = exports.ReputationOracleClient = exports.DisputeArbitrationClient = exports.ServiceAgreementClient = exports.AgentRegistryClient = exports.MultiAgentSettlement = exports.SettlementClient = exports.IntentAttestation = exports.IntentAttestationClient = exports.TrustPrimitive = exports.TrustClient = exports.PolicyValidator = exports.PolicyObject = exports.PolicyClient = exports.ContractInteractionClient = exports.ARC402Operator = exports.ARC402OperatorClient = exports.ARC402Wallet = exports.ARC402WalletClient = void 0;
|
|
18
|
-
exports.ARC402_REGISTRY_V3_ADDRESS = exports.ARC402_REGISTRY_V2_ADDRESS = exports.SUBSCRIPTION_AGREEMENT_ADDRESS = exports.COMPUTE_AGREEMENT_ADDRESS = exports.DEFAULT_DAEMON_URL = exports.DeliveryClient = exports.DEFAULT_REGISTRY_ADDRESS = exports.notifyMessage = exports.notifyDispute = exports.notifyDeliveryAccepted = exports.notifyDelivery = exports.notifyHireAccepted = exports.notifyHandshake = exports.notifyHire = exports.notifyEndpoint = exports.resolveEndpoint = exports.ARENA_ADDRESSES = exports.ArenaClient = exports.MigrationClient = exports.ComputeAgreementClient = exports.ColdStartClient = void 0;
|
|
18
|
+
exports.ARC402_REGISTRY_V3_ADDRESS = exports.ARC402_REGISTRY_V2_ADDRESS = exports.SUBSCRIPTION_AGREEMENT_ADDRESS = exports.COMPUTE_AGREEMENT_ADDRESS = exports.loadLocalDaemonToken = exports.resolveDaemonApiBaseUrl = exports.resolveDaemonHttpBaseUrl = exports.DEFAULT_DAEMON_CONFIG_PATH = exports.DEFAULT_DAEMON_TOKEN_PATH = exports.DEFAULT_DAEMON_API_URL = exports.DEFAULT_DAEMON_HTTP_URL = exports.DaemonClientError = exports.DaemonNodeClient = exports.DaemonClient = exports.DEFAULT_DAEMON_URL = exports.DeliveryClient = exports.DEFAULT_REGISTRY_ADDRESS = exports.notifyMessage = exports.notifyDispute = exports.notifyDeliveryAccepted = exports.notifyDelivery = exports.notifyHireAccepted = exports.notifyHandshake = exports.notifyHire = exports.notifyEndpoint = exports.resolveEndpoint = exports.ARENA_ADDRESSES = exports.ArenaClient = exports.MigrationClient = exports.ComputeAgreementClient = exports.ColdStartClient = void 0;
|
|
19
19
|
var wallet_1 = require("./wallet");
|
|
20
20
|
Object.defineProperty(exports, "ARC402WalletClient", { enumerable: true, get: function () { return wallet_1.ARC402WalletClient; } });
|
|
21
21
|
Object.defineProperty(exports, "ARC402Wallet", { enumerable: true, get: function () { return wallet_1.ARC402Wallet; } });
|
|
@@ -113,6 +113,17 @@ Object.defineProperty(exports, "DEFAULT_REGISTRY_ADDRESS", { enumerable: true, g
|
|
|
113
113
|
var delivery_1 = require("./delivery");
|
|
114
114
|
Object.defineProperty(exports, "DeliveryClient", { enumerable: true, get: function () { return delivery_1.DeliveryClient; } });
|
|
115
115
|
Object.defineProperty(exports, "DEFAULT_DAEMON_URL", { enumerable: true, get: function () { return delivery_1.DEFAULT_DAEMON_URL; } });
|
|
116
|
+
var daemon_1 = require("./daemon");
|
|
117
|
+
Object.defineProperty(exports, "DaemonClient", { enumerable: true, get: function () { return daemon_1.DaemonClient; } });
|
|
118
|
+
Object.defineProperty(exports, "DaemonNodeClient", { enumerable: true, get: function () { return daemon_1.DaemonNodeClient; } });
|
|
119
|
+
Object.defineProperty(exports, "DaemonClientError", { enumerable: true, get: function () { return daemon_1.DaemonClientError; } });
|
|
120
|
+
Object.defineProperty(exports, "DEFAULT_DAEMON_HTTP_URL", { enumerable: true, get: function () { return daemon_1.DEFAULT_DAEMON_HTTP_URL; } });
|
|
121
|
+
Object.defineProperty(exports, "DEFAULT_DAEMON_API_URL", { enumerable: true, get: function () { return daemon_1.DEFAULT_DAEMON_API_URL; } });
|
|
122
|
+
Object.defineProperty(exports, "DEFAULT_DAEMON_TOKEN_PATH", { enumerable: true, get: function () { return daemon_1.DEFAULT_DAEMON_TOKEN_PATH; } });
|
|
123
|
+
Object.defineProperty(exports, "DEFAULT_DAEMON_CONFIG_PATH", { enumerable: true, get: function () { return daemon_1.DEFAULT_DAEMON_CONFIG_PATH; } });
|
|
124
|
+
Object.defineProperty(exports, "resolveDaemonHttpBaseUrl", { enumerable: true, get: function () { return daemon_1.resolveDaemonHttpBaseUrl; } });
|
|
125
|
+
Object.defineProperty(exports, "resolveDaemonApiBaseUrl", { enumerable: true, get: function () { return daemon_1.resolveDaemonApiBaseUrl; } });
|
|
126
|
+
Object.defineProperty(exports, "loadLocalDaemonToken", { enumerable: true, get: function () { return daemon_1.loadLocalDaemonToken; } });
|
|
116
127
|
// Base Mainnet contract addresses
|
|
117
128
|
exports.COMPUTE_AGREEMENT_ADDRESS = "0xf898A8A2cF9900A588B174d9f96349BBA95e57F3";
|
|
118
129
|
exports.SUBSCRIPTION_AGREEMENT_ADDRESS = "0x809c1D997Eab3531Eb2d01FCD5120Ac786D850D6";
|
package/package.json
CHANGED
package/src/daemon.ts
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_DAEMON_HTTP_URL = "http://127.0.0.1:4402";
|
|
6
|
+
export const DEFAULT_DAEMON_API_URL = "http://127.0.0.1:4403";
|
|
7
|
+
export const DEFAULT_DAEMON_TOKEN_PATH = path.join(os.homedir(), ".arc402", "daemon.token");
|
|
8
|
+
export const DEFAULT_DAEMON_CONFIG_PATH = path.join(os.homedir(), ".arc402", "daemon.toml");
|
|
9
|
+
|
|
10
|
+
export interface DaemonHealthStatus {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
wallet: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DaemonWalletStatus {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
wallet: string;
|
|
18
|
+
daemonId: string;
|
|
19
|
+
chainId: number;
|
|
20
|
+
rpcUrl: string;
|
|
21
|
+
policyEngineAddress: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DaemonWorkroomStatus {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
status: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DaemonAgreementRecord {
|
|
30
|
+
id: string;
|
|
31
|
+
agreement_id: string | null;
|
|
32
|
+
hirer_address: string;
|
|
33
|
+
capability: string;
|
|
34
|
+
price_eth: string;
|
|
35
|
+
deadline_unix: number;
|
|
36
|
+
spec_hash: string;
|
|
37
|
+
task_description: string | null;
|
|
38
|
+
status: string;
|
|
39
|
+
created_at: number;
|
|
40
|
+
updated_at: number;
|
|
41
|
+
reject_reason: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DaemonAgreementsResponse {
|
|
45
|
+
ok: boolean;
|
|
46
|
+
agreements: DaemonAgreementRecord[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AuthChallengeResponse {
|
|
50
|
+
challengeId: string;
|
|
51
|
+
challenge: string;
|
|
52
|
+
daemonId: string;
|
|
53
|
+
wallet: string;
|
|
54
|
+
chainId: number;
|
|
55
|
+
scope: string;
|
|
56
|
+
expiresAt: number;
|
|
57
|
+
issuedAt: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface AuthSessionResponse {
|
|
61
|
+
ok: true;
|
|
62
|
+
token: string;
|
|
63
|
+
wallets: string[];
|
|
64
|
+
wallet: string;
|
|
65
|
+
scope: string;
|
|
66
|
+
expiresAt: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface RevokeSessionsResponse {
|
|
70
|
+
ok: boolean;
|
|
71
|
+
revoked: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface DaemonNodeClientOptions {
|
|
75
|
+
/** Split daemon API base URL. Defaults to http://127.0.0.1:4403. */
|
|
76
|
+
apiUrl?: string;
|
|
77
|
+
/** Alias for apiUrl to match older docs/examples. */
|
|
78
|
+
baseUrl?: string;
|
|
79
|
+
/** Local daemon token. Defaults to ~/.arc402/daemon.token when present. */
|
|
80
|
+
token?: string;
|
|
81
|
+
/** Explicit path to daemon.token if you don't want the default. */
|
|
82
|
+
tokenPath?: string;
|
|
83
|
+
/** Explicit path to daemon.toml if you want port inference from a non-default location. */
|
|
84
|
+
configPath?: string;
|
|
85
|
+
/** Custom fetch implementation for testing or non-standard runtimes. */
|
|
86
|
+
fetchImpl?: typeof fetch;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class DaemonClientError extends Error {
|
|
90
|
+
constructor(
|
|
91
|
+
message: string,
|
|
92
|
+
public readonly statusCode?: number,
|
|
93
|
+
public readonly details?: unknown,
|
|
94
|
+
) {
|
|
95
|
+
super(message);
|
|
96
|
+
this.name = "DaemonClientError";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function trimTrailingSlash(input: string): string {
|
|
101
|
+
return input.replace(/\/$/, "");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readTomlInteger(contents: string, key: string): number | undefined {
|
|
105
|
+
const match = contents.match(new RegExp(`^\\s*${key}\\s*=\\s*(\\d+)\\s*$`, "m"));
|
|
106
|
+
if (!match) return undefined;
|
|
107
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
108
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function loadLocalDaemonToken(tokenPath = DEFAULT_DAEMON_TOKEN_PATH): string | undefined {
|
|
112
|
+
try {
|
|
113
|
+
const token = readFileSync(tokenPath, "utf-8").trim();
|
|
114
|
+
return token.length > 0 ? token : undefined;
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function resolveDaemonHttpBaseUrl(configPath = DEFAULT_DAEMON_CONFIG_PATH): string {
|
|
121
|
+
try {
|
|
122
|
+
if (existsSync(configPath)) {
|
|
123
|
+
const config = readFileSync(configPath, "utf-8");
|
|
124
|
+
const port = readTomlInteger(config, "listen_port") ?? 4402;
|
|
125
|
+
return `http://127.0.0.1:${port}`;
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Fall through to default.
|
|
129
|
+
}
|
|
130
|
+
return DEFAULT_DAEMON_HTTP_URL;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function resolveDaemonApiBaseUrl(options: Pick<DaemonNodeClientOptions, "apiUrl" | "baseUrl" | "configPath"> = {}): string {
|
|
134
|
+
const explicit = options.apiUrl ?? options.baseUrl;
|
|
135
|
+
if (explicit && explicit.trim().length > 0) {
|
|
136
|
+
return trimTrailingSlash(explicit.trim());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const httpBase = resolveDaemonHttpBaseUrl(options.configPath ?? DEFAULT_DAEMON_CONFIG_PATH);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const url = new URL(httpBase);
|
|
143
|
+
const httpPort = url.port ? Number.parseInt(url.port, 10) : (url.protocol === "https:" ? 443 : 80);
|
|
144
|
+
if (Number.isFinite(httpPort)) {
|
|
145
|
+
url.port = String(httpPort + 1);
|
|
146
|
+
return trimTrailingSlash(url.toString());
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// Fall through to default.
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return DEFAULT_DAEMON_API_URL;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function safeJsonParse(value: string): unknown {
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(value);
|
|
158
|
+
} catch {
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Thin typed client for the split ARC-402 node API (`arc402-api`).
|
|
165
|
+
*
|
|
166
|
+
* This surface is for operator-side reads and auth against the host daemon/node process:
|
|
167
|
+
* - `/health`
|
|
168
|
+
* - `/auth/challenge`
|
|
169
|
+
* - `/auth/session`
|
|
170
|
+
* - `/auth/revoke`
|
|
171
|
+
* - `/wallet/status`
|
|
172
|
+
* - `/workroom/status`
|
|
173
|
+
* - `/agreements`
|
|
174
|
+
*
|
|
175
|
+
* The delivery plane still lives on the host daemon HTTP port (usually `:4402`)
|
|
176
|
+
* and is wrapped separately by `DeliveryClient`.
|
|
177
|
+
*/
|
|
178
|
+
export class DaemonNodeClient {
|
|
179
|
+
readonly apiUrl: string;
|
|
180
|
+
private readonly options: DaemonNodeClientOptions;
|
|
181
|
+
private readonly fetchImpl: typeof fetch;
|
|
182
|
+
|
|
183
|
+
constructor(options: DaemonNodeClientOptions = {}) {
|
|
184
|
+
this.options = options;
|
|
185
|
+
this.apiUrl = resolveDaemonApiBaseUrl(options);
|
|
186
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async request<T>(
|
|
190
|
+
method: "GET" | "POST",
|
|
191
|
+
urlPath: string,
|
|
192
|
+
body?: unknown,
|
|
193
|
+
useSession = false,
|
|
194
|
+
): Promise<T> {
|
|
195
|
+
const token = this.options.token ?? loadLocalDaemonToken(this.options.tokenPath ?? DEFAULT_DAEMON_TOKEN_PATH);
|
|
196
|
+
const headers: Record<string, string> = {};
|
|
197
|
+
|
|
198
|
+
if (body !== undefined) {
|
|
199
|
+
headers["Content-Type"] = "application/json";
|
|
200
|
+
}
|
|
201
|
+
if (useSession) {
|
|
202
|
+
if (!token) {
|
|
203
|
+
throw new DaemonClientError("No daemon session token available");
|
|
204
|
+
}
|
|
205
|
+
headers.Authorization = `Bearer ${token}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const response = await this.fetchImpl(`${this.apiUrl}${urlPath}`, {
|
|
209
|
+
method,
|
|
210
|
+
headers,
|
|
211
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const text = await response.text();
|
|
215
|
+
const payload = text.length > 0 ? safeJsonParse(text) : undefined;
|
|
216
|
+
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
const message =
|
|
219
|
+
typeof payload === "object" &&
|
|
220
|
+
payload !== null &&
|
|
221
|
+
"error" in payload &&
|
|
222
|
+
typeof (payload as { error?: unknown }).error === "string"
|
|
223
|
+
? (payload as { error: string }).error
|
|
224
|
+
: text || `Daemon request failed (${response.status})`;
|
|
225
|
+
throw new DaemonClientError(message, response.status, payload ?? text);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return (payload ?? {}) as T;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getHealth(): Promise<DaemonHealthStatus> {
|
|
232
|
+
return this.request<DaemonHealthStatus>("GET", "/health");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async requestAuthChallenge(wallet: string, requestedScope = "operator"): Promise<AuthChallengeResponse> {
|
|
236
|
+
return this.request<AuthChallengeResponse>("POST", "/auth/challenge", { wallet, requestedScope });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async health(): Promise<DaemonHealthStatus> {
|
|
240
|
+
return this.getHealth();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async createSession(challengeId: string, signature: string): Promise<AuthSessionResponse> {
|
|
244
|
+
return this.request<AuthSessionResponse>("POST", "/auth/session", { challengeId, signature });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async revokeSessions(): Promise<RevokeSessionsResponse> {
|
|
248
|
+
return this.request<RevokeSessionsResponse>("POST", "/auth/revoke", undefined, true);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async revokeSession(): Promise<RevokeSessionsResponse> {
|
|
252
|
+
return this.revokeSessions();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async getWalletStatus(): Promise<DaemonWalletStatus> {
|
|
256
|
+
return this.request<DaemonWalletStatus>("GET", "/wallet/status", undefined, true);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async walletStatus(): Promise<DaemonWalletStatus> {
|
|
260
|
+
return this.getWalletStatus();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async getWorkroomStatus(): Promise<DaemonWorkroomStatus> {
|
|
264
|
+
return this.request<DaemonWorkroomStatus>("GET", "/workroom/status", undefined, true);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async workroomStatus(): Promise<DaemonWorkroomStatus> {
|
|
268
|
+
return this.getWorkroomStatus();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async listAgreements(): Promise<DaemonAgreementsResponse> {
|
|
272
|
+
return this.request<DaemonAgreementsResponse>("GET", "/agreements", undefined, true);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async agreements(): Promise<DaemonAgreementsResponse> {
|
|
276
|
+
return this.listAgreements();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export { DaemonNodeClient as DaemonClient };
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,8 @@ export { resolveEndpoint, notifyEndpoint, notifyHire, notifyHandshake, notifyHir
|
|
|
37
37
|
export type { EndpointNotifyResult } from "./endpoint";
|
|
38
38
|
export { DeliveryClient, DEFAULT_DAEMON_URL } from "./delivery";
|
|
39
39
|
export type { DeliveryFile, DeliveryManifest, DeliveryClientOptions } from "./delivery";
|
|
40
|
+
export { DaemonClient, DaemonNodeClient, DaemonClientError, DEFAULT_DAEMON_HTTP_URL, DEFAULT_DAEMON_API_URL, DEFAULT_DAEMON_TOKEN_PATH, DEFAULT_DAEMON_CONFIG_PATH, resolveDaemonHttpBaseUrl, resolveDaemonApiBaseUrl, loadLocalDaemonToken } from "./daemon";
|
|
41
|
+
export type { DaemonNodeClientOptions, DaemonHealthStatus, DaemonWalletStatus, DaemonWorkroomStatus, DaemonAgreementRecord, DaemonAgreementsResponse, AuthChallengeResponse, AuthSessionResponse, RevokeSessionsResponse } from "./daemon";
|
|
40
42
|
|
|
41
43
|
// Base Mainnet contract addresses
|
|
42
44
|
export const COMPUTE_AGREEMENT_ADDRESS = "0xf898A8A2cF9900A588B174d9f96349BBA95e57F3";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
DaemonClient,
|
|
9
|
+
DaemonNodeClient,
|
|
10
|
+
loadLocalDaemonToken,
|
|
11
|
+
resolveDaemonHttpBaseUrl,
|
|
12
|
+
resolveDaemonApiBaseUrl,
|
|
13
|
+
DEFAULT_DAEMON_HTTP_URL,
|
|
14
|
+
DEFAULT_DAEMON_API_URL,
|
|
15
|
+
} = require('../dist');
|
|
16
|
+
|
|
17
|
+
test('daemon URL helpers infer split ports from daemon.toml', () => {
|
|
18
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'arc402-sdk-daemon-'));
|
|
19
|
+
const configPath = path.join(tmp, 'daemon.toml');
|
|
20
|
+
fs.writeFileSync(configPath, '[relay]\nlisten_port = 5510\n');
|
|
21
|
+
|
|
22
|
+
assert.equal(resolveDaemonHttpBaseUrl(configPath), 'http://127.0.0.1:5510');
|
|
23
|
+
assert.equal(resolveDaemonApiBaseUrl({ configPath }), 'http://127.0.0.1:5511');
|
|
24
|
+
assert.equal(resolveDaemonApiBaseUrl({ baseUrl: 'https://node.example.com/' }), 'https://node.example.com');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('daemon token loader trims file contents and tolerates missing file', () => {
|
|
28
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'arc402-sdk-token-'));
|
|
29
|
+
const tokenPath = path.join(tmp, 'daemon.token');
|
|
30
|
+
fs.writeFileSync(tokenPath, 'secret-token\n');
|
|
31
|
+
|
|
32
|
+
assert.equal(loadLocalDaemonToken(tokenPath), 'secret-token');
|
|
33
|
+
assert.equal(loadLocalDaemonToken(path.join(tmp, 'missing.token')), undefined);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('daemon client reads split API endpoints and keeps compatibility aliases', async () => {
|
|
37
|
+
const calls = [];
|
|
38
|
+
const originalFetch = global.fetch;
|
|
39
|
+
global.fetch = async (url, init = {}) => {
|
|
40
|
+
calls.push({ url, init });
|
|
41
|
+
const pathname = new URL(url).pathname;
|
|
42
|
+
const payloads = {
|
|
43
|
+
'/health': { ok: true, wallet: '0xabc' },
|
|
44
|
+
'/wallet/status': { ok: true, wallet: '0xabc', daemonId: '0xabc', chainId: 8453, rpcUrl: 'http://rpc', policyEngineAddress: '0xdef' },
|
|
45
|
+
'/workroom/status': { ok: true, status: 'running' },
|
|
46
|
+
'/agreements': { ok: true, agreements: [{ agreement_id: '1' }] },
|
|
47
|
+
'/auth/challenge': { challengeId: 'cid', challenge: 'sign me', daemonId: '0xabc', wallet: '0xabc', chainId: 8453, scope: 'operator', expiresAt: 1, issuedAt: 1 },
|
|
48
|
+
'/auth/session': { ok: true, token: 'tok', wallets: ['0xabc'], wallet: '0xabc', scope: 'operator', expiresAt: 2 },
|
|
49
|
+
'/auth/revoke': { ok: true },
|
|
50
|
+
};
|
|
51
|
+
return new Response(JSON.stringify(payloads[pathname]), {
|
|
52
|
+
status: 200,
|
|
53
|
+
headers: { 'content-type': 'application/json' },
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const client = new DaemonClient({ baseUrl: 'http://127.0.0.1:6603', token: 'daemon-token' });
|
|
59
|
+
const aliasClient = new DaemonNodeClient({ apiUrl: 'http://127.0.0.1:6603', token: 'daemon-token' });
|
|
60
|
+
|
|
61
|
+
assert.equal(client.apiUrl, 'http://127.0.0.1:6603');
|
|
62
|
+
assert.equal(aliasClient.apiUrl, 'http://127.0.0.1:6603');
|
|
63
|
+
assert.equal(DEFAULT_DAEMON_HTTP_URL, 'http://127.0.0.1:4402');
|
|
64
|
+
assert.equal(DEFAULT_DAEMON_API_URL, 'http://127.0.0.1:4403');
|
|
65
|
+
|
|
66
|
+
assert.deepEqual(await client.health(), { ok: true, wallet: '0xabc' });
|
|
67
|
+
assert.equal((await client.walletStatus()).chainId, 8453);
|
|
68
|
+
assert.equal((await client.workroomStatus()).status, 'running');
|
|
69
|
+
assert.equal((await client.agreements()).agreements.length, 1);
|
|
70
|
+
assert.equal((await client.requestAuthChallenge('0xabc')).challengeId, 'cid');
|
|
71
|
+
assert.equal((await client.createSession('cid', '0xsig')).ok, true);
|
|
72
|
+
assert.equal((await client.revokeSession()).ok, true);
|
|
73
|
+
assert.deepEqual(await aliasClient.getHealth(), { ok: true, wallet: '0xabc' });
|
|
74
|
+
|
|
75
|
+
const challengeCall = calls.find((entry) => new URL(entry.url).pathname === '/auth/challenge');
|
|
76
|
+
assert.equal(challengeCall.init.method, 'POST');
|
|
77
|
+
assert.equal(challengeCall.init.headers.Authorization, undefined);
|
|
78
|
+
assert.match(challengeCall.init.body, /"wallet":"0xabc"/);
|
|
79
|
+
} finally {
|
|
80
|
+
global.fetch = originalFetch;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
DaemonClient,
|
|
9
|
+
DaemonClientError,
|
|
10
|
+
resolveDaemonApiBaseUrl,
|
|
11
|
+
loadLocalDaemonToken,
|
|
12
|
+
} = require('../dist');
|
|
13
|
+
|
|
14
|
+
test('resolveDaemonApiBaseUrl prefers explicit value and trims trailing slashes', () => {
|
|
15
|
+
assert.equal(resolveDaemonApiBaseUrl(), 'http://127.0.0.1:4403');
|
|
16
|
+
assert.equal(resolveDaemonApiBaseUrl({ apiUrl: 'http://example.com:1234/' }), 'http://example.com:1234');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('loadLocalDaemonToken reads trimmed token values', async () => {
|
|
20
|
+
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'arc402-sdk-daemon-'));
|
|
21
|
+
const tokenPath = path.join(dir, 'daemon.token');
|
|
22
|
+
await fs.promises.writeFile(tokenPath, 'secret-token\n', 'utf8');
|
|
23
|
+
assert.equal(loadLocalDaemonToken(tokenPath), 'secret-token');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('DaemonClient sends bearer token to authenticated endpoints', async () => {
|
|
27
|
+
const calls = [];
|
|
28
|
+
const daemon = new DaemonClient({
|
|
29
|
+
apiUrl: 'http://127.0.0.1:4403',
|
|
30
|
+
token: 'token-123',
|
|
31
|
+
fetchImpl: async (url, init) => {
|
|
32
|
+
calls.push({ url, init });
|
|
33
|
+
return new Response(JSON.stringify({ ok: true, status: 'running' }), {
|
|
34
|
+
status: 200,
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const result = await daemon.getWorkroomStatus();
|
|
41
|
+
assert.deepEqual(result, { ok: true, status: 'running' });
|
|
42
|
+
assert.equal(calls[0].url, 'http://127.0.0.1:4403/workroom/status');
|
|
43
|
+
assert.equal(calls[0].init.headers.Authorization, 'Bearer token-123');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('DaemonClient exposes auth challenge and session endpoints without bearer auth', async () => {
|
|
47
|
+
const seen = [];
|
|
48
|
+
const daemon = new DaemonClient({
|
|
49
|
+
apiUrl: 'http://node.example.com',
|
|
50
|
+
fetchImpl: async (url, init) => {
|
|
51
|
+
seen.push({ url, init });
|
|
52
|
+
if (url.endsWith('/auth/challenge')) {
|
|
53
|
+
return new Response(JSON.stringify({
|
|
54
|
+
challengeId: 'abc',
|
|
55
|
+
challenge: 'ARC-402 Remote Auth\nChallenge: 0x123',
|
|
56
|
+
daemonId: '0x0000000000000000000000000000000000000001',
|
|
57
|
+
wallet: '0x0000000000000000000000000000000000000002',
|
|
58
|
+
chainId: 8453,
|
|
59
|
+
scope: 'operator',
|
|
60
|
+
expiresAt: 123,
|
|
61
|
+
issuedAt: 100,
|
|
62
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return new Response(JSON.stringify({
|
|
66
|
+
ok: true,
|
|
67
|
+
token: 'session-token',
|
|
68
|
+
wallets: ['0x0000000000000000000000000000000000000002'],
|
|
69
|
+
wallet: '0x0000000000000000000000000000000000000002',
|
|
70
|
+
scope: 'operator',
|
|
71
|
+
expiresAt: 999,
|
|
72
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const challenge = await daemon.requestAuthChallenge('0x0000000000000000000000000000000000000002');
|
|
77
|
+
const session = await daemon.createSession('abc', '0xsig');
|
|
78
|
+
|
|
79
|
+
assert.equal(challenge.challengeId, 'abc');
|
|
80
|
+
assert.equal(session.token, 'session-token');
|
|
81
|
+
assert.equal(seen[0].init.headers.Authorization, undefined);
|
|
82
|
+
assert.equal(seen[1].init.headers.Authorization, undefined);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('DaemonClient throws typed errors for HTTP failures', async () => {
|
|
86
|
+
const daemon = new DaemonClient({
|
|
87
|
+
token: 'token-123',
|
|
88
|
+
fetchImpl: async () => new Response(JSON.stringify({ error: 'invalid_session' }), {
|
|
89
|
+
status: 401,
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await assert.rejects(
|
|
95
|
+
daemon.getWalletStatus(),
|
|
96
|
+
(error) => {
|
|
97
|
+
assert.ok(error instanceof DaemonClientError);
|
|
98
|
+
assert.equal(error.message, 'invalid_session');
|
|
99
|
+
assert.equal(error.statusCode, 401);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
});
|