@provenonce/sdk 0.6.0 → 0.9.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 +82 -25
- package/dist/index.d.mts +293 -27
- package/dist/index.d.ts +293 -27
- package/dist/index.js +641 -62
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +631 -63
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,105 @@
|
|
|
1
1
|
// src/beat-sdk.ts
|
|
2
|
-
import { createHash } from "crypto";
|
|
2
|
+
import { createHash, generateKeyPairSync, sign, verify, createPrivateKey, createPublicKey } from "crypto";
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
6
|
+
ErrorCode2["VALIDATION"] = "VALIDATION";
|
|
7
|
+
ErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
|
|
8
|
+
ErrorCode2["AUTH_MISSING"] = "AUTH_MISSING";
|
|
9
|
+
ErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
|
|
10
|
+
ErrorCode2["AGENT_FROZEN"] = "AGENT_FROZEN";
|
|
11
|
+
ErrorCode2["AGENT_NOT_INITIALIZED"] = "AGENT_NOT_INITIALIZED";
|
|
12
|
+
ErrorCode2["AGENT_WRONG_STATE"] = "AGENT_WRONG_STATE";
|
|
13
|
+
ErrorCode2["NOT_FOUND"] = "NOT_FOUND";
|
|
14
|
+
ErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
15
|
+
ErrorCode2["TIMEOUT"] = "TIMEOUT";
|
|
16
|
+
ErrorCode2["SERVER_ERROR"] = "SERVER_ERROR";
|
|
17
|
+
return ErrorCode2;
|
|
18
|
+
})(ErrorCode || {});
|
|
19
|
+
var ProvenonceError = class extends Error {
|
|
20
|
+
constructor(message, code, statusCode, details) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "ProvenonceError";
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.statusCode = statusCode;
|
|
25
|
+
this.details = details;
|
|
26
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var ValidationError = class extends ProvenonceError {
|
|
30
|
+
constructor(message, details) {
|
|
31
|
+
super(message, "VALIDATION" /* VALIDATION */, void 0, details);
|
|
32
|
+
this.name = "ValidationError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var AuthError = class extends ProvenonceError {
|
|
36
|
+
constructor(message, code = "AUTH_INVALID" /* AUTH_INVALID */, statusCode) {
|
|
37
|
+
super(message, code, statusCode);
|
|
38
|
+
this.name = "AuthError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var RateLimitError = class extends ProvenonceError {
|
|
42
|
+
constructor(message, statusCode = 429, retryAfterMs) {
|
|
43
|
+
super(message, "RATE_LIMITED" /* RATE_LIMITED */, statusCode);
|
|
44
|
+
this.name = "RateLimitError";
|
|
45
|
+
this.retryAfterMs = retryAfterMs;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var FrozenError = class extends ProvenonceError {
|
|
49
|
+
constructor(message = "Agent is frozen. Use resync() to re-establish provenance.") {
|
|
50
|
+
super(message, "AGENT_FROZEN" /* AGENT_FROZEN */);
|
|
51
|
+
this.name = "FrozenError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var StateError = class extends ProvenonceError {
|
|
55
|
+
constructor(message, currentState, code = "AGENT_WRONG_STATE" /* AGENT_WRONG_STATE */) {
|
|
56
|
+
super(message, code);
|
|
57
|
+
this.name = "StateError";
|
|
58
|
+
this.currentState = currentState;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var NotFoundError = class extends ProvenonceError {
|
|
62
|
+
constructor(message, statusCode = 404) {
|
|
63
|
+
super(message, "NOT_FOUND" /* NOT_FOUND */, statusCode);
|
|
64
|
+
this.name = "NotFoundError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var NetworkError = class extends ProvenonceError {
|
|
68
|
+
constructor(message, code = "NETWORK_ERROR" /* NETWORK_ERROR */) {
|
|
69
|
+
super(message, code);
|
|
70
|
+
this.name = "NetworkError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var ServerError = class extends ProvenonceError {
|
|
74
|
+
constructor(message, statusCode = 500) {
|
|
75
|
+
super(message, "SERVER_ERROR" /* SERVER_ERROR */, statusCode);
|
|
76
|
+
this.name = "ServerError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
function mapApiError(statusCode, body, path) {
|
|
80
|
+
const msg = typeof body.error === "string" ? body.error : `API error ${statusCode}`;
|
|
81
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
82
|
+
const code = statusCode === 401 ? "AUTH_MISSING" /* AUTH_MISSING */ : "AUTH_INVALID" /* AUTH_INVALID */;
|
|
83
|
+
return new AuthError(msg, code, statusCode);
|
|
84
|
+
}
|
|
85
|
+
if (statusCode === 429) {
|
|
86
|
+
const retryAfter = typeof body.retry_after_ms === "number" ? body.retry_after_ms : void 0;
|
|
87
|
+
return new RateLimitError(msg, statusCode, retryAfter);
|
|
88
|
+
}
|
|
89
|
+
if (statusCode === 404) {
|
|
90
|
+
return new NotFoundError(msg, statusCode);
|
|
91
|
+
}
|
|
92
|
+
if (statusCode >= 500) {
|
|
93
|
+
return new ServerError(msg, statusCode);
|
|
94
|
+
}
|
|
95
|
+
const lowerMsg = msg.toLowerCase();
|
|
96
|
+
if (lowerMsg.includes("frozen")) {
|
|
97
|
+
return new FrozenError(msg);
|
|
98
|
+
}
|
|
99
|
+
return new ProvenonceError(msg, "SERVER_ERROR" /* SERVER_ERROR */, statusCode);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/beat-sdk.ts
|
|
3
103
|
function computeBeat(prevHash, beatIndex, difficulty, nonce, anchorHash) {
|
|
4
104
|
const timestamp = Date.now();
|
|
5
105
|
const seed = anchorHash ? `${prevHash}:${beatIndex}:${nonce || ""}:${anchorHash}` : `${prevHash}:${beatIndex}:${nonce || ""}`;
|
|
@@ -9,26 +109,240 @@ function computeBeat(prevHash, beatIndex, difficulty, nonce, anchorHash) {
|
|
|
9
109
|
}
|
|
10
110
|
return { index: beatIndex, hash: current, prev: prevHash, timestamp, nonce, anchor_hash: anchorHash };
|
|
11
111
|
}
|
|
112
|
+
var ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
113
|
+
function generateWalletKeypair() {
|
|
114
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
115
|
+
const pubRaw = publicKey.export({ type: "spki", format: "der" }).subarray(12);
|
|
116
|
+
const privRaw = privateKey.export({ type: "pkcs8", format: "der" }).subarray(16);
|
|
117
|
+
return {
|
|
118
|
+
publicKey: Buffer.from(pubRaw).toString("hex"),
|
|
119
|
+
secretKey: Buffer.from(privRaw).toString("hex")
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function signMessage(secretKeyHex, message) {
|
|
123
|
+
const privRaw = Buffer.from(secretKeyHex, "hex");
|
|
124
|
+
const privKeyDer = Buffer.concat([ED25519_PKCS8_PREFIX, privRaw]);
|
|
125
|
+
const keyObject = createPrivateKey({ key: privKeyDer, format: "der", type: "pkcs8" });
|
|
126
|
+
const sig = sign(null, Buffer.from(message), keyObject);
|
|
127
|
+
return Buffer.from(sig).toString("hex");
|
|
128
|
+
}
|
|
12
129
|
async function register(name, options) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
|
|
130
|
+
if (!name || typeof name !== "string" || name.trim().length === 0) {
|
|
131
|
+
throw new ValidationError("name is required (must be a non-empty string)");
|
|
132
|
+
}
|
|
133
|
+
if (name.length > 64) {
|
|
134
|
+
throw new ValidationError("name must be 64 characters or fewer");
|
|
18
135
|
}
|
|
19
|
-
|
|
20
|
-
|
|
136
|
+
const url = options?.registryUrl || "https://provenonce.io";
|
|
137
|
+
try {
|
|
138
|
+
new URL(url);
|
|
139
|
+
} catch {
|
|
140
|
+
throw new ValidationError("registryUrl is not a valid URL");
|
|
21
141
|
}
|
|
142
|
+
const headers = { "Content-Type": "application/json" };
|
|
22
143
|
if (options?.registrationSecret) {
|
|
23
144
|
headers["x-registration-secret"] = options.registrationSecret;
|
|
24
145
|
}
|
|
146
|
+
if (options?.parentHash) {
|
|
147
|
+
if (options.parentApiKey) {
|
|
148
|
+
headers["Authorization"] = `Bearer ${options.parentApiKey}`;
|
|
149
|
+
}
|
|
150
|
+
const res2 = await fetch(`${url}/api/v1/register`, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers,
|
|
153
|
+
body: JSON.stringify({ name, parent: options.parentHash, ...options.metadata && { metadata: options.metadata } })
|
|
154
|
+
});
|
|
155
|
+
let data2;
|
|
156
|
+
try {
|
|
157
|
+
data2 = await res2.json();
|
|
158
|
+
} catch {
|
|
159
|
+
throw new NetworkError(`Registration failed: ${res2.status} ${res2.statusText} (non-JSON response)`);
|
|
160
|
+
}
|
|
161
|
+
if (!res2.ok) throw mapApiError(res2.status, data2, "/api/v1/register");
|
|
162
|
+
return data2;
|
|
163
|
+
}
|
|
164
|
+
if (options?.walletChain === "ethereum") {
|
|
165
|
+
if (!options.walletAddress || !options.walletSignFn) {
|
|
166
|
+
throw new ValidationError("Ethereum registration requires walletAddress and walletSignFn");
|
|
167
|
+
}
|
|
168
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(options.walletAddress)) {
|
|
169
|
+
throw new ValidationError("walletAddress must be a valid Ethereum address (0x + 40 hex chars)");
|
|
170
|
+
}
|
|
171
|
+
const challengeRes = await fetch(`${url}/api/v1/register`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers,
|
|
174
|
+
body: JSON.stringify({ name, action: "challenge", wallet_chain: "ethereum" })
|
|
175
|
+
});
|
|
176
|
+
let challengeData;
|
|
177
|
+
try {
|
|
178
|
+
challengeData = await challengeRes.json();
|
|
179
|
+
} catch {
|
|
180
|
+
throw new NetworkError(`Registration challenge failed: ${challengeRes.status} (non-JSON response)`);
|
|
181
|
+
}
|
|
182
|
+
if (!challengeRes.ok || !challengeData.nonce) {
|
|
183
|
+
throw mapApiError(challengeRes.status, challengeData, "/api/v1/register");
|
|
184
|
+
}
|
|
185
|
+
const nonce = challengeData.nonce;
|
|
186
|
+
const message = `provenonce-register-ethereum:${nonce}:${options.walletAddress}:${name}`;
|
|
187
|
+
const walletSignature = await options.walletSignFn(message);
|
|
188
|
+
const registerRes = await fetch(`${url}/api/v1/register`, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers,
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
name,
|
|
193
|
+
wallet_chain: "ethereum",
|
|
194
|
+
wallet_address: options.walletAddress,
|
|
195
|
+
wallet_signature: walletSignature,
|
|
196
|
+
wallet_nonce: nonce,
|
|
197
|
+
...options.metadata && { metadata: options.metadata }
|
|
198
|
+
})
|
|
199
|
+
});
|
|
200
|
+
let data2;
|
|
201
|
+
try {
|
|
202
|
+
data2 = await registerRes.json();
|
|
203
|
+
} catch {
|
|
204
|
+
throw new NetworkError(`Ethereum registration failed: ${registerRes.status} (non-JSON response)`);
|
|
205
|
+
}
|
|
206
|
+
if (!registerRes.ok) throw mapApiError(registerRes.status, data2, "/api/v1/register");
|
|
207
|
+
data2.wallet = {
|
|
208
|
+
public_key: "",
|
|
209
|
+
secret_key: "",
|
|
210
|
+
address: data2.wallet?.address || options.walletAddress,
|
|
211
|
+
chain: "ethereum"
|
|
212
|
+
};
|
|
213
|
+
return data2;
|
|
214
|
+
}
|
|
215
|
+
if (options?.walletModel === "operator") {
|
|
216
|
+
if (!options.operatorWalletAddress || !options.operatorSignFn) {
|
|
217
|
+
throw new ValidationError("Operator registration requires operatorWalletAddress and operatorSignFn");
|
|
218
|
+
}
|
|
219
|
+
const challengeRes = await fetch(`${url}/api/v1/register`, {
|
|
220
|
+
method: "POST",
|
|
221
|
+
headers,
|
|
222
|
+
body: JSON.stringify({ name, action: "challenge", wallet_model: "operator" })
|
|
223
|
+
});
|
|
224
|
+
let challengeData;
|
|
225
|
+
try {
|
|
226
|
+
challengeData = await challengeRes.json();
|
|
227
|
+
} catch {
|
|
228
|
+
throw new NetworkError(`Registration challenge failed: ${challengeRes.status} (non-JSON response)`);
|
|
229
|
+
}
|
|
230
|
+
if (!challengeRes.ok || !challengeData.nonce) {
|
|
231
|
+
throw mapApiError(challengeRes.status, challengeData, "/api/v1/register");
|
|
232
|
+
}
|
|
233
|
+
const nonce = challengeData.nonce;
|
|
234
|
+
const message = `provenonce-register-operator:${nonce}:${options.operatorWalletAddress}:${name}`;
|
|
235
|
+
const walletSignature = await options.operatorSignFn(message);
|
|
236
|
+
const registerRes = await fetch(`${url}/api/v1/register`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers,
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
name,
|
|
241
|
+
wallet_model: "operator",
|
|
242
|
+
operator_wallet_address: options.operatorWalletAddress,
|
|
243
|
+
wallet_signature: walletSignature,
|
|
244
|
+
wallet_nonce: nonce,
|
|
245
|
+
...options.metadata && { metadata: options.metadata }
|
|
246
|
+
})
|
|
247
|
+
});
|
|
248
|
+
let data2;
|
|
249
|
+
try {
|
|
250
|
+
data2 = await registerRes.json();
|
|
251
|
+
} catch {
|
|
252
|
+
throw new NetworkError(`Operator registration failed: ${registerRes.status} (non-JSON response)`);
|
|
253
|
+
}
|
|
254
|
+
if (!registerRes.ok) throw mapApiError(registerRes.status, data2, "/api/v1/register");
|
|
255
|
+
const addr = data2.wallet?.address || data2.wallet?.solana_address || options.operatorWalletAddress;
|
|
256
|
+
data2.wallet = {
|
|
257
|
+
public_key: "",
|
|
258
|
+
secret_key: "",
|
|
259
|
+
solana_address: addr,
|
|
260
|
+
address: addr,
|
|
261
|
+
chain: "solana"
|
|
262
|
+
};
|
|
263
|
+
return data2;
|
|
264
|
+
}
|
|
265
|
+
if (options?.walletModel === "self-custody" || options?.walletSecretKey) {
|
|
266
|
+
let walletKeys;
|
|
267
|
+
if (options?.walletSecretKey) {
|
|
268
|
+
const privRaw = Buffer.from(options.walletSecretKey, "hex");
|
|
269
|
+
const privKeyDer = Buffer.concat([ED25519_PKCS8_PREFIX, privRaw]);
|
|
270
|
+
const keyObject = createPrivateKey({ key: privKeyDer, format: "der", type: "pkcs8" });
|
|
271
|
+
const pubRaw = keyObject.export({ type: "spki", format: "der" }).subarray(12);
|
|
272
|
+
walletKeys = {
|
|
273
|
+
publicKey: Buffer.from(pubRaw).toString("hex"),
|
|
274
|
+
secretKey: options.walletSecretKey
|
|
275
|
+
};
|
|
276
|
+
} else {
|
|
277
|
+
walletKeys = generateWalletKeypair();
|
|
278
|
+
}
|
|
279
|
+
const challengeRes = await fetch(`${url}/api/v1/register`, {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers,
|
|
282
|
+
body: JSON.stringify({ name, action: "challenge" })
|
|
283
|
+
});
|
|
284
|
+
let challengeData;
|
|
285
|
+
try {
|
|
286
|
+
challengeData = await challengeRes.json();
|
|
287
|
+
} catch {
|
|
288
|
+
const err = new NetworkError(`Registration challenge failed: ${challengeRes.status} (non-JSON response)`);
|
|
289
|
+
err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
|
|
290
|
+
throw err;
|
|
291
|
+
}
|
|
292
|
+
if (!challengeRes.ok || !challengeData.nonce) {
|
|
293
|
+
const err = mapApiError(challengeRes.status, challengeData, "/api/v1/register");
|
|
294
|
+
err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
|
|
295
|
+
throw err;
|
|
296
|
+
}
|
|
297
|
+
const nonce = challengeData.nonce;
|
|
298
|
+
const message = `provenonce-register:${nonce}:${walletKeys.publicKey}:${name}`;
|
|
299
|
+
const walletSignature = signMessage(walletKeys.secretKey, message);
|
|
300
|
+
const registerRes = await fetch(`${url}/api/v1/register`, {
|
|
301
|
+
method: "POST",
|
|
302
|
+
headers,
|
|
303
|
+
body: JSON.stringify({
|
|
304
|
+
name,
|
|
305
|
+
wallet_public_key: walletKeys.publicKey,
|
|
306
|
+
wallet_signature: walletSignature,
|
|
307
|
+
wallet_nonce: nonce,
|
|
308
|
+
...options?.metadata && { metadata: options.metadata }
|
|
309
|
+
})
|
|
310
|
+
});
|
|
311
|
+
let data2;
|
|
312
|
+
try {
|
|
313
|
+
data2 = await registerRes.json();
|
|
314
|
+
} catch {
|
|
315
|
+
const err = new NetworkError(`Registration failed: ${registerRes.status} (non-JSON response)`);
|
|
316
|
+
err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
|
|
317
|
+
throw err;
|
|
318
|
+
}
|
|
319
|
+
if (!registerRes.ok) {
|
|
320
|
+
const err = mapApiError(registerRes.status, data2, "/api/v1/register");
|
|
321
|
+
err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
|
|
322
|
+
throw err;
|
|
323
|
+
}
|
|
324
|
+
const addr = data2.wallet?.address || data2.wallet?.solana_address || "";
|
|
325
|
+
data2.wallet = {
|
|
326
|
+
public_key: walletKeys.publicKey,
|
|
327
|
+
secret_key: walletKeys.secretKey,
|
|
328
|
+
solana_address: addr,
|
|
329
|
+
address: addr,
|
|
330
|
+
chain: "solana"
|
|
331
|
+
};
|
|
332
|
+
return data2;
|
|
333
|
+
}
|
|
25
334
|
const res = await fetch(`${url}/api/v1/register`, {
|
|
26
335
|
method: "POST",
|
|
27
336
|
headers,
|
|
28
|
-
body: JSON.stringify(
|
|
337
|
+
body: JSON.stringify({ name, ...options?.metadata && { metadata: options.metadata } })
|
|
29
338
|
});
|
|
30
|
-
|
|
31
|
-
|
|
339
|
+
let data;
|
|
340
|
+
try {
|
|
341
|
+
data = await res.json();
|
|
342
|
+
} catch {
|
|
343
|
+
throw new NetworkError(`Registration failed: ${res.status} ${res.statusText} (non-JSON response)`);
|
|
344
|
+
}
|
|
345
|
+
if (!res.ok) throw mapApiError(res.status, data, "/api/v1/register");
|
|
32
346
|
return data;
|
|
33
347
|
}
|
|
34
348
|
var BeatAgent = class {
|
|
@@ -43,13 +357,36 @@ var BeatAgent = class {
|
|
|
43
357
|
this.heartbeatInterval = null;
|
|
44
358
|
this.globalBeat = 0;
|
|
45
359
|
this.globalAnchorHash = "";
|
|
360
|
+
// ── PHASE 2: SIGIL + HEARTBEAT + PROOF ──
|
|
361
|
+
/** Cached lineage proof from the most recent heartbeat or SIGIL purchase */
|
|
362
|
+
this.cachedProof = null;
|
|
363
|
+
if (!config.apiKey || typeof config.apiKey !== "string") {
|
|
364
|
+
throw new ValidationError("BeatAgentConfig.apiKey is required (must be a non-empty string)");
|
|
365
|
+
}
|
|
366
|
+
if (!config.registryUrl || typeof config.registryUrl !== "string") {
|
|
367
|
+
throw new ValidationError("BeatAgentConfig.registryUrl is required (must be a non-empty string)");
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
new URL(config.registryUrl);
|
|
371
|
+
} catch {
|
|
372
|
+
throw new ValidationError("BeatAgentConfig.registryUrl is not a valid URL");
|
|
373
|
+
}
|
|
374
|
+
if (config.beatsPerPulse !== void 0 && (!Number.isInteger(config.beatsPerPulse) || config.beatsPerPulse < 1 || config.beatsPerPulse > 1e4)) {
|
|
375
|
+
throw new ValidationError("BeatAgentConfig.beatsPerPulse must be an integer between 1 and 10000");
|
|
376
|
+
}
|
|
377
|
+
if (config.checkinIntervalSec !== void 0 && (!Number.isFinite(config.checkinIntervalSec) || config.checkinIntervalSec < 10 || config.checkinIntervalSec > 86400)) {
|
|
378
|
+
throw new ValidationError("BeatAgentConfig.checkinIntervalSec must be between 10 and 86400");
|
|
379
|
+
}
|
|
46
380
|
this.config = {
|
|
47
381
|
beatsPerPulse: 10,
|
|
48
382
|
checkinIntervalSec: 300,
|
|
383
|
+
heartbeatIntervalSec: 300,
|
|
49
384
|
onPulse: () => {
|
|
50
385
|
},
|
|
51
386
|
onCheckin: () => {
|
|
52
387
|
},
|
|
388
|
+
onHeartbeat: () => {
|
|
389
|
+
},
|
|
53
390
|
onError: () => {
|
|
54
391
|
},
|
|
55
392
|
onStatusChange: () => {
|
|
@@ -98,26 +435,42 @@ var BeatAgent = class {
|
|
|
98
435
|
}
|
|
99
436
|
// ── PULSE (COMPUTE BEATS) ──
|
|
100
437
|
/**
|
|
438
|
+
* @deprecated Phase 2: VDF computation retired (D-68). Payment is the liveness mechanism.
|
|
439
|
+
* Use heartbeat() instead. This method will be removed in the next major version.
|
|
440
|
+
*
|
|
101
441
|
* Compute N beats locally (VDF hash chain).
|
|
102
|
-
* This is the "heartbeat" — proof that the agent has lived
|
|
103
|
-
* through a specific window of computational time.
|
|
104
442
|
*/
|
|
105
443
|
pulse(count) {
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
throw new
|
|
444
|
+
console.warn("[Provenonce SDK] pulse() is deprecated. Use heartbeat() instead (Phase 2).");
|
|
445
|
+
if (this.status === "frozen") {
|
|
446
|
+
throw new FrozenError("Cannot pulse: agent is frozen. Use resync() to re-establish provenance.");
|
|
109
447
|
}
|
|
110
448
|
if (this.status !== "active") {
|
|
111
|
-
throw new
|
|
449
|
+
throw new StateError(`Cannot pulse: agent is ${this.status}.`, this.status);
|
|
450
|
+
}
|
|
451
|
+
if (count !== void 0 && (!Number.isInteger(count) || count < 1 || count > 1e4)) {
|
|
452
|
+
throw new ValidationError("pulse count must be an integer between 1 and 10000");
|
|
453
|
+
}
|
|
454
|
+
return this.computeBeats(count);
|
|
455
|
+
}
|
|
456
|
+
/** Internal beat computation — no status check. Used by both pulse() and resync(). */
|
|
457
|
+
computeBeats(count, onProgress) {
|
|
458
|
+
const n = count || this.config.beatsPerPulse;
|
|
459
|
+
if (!this.latestBeat) {
|
|
460
|
+
throw new StateError("Beat chain not initialized. Call init() first.", "uninitialized", "AGENT_NOT_INITIALIZED" /* AGENT_NOT_INITIALIZED */);
|
|
112
461
|
}
|
|
113
462
|
const newBeats = [];
|
|
114
463
|
let prevHash = this.latestBeat.hash;
|
|
115
464
|
let startIndex = this.latestBeat.index + 1;
|
|
116
465
|
const t0 = Date.now();
|
|
466
|
+
const progressInterval = Math.max(1, Math.floor(n / 10));
|
|
117
467
|
for (let i = 0; i < n; i++) {
|
|
118
468
|
const beat = computeBeat(prevHash, startIndex + i, this.difficulty, void 0, this.globalAnchorHash || void 0);
|
|
119
469
|
newBeats.push(beat);
|
|
120
470
|
prevHash = beat.hash;
|
|
471
|
+
if (onProgress && (i + 1) % progressInterval === 0) {
|
|
472
|
+
onProgress(i + 1, n);
|
|
473
|
+
}
|
|
121
474
|
}
|
|
122
475
|
const elapsed = Date.now() - t0;
|
|
123
476
|
this.chain.push(...newBeats);
|
|
@@ -132,27 +485,31 @@ var BeatAgent = class {
|
|
|
132
485
|
}
|
|
133
486
|
// ── CHECK-IN ──
|
|
134
487
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* "To remain on the Whitelist, an agent must periodically
|
|
138
|
-
* submit a proof of its Local Beats to the Registry."
|
|
488
|
+
* @deprecated Phase 2: VDF check-in retired (D-68). Use heartbeat() instead.
|
|
489
|
+
* This method will be removed in the next major version.
|
|
139
490
|
*/
|
|
140
491
|
async checkin() {
|
|
141
|
-
|
|
492
|
+
console.warn("[Provenonce SDK] checkin() is deprecated. Use heartbeat() instead (Phase 2).");
|
|
493
|
+
if (!this.latestBeat || this.latestBeat.index <= this.lastCheckinBeat) {
|
|
494
|
+
this.log("No new beats since last check-in. Call pulse() first.");
|
|
142
495
|
return { ok: true, total_beats: this.totalBeats };
|
|
143
496
|
}
|
|
144
497
|
try {
|
|
498
|
+
const fromBeat = this.lastCheckinBeat;
|
|
499
|
+
const toBeat = this.latestBeat.index;
|
|
145
500
|
const spotChecks = [];
|
|
146
|
-
const
|
|
147
|
-
|
|
501
|
+
const toBeatEntry = this.chain.find((b) => b.index === toBeat);
|
|
502
|
+
if (toBeatEntry) {
|
|
503
|
+
spotChecks.push({ index: toBeatEntry.index, hash: toBeatEntry.hash, prev: toBeatEntry.prev, nonce: toBeatEntry.nonce });
|
|
504
|
+
}
|
|
505
|
+
const available = this.chain.filter((b) => b.index > this.lastCheckinBeat && b.index !== toBeat);
|
|
506
|
+
const sampleCount = Math.min(4, available.length);
|
|
148
507
|
for (let i = 0; i < sampleCount; i++) {
|
|
149
508
|
const idx = Math.floor(Math.random() * available.length);
|
|
150
509
|
const beat = available[idx];
|
|
151
510
|
spotChecks.push({ index: beat.index, hash: beat.hash, prev: beat.prev, nonce: beat.nonce });
|
|
152
511
|
available.splice(idx, 1);
|
|
153
512
|
}
|
|
154
|
-
const fromBeat = this.lastCheckinBeat;
|
|
155
|
-
const toBeat = this.latestBeat.index;
|
|
156
513
|
const fromHash = this.chain.find((b) => b.index === fromBeat)?.hash || this.genesisHash;
|
|
157
514
|
const toHash = this.latestBeat.hash;
|
|
158
515
|
const res = await this.api("POST", "/api/v1/agent/checkin", {
|
|
@@ -186,52 +543,57 @@ var BeatAgent = class {
|
|
|
186
543
|
// ── AUTONOMOUS HEARTBEAT ──
|
|
187
544
|
/**
|
|
188
545
|
* Start the autonomous heartbeat loop.
|
|
189
|
-
*
|
|
190
|
-
*
|
|
546
|
+
* Phase 2: Sends paid heartbeats at regular intervals.
|
|
547
|
+
*
|
|
548
|
+
* @param paymentTxFn - Optional function that returns a payment tx for each heartbeat.
|
|
549
|
+
* If not provided, uses 'devnet-skip' (devnet only).
|
|
191
550
|
*/
|
|
192
|
-
startHeartbeat() {
|
|
551
|
+
startHeartbeat(paymentTxFn) {
|
|
193
552
|
if (this.heartbeatInterval) {
|
|
194
553
|
this.log("Heartbeat already running.");
|
|
195
554
|
return;
|
|
196
555
|
}
|
|
197
|
-
if (this.status !== "active") {
|
|
198
|
-
throw new
|
|
556
|
+
if (this.status !== "active" && this.status !== "uninitialized") {
|
|
557
|
+
throw new StateError(`Cannot start heartbeat in status '${this.status}'.`, this.status);
|
|
199
558
|
}
|
|
200
|
-
this.
|
|
559
|
+
const intervalSec = this.config.heartbeatIntervalSec || this.config.checkinIntervalSec || 300;
|
|
560
|
+
this.log(`Starting heartbeat (interval: ${intervalSec}s)...`);
|
|
561
|
+
let consecutiveErrors = 0;
|
|
562
|
+
let skipCount = 0;
|
|
201
563
|
this.heartbeatInterval = setInterval(async () => {
|
|
564
|
+
if (skipCount > 0) {
|
|
565
|
+
skipCount--;
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
202
568
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (shouldCheckin) {
|
|
207
|
-
await this.checkin();
|
|
208
|
-
await this.syncGlobal();
|
|
209
|
-
}
|
|
569
|
+
const paymentTx = paymentTxFn ? await paymentTxFn() : "devnet-skip";
|
|
570
|
+
await this.heartbeat(paymentTx);
|
|
571
|
+
consecutiveErrors = 0;
|
|
210
572
|
} catch (err) {
|
|
573
|
+
consecutiveErrors++;
|
|
211
574
|
this.config.onError(err, "heartbeat");
|
|
575
|
+
skipCount = Math.min(32, Math.pow(2, consecutiveErrors - 1));
|
|
576
|
+
this.log(`Heartbeat error #${consecutiveErrors}, backing off ${skipCount} ticks`);
|
|
212
577
|
}
|
|
213
|
-
},
|
|
578
|
+
}, intervalSec * 1e3);
|
|
214
579
|
}
|
|
215
580
|
/**
|
|
216
|
-
* Stop the heartbeat
|
|
217
|
-
* Must call resync() when waking up.
|
|
581
|
+
* Stop the heartbeat loop.
|
|
218
582
|
*/
|
|
219
583
|
stopHeartbeat() {
|
|
220
584
|
if (this.heartbeatInterval) {
|
|
221
585
|
clearInterval(this.heartbeatInterval);
|
|
222
586
|
this.heartbeatInterval = null;
|
|
223
|
-
this.log("
|
|
587
|
+
this.log("Heartbeat stopped.");
|
|
224
588
|
}
|
|
225
589
|
}
|
|
226
590
|
// ── RE-SYNC ──
|
|
227
591
|
/**
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* "When an agent powers down, its time 'freezes.' Upon waking,
|
|
231
|
-
* it must perform a Re-Sync Challenge with the Registry to
|
|
232
|
-
* fill the 'Temporal Gap' and re-establish its provenance."
|
|
592
|
+
* @deprecated Phase 2: Resync retired (D-67). Dormancy resume is free — just call heartbeat().
|
|
593
|
+
* This method will be removed in the next major version.
|
|
233
594
|
*/
|
|
234
595
|
async resync() {
|
|
596
|
+
console.warn("[Provenonce SDK] resync() is deprecated (D-67). Use heartbeat() to resume (Phase 2).");
|
|
235
597
|
try {
|
|
236
598
|
this.log("Requesting re-sync challenge...");
|
|
237
599
|
const challenge = await this.api("POST", "/api/v1/agent/resync", {
|
|
@@ -243,12 +605,13 @@ var BeatAgent = class {
|
|
|
243
605
|
const required = challenge.challenge.required_beats;
|
|
244
606
|
this.difficulty = challenge.challenge.difficulty;
|
|
245
607
|
this.log(`Re-sync challenge: compute ${required} beats at D=${this.difficulty}`);
|
|
608
|
+
await this.syncGlobal();
|
|
246
609
|
const startHash = challenge.challenge.start_from_hash;
|
|
247
610
|
const startBeat = challenge.challenge.start_from_beat;
|
|
248
611
|
this.latestBeat = { index: startBeat, hash: startHash, prev: "", timestamp: Date.now() };
|
|
249
612
|
this.chain = [this.latestBeat];
|
|
250
613
|
const t0 = Date.now();
|
|
251
|
-
this.
|
|
614
|
+
this.computeBeats(required);
|
|
252
615
|
const elapsed = Date.now() - t0;
|
|
253
616
|
this.log(`Re-sync beats computed in ${elapsed}ms`);
|
|
254
617
|
const proof = await this.api("POST", "/api/v1/agent/resync", {
|
|
@@ -261,7 +624,15 @@ var BeatAgent = class {
|
|
|
261
624
|
to_hash: this.latestBeat.hash,
|
|
262
625
|
beats_computed: required,
|
|
263
626
|
global_anchor: challenge.challenge.sync_to_global,
|
|
264
|
-
|
|
627
|
+
anchor_hash: this.globalAnchorHash || void 0,
|
|
628
|
+
spot_checks: (() => {
|
|
629
|
+
const toBeatEntry = this.chain.find((b) => b.index === this.latestBeat.index);
|
|
630
|
+
const available = this.chain.filter((b) => b.index !== this.latestBeat.index && b.index > startBeat);
|
|
631
|
+
const step = Math.max(1, Math.ceil(available.length / 5));
|
|
632
|
+
const others = available.filter((_, i) => i % step === 0).slice(0, 4);
|
|
633
|
+
const checks = toBeatEntry ? [toBeatEntry, ...others] : others;
|
|
634
|
+
return checks.map((b) => ({ index: b.index, hash: b.hash, prev: b.prev, nonce: b.nonce }));
|
|
635
|
+
})()
|
|
265
636
|
}
|
|
266
637
|
});
|
|
267
638
|
if (proof.ok) {
|
|
@@ -284,6 +655,14 @@ var BeatAgent = class {
|
|
|
284
655
|
*/
|
|
285
656
|
async requestSpawn(childName, childHash) {
|
|
286
657
|
try {
|
|
658
|
+
if (childName !== void 0) {
|
|
659
|
+
if (typeof childName !== "string" || childName.trim().length === 0) {
|
|
660
|
+
throw new ValidationError("childName must be a non-empty string");
|
|
661
|
+
}
|
|
662
|
+
if (childName.length > 64) {
|
|
663
|
+
throw new ValidationError("childName must be 64 characters or fewer");
|
|
664
|
+
}
|
|
665
|
+
}
|
|
287
666
|
const res = await this.api("POST", "/api/v1/agent/spawn", {
|
|
288
667
|
child_name: childName,
|
|
289
668
|
child_hash: childHash
|
|
@@ -299,6 +678,147 @@ var BeatAgent = class {
|
|
|
299
678
|
throw err;
|
|
300
679
|
}
|
|
301
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Purchase a SIGIL (cryptographic identity) for this agent.
|
|
683
|
+
* SIGILs gate heartbeating, lineage proofs, and offline verification.
|
|
684
|
+
* One-time purchase — cannot be re-purchased.
|
|
685
|
+
*
|
|
686
|
+
* @param identityClass - 'narrow_task' (0.05 SOL), 'autonomous' (0.15 SOL), or 'orchestrator' (0.35 SOL)
|
|
687
|
+
* @param paymentTx - Solana transaction signature proving payment. Use 'devnet-skip' on devnet.
|
|
688
|
+
*/
|
|
689
|
+
async purchaseSigil(identityClass, paymentTx) {
|
|
690
|
+
if (!identityClass || !["narrow_task", "autonomous", "orchestrator"].includes(identityClass)) {
|
|
691
|
+
throw new ValidationError("identityClass must be narrow_task, autonomous, or orchestrator");
|
|
692
|
+
}
|
|
693
|
+
if (!paymentTx || typeof paymentTx !== "string") {
|
|
694
|
+
throw new ValidationError('paymentTx is required (Solana transaction signature or "devnet-skip")');
|
|
695
|
+
}
|
|
696
|
+
try {
|
|
697
|
+
const res = await this.api("POST", "/api/v1/sigil", {
|
|
698
|
+
identity_class: identityClass,
|
|
699
|
+
payment_tx: paymentTx
|
|
700
|
+
});
|
|
701
|
+
if (res.lineage_proof) {
|
|
702
|
+
this.cachedProof = res.lineage_proof;
|
|
703
|
+
}
|
|
704
|
+
this.log(`SIGIL purchased: ${identityClass}`);
|
|
705
|
+
this.config.onStatusChange("sigil_issued", { identity_class: identityClass });
|
|
706
|
+
return {
|
|
707
|
+
ok: true,
|
|
708
|
+
sigil: res.sigil,
|
|
709
|
+
lineage_proof: res.lineage_proof,
|
|
710
|
+
fee: res.fee
|
|
711
|
+
};
|
|
712
|
+
} catch (err) {
|
|
713
|
+
this.config.onError(err, "purchaseSigil");
|
|
714
|
+
return { ok: false, error: err.message };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Send a paid heartbeat to the registry.
|
|
719
|
+
* Requires a SIGIL. Returns a signed lineage proof.
|
|
720
|
+
* This is the Phase 2 replacement for pulse() + checkin().
|
|
721
|
+
*
|
|
722
|
+
* @param paymentTx - Solana transaction signature. Omit or 'devnet-skip' on devnet.
|
|
723
|
+
* @param globalAnchor - Optional: the global anchor index to reference.
|
|
724
|
+
*/
|
|
725
|
+
async heartbeat(paymentTx, globalAnchor) {
|
|
726
|
+
try {
|
|
727
|
+
const res = await this.api("POST", "/api/v1/agent/heartbeat", {
|
|
728
|
+
payment_tx: paymentTx || "devnet-skip",
|
|
729
|
+
global_anchor: globalAnchor
|
|
730
|
+
});
|
|
731
|
+
if (res.lineage_proof) {
|
|
732
|
+
this.cachedProof = res.lineage_proof;
|
|
733
|
+
}
|
|
734
|
+
if (res.ok) {
|
|
735
|
+
this.status = "active";
|
|
736
|
+
const onHb = this.config.onHeartbeat || this.config.onCheckin;
|
|
737
|
+
if (onHb) onHb(res);
|
|
738
|
+
this.log(`Heartbeat accepted: epoch=${res.billing_epoch}, count=${res.heartbeat_count_epoch}`);
|
|
739
|
+
}
|
|
740
|
+
return {
|
|
741
|
+
ok: res.ok,
|
|
742
|
+
lineage_proof: res.lineage_proof,
|
|
743
|
+
heartbeat_count_epoch: res.heartbeat_count_epoch,
|
|
744
|
+
billing_epoch: res.billing_epoch,
|
|
745
|
+
current_beat: res.current_beat,
|
|
746
|
+
fee: res.fee
|
|
747
|
+
};
|
|
748
|
+
} catch (err) {
|
|
749
|
+
this.config.onError(err, "heartbeat");
|
|
750
|
+
return { ok: false, error: err.message };
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Reissue a lineage proof. "Reprint, not a renewal."
|
|
755
|
+
* Does NOT create a new lineage event.
|
|
756
|
+
*
|
|
757
|
+
* @param paymentTx - Solana transaction signature. Omit or 'devnet-skip' on devnet.
|
|
758
|
+
*/
|
|
759
|
+
async reissueProof(paymentTx) {
|
|
760
|
+
try {
|
|
761
|
+
const res = await this.api("POST", "/api/v1/agent/reissue-proof", {
|
|
762
|
+
payment_tx: paymentTx || "devnet-skip"
|
|
763
|
+
});
|
|
764
|
+
if (res.lineage_proof) {
|
|
765
|
+
this.cachedProof = res.lineage_proof;
|
|
766
|
+
}
|
|
767
|
+
return { ok: true, lineage_proof: res.lineage_proof };
|
|
768
|
+
} catch (err) {
|
|
769
|
+
this.config.onError(err, "reissueProof");
|
|
770
|
+
return { ok: false, error: err.message };
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get the latest cached lineage proof (no network call).
|
|
775
|
+
* Returns null if no proof has been obtained yet.
|
|
776
|
+
*/
|
|
777
|
+
getLatestProof() {
|
|
778
|
+
return this.cachedProof;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Get the agent's passport (alias for getLatestProof).
|
|
782
|
+
* The passport is the agent's portable, offline-verifiable credential.
|
|
783
|
+
* Returns null if no passport has been issued yet (requires SIGIL + heartbeat).
|
|
784
|
+
*/
|
|
785
|
+
getPassport() {
|
|
786
|
+
return this.cachedProof;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Verify a lineage proof locally using the authority public key.
|
|
790
|
+
* Offline verification — no API call, no SOL cost.
|
|
791
|
+
*
|
|
792
|
+
* @param proof - The LineageProof to verify
|
|
793
|
+
* @param authorityPubKeyHex - 32-byte hex-encoded Ed25519 public key from /.well-known/provenonce-authority.json
|
|
794
|
+
*/
|
|
795
|
+
static verifyProofLocally(proof, authorityPubKeyHex) {
|
|
796
|
+
try {
|
|
797
|
+
if (Date.now() > proof.valid_until) {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
const canonical = JSON.stringify({
|
|
801
|
+
agent_hash: proof.agent_hash,
|
|
802
|
+
agent_public_key: proof.agent_public_key,
|
|
803
|
+
identity_class: proof.identity_class,
|
|
804
|
+
registered_at_beat: proof.registered_at_beat,
|
|
805
|
+
sigil_issued_at_beat: proof.sigil_issued_at_beat,
|
|
806
|
+
last_heartbeat_beat: proof.last_heartbeat_beat,
|
|
807
|
+
lineage_chain_hash: proof.lineage_chain_hash,
|
|
808
|
+
issued_at: proof.issued_at,
|
|
809
|
+
valid_until: proof.valid_until
|
|
810
|
+
});
|
|
811
|
+
const pubBytes = Buffer.from(authorityPubKeyHex, "hex");
|
|
812
|
+
if (pubBytes.length !== 32) return false;
|
|
813
|
+
const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
814
|
+
const pubKeyDer = Buffer.concat([ED25519_SPKI_PREFIX, pubBytes]);
|
|
815
|
+
const keyObject = createPublicKey({ key: pubKeyDer, format: "der", type: "spki" });
|
|
816
|
+
const sigBuffer = Buffer.from(proof.provenonce_signature, "hex");
|
|
817
|
+
return verify(null, Buffer.from(canonical), keyObject, sigBuffer);
|
|
818
|
+
} catch {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
302
822
|
// ── STATUS ──
|
|
303
823
|
/**
|
|
304
824
|
* Get this agent's full beat status from the registry.
|
|
@@ -328,7 +848,10 @@ var BeatAgent = class {
|
|
|
328
848
|
// ── INTERNALS ──
|
|
329
849
|
async syncGlobal() {
|
|
330
850
|
try {
|
|
331
|
-
const
|
|
851
|
+
const controller = new AbortController();
|
|
852
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
853
|
+
const res = await fetch(`${this.config.registryUrl}/api/v1/beat/anchor`, { signal: controller.signal });
|
|
854
|
+
clearTimeout(timeout);
|
|
332
855
|
const data = await res.json();
|
|
333
856
|
if (data.anchor) {
|
|
334
857
|
this.globalBeat = data.anchor.beat_index;
|
|
@@ -346,23 +869,51 @@ var BeatAgent = class {
|
|
|
346
869
|
this.totalBeats = res.total_beats;
|
|
347
870
|
this.genesisHash = res.genesis_hash;
|
|
348
871
|
this.status = res.status;
|
|
872
|
+
this.difficulty = res.difficulty || this.difficulty;
|
|
873
|
+
this.lastCheckinBeat = res.last_checkin_beat || 0;
|
|
874
|
+
if (!this.latestBeat && this.genesisHash) {
|
|
875
|
+
this.latestBeat = {
|
|
876
|
+
index: res.latest_beat || this.totalBeats,
|
|
877
|
+
hash: res.latest_hash || this.genesisHash,
|
|
878
|
+
prev: "0".repeat(64),
|
|
879
|
+
timestamp: Date.now()
|
|
880
|
+
};
|
|
881
|
+
this.chain = [this.latestBeat];
|
|
882
|
+
}
|
|
349
883
|
}
|
|
350
884
|
return res;
|
|
351
885
|
}
|
|
352
886
|
async api(method, path, body) {
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
887
|
+
const controller = new AbortController();
|
|
888
|
+
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
889
|
+
try {
|
|
890
|
+
const res = await fetch(`${this.config.registryUrl}${path}`, {
|
|
891
|
+
method,
|
|
892
|
+
headers: {
|
|
893
|
+
"Content-Type": "application/json",
|
|
894
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
895
|
+
},
|
|
896
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
897
|
+
signal: controller.signal
|
|
898
|
+
});
|
|
899
|
+
let data;
|
|
900
|
+
try {
|
|
901
|
+
data = await res.json();
|
|
902
|
+
} catch {
|
|
903
|
+
throw new NetworkError(`API error: ${res.status} non-JSON response from ${path}`);
|
|
904
|
+
}
|
|
905
|
+
if (!res.ok && !data.ok && !data.already_initialized && !data.eligible) {
|
|
906
|
+
throw mapApiError(res.status, data, path);
|
|
907
|
+
}
|
|
908
|
+
return data;
|
|
909
|
+
} catch (err) {
|
|
910
|
+
if (err.name === "AbortError") {
|
|
911
|
+
throw new NetworkError(`Request timeout: ${method} ${path}`, "TIMEOUT" /* TIMEOUT */);
|
|
912
|
+
}
|
|
913
|
+
throw err;
|
|
914
|
+
} finally {
|
|
915
|
+
clearTimeout(timeout);
|
|
364
916
|
}
|
|
365
|
-
return data;
|
|
366
917
|
}
|
|
367
918
|
log(msg) {
|
|
368
919
|
if (this.config.verbose) {
|
|
@@ -371,6 +922,12 @@ var BeatAgent = class {
|
|
|
371
922
|
}
|
|
372
923
|
};
|
|
373
924
|
function computeBeatsLite(startHash, startIndex, count, difficulty = 1e3, anchorHash) {
|
|
925
|
+
if (!startHash || typeof startHash !== "string") {
|
|
926
|
+
throw new ValidationError("computeBeatsLite: startHash must be a non-empty string");
|
|
927
|
+
}
|
|
928
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
929
|
+
throw new ValidationError("computeBeatsLite: count must be a positive integer");
|
|
930
|
+
}
|
|
374
931
|
const t0 = Date.now();
|
|
375
932
|
let prev = startHash;
|
|
376
933
|
let lastBeat = null;
|
|
@@ -381,9 +938,20 @@ function computeBeatsLite(startHash, startIndex, count, difficulty = 1e3, anchor
|
|
|
381
938
|
return { lastBeat, elapsed: Date.now() - t0 };
|
|
382
939
|
}
|
|
383
940
|
export {
|
|
941
|
+
AuthError,
|
|
384
942
|
BeatAgent,
|
|
943
|
+
ErrorCode,
|
|
944
|
+
FrozenError,
|
|
945
|
+
NetworkError,
|
|
946
|
+
NotFoundError,
|
|
947
|
+
ProvenonceError,
|
|
948
|
+
RateLimitError,
|
|
949
|
+
ServerError,
|
|
950
|
+
StateError,
|
|
951
|
+
ValidationError,
|
|
385
952
|
computeBeat,
|
|
386
953
|
computeBeatsLite,
|
|
954
|
+
generateWalletKeypair,
|
|
387
955
|
register
|
|
388
956
|
};
|
|
389
957
|
//# sourceMappingURL=index.mjs.map
|