@chaoschain/sdk 0.3.1 → 0.3.2
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 +15 -0
- package/README.md +49 -0
- package/dist/{IPFSLocal-B4hnMEfS.d.ts → IPFSLocal-B_Sgmd_Q.d.ts} +1 -1
- package/dist/{IPFSLocal-zRj6kG8e.d.cts → IPFSLocal-DjFddwHD.d.cts} +1 -1
- package/dist/gateway/index.cjs +591 -0
- package/dist/gateway/index.cjs.map +1 -0
- package/dist/gateway/index.d.cts +3 -0
- package/dist/gateway/index.d.ts +3 -0
- package/dist/gateway/index.js +581 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/index-CL0fidQs.d.ts +223 -0
- package/dist/index-iUO5l1VD.d.cts +223 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -393
- package/dist/index.d.ts +9 -393
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/providers/compute/index.d.cts +1 -1
- package/dist/providers/compute/index.d.ts +1 -1
- package/dist/providers/storage/index.d.cts +2 -2
- package/dist/providers/storage/index.d.ts +2 -2
- package/dist/session/index.cjs +196 -0
- package/dist/session/index.cjs.map +1 -0
- package/dist/session/index.d.cts +169 -0
- package/dist/session/index.d.ts +169 -0
- package/dist/session/index.js +189 -0
- package/dist/session/index.js.map +1 -0
- package/dist/{types-BBVtx_jV.d.cts → types-C0Ay90UI.d.cts} +1 -1
- package/dist/{types-BBVtx_jV.d.ts → types-C0Ay90UI.d.ts} +1 -1
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to the ChaosChain TypeScript SDK will be documented in this
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.2] - 2026-03-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Subpath package exports** for edge/serverless: `@chaoschain/sdk/session` and `@chaoschain/sdk/gateway` (lightweight bundles; no ethers / IPFS / heavy Node deps in those entry points)
|
|
13
|
+
- **`src/gateway/index.ts`** — re-exports `GatewayClient`, selected gateway types, and gateway-related exceptions
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **`src/types.ts`** — `import type` for `ethers` where only types are needed (no runtime change)
|
|
18
|
+
|
|
19
|
+
### Documentation
|
|
20
|
+
|
|
21
|
+
- README **Edge Runtime / Serverless Usage**: caveat that `GatewayClient.submitWork()` uses `Buffer`; read/poll APIs are safe on strict V8 isolates; binary `submitWork` needs Node compat or a Buffer polyfill
|
|
22
|
+
|
|
8
23
|
## [0.3.1] - 2026-03-22
|
|
9
24
|
|
|
10
25
|
### Added
|
package/README.md
CHANGED
|
@@ -52,6 +52,55 @@ Storage backends are optional and intended for development/testing. In productio
|
|
|
52
52
|
- **Missing RPC URL** → set `rpcUrl` explicitly (recommended for production).
|
|
53
53
|
- **Using Gateway without config** → pass `gatewayConfig` or `gatewayUrl` to the constructor.
|
|
54
54
|
|
|
55
|
+
## Edge Runtime / Serverless Usage
|
|
56
|
+
|
|
57
|
+
The main entry point (`@chaoschain/sdk`) includes ethers, IPFS, and Node.js-only dependencies that break in edge runtimes. If you are deploying to **Cloudflare Workers**, **Vercel Edge Functions**, **Deno Deploy**, or any V8 isolate environment, use the lightweight subpath imports instead:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { SessionClient } from '@chaoschain/sdk/session'; // 6.59 KB
|
|
61
|
+
import { GatewayClient } from '@chaoschain/sdk/gateway'; // 20 KB
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> **Note:** `GatewayClient.submitWork()` uses `Buffer` internally for binary encoding.
|
|
65
|
+
> For read-only and polling operations (`getWorkflow`, `getPendingWork`, `getWorkEvidence`),
|
|
66
|
+
> the gateway subpath works in all V8 isolate environments.
|
|
67
|
+
> For `submitWork` with binary content, use Node.js compat mode or a Buffer polyfill.
|
|
68
|
+
|
|
69
|
+
| Entry point | Size | What's included |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `@chaoschain/sdk` | 490 KB | Everything: ethers, storage backends, IPFS, all modules |
|
|
72
|
+
| `@chaoschain/sdk/session` | 6.59 KB | `SessionClient`, `Session`, session types |
|
|
73
|
+
| `@chaoschain/sdk/gateway` | 20 KB | `GatewayClient`, workflow types, gateway exceptions |
|
|
74
|
+
|
|
75
|
+
Both subpaths are free of ethers, StorageBackends, form-data, and Node.js built-in dependencies. Use them when you only need to interact with the gateway via HTTP (sessions, score submission, workflow polling).
|
|
76
|
+
|
|
77
|
+
**Example: Cloudflare Worker**
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { SessionClient } from '@chaoschain/sdk/session';
|
|
81
|
+
import { GatewayClient } from '@chaoschain/sdk/gateway';
|
|
82
|
+
|
|
83
|
+
export default {
|
|
84
|
+
async fetch(request: Request, env: Env) {
|
|
85
|
+
const client = new SessionClient({
|
|
86
|
+
gatewayUrl: env.GATEWAY_URL,
|
|
87
|
+
apiKey: env.CHAOSCHAIN_API_KEY,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const session = await client.start({
|
|
91
|
+
studio_address: env.STUDIO_ADDRESS,
|
|
92
|
+
agent_address: env.AGENT_ADDRESS,
|
|
93
|
+
task_type: 'feature',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await session.step('implementing', 'Built feature X');
|
|
97
|
+
const { workflow_id, data_hash } = await session.complete();
|
|
98
|
+
|
|
99
|
+
return Response.json({ workflow_id, data_hash });
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
```
|
|
103
|
+
|
|
55
104
|
## Engineering Studio — Session SDK
|
|
56
105
|
|
|
57
106
|
The Session SDK enables AI agents to capture their work sessions without manually constructing event schemas or DAGs. Sessions automatically build a verifiable Evidence DAG that produces trust profiles for reputation and scoring.
|
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var axios = require('axios');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
8
|
+
|
|
9
|
+
// src/GatewayClient.ts
|
|
10
|
+
|
|
11
|
+
// src/exceptions.ts
|
|
12
|
+
var ChaosChainSDKError = class _ChaosChainSDKError extends Error {
|
|
13
|
+
details;
|
|
14
|
+
constructor(message, details = {}) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "ChaosChainSDKError";
|
|
17
|
+
this.details = details;
|
|
18
|
+
Object.setPrototypeOf(this, _ChaosChainSDKError.prototype);
|
|
19
|
+
}
|
|
20
|
+
toString() {
|
|
21
|
+
if (Object.keys(this.details).length > 0) {
|
|
22
|
+
return `${this.message} | Details: ${JSON.stringify(this.details)}`;
|
|
23
|
+
}
|
|
24
|
+
return this.message;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var GatewayError = class _GatewayError extends ChaosChainSDKError {
|
|
28
|
+
statusCode;
|
|
29
|
+
response;
|
|
30
|
+
category;
|
|
31
|
+
retryable;
|
|
32
|
+
constructor(message, details) {
|
|
33
|
+
super(message, details || {});
|
|
34
|
+
this.name = "GatewayError";
|
|
35
|
+
this.statusCode = details?.statusCode;
|
|
36
|
+
this.response = details?.response;
|
|
37
|
+
this.category = details?.category;
|
|
38
|
+
this.retryable = details?.retryable;
|
|
39
|
+
Object.setPrototypeOf(this, _GatewayError.prototype);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var GatewayConnectionError = class _GatewayConnectionError extends GatewayError {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "GatewayConnectionError";
|
|
46
|
+
Object.setPrototypeOf(this, _GatewayConnectionError.prototype);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var GatewayTimeoutError = class _GatewayTimeoutError extends GatewayError {
|
|
50
|
+
workflowId;
|
|
51
|
+
lastStatus;
|
|
52
|
+
constructor(workflowId, message, lastStatus) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = "GatewayTimeoutError";
|
|
55
|
+
this.workflowId = workflowId;
|
|
56
|
+
this.lastStatus = lastStatus;
|
|
57
|
+
Object.setPrototypeOf(this, _GatewayTimeoutError.prototype);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var WorkflowFailedError = class _WorkflowFailedError extends GatewayError {
|
|
61
|
+
workflowId;
|
|
62
|
+
workflowError;
|
|
63
|
+
constructor(workflowId, error) {
|
|
64
|
+
super(`Workflow ${workflowId} failed at step ${error.step}: ${error.message}`);
|
|
65
|
+
this.name = "WorkflowFailedError";
|
|
66
|
+
this.workflowId = workflowId;
|
|
67
|
+
this.workflowError = error;
|
|
68
|
+
Object.setPrototypeOf(this, _WorkflowFailedError.prototype);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/GatewayClient.ts
|
|
73
|
+
var GatewayClient = class {
|
|
74
|
+
gatewayUrl;
|
|
75
|
+
timeout;
|
|
76
|
+
maxPollTime;
|
|
77
|
+
pollInterval;
|
|
78
|
+
defaultHeaders;
|
|
79
|
+
auth;
|
|
80
|
+
retryConfig;
|
|
81
|
+
constructor(config) {
|
|
82
|
+
const rawBaseUrl = config.baseUrl ?? config.gatewayUrl ?? "https://gateway.chaoscha.in";
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = new URL(rawBaseUrl);
|
|
86
|
+
} catch {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Invalid gateway baseUrl "${rawBaseUrl}". Provide a valid absolute URL, e.g. https://gateway.chaoscha.in`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Invalid gateway baseUrl protocol "${parsed.protocol}". Only http/https are supported.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
this.gatewayUrl = parsed.toString().replace(/\/$/, "");
|
|
97
|
+
this.timeout = this._resolveTimeout(
|
|
98
|
+
config.timeoutMs,
|
|
99
|
+
config.timeoutSeconds,
|
|
100
|
+
config.timeout,
|
|
101
|
+
3e4
|
|
102
|
+
);
|
|
103
|
+
this.maxPollTime = this._resolveTimeout(
|
|
104
|
+
config.maxPollTimeMs,
|
|
105
|
+
config.maxPollTimeSeconds,
|
|
106
|
+
config.maxPollTime,
|
|
107
|
+
6e5
|
|
108
|
+
);
|
|
109
|
+
this.pollInterval = this._resolveTimeout(
|
|
110
|
+
config.pollIntervalMs,
|
|
111
|
+
config.pollIntervalSeconds,
|
|
112
|
+
config.pollInterval,
|
|
113
|
+
2e3
|
|
114
|
+
);
|
|
115
|
+
this.defaultHeaders = config.headers;
|
|
116
|
+
this.auth = config.auth;
|
|
117
|
+
this.retryConfig = config.retry;
|
|
118
|
+
}
|
|
119
|
+
// ===========================================================================
|
|
120
|
+
// Private: HTTP Request
|
|
121
|
+
// ===========================================================================
|
|
122
|
+
// Resolve timeout with explicit ms taking precedence, then seconds, then legacy ms.
|
|
123
|
+
_resolveTimeout(timeoutMs, timeoutSeconds, legacyTimeoutMs, defaultMs) {
|
|
124
|
+
if (typeof timeoutMs === "number") return timeoutMs;
|
|
125
|
+
if (typeof timeoutSeconds === "number") return timeoutSeconds * 1e3;
|
|
126
|
+
if (typeof legacyTimeoutMs === "number") return legacyTimeoutMs;
|
|
127
|
+
return defaultMs ?? 0;
|
|
128
|
+
}
|
|
129
|
+
_resolveAuthMode() {
|
|
130
|
+
if (!this.auth) return void 0;
|
|
131
|
+
if (this.auth.authMode) return this.auth.authMode;
|
|
132
|
+
if (this.auth.apiKey) return "apiKey";
|
|
133
|
+
if (this.auth.signature) return "signature";
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
_buildHeaders() {
|
|
137
|
+
const headers = {
|
|
138
|
+
"Content-Type": "application/json"
|
|
139
|
+
};
|
|
140
|
+
if (this.defaultHeaders) {
|
|
141
|
+
Object.assign(headers, this.defaultHeaders);
|
|
142
|
+
}
|
|
143
|
+
const authMode = this._resolveAuthMode();
|
|
144
|
+
if (authMode === "apiKey" && this.auth?.apiKey) {
|
|
145
|
+
headers["X-API-Key"] = this.auth.apiKey;
|
|
146
|
+
}
|
|
147
|
+
if (authMode === "signature" && this.auth?.signature) {
|
|
148
|
+
const timestamp = this.auth.signature.timestamp ?? Date.now();
|
|
149
|
+
headers["X-Signature"] = this.auth.signature.signature;
|
|
150
|
+
headers["X-Timestamp"] = `${timestamp}`;
|
|
151
|
+
headers["X-Address"] = this.auth.signature.address;
|
|
152
|
+
}
|
|
153
|
+
if (!headers["Content-Type"]) {
|
|
154
|
+
headers["Content-Type"] = "application/json";
|
|
155
|
+
}
|
|
156
|
+
return headers;
|
|
157
|
+
}
|
|
158
|
+
_classifyStatusCode(statusCode) {
|
|
159
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
160
|
+
return { statusCode, category: "auth", retryable: false };
|
|
161
|
+
}
|
|
162
|
+
if (statusCode === 408 || statusCode === 429 || statusCode !== void 0 && statusCode >= 500) {
|
|
163
|
+
return { statusCode, category: "transient", retryable: true };
|
|
164
|
+
}
|
|
165
|
+
if (statusCode !== void 0 && statusCode >= 400) {
|
|
166
|
+
return { statusCode, category: "permanent", retryable: false };
|
|
167
|
+
}
|
|
168
|
+
return { statusCode, category: "unknown", retryable: false };
|
|
169
|
+
}
|
|
170
|
+
_normalizeError(error) {
|
|
171
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOTFOUND") {
|
|
172
|
+
const connectionError = new GatewayConnectionError(
|
|
173
|
+
`Failed to connect to Gateway at ${this.gatewayUrl}`
|
|
174
|
+
);
|
|
175
|
+
connectionError.details.category = "transient";
|
|
176
|
+
connectionError.details.retryable = true;
|
|
177
|
+
connectionError.category = "transient";
|
|
178
|
+
connectionError.retryable = true;
|
|
179
|
+
return connectionError;
|
|
180
|
+
}
|
|
181
|
+
if (error.code === "ETIMEDOUT" || error.code === "ECONNABORTED") {
|
|
182
|
+
const timeoutError = new GatewayTimeoutError(
|
|
183
|
+
"timeout",
|
|
184
|
+
`request to Gateway timed out: ${error.message}`
|
|
185
|
+
);
|
|
186
|
+
timeoutError.details.category = "transient";
|
|
187
|
+
timeoutError.details.retryable = true;
|
|
188
|
+
timeoutError.category = "transient";
|
|
189
|
+
timeoutError.retryable = true;
|
|
190
|
+
return timeoutError;
|
|
191
|
+
}
|
|
192
|
+
if (error.response) {
|
|
193
|
+
const data = error.response.data;
|
|
194
|
+
const message = data?.error || data?.message || "Unknown error from Gateway";
|
|
195
|
+
const classification2 = this._classifyStatusCode(error.response.status);
|
|
196
|
+
return new GatewayError(`Gateway returned error: ${message}`, {
|
|
197
|
+
statusCode: error.response.status,
|
|
198
|
+
response: data,
|
|
199
|
+
category: classification2.category,
|
|
200
|
+
retryable: classification2.retryable
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const classification = this._classifyStatusCode(void 0);
|
|
204
|
+
return new GatewayError(`Gateway request failed: ${error.message}`, {
|
|
205
|
+
category: classification.category,
|
|
206
|
+
retryable: classification.retryable
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
_getRetryDelayMs(attempt) {
|
|
210
|
+
const initialDelayMs = this.retryConfig?.initialDelayMs ?? 500;
|
|
211
|
+
const backoffFactor = this.retryConfig?.backoffFactor ?? 2;
|
|
212
|
+
const maxDelayMs = this.retryConfig?.maxDelayMs ?? 8e3;
|
|
213
|
+
const jitterEnabled = this.retryConfig?.jitter ?? true;
|
|
214
|
+
const jitterRatio = this.retryConfig?.jitterRatio ?? 0.2;
|
|
215
|
+
let delay = Math.min(maxDelayMs, initialDelayMs * Math.pow(backoffFactor, attempt));
|
|
216
|
+
if (jitterEnabled) {
|
|
217
|
+
const delta = delay * jitterRatio;
|
|
218
|
+
delay = delay + (Math.random() * 2 - 1) * delta;
|
|
219
|
+
delay = Math.max(0, delay);
|
|
220
|
+
}
|
|
221
|
+
return Math.round(delay);
|
|
222
|
+
}
|
|
223
|
+
async _sleep(durationMs) {
|
|
224
|
+
await new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Make HTTP request to Gateway.
|
|
228
|
+
* Handles errors and transforms them to Gateway exceptions.
|
|
229
|
+
*/
|
|
230
|
+
async _request(method, path, data) {
|
|
231
|
+
const url = `${this.gatewayUrl}${path}`;
|
|
232
|
+
const maxRetries = this.retryConfig?.maxRetries ?? 3;
|
|
233
|
+
const retriesEnabled = this.retryConfig?.enabled === true;
|
|
234
|
+
let attempt = 0;
|
|
235
|
+
while (true) {
|
|
236
|
+
try {
|
|
237
|
+
const response = await axios__default.default({
|
|
238
|
+
method,
|
|
239
|
+
url,
|
|
240
|
+
data,
|
|
241
|
+
timeout: this.timeout,
|
|
242
|
+
headers: this._buildHeaders()
|
|
243
|
+
});
|
|
244
|
+
return response.data;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
const normalizedError = this._normalizeError(error);
|
|
247
|
+
const category = normalizedError.category;
|
|
248
|
+
const retryable = normalizedError.retryable;
|
|
249
|
+
const shouldRetry = retriesEnabled === true && category === "transient" && retryable === true && attempt < maxRetries;
|
|
250
|
+
if (!shouldRetry) {
|
|
251
|
+
throw normalizedError;
|
|
252
|
+
}
|
|
253
|
+
const delay = this._getRetryDelayMs(attempt);
|
|
254
|
+
attempt += 1;
|
|
255
|
+
await this._sleep(delay);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Parse workflow status from API response.
|
|
261
|
+
*/
|
|
262
|
+
_parseWorkflowStatus(data) {
|
|
263
|
+
const progress = {
|
|
264
|
+
arweaveTxId: data.progress?.arweave_tx_id,
|
|
265
|
+
arweaveConfirmed: data.progress?.arweave_confirmed,
|
|
266
|
+
onchainTxHash: data.progress?.onchain_tx_hash,
|
|
267
|
+
onchainConfirmed: data.progress?.onchain_confirmed,
|
|
268
|
+
onchainBlock: data.progress?.onchain_block,
|
|
269
|
+
scoreTxHash: data.progress?.score_tx_hash,
|
|
270
|
+
commitTxHash: data.progress?.commit_tx_hash,
|
|
271
|
+
revealTxHash: data.progress?.reveal_tx_hash
|
|
272
|
+
};
|
|
273
|
+
const error = data.error ? {
|
|
274
|
+
step: data.error.step || "",
|
|
275
|
+
message: data.error.message || "",
|
|
276
|
+
code: data.error.code
|
|
277
|
+
} : void 0;
|
|
278
|
+
return {
|
|
279
|
+
workflowId: data.id,
|
|
280
|
+
workflowType: data.type,
|
|
281
|
+
state: data.state,
|
|
282
|
+
step: data.step,
|
|
283
|
+
createdAt: data.created_at,
|
|
284
|
+
updatedAt: data.updated_at,
|
|
285
|
+
progress,
|
|
286
|
+
error
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
// ===========================================================================
|
|
290
|
+
// Health Check
|
|
291
|
+
// ===========================================================================
|
|
292
|
+
async healthCheck() {
|
|
293
|
+
return this._request("GET", "/health");
|
|
294
|
+
}
|
|
295
|
+
async isHealthy() {
|
|
296
|
+
try {
|
|
297
|
+
const result = await this.healthCheck();
|
|
298
|
+
return result.status === "ok";
|
|
299
|
+
} catch (error) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// ===========================================================================
|
|
304
|
+
// Workflow Submission
|
|
305
|
+
// ===========================================================================
|
|
306
|
+
/**
|
|
307
|
+
* Create a work submission workflow.
|
|
308
|
+
* POST /workflows/work-submission
|
|
309
|
+
*
|
|
310
|
+
* SDK prepares inputs; Gateway handles:
|
|
311
|
+
* - Evidence upload to Arweave
|
|
312
|
+
* - Transaction submission
|
|
313
|
+
* - Confirmation waiting
|
|
314
|
+
*
|
|
315
|
+
* @param studioAddress - Ethereum address of the studio
|
|
316
|
+
* @param epoch - Epoch number
|
|
317
|
+
* @param agentAddress - Ethereum address of the submitting agent
|
|
318
|
+
* @param dataHash - Bytes32 hash of the work (as hex string)
|
|
319
|
+
* @param threadRoot - Bytes32 DKG thread root (as hex string)
|
|
320
|
+
* @param evidenceRoot - Bytes32 evidence Merkle root (as hex string)
|
|
321
|
+
* @param evidenceContent - Raw evidence bytes (will be base64 encoded)
|
|
322
|
+
* @param signerAddress - Ethereum address of the signer (must be registered in Gateway)
|
|
323
|
+
* @returns WorkflowStatus - Initial status of the created workflow
|
|
324
|
+
*/
|
|
325
|
+
async submitWork(studioAddress, epoch, agentAddress, dataHash, threadRoot, evidenceRoot, evidenceContent, signerAddress) {
|
|
326
|
+
const evidenceContentBase64 = Buffer.isBuffer(evidenceContent) ? evidenceContent.toString("base64") : Buffer.from(evidenceContent, "utf-8").toString("base64");
|
|
327
|
+
const payload = {
|
|
328
|
+
studio_address: studioAddress,
|
|
329
|
+
epoch,
|
|
330
|
+
agent_address: agentAddress,
|
|
331
|
+
data_hash: dataHash,
|
|
332
|
+
thread_root: threadRoot,
|
|
333
|
+
evidence_root: evidenceRoot,
|
|
334
|
+
evidence_content: evidenceContentBase64,
|
|
335
|
+
signer_address: signerAddress
|
|
336
|
+
};
|
|
337
|
+
const result = await this._request(
|
|
338
|
+
"POST",
|
|
339
|
+
"/workflows/work-submission",
|
|
340
|
+
payload
|
|
341
|
+
);
|
|
342
|
+
return this._parseWorkflowStatus(result);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Create a score submission workflow.
|
|
346
|
+
* POST /workflows/score-submission
|
|
347
|
+
*
|
|
348
|
+
* Supports two modes:
|
|
349
|
+
* - DIRECT (default): Simple direct scoring, requires workerAddress
|
|
350
|
+
* - COMMIT_REVEAL: Commit-reveal pattern, requires salt
|
|
351
|
+
*
|
|
352
|
+
* @param studioAddress - Ethereum address of the studio
|
|
353
|
+
* @param epoch - Epoch number
|
|
354
|
+
* @param validatorAddress - Ethereum address of the validator
|
|
355
|
+
* @param dataHash - Bytes32 hash of the work being scored (as hex string)
|
|
356
|
+
* @param scores - Array of dimension scores (0-10000 basis points)
|
|
357
|
+
* @param signerAddress - Ethereum address of the signer
|
|
358
|
+
* @param options - Additional options (workerAddress, salt, mode)
|
|
359
|
+
*/
|
|
360
|
+
async submitScore(studioAddress, epoch, validatorAddress, dataHash, scores, signerAddress, options) {
|
|
361
|
+
const mode = options?.mode ?? "direct" /* DIRECT */;
|
|
362
|
+
if (mode === "direct" /* DIRECT */ && !options?.workerAddress) {
|
|
363
|
+
throw new Error("workerAddress is required for DIRECT score scoring mode");
|
|
364
|
+
}
|
|
365
|
+
if (mode === "commit_reveal" /* COMMIT_REVEAL */ && !options?.salt) {
|
|
366
|
+
throw new Error("salt is required for COMMIT_REVEAL score scoring mode");
|
|
367
|
+
}
|
|
368
|
+
const payload = {
|
|
369
|
+
studio_address: studioAddress,
|
|
370
|
+
epoch,
|
|
371
|
+
validator_address: validatorAddress,
|
|
372
|
+
data_hash: dataHash,
|
|
373
|
+
scores,
|
|
374
|
+
signer_address: signerAddress,
|
|
375
|
+
mode,
|
|
376
|
+
salt: options?.salt ?? "0x" + "0".repeat(64)
|
|
377
|
+
};
|
|
378
|
+
if (options?.workerAddress) {
|
|
379
|
+
payload.worker_address = options.workerAddress;
|
|
380
|
+
}
|
|
381
|
+
const result = await this._request(
|
|
382
|
+
"POST",
|
|
383
|
+
"/workflows/score-submission",
|
|
384
|
+
payload
|
|
385
|
+
);
|
|
386
|
+
return this._parseWorkflowStatus(result);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Create a close epoch workflow.
|
|
390
|
+
* POST /workflows/close-epoch
|
|
391
|
+
*
|
|
392
|
+
* This is economically final — cannot be undone.
|
|
393
|
+
*
|
|
394
|
+
* @param studioAddress - Ethereum address of the studio
|
|
395
|
+
* @param epoch - Epoch number to close
|
|
396
|
+
* @param signerAddress - Ethereum address of the signer
|
|
397
|
+
*/
|
|
398
|
+
async closeEpoch(studioAddress, epoch, signerAddress) {
|
|
399
|
+
const payload = {
|
|
400
|
+
studio_address: studioAddress,
|
|
401
|
+
epoch,
|
|
402
|
+
signer_address: signerAddress
|
|
403
|
+
};
|
|
404
|
+
const result = await this._request(
|
|
405
|
+
"POST",
|
|
406
|
+
"/workflows/close-epoch",
|
|
407
|
+
payload
|
|
408
|
+
);
|
|
409
|
+
return this._parseWorkflowStatus(result);
|
|
410
|
+
}
|
|
411
|
+
// ===========================================================================
|
|
412
|
+
// Workflow Status
|
|
413
|
+
// ===========================================================================
|
|
414
|
+
/**
|
|
415
|
+
* Get workflow status by ID.
|
|
416
|
+
* GET /workflows/{id}
|
|
417
|
+
*/
|
|
418
|
+
async getWorkflow(workflowId) {
|
|
419
|
+
const result = await this._request("GET", `/workflows/${workflowId}`);
|
|
420
|
+
return this._parseWorkflowStatus(result);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* List workflows with optional filters.
|
|
424
|
+
* GET /workflows?studio=&state=&type=
|
|
425
|
+
*/
|
|
426
|
+
async listWorkflows(options) {
|
|
427
|
+
const params = [];
|
|
428
|
+
if (options?.studio) params.push(`studio=${options.studio}`);
|
|
429
|
+
if (options?.state) params.push(`state=${options.state}`);
|
|
430
|
+
if (options?.workflowType) params.push(`type=${options.workflowType}`);
|
|
431
|
+
const queryString = params.length > 0 ? `?${params.join("&")}` : "";
|
|
432
|
+
const result = await this._request(
|
|
433
|
+
"GET",
|
|
434
|
+
`/workflows${queryString}`
|
|
435
|
+
);
|
|
436
|
+
return (result.workflows || []).map((w) => this._parseWorkflowStatus(w));
|
|
437
|
+
}
|
|
438
|
+
// ===========================================================================
|
|
439
|
+
// Polling and Waiting
|
|
440
|
+
// ===========================================================================
|
|
441
|
+
/**
|
|
442
|
+
* Poll workflow until it reaches a terminal state.
|
|
443
|
+
*
|
|
444
|
+
* @param workflowId - UUID of the workflow
|
|
445
|
+
* @param options - Polling options
|
|
446
|
+
* @throws WorkflowFailedError - If workflow reaches FAILED state
|
|
447
|
+
* @throws GatewayTimeoutError - If maxWait exceeded
|
|
448
|
+
*/
|
|
449
|
+
async waitForCompletion(workflowId, options) {
|
|
450
|
+
const maxWait = options?.maxWait || this.maxPollTime;
|
|
451
|
+
const pollInterval = options?.pollInterval || this.pollInterval;
|
|
452
|
+
const startTime = Date.now();
|
|
453
|
+
while (true) {
|
|
454
|
+
const status = await this.getWorkflow(workflowId);
|
|
455
|
+
if (options?.onProgress) {
|
|
456
|
+
options.onProgress(status);
|
|
457
|
+
}
|
|
458
|
+
if (status.state === "COMPLETED" /* COMPLETED */) {
|
|
459
|
+
return status;
|
|
460
|
+
}
|
|
461
|
+
if (status.state === "FAILED" /* FAILED */) {
|
|
462
|
+
throw new WorkflowFailedError(workflowId, status.error);
|
|
463
|
+
}
|
|
464
|
+
const elapsed = Date.now() - startTime;
|
|
465
|
+
if (elapsed >= maxWait) {
|
|
466
|
+
throw new GatewayTimeoutError(
|
|
467
|
+
workflowId,
|
|
468
|
+
`Workflow ${workflowId} did not complete within ${maxWait} ms.Current state: ${status.state}, step: ${status.step}`,
|
|
469
|
+
status
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// ===========================================================================
|
|
476
|
+
// Convenience Methods (submit + wait)
|
|
477
|
+
// ===========================================================================
|
|
478
|
+
/**
|
|
479
|
+
* Submit work and wait for completion.
|
|
480
|
+
*/
|
|
481
|
+
async submitWorkAndWait(studioAddress, epoch, agentAddress, dataHash, threadRoot, evidenceRoot, evidenceContent, signerAddress, options) {
|
|
482
|
+
const workflow = await this.submitWork(
|
|
483
|
+
studioAddress,
|
|
484
|
+
epoch,
|
|
485
|
+
agentAddress,
|
|
486
|
+
dataHash,
|
|
487
|
+
threadRoot,
|
|
488
|
+
evidenceRoot,
|
|
489
|
+
evidenceContent,
|
|
490
|
+
signerAddress
|
|
491
|
+
);
|
|
492
|
+
return this.waitForCompletion(workflow.workflowId, options);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Submit score and wait for completion.
|
|
496
|
+
*/
|
|
497
|
+
async submitScoreAndWait(studioAddress, epoch, validatorAddress, dataHash, scores, signerAddress, options) {
|
|
498
|
+
const workerAddress = options?.workerAddress ?? options?.workAddress;
|
|
499
|
+
const workflow = await this.submitScore(
|
|
500
|
+
studioAddress,
|
|
501
|
+
epoch,
|
|
502
|
+
validatorAddress,
|
|
503
|
+
dataHash,
|
|
504
|
+
scores,
|
|
505
|
+
signerAddress,
|
|
506
|
+
{
|
|
507
|
+
workerAddress,
|
|
508
|
+
salt: options?.salt,
|
|
509
|
+
mode: options?.mode
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
return this.waitForCompletion(workflow.workflowId, { onProgress: options?.onProgress });
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Close epoch and wait for completion.
|
|
516
|
+
*/
|
|
517
|
+
async closeEpochAndWait(studioAddress, epoch, signerAddress, options) {
|
|
518
|
+
const workflow = await this.closeEpoch(studioAddress, epoch, signerAddress);
|
|
519
|
+
return this.waitForCompletion(workflow.workflowId, options);
|
|
520
|
+
}
|
|
521
|
+
// ===========================================================================
|
|
522
|
+
// Read API — Studio Work Discovery
|
|
523
|
+
// ===========================================================================
|
|
524
|
+
/**
|
|
525
|
+
* Fetch pending (unfinalized) work for a studio from the gateway.
|
|
526
|
+
*
|
|
527
|
+
* @param studioAddress - 0x-prefixed studio contract address
|
|
528
|
+
* @param options - Optional limit/offset for pagination
|
|
529
|
+
* @returns Typed pending work response
|
|
530
|
+
*/
|
|
531
|
+
async getPendingWork(studioAddress, options) {
|
|
532
|
+
const limit = options?.limit ?? 20;
|
|
533
|
+
const offset = options?.offset ?? 0;
|
|
534
|
+
const url = `${this.gatewayUrl}/v1/studio/${studioAddress}/work?status=pending&limit=${limit}&offset=${offset}`;
|
|
535
|
+
try {
|
|
536
|
+
const response = await axios__default.default.get(url, {
|
|
537
|
+
timeout: this.timeout,
|
|
538
|
+
headers: this._buildHeaders()
|
|
539
|
+
});
|
|
540
|
+
return response.data;
|
|
541
|
+
} catch (error) {
|
|
542
|
+
const axiosErr = error;
|
|
543
|
+
if (axiosErr.code === "ECONNREFUSED" || axiosErr.code === "ENOTFOUND" || !axiosErr.response) {
|
|
544
|
+
throw new GatewayConnectionError(
|
|
545
|
+
`ChaosChain gateway unreachable at ${this.gatewayUrl}. Check GATEWAY_URL.`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
if (axiosErr.response) {
|
|
549
|
+
throw new GatewayError(
|
|
550
|
+
`Gateway returned ${axiosErr.response.status}: ${JSON.stringify(axiosErr.response.data)}`
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
throw error;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Fetch full evidence graph for a work submission.
|
|
558
|
+
* Endpoint: GET /v1/work/{hash}/evidence
|
|
559
|
+
*/
|
|
560
|
+
async getWorkEvidence(workHash) {
|
|
561
|
+
const url = `${this.gatewayUrl}/v1/work/${workHash}/evidence`;
|
|
562
|
+
try {
|
|
563
|
+
const response = await axios__default.default.get(url, {
|
|
564
|
+
timeout: this.timeout,
|
|
565
|
+
headers: this._buildHeaders()
|
|
566
|
+
});
|
|
567
|
+
return response.data;
|
|
568
|
+
} catch (error) {
|
|
569
|
+
const axiosErr = error;
|
|
570
|
+
if (axiosErr.code === "ECONNREFUSED" || axiosErr.code === "ENOTFOUND" || !axiosErr.response) {
|
|
571
|
+
throw new GatewayConnectionError(
|
|
572
|
+
`ChaosChain gateway unreachable at ${this.gatewayUrl}. Check GATEWAY_URL.`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
if (axiosErr.response) {
|
|
576
|
+
throw new GatewayError(
|
|
577
|
+
`Gateway returned ${axiosErr.response.status}: ${JSON.stringify(axiosErr.response.data)}`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
exports.GatewayClient = GatewayClient;
|
|
586
|
+
exports.GatewayConnectionError = GatewayConnectionError;
|
|
587
|
+
exports.GatewayError = GatewayError;
|
|
588
|
+
exports.GatewayTimeoutError = GatewayTimeoutError;
|
|
589
|
+
exports.WorkflowFailedError = WorkflowFailedError;
|
|
590
|
+
//# sourceMappingURL=index.cjs.map
|
|
591
|
+
//# sourceMappingURL=index.cjs.map
|