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