@provenonce/sdk 0.5.0 → 0.8.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/dist/index.js CHANGED
@@ -22,19 +22,256 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  BeatAgent: () => BeatAgent,
24
24
  computeBeat: () => computeBeat,
25
- computeBeatsLite: () => computeBeatsLite
25
+ computeBeatsLite: () => computeBeatsLite,
26
+ generateWalletKeypair: () => generateWalletKeypair,
27
+ register: () => register
26
28
  });
27
29
  module.exports = __toCommonJS(index_exports);
28
30
 
29
31
  // src/beat-sdk.ts
30
32
  var import_crypto = require("crypto");
31
- function computeBeat(prevHash, beatIndex, difficulty, nonce) {
33
+ function computeBeat(prevHash, beatIndex, difficulty, nonce, anchorHash) {
32
34
  const timestamp = Date.now();
33
- let current = (0, import_crypto.createHash)("sha256").update(`${prevHash}:${beatIndex}:${nonce || ""}`).digest("hex");
35
+ const seed = anchorHash ? `${prevHash}:${beatIndex}:${nonce || ""}:${anchorHash}` : `${prevHash}:${beatIndex}:${nonce || ""}`;
36
+ let current = (0, import_crypto.createHash)("sha256").update(seed).digest("hex");
34
37
  for (let i = 0; i < difficulty; i++) {
35
38
  current = (0, import_crypto.createHash)("sha256").update(current).digest("hex");
36
39
  }
37
- return { index: beatIndex, hash: current, prev: prevHash, timestamp, nonce };
40
+ return { index: beatIndex, hash: current, prev: prevHash, timestamp, nonce, anchor_hash: anchorHash };
41
+ }
42
+ var ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
43
+ function generateWalletKeypair() {
44
+ const { publicKey, privateKey } = (0, import_crypto.generateKeyPairSync)("ed25519");
45
+ const pubRaw = publicKey.export({ type: "spki", format: "der" }).subarray(12);
46
+ const privRaw = privateKey.export({ type: "pkcs8", format: "der" }).subarray(16);
47
+ return {
48
+ publicKey: Buffer.from(pubRaw).toString("hex"),
49
+ secretKey: Buffer.from(privRaw).toString("hex")
50
+ };
51
+ }
52
+ function signMessage(secretKeyHex, message) {
53
+ const privRaw = Buffer.from(secretKeyHex, "hex");
54
+ const privKeyDer = Buffer.concat([ED25519_PKCS8_PREFIX, privRaw]);
55
+ const keyObject = (0, import_crypto.createPrivateKey)({ key: privKeyDer, format: "der", type: "pkcs8" });
56
+ const sig = (0, import_crypto.sign)(null, Buffer.from(message), keyObject);
57
+ return Buffer.from(sig).toString("hex");
58
+ }
59
+ async function register(name, options) {
60
+ if (!name || typeof name !== "string" || name.trim().length === 0) {
61
+ throw new Error("name is required (must be a non-empty string)");
62
+ }
63
+ if (name.length > 64) {
64
+ throw new Error("name must be 64 characters or fewer");
65
+ }
66
+ const url = options?.registryUrl || "https://provenonce.io";
67
+ try {
68
+ new URL(url);
69
+ } catch {
70
+ throw new Error("registryUrl is not a valid URL");
71
+ }
72
+ const headers = { "Content-Type": "application/json" };
73
+ if (options?.registrationSecret) {
74
+ headers["x-registration-secret"] = options.registrationSecret;
75
+ }
76
+ if (options?.parentHash) {
77
+ if (options.parentApiKey) {
78
+ headers["Authorization"] = `Bearer ${options.parentApiKey}`;
79
+ }
80
+ const res2 = await fetch(`${url}/api/v1/register`, {
81
+ method: "POST",
82
+ headers,
83
+ body: JSON.stringify({ name, parent: options.parentHash, ...options.metadata && { metadata: options.metadata } })
84
+ });
85
+ let data2;
86
+ try {
87
+ data2 = await res2.json();
88
+ } catch {
89
+ throw new Error(`Registration failed: ${res2.status} ${res2.statusText} (non-JSON response)`);
90
+ }
91
+ if (!res2.ok) throw new Error(data2.error || "Registration failed");
92
+ return data2;
93
+ }
94
+ if (options?.walletChain === "ethereum") {
95
+ if (!options.walletAddress || !options.walletSignFn) {
96
+ throw new Error("Ethereum registration requires walletAddress and walletSignFn");
97
+ }
98
+ if (!/^0x[0-9a-fA-F]{40}$/.test(options.walletAddress)) {
99
+ throw new Error("walletAddress must be a valid Ethereum address (0x + 40 hex chars)");
100
+ }
101
+ const challengeRes = await fetch(`${url}/api/v1/register`, {
102
+ method: "POST",
103
+ headers,
104
+ body: JSON.stringify({ name, action: "challenge", wallet_chain: "ethereum" })
105
+ });
106
+ let challengeData;
107
+ try {
108
+ challengeData = await challengeRes.json();
109
+ } catch {
110
+ throw new Error(`Registration challenge failed: ${challengeRes.status} (non-JSON response)`);
111
+ }
112
+ if (!challengeRes.ok || !challengeData.nonce) {
113
+ throw new Error(challengeData.error || "Failed to get registration challenge");
114
+ }
115
+ const nonce = challengeData.nonce;
116
+ const message = `provenonce-register-ethereum:${nonce}:${options.walletAddress}:${name}`;
117
+ const walletSignature = await options.walletSignFn(message);
118
+ const registerRes = await fetch(`${url}/api/v1/register`, {
119
+ method: "POST",
120
+ headers,
121
+ body: JSON.stringify({
122
+ name,
123
+ wallet_chain: "ethereum",
124
+ wallet_address: options.walletAddress,
125
+ wallet_signature: walletSignature,
126
+ wallet_nonce: nonce,
127
+ ...options.metadata && { metadata: options.metadata }
128
+ })
129
+ });
130
+ let data2;
131
+ try {
132
+ data2 = await registerRes.json();
133
+ } catch {
134
+ throw new Error(`Ethereum registration failed: ${registerRes.status} (non-JSON response)`);
135
+ }
136
+ if (!registerRes.ok) throw new Error(data2.error || "Registration failed");
137
+ data2.wallet = {
138
+ public_key: "",
139
+ secret_key: "",
140
+ address: data2.wallet?.address || options.walletAddress,
141
+ chain: "ethereum"
142
+ };
143
+ return data2;
144
+ }
145
+ if (options?.walletModel === "operator") {
146
+ if (!options.operatorWalletAddress || !options.operatorSignFn) {
147
+ throw new Error("Operator registration requires operatorWalletAddress and operatorSignFn");
148
+ }
149
+ const challengeRes = await fetch(`${url}/api/v1/register`, {
150
+ method: "POST",
151
+ headers,
152
+ body: JSON.stringify({ name, action: "challenge", wallet_model: "operator" })
153
+ });
154
+ let challengeData;
155
+ try {
156
+ challengeData = await challengeRes.json();
157
+ } catch {
158
+ throw new Error(`Registration challenge failed: ${challengeRes.status} (non-JSON response)`);
159
+ }
160
+ if (!challengeRes.ok || !challengeData.nonce) {
161
+ throw new Error(challengeData.error || "Failed to get registration challenge");
162
+ }
163
+ const nonce = challengeData.nonce;
164
+ const message = `provenonce-register-operator:${nonce}:${options.operatorWalletAddress}:${name}`;
165
+ const walletSignature = await options.operatorSignFn(message);
166
+ const registerRes = await fetch(`${url}/api/v1/register`, {
167
+ method: "POST",
168
+ headers,
169
+ body: JSON.stringify({
170
+ name,
171
+ wallet_model: "operator",
172
+ operator_wallet_address: options.operatorWalletAddress,
173
+ wallet_signature: walletSignature,
174
+ wallet_nonce: nonce,
175
+ ...options.metadata && { metadata: options.metadata }
176
+ })
177
+ });
178
+ let data2;
179
+ try {
180
+ data2 = await registerRes.json();
181
+ } catch {
182
+ throw new Error(`Operator registration failed: ${registerRes.status} (non-JSON response)`);
183
+ }
184
+ if (!registerRes.ok) throw new Error(data2.error || "Registration failed");
185
+ const addr = data2.wallet?.address || data2.wallet?.solana_address || options.operatorWalletAddress;
186
+ data2.wallet = {
187
+ public_key: "",
188
+ secret_key: "",
189
+ solana_address: addr,
190
+ address: addr,
191
+ chain: "solana"
192
+ };
193
+ return data2;
194
+ }
195
+ if (options?.walletModel === "self-custody" || options?.walletSecretKey) {
196
+ let walletKeys;
197
+ if (options?.walletSecretKey) {
198
+ const privRaw = Buffer.from(options.walletSecretKey, "hex");
199
+ const privKeyDer = Buffer.concat([ED25519_PKCS8_PREFIX, privRaw]);
200
+ const keyObject = (0, import_crypto.createPrivateKey)({ key: privKeyDer, format: "der", type: "pkcs8" });
201
+ const pubRaw = keyObject.export({ type: "spki", format: "der" }).subarray(12);
202
+ walletKeys = {
203
+ publicKey: Buffer.from(pubRaw).toString("hex"),
204
+ secretKey: options.walletSecretKey
205
+ };
206
+ } else {
207
+ walletKeys = generateWalletKeypair();
208
+ }
209
+ const challengeRes = await fetch(`${url}/api/v1/register`, {
210
+ method: "POST",
211
+ headers,
212
+ body: JSON.stringify({ name, action: "challenge" })
213
+ });
214
+ let challengeData;
215
+ try {
216
+ challengeData = await challengeRes.json();
217
+ } catch {
218
+ throw new Error(`Registration challenge failed: ${challengeRes.status} (non-JSON response)`);
219
+ }
220
+ if (!challengeRes.ok || !challengeData.nonce) {
221
+ const err = new Error(challengeData.error || "Failed to get registration challenge");
222
+ err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
223
+ throw err;
224
+ }
225
+ const nonce = challengeData.nonce;
226
+ const message = `provenonce-register:${nonce}:${walletKeys.publicKey}:${name}`;
227
+ const walletSignature = signMessage(walletKeys.secretKey, message);
228
+ const registerRes = await fetch(`${url}/api/v1/register`, {
229
+ method: "POST",
230
+ headers,
231
+ body: JSON.stringify({
232
+ name,
233
+ wallet_public_key: walletKeys.publicKey,
234
+ wallet_signature: walletSignature,
235
+ wallet_nonce: nonce,
236
+ ...options?.metadata && { metadata: options.metadata }
237
+ })
238
+ });
239
+ let data2;
240
+ try {
241
+ data2 = await registerRes.json();
242
+ } catch {
243
+ const err = new Error(`Registration failed: ${registerRes.status} (non-JSON response)`);
244
+ err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
245
+ throw err;
246
+ }
247
+ if (!registerRes.ok) {
248
+ const err = new Error(data2.error || "Registration failed");
249
+ err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
250
+ throw err;
251
+ }
252
+ const addr = data2.wallet?.address || data2.wallet?.solana_address || "";
253
+ data2.wallet = {
254
+ public_key: walletKeys.publicKey,
255
+ secret_key: walletKeys.secretKey,
256
+ solana_address: addr,
257
+ address: addr,
258
+ chain: "solana"
259
+ };
260
+ return data2;
261
+ }
262
+ const res = await fetch(`${url}/api/v1/register`, {
263
+ method: "POST",
264
+ headers,
265
+ body: JSON.stringify({ name, ...options?.metadata && { metadata: options.metadata } })
266
+ });
267
+ let data;
268
+ try {
269
+ data = await res.json();
270
+ } catch {
271
+ throw new Error(`Registration failed: ${res.status} ${res.statusText} (non-JSON response)`);
272
+ }
273
+ if (!res.ok) throw new Error(data.error || "Registration failed");
274
+ return data;
38
275
  }
39
276
  var BeatAgent = class {
40
277
  constructor(config) {
@@ -47,6 +284,24 @@ var BeatAgent = class {
47
284
  this.status = "uninitialized";
48
285
  this.heartbeatInterval = null;
49
286
  this.globalBeat = 0;
287
+ this.globalAnchorHash = "";
288
+ if (!config.apiKey || typeof config.apiKey !== "string") {
289
+ throw new Error("BeatAgentConfig.apiKey is required (must be a non-empty string)");
290
+ }
291
+ if (!config.registryUrl || typeof config.registryUrl !== "string") {
292
+ throw new Error("BeatAgentConfig.registryUrl is required (must be a non-empty string)");
293
+ }
294
+ try {
295
+ new URL(config.registryUrl);
296
+ } catch {
297
+ throw new Error("BeatAgentConfig.registryUrl is not a valid URL");
298
+ }
299
+ if (config.beatsPerPulse !== void 0 && (!Number.isInteger(config.beatsPerPulse) || config.beatsPerPulse < 1 || config.beatsPerPulse > 1e4)) {
300
+ throw new Error("BeatAgentConfig.beatsPerPulse must be an integer between 1 and 10000");
301
+ }
302
+ if (config.checkinIntervalSec !== void 0 && (!Number.isFinite(config.checkinIntervalSec) || config.checkinIntervalSec < 10 || config.checkinIntervalSec > 86400)) {
303
+ throw new Error("BeatAgentConfig.checkinIntervalSec must be between 10 and 86400");
304
+ }
50
305
  this.config = {
51
306
  beatsPerPulse: 10,
52
307
  checkinIntervalSec: 300,
@@ -107,21 +362,32 @@ var BeatAgent = class {
107
362
  * through a specific window of computational time.
108
363
  */
109
364
  pulse(count) {
365
+ if (this.status !== "active") {
366
+ throw new Error(`Cannot pulse: agent is ${this.status}. Use resync() if frozen.`);
367
+ }
368
+ if (count !== void 0 && (!Number.isInteger(count) || count < 1 || count > 1e4)) {
369
+ throw new Error("pulse count must be an integer between 1 and 10000");
370
+ }
371
+ return this.computeBeats(count);
372
+ }
373
+ /** Internal beat computation — no status check. Used by both pulse() and resync(). */
374
+ computeBeats(count, onProgress) {
110
375
  const n = count || this.config.beatsPerPulse;
111
376
  if (!this.latestBeat) {
112
377
  throw new Error("Beat chain not initialized. Call init() first.");
113
378
  }
114
- if (this.status !== "active") {
115
- throw new Error(`Cannot pulse in status '${this.status}'. Use resync() if frozen.`);
116
- }
117
379
  const newBeats = [];
118
380
  let prevHash = this.latestBeat.hash;
119
381
  let startIndex = this.latestBeat.index + 1;
120
382
  const t0 = Date.now();
383
+ const progressInterval = Math.max(1, Math.floor(n / 10));
121
384
  for (let i = 0; i < n; i++) {
122
- const beat = computeBeat(prevHash, startIndex + i, this.difficulty);
385
+ const beat = computeBeat(prevHash, startIndex + i, this.difficulty, void 0, this.globalAnchorHash || void 0);
123
386
  newBeats.push(beat);
124
387
  prevHash = beat.hash;
388
+ if (onProgress && (i + 1) % progressInterval === 0) {
389
+ onProgress(i + 1, n);
390
+ }
125
391
  }
126
392
  const elapsed = Date.now() - t0;
127
393
  this.chain.push(...newBeats);
@@ -142,21 +408,26 @@ var BeatAgent = class {
142
408
  * submit a proof of its Local Beats to the Registry."
143
409
  */
144
410
  async checkin() {
145
- if (!this.latestBeat || this.totalBeats === this.lastCheckinBeat) {
411
+ if (!this.latestBeat || this.latestBeat.index <= this.lastCheckinBeat) {
412
+ this.log("No new beats since last check-in. Call pulse() first.");
146
413
  return { ok: true, total_beats: this.totalBeats };
147
414
  }
148
415
  try {
416
+ const fromBeat = this.lastCheckinBeat;
417
+ const toBeat = this.latestBeat.index;
149
418
  const spotChecks = [];
150
- const available = this.chain.filter((b) => b.index > this.lastCheckinBeat);
151
- const sampleCount = Math.min(5, available.length);
419
+ const toBeatEntry = this.chain.find((b) => b.index === toBeat);
420
+ if (toBeatEntry) {
421
+ spotChecks.push({ index: toBeatEntry.index, hash: toBeatEntry.hash, prev: toBeatEntry.prev, nonce: toBeatEntry.nonce });
422
+ }
423
+ const available = this.chain.filter((b) => b.index > this.lastCheckinBeat && b.index !== toBeat);
424
+ const sampleCount = Math.min(4, available.length);
152
425
  for (let i = 0; i < sampleCount; i++) {
153
426
  const idx = Math.floor(Math.random() * available.length);
154
427
  const beat = available[idx];
155
428
  spotChecks.push({ index: beat.index, hash: beat.hash, prev: beat.prev, nonce: beat.nonce });
156
429
  available.splice(idx, 1);
157
430
  }
158
- const fromBeat = this.lastCheckinBeat;
159
- const toBeat = this.latestBeat.index;
160
431
  const fromHash = this.chain.find((b) => b.index === fromBeat)?.hash || this.genesisHash;
161
432
  const toHash = this.latestBeat.hash;
162
433
  const res = await this.api("POST", "/api/v1/agent/checkin", {
@@ -167,6 +438,7 @@ var BeatAgent = class {
167
438
  to_hash: toHash,
168
439
  beats_computed: toBeat - fromBeat,
169
440
  global_anchor: this.globalBeat,
441
+ anchor_hash: this.globalAnchorHash || void 0,
170
442
  spot_checks: spotChecks
171
443
  }
172
444
  });
@@ -201,7 +473,13 @@ var BeatAgent = class {
201
473
  throw new Error(`Cannot start heartbeat in status '${this.status}'.`);
202
474
  }
203
475
  this.log("\u2661 Starting heartbeat...");
476
+ let consecutiveErrors = 0;
477
+ let skipCount = 0;
204
478
  this.heartbeatInterval = setInterval(async () => {
479
+ if (skipCount > 0) {
480
+ skipCount--;
481
+ return;
482
+ }
205
483
  try {
206
484
  this.pulse();
207
485
  const beatsSinceCheckin = this.latestBeat.index - this.lastCheckinBeat;
@@ -210,8 +488,12 @@ var BeatAgent = class {
210
488
  await this.checkin();
211
489
  await this.syncGlobal();
212
490
  }
491
+ consecutiveErrors = 0;
213
492
  } catch (err) {
493
+ consecutiveErrors++;
214
494
  this.config.onError(err, "heartbeat");
495
+ skipCount = Math.min(32, Math.pow(2, consecutiveErrors - 1));
496
+ this.log(`Heartbeat error #${consecutiveErrors}, backing off ${skipCount} ticks`);
215
497
  }
216
498
  }, this.config.checkinIntervalSec * 1e3 / 10);
217
499
  }
@@ -246,16 +528,18 @@ var BeatAgent = class {
246
528
  const required = challenge.challenge.required_beats;
247
529
  this.difficulty = challenge.challenge.difficulty;
248
530
  this.log(`Re-sync challenge: compute ${required} beats at D=${this.difficulty}`);
531
+ await this.syncGlobal();
249
532
  const startHash = challenge.challenge.start_from_hash;
250
533
  const startBeat = challenge.challenge.start_from_beat;
251
534
  this.latestBeat = { index: startBeat, hash: startHash, prev: "", timestamp: Date.now() };
252
535
  this.chain = [this.latestBeat];
253
536
  const t0 = Date.now();
254
- this.pulse(required);
537
+ this.computeBeats(required);
255
538
  const elapsed = Date.now() - t0;
256
539
  this.log(`Re-sync beats computed in ${elapsed}ms`);
257
540
  const proof = await this.api("POST", "/api/v1/agent/resync", {
258
541
  action: "prove",
542
+ challenge_nonce: challenge.challenge.nonce,
259
543
  proof: {
260
544
  from_beat: startBeat,
261
545
  to_beat: this.latestBeat.index,
@@ -263,7 +547,15 @@ var BeatAgent = class {
263
547
  to_hash: this.latestBeat.hash,
264
548
  beats_computed: required,
265
549
  global_anchor: challenge.challenge.sync_to_global,
266
- spot_checks: this.chain.filter((_, i) => i % Math.ceil(required / 5) === 0).slice(0, 5).map((b) => ({ index: b.index, hash: b.hash, prev: b.prev, nonce: b.nonce }))
550
+ anchor_hash: this.globalAnchorHash || void 0,
551
+ spot_checks: (() => {
552
+ const toBeatEntry = this.chain.find((b) => b.index === this.latestBeat.index);
553
+ const available = this.chain.filter((b) => b.index !== this.latestBeat.index && b.index > startBeat);
554
+ const step = Math.max(1, Math.ceil(available.length / 5));
555
+ const others = available.filter((_, i) => i % step === 0).slice(0, 4);
556
+ const checks = toBeatEntry ? [toBeatEntry, ...others] : others;
557
+ return checks.map((b) => ({ index: b.index, hash: b.hash, prev: b.prev, nonce: b.nonce }));
558
+ })()
267
559
  }
268
560
  });
269
561
  if (proof.ok) {
@@ -286,6 +578,14 @@ var BeatAgent = class {
286
578
  */
287
579
  async requestSpawn(childName, childHash) {
288
580
  try {
581
+ if (childName !== void 0) {
582
+ if (typeof childName !== "string" || childName.trim().length === 0) {
583
+ throw new Error("childName must be a non-empty string");
584
+ }
585
+ if (childName.length > 64) {
586
+ throw new Error("childName must be 64 characters or fewer");
587
+ }
588
+ }
289
589
  const res = await this.api("POST", "/api/v1/agent/spawn", {
290
590
  child_name: childName,
291
591
  child_hash: childHash
@@ -330,10 +630,14 @@ var BeatAgent = class {
330
630
  // ── INTERNALS ──
331
631
  async syncGlobal() {
332
632
  try {
333
- const res = await fetch(`${this.config.registryUrl}/api/v1/beat/anchor`);
633
+ const controller = new AbortController();
634
+ const timeout = setTimeout(() => controller.abort(), 15e3);
635
+ const res = await fetch(`${this.config.registryUrl}/api/v1/beat/anchor`, { signal: controller.signal });
636
+ clearTimeout(timeout);
334
637
  const data = await res.json();
335
638
  if (data.anchor) {
336
639
  this.globalBeat = data.anchor.beat_index;
640
+ this.globalAnchorHash = data.anchor.hash || "";
337
641
  if (data.anchor.difficulty) this.difficulty = data.anchor.difficulty;
338
642
  this.log(`Synced to global beat ${this.globalBeat} (D=${this.difficulty})`);
339
643
  }
@@ -347,23 +651,52 @@ var BeatAgent = class {
347
651
  this.totalBeats = res.total_beats;
348
652
  this.genesisHash = res.genesis_hash;
349
653
  this.status = res.status;
654
+ this.difficulty = res.difficulty || this.difficulty;
655
+ this.lastCheckinBeat = res.last_checkin_beat || 0;
656
+ if (!this.latestBeat && this.genesisHash) {
657
+ this.latestBeat = {
658
+ index: res.latest_beat || this.totalBeats,
659
+ hash: res.latest_hash || this.genesisHash,
660
+ prev: "0".repeat(64),
661
+ timestamp: Date.now()
662
+ };
663
+ this.chain = [this.latestBeat];
664
+ }
350
665
  }
351
666
  return res;
352
667
  }
353
668
  async api(method, path, body) {
354
- const res = await fetch(`${this.config.registryUrl}${path}`, {
355
- method,
356
- headers: {
357
- "Content-Type": "application/json",
358
- "Authorization": `Bearer ${this.config.apiKey}`
359
- },
360
- body: body ? JSON.stringify(body) : void 0
361
- });
362
- const data = await res.json();
363
- if (!res.ok && !data.ok && !data.already_initialized && !data.eligible) {
364
- throw new Error(data.error || `API ${res.status}: ${res.statusText}`);
669
+ const controller = new AbortController();
670
+ const timeout = setTimeout(() => controller.abort(), 3e4);
671
+ try {
672
+ const res = await fetch(`${this.config.registryUrl}${path}`, {
673
+ method,
674
+ headers: {
675
+ "Content-Type": "application/json",
676
+ "Authorization": `Bearer ${this.config.apiKey}`
677
+ },
678
+ body: body ? JSON.stringify(body) : void 0,
679
+ signal: controller.signal
680
+ });
681
+ let data;
682
+ try {
683
+ data = await res.json();
684
+ } catch {
685
+ throw new Error(`API error: ${res.status} non-JSON response from ${path}`);
686
+ }
687
+ if (!res.ok && !data.ok && !data.already_initialized && !data.eligible) {
688
+ const serverMsg = typeof data.error === "string" ? data.error : `API error ${res.status}`;
689
+ throw new Error(serverMsg);
690
+ }
691
+ return data;
692
+ } catch (err) {
693
+ if (err.name === "AbortError") {
694
+ throw new Error(`Request timeout: ${method} ${path}`);
695
+ }
696
+ throw err;
697
+ } finally {
698
+ clearTimeout(timeout);
365
699
  }
366
- return data;
367
700
  }
368
701
  log(msg) {
369
702
  if (this.config.verbose) {
@@ -371,12 +704,18 @@ var BeatAgent = class {
371
704
  }
372
705
  }
373
706
  };
374
- function computeBeatsLite(startHash, startIndex, count, difficulty = 1e3) {
707
+ function computeBeatsLite(startHash, startIndex, count, difficulty = 1e3, anchorHash) {
708
+ if (!startHash || typeof startHash !== "string") {
709
+ throw new Error("computeBeatsLite: startHash must be a non-empty string");
710
+ }
711
+ if (!Number.isInteger(count) || count < 1) {
712
+ throw new Error("computeBeatsLite: count must be a positive integer");
713
+ }
375
714
  const t0 = Date.now();
376
715
  let prev = startHash;
377
716
  let lastBeat = null;
378
717
  for (let i = 0; i < count; i++) {
379
- lastBeat = computeBeat(prev, startIndex + i, difficulty);
718
+ lastBeat = computeBeat(prev, startIndex + i, difficulty, void 0, anchorHash);
380
719
  prev = lastBeat.hash;
381
720
  }
382
721
  return { lastBeat, elapsed: Date.now() - t0 };
@@ -385,6 +724,8 @@ function computeBeatsLite(startHash, startIndex, count, difficulty = 1e3) {
385
724
  0 && (module.exports = {
386
725
  BeatAgent,
387
726
  computeBeat,
388
- computeBeatsLite
727
+ computeBeatsLite,
728
+ generateWalletKeypair,
729
+ register
389
730
  });
390
731
  //# sourceMappingURL=index.js.map