@provenonce/sdk 0.12.0 → 0.14.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.d.mts +82 -58
- package/dist/index.d.ts +82 -58
- package/dist/index.js +191 -220
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +192 -220
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,7 +33,6 @@ __export(index_exports, {
|
|
|
33
33
|
ValidationError: () => ValidationError,
|
|
34
34
|
computeBeat: () => computeBeat,
|
|
35
35
|
computeBeatsLite: () => computeBeatsLite,
|
|
36
|
-
generateWalletKeypair: () => generateWalletKeypair,
|
|
37
36
|
register: () => register,
|
|
38
37
|
verifyAnchorHash: () => verifyAnchorHash
|
|
39
38
|
});
|
|
@@ -198,23 +197,6 @@ function verifyAnchorHash(anchor) {
|
|
|
198
197
|
}
|
|
199
198
|
return current === anchor.hash;
|
|
200
199
|
}
|
|
201
|
-
var ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
202
|
-
function generateWalletKeypair() {
|
|
203
|
-
const { publicKey, privateKey } = (0, import_crypto.generateKeyPairSync)("ed25519");
|
|
204
|
-
const pubRaw = publicKey.export({ type: "spki", format: "der" }).subarray(12);
|
|
205
|
-
const privRaw = privateKey.export({ type: "pkcs8", format: "der" }).subarray(16);
|
|
206
|
-
return {
|
|
207
|
-
publicKey: Buffer.from(pubRaw).toString("hex"),
|
|
208
|
-
secretKey: Buffer.from(privRaw).toString("hex")
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
function signMessage(secretKeyHex, message) {
|
|
212
|
-
const privRaw = Buffer.from(secretKeyHex, "hex");
|
|
213
|
-
const privKeyDer = Buffer.concat([ED25519_PKCS8_PREFIX, privRaw]);
|
|
214
|
-
const keyObject = (0, import_crypto.createPrivateKey)({ key: privKeyDer, format: "der", type: "pkcs8" });
|
|
215
|
-
const sig = (0, import_crypto.sign)(null, Buffer.from(message), keyObject);
|
|
216
|
-
return Buffer.from(sig).toString("hex");
|
|
217
|
-
}
|
|
218
200
|
async function register(name, options) {
|
|
219
201
|
if (!name || typeof name !== "string" || name.trim().length === 0) {
|
|
220
202
|
throw new ValidationError("name is required (must be a non-empty string)");
|
|
@@ -300,8 +282,6 @@ async function register(name, options) {
|
|
|
300
282
|
}
|
|
301
283
|
if (!registerRes.ok) throw mapApiError(registerRes.status, data2, "/api/v1/register");
|
|
302
284
|
data2.wallet = {
|
|
303
|
-
public_key: "",
|
|
304
|
-
secret_key: "",
|
|
305
285
|
address: data2.wallet?.address || options.walletAddress,
|
|
306
286
|
chain: "ethereum"
|
|
307
287
|
};
|
|
@@ -349,77 +329,6 @@ async function register(name, options) {
|
|
|
349
329
|
if (!registerRes.ok) throw mapApiError(registerRes.status, data2, "/api/v1/register");
|
|
350
330
|
const addr = data2.wallet?.address || data2.wallet?.solana_address || options.operatorWalletAddress;
|
|
351
331
|
data2.wallet = {
|
|
352
|
-
public_key: "",
|
|
353
|
-
secret_key: "",
|
|
354
|
-
solana_address: addr,
|
|
355
|
-
address: addr,
|
|
356
|
-
chain: "solana"
|
|
357
|
-
};
|
|
358
|
-
return data2;
|
|
359
|
-
}
|
|
360
|
-
if (options?.walletModel === "self-custody" || options?.walletSecretKey) {
|
|
361
|
-
let walletKeys;
|
|
362
|
-
if (options?.walletSecretKey) {
|
|
363
|
-
const privRaw = Buffer.from(options.walletSecretKey, "hex");
|
|
364
|
-
const privKeyDer = Buffer.concat([ED25519_PKCS8_PREFIX, privRaw]);
|
|
365
|
-
const keyObject = (0, import_crypto.createPrivateKey)({ key: privKeyDer, format: "der", type: "pkcs8" });
|
|
366
|
-
const pubRaw = keyObject.export({ type: "spki", format: "der" }).subarray(12);
|
|
367
|
-
walletKeys = {
|
|
368
|
-
publicKey: Buffer.from(pubRaw).toString("hex"),
|
|
369
|
-
secretKey: options.walletSecretKey
|
|
370
|
-
};
|
|
371
|
-
} else {
|
|
372
|
-
walletKeys = generateWalletKeypair();
|
|
373
|
-
}
|
|
374
|
-
const challengeRes = await fetch(`${url}/api/v1/register`, {
|
|
375
|
-
method: "POST",
|
|
376
|
-
headers,
|
|
377
|
-
body: JSON.stringify({ name, action: "challenge" })
|
|
378
|
-
});
|
|
379
|
-
let challengeData;
|
|
380
|
-
try {
|
|
381
|
-
challengeData = await challengeRes.json();
|
|
382
|
-
} catch {
|
|
383
|
-
const err = new NetworkError(`Registration challenge failed: ${challengeRes.status} (non-JSON response)`);
|
|
384
|
-
err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
|
|
385
|
-
throw err;
|
|
386
|
-
}
|
|
387
|
-
if (!challengeRes.ok || !challengeData.nonce) {
|
|
388
|
-
const err = mapApiError(challengeRes.status, challengeData, "/api/v1/register");
|
|
389
|
-
err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
|
|
390
|
-
throw err;
|
|
391
|
-
}
|
|
392
|
-
const nonce = challengeData.nonce;
|
|
393
|
-
const message = `provenonce-register:${nonce}:${walletKeys.publicKey}:${name}`;
|
|
394
|
-
const walletSignature = signMessage(walletKeys.secretKey, message);
|
|
395
|
-
const registerRes = await fetch(`${url}/api/v1/register`, {
|
|
396
|
-
method: "POST",
|
|
397
|
-
headers,
|
|
398
|
-
body: JSON.stringify({
|
|
399
|
-
name,
|
|
400
|
-
wallet_public_key: walletKeys.publicKey,
|
|
401
|
-
wallet_signature: walletSignature,
|
|
402
|
-
wallet_nonce: nonce,
|
|
403
|
-
...options?.metadata && { metadata: options.metadata }
|
|
404
|
-
})
|
|
405
|
-
});
|
|
406
|
-
let data2;
|
|
407
|
-
try {
|
|
408
|
-
data2 = await registerRes.json();
|
|
409
|
-
} catch {
|
|
410
|
-
const err = new NetworkError(`Registration failed: ${registerRes.status} (non-JSON response)`);
|
|
411
|
-
err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
|
|
412
|
-
throw err;
|
|
413
|
-
}
|
|
414
|
-
if (!registerRes.ok) {
|
|
415
|
-
const err = mapApiError(registerRes.status, data2, "/api/v1/register");
|
|
416
|
-
err.walletKeys = { publicKey: walletKeys.publicKey, secretKey: walletKeys.secretKey };
|
|
417
|
-
throw err;
|
|
418
|
-
}
|
|
419
|
-
const addr = data2.wallet?.address || data2.wallet?.solana_address || "";
|
|
420
|
-
data2.wallet = {
|
|
421
|
-
public_key: walletKeys.publicKey,
|
|
422
|
-
secret_key: walletKeys.secretKey,
|
|
423
332
|
solana_address: addr,
|
|
424
333
|
address: addr,
|
|
425
334
|
chain: "solana"
|
|
@@ -488,6 +397,7 @@ var BeatAgent = class {
|
|
|
488
397
|
},
|
|
489
398
|
verbose: false,
|
|
490
399
|
verifyAnchors: true,
|
|
400
|
+
beatsUrl: "https://beats.provenonce.dev",
|
|
491
401
|
...config
|
|
492
402
|
};
|
|
493
403
|
}
|
|
@@ -529,27 +439,7 @@ var BeatAgent = class {
|
|
|
529
439
|
return { ok: false, error: err.message };
|
|
530
440
|
}
|
|
531
441
|
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* @deprecated Phase 2: VDF computation retired (D-68). Payment is the liveness mechanism.
|
|
535
|
-
* Use heartbeat() instead. This method will be removed in the next major version.
|
|
536
|
-
*
|
|
537
|
-
* Compute N beats locally (VDF hash chain).
|
|
538
|
-
*/
|
|
539
|
-
pulse(count) {
|
|
540
|
-
console.warn("[Provenonce SDK] pulse() is deprecated. Use heartbeat() instead (Phase 2).");
|
|
541
|
-
if (this.status === "frozen") {
|
|
542
|
-
throw new FrozenError("Cannot pulse: agent is frozen. Use resync() to re-establish provenance.");
|
|
543
|
-
}
|
|
544
|
-
if (this.status !== "active") {
|
|
545
|
-
throw new StateError(`Cannot pulse: agent is ${this.status}.`, this.status);
|
|
546
|
-
}
|
|
547
|
-
if (count !== void 0 && (!Number.isInteger(count) || count < 1 || count > 1e4)) {
|
|
548
|
-
throw new ValidationError("pulse count must be an integer between 1 and 10000");
|
|
549
|
-
}
|
|
550
|
-
return this.computeBeats(count);
|
|
551
|
-
}
|
|
552
|
-
/** Internal beat computation — no status check. Used by both pulse() and resync(). */
|
|
442
|
+
/** Internal beat computation — no status check. Used by resync(). */
|
|
553
443
|
computeBeats(count, onProgress) {
|
|
554
444
|
const n = count || this.config.beatsPerPulse;
|
|
555
445
|
if (!this.latestBeat) {
|
|
@@ -579,63 +469,6 @@ var BeatAgent = class {
|
|
|
579
469
|
this.log(`Pulse: ${n} beats in ${elapsed}ms (${(elapsed / n).toFixed(1)}ms/beat, D=${this.difficulty})`);
|
|
580
470
|
return newBeats;
|
|
581
471
|
}
|
|
582
|
-
// ── CHECK-IN ──
|
|
583
|
-
/**
|
|
584
|
-
* @deprecated Phase 2: VDF check-in retired (D-68). Use heartbeat() instead.
|
|
585
|
-
* This method will be removed in the next major version.
|
|
586
|
-
*/
|
|
587
|
-
async checkin() {
|
|
588
|
-
console.warn("[Provenonce SDK] checkin() is deprecated. Use heartbeat() instead (Phase 2).");
|
|
589
|
-
if (!this.latestBeat || this.latestBeat.index <= this.lastCheckinBeat) {
|
|
590
|
-
this.log("No new beats since last check-in. Call pulse() first.");
|
|
591
|
-
return { ok: true, total_beats: this.totalBeats };
|
|
592
|
-
}
|
|
593
|
-
try {
|
|
594
|
-
const fromBeat = this.lastCheckinBeat;
|
|
595
|
-
const toBeat = this.latestBeat.index;
|
|
596
|
-
const spotChecks = [];
|
|
597
|
-
const toBeatEntry = this.chain.find((b) => b.index === toBeat);
|
|
598
|
-
if (toBeatEntry) {
|
|
599
|
-
spotChecks.push({ index: toBeatEntry.index, hash: toBeatEntry.hash, prev: toBeatEntry.prev, nonce: toBeatEntry.nonce });
|
|
600
|
-
}
|
|
601
|
-
const available = this.chain.filter((b) => b.index > this.lastCheckinBeat && b.index !== toBeat);
|
|
602
|
-
const sampleCount = Math.min(4, available.length);
|
|
603
|
-
for (let i = 0; i < sampleCount; i++) {
|
|
604
|
-
const idx = Math.floor(Math.random() * available.length);
|
|
605
|
-
const beat = available[idx];
|
|
606
|
-
spotChecks.push({ index: beat.index, hash: beat.hash, prev: beat.prev, nonce: beat.nonce });
|
|
607
|
-
available.splice(idx, 1);
|
|
608
|
-
}
|
|
609
|
-
const fromHash = this.chain.find((b) => b.index === fromBeat)?.hash || this.genesisHash;
|
|
610
|
-
const toHash = this.latestBeat.hash;
|
|
611
|
-
const res = await this.api("POST", "/api/v1/agent/checkin", {
|
|
612
|
-
proof: {
|
|
613
|
-
from_beat: fromBeat,
|
|
614
|
-
to_beat: toBeat,
|
|
615
|
-
from_hash: fromHash,
|
|
616
|
-
to_hash: toHash,
|
|
617
|
-
beats_computed: toBeat - fromBeat,
|
|
618
|
-
global_anchor: this.globalBeat,
|
|
619
|
-
anchor_hash: this.globalAnchorHash || void 0,
|
|
620
|
-
spot_checks: spotChecks
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
if (res.ok) {
|
|
624
|
-
this.lastCheckinBeat = toBeat;
|
|
625
|
-
this.totalBeats = res.total_beats;
|
|
626
|
-
this.config.onCheckin(res);
|
|
627
|
-
this.log(`Check-in accepted: ${res.beats_accepted} beats, total=${res.total_beats}, global=${res.global_beat}`);
|
|
628
|
-
if (res.status === "warning_overdue") {
|
|
629
|
-
this.config.onStatusChange("warning", { beats_behind: res.beats_behind });
|
|
630
|
-
this.log(`\u26A0 WARNING: ${res.beats_behind} anchors behind. Check in more frequently.`);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
return { ok: res.ok, total_beats: res.total_beats };
|
|
634
|
-
} catch (err) {
|
|
635
|
-
this.config.onError(err, "checkin");
|
|
636
|
-
return { ok: false, error: err.message };
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
472
|
// ── AUTONOMOUS HEARTBEAT ──
|
|
640
473
|
/**
|
|
641
474
|
* Start the autonomous heartbeat loop.
|
|
@@ -685,60 +518,66 @@ var BeatAgent = class {
|
|
|
685
518
|
}
|
|
686
519
|
// ── RE-SYNC ──
|
|
687
520
|
/**
|
|
688
|
-
*
|
|
689
|
-
*
|
|
521
|
+
* Re-Sync Challenge (D-67 reversal): reactivate a frozen agent by proving CPU work.
|
|
522
|
+
*
|
|
523
|
+
* When BEATS_REQUIRED=true on the server: requires a signed Beats work-proof
|
|
524
|
+
* receipt. This method computes the proof automatically using computeWorkProof().
|
|
525
|
+
*
|
|
526
|
+
* When BEATS_REQUIRED=false (devnet): no receipt needed — agent is reactivated freely.
|
|
527
|
+
*
|
|
528
|
+
* Gap formula: min(gap_anchors * 100, 10_000) beats required (matches Beats constants).
|
|
690
529
|
*/
|
|
691
530
|
async resync() {
|
|
692
|
-
console.warn("[Provenonce SDK] resync() is deprecated (D-67). Use heartbeat() to resume (Phase 2).");
|
|
693
531
|
try {
|
|
694
|
-
this.log("
|
|
695
|
-
const challenge = await this.api("POST", "/api/v1/agent/resync", {
|
|
696
|
-
action: "challenge"
|
|
697
|
-
});
|
|
698
|
-
if (!challenge.challenge) {
|
|
699
|
-
return { ok: false, error: "Failed to get challenge" };
|
|
700
|
-
}
|
|
701
|
-
const required = challenge.challenge.required_beats;
|
|
702
|
-
this.difficulty = challenge.challenge.difficulty;
|
|
703
|
-
this.log(`Re-sync challenge: compute ${required} beats at D=${this.difficulty}`);
|
|
532
|
+
this.log("Attempting resync...");
|
|
704
533
|
await this.syncGlobal();
|
|
705
|
-
const
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
spot_checks: (() => {
|
|
725
|
-
const toBeatEntry = this.chain.find((b) => b.index === this.latestBeat.index);
|
|
726
|
-
const available = this.chain.filter((b) => b.index !== this.latestBeat.index && b.index > startBeat);
|
|
727
|
-
const step = Math.max(1, Math.ceil(available.length / 5));
|
|
728
|
-
const others = available.filter((_, i) => i % step === 0).slice(0, 4);
|
|
729
|
-
const checks = toBeatEntry ? [toBeatEntry, ...others] : others;
|
|
730
|
-
return checks.map((b) => ({ index: b.index, hash: b.hash, prev: b.prev, nonce: b.nonce }));
|
|
731
|
-
})()
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
if (proof.ok) {
|
|
534
|
+
const controller = new AbortController();
|
|
535
|
+
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
536
|
+
let probeRes;
|
|
537
|
+
let probeData;
|
|
538
|
+
try {
|
|
539
|
+
probeRes = await fetch(`${this.config.registryUrl}/api/v1/agent/resync`, {
|
|
540
|
+
method: "POST",
|
|
541
|
+
headers: {
|
|
542
|
+
"Content-Type": "application/json",
|
|
543
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
544
|
+
},
|
|
545
|
+
body: JSON.stringify({}),
|
|
546
|
+
signal: controller.signal
|
|
547
|
+
});
|
|
548
|
+
probeData = await probeRes.json();
|
|
549
|
+
} finally {
|
|
550
|
+
clearTimeout(timeout);
|
|
551
|
+
}
|
|
552
|
+
if (probeData.ok && probeData.status === "active") {
|
|
735
553
|
this.status = "active";
|
|
736
|
-
this.totalBeats = proof.total_beats;
|
|
737
|
-
this.lastCheckinBeat = this.latestBeat.index;
|
|
738
554
|
this.config.onStatusChange("active", { resynced: true });
|
|
739
|
-
this.log("\u2713 Re-synced. Agent is alive again in Beat time.");
|
|
555
|
+
this.log("\u2713 Re-synced (free). Agent is alive again in Beat time.");
|
|
556
|
+
return { ok: true, beats_required: 0 };
|
|
740
557
|
}
|
|
741
|
-
|
|
558
|
+
if (probeRes.status === 402 || probeData.code === "RECEIPT_REQUIRED") {
|
|
559
|
+
const requiredBeats = probeData.required_beats ?? 1e3;
|
|
560
|
+
this.log(`Re-sync challenge: compute ${requiredBeats} beats at D=${this.difficulty}`);
|
|
561
|
+
const proofResult = await this.computeWorkProof({
|
|
562
|
+
beatsNeeded: requiredBeats,
|
|
563
|
+
anchorHash: this.globalAnchorHash,
|
|
564
|
+
anchorIndex: this.globalBeat
|
|
565
|
+
});
|
|
566
|
+
if (!proofResult.ok || !proofResult.receipt) {
|
|
567
|
+
return { ok: false, error: proofResult.error || "Failed to compute work proof for resync", beats_required: requiredBeats };
|
|
568
|
+
}
|
|
569
|
+
this.log(`Work proof computed: ${proofResult.beats_computed} beats in ${proofResult.elapsed_ms}ms`);
|
|
570
|
+
const result = await this.api("POST", "/api/v1/agent/resync", {
|
|
571
|
+
beats_receipt: proofResult.receipt
|
|
572
|
+
});
|
|
573
|
+
if (result.ok && result.status === "active") {
|
|
574
|
+
this.status = "active";
|
|
575
|
+
this.config.onStatusChange("active", { resynced: true });
|
|
576
|
+
this.log("\u2713 Re-synced with work proof. Agent is alive again in Beat time.");
|
|
577
|
+
}
|
|
578
|
+
return { ok: !!result.ok, beats_required: requiredBeats };
|
|
579
|
+
}
|
|
580
|
+
return { ok: false, error: probeData.error || `Resync failed (status ${probeRes.status})` };
|
|
742
581
|
} catch (err) {
|
|
743
582
|
this.config.onError(err, "resync");
|
|
744
583
|
return { ok: false, error: err.message };
|
|
@@ -747,9 +586,13 @@ var BeatAgent = class {
|
|
|
747
586
|
// ── SPAWN ──
|
|
748
587
|
/**
|
|
749
588
|
* Request to spawn a child agent.
|
|
750
|
-
* Requires sufficient accumulated beats (Temporal Gestation).
|
|
589
|
+
* Requires sufficient accumulated beats (Temporal Gestation), OR a valid Beats work-proof receipt.
|
|
590
|
+
*
|
|
591
|
+
* @param childName Optional name for the child agent
|
|
592
|
+
* @param childHash Pre-registered child hash (Step 2 finalization)
|
|
593
|
+
* @param beatsReceipt Signed work-proof receipt from computeWorkProof() (receipt-based spawn)
|
|
751
594
|
*/
|
|
752
|
-
async requestSpawn(childName, childHash) {
|
|
595
|
+
async requestSpawn(childName, childHash, beatsReceipt) {
|
|
753
596
|
try {
|
|
754
597
|
if (childName !== void 0) {
|
|
755
598
|
if (typeof childName !== "string" || childName.trim().length === 0) {
|
|
@@ -761,12 +604,15 @@ var BeatAgent = class {
|
|
|
761
604
|
}
|
|
762
605
|
const res = await this.api("POST", "/api/v1/agent/spawn", {
|
|
763
606
|
child_name: childName,
|
|
764
|
-
child_hash: childHash
|
|
607
|
+
child_hash: childHash,
|
|
608
|
+
...beatsReceipt && { beats_receipt: beatsReceipt }
|
|
765
609
|
});
|
|
766
610
|
if (res.eligible === false) {
|
|
767
611
|
this.log(`Gestation incomplete: ${res.progress_pct}% (need ${res.deficit} more beats)`);
|
|
768
612
|
} else if (res.ok) {
|
|
769
613
|
this.log(`Child spawned: ${res.child_hash?.slice(0, 16)}...`);
|
|
614
|
+
} else if (res.spawn_authorization) {
|
|
615
|
+
this.log(`Spawn authorized${res.receipt_based ? " (receipt-based)" : ""}`);
|
|
770
616
|
}
|
|
771
617
|
return res;
|
|
772
618
|
} catch (err) {
|
|
@@ -774,6 +620,132 @@ var BeatAgent = class {
|
|
|
774
620
|
throw err;
|
|
775
621
|
}
|
|
776
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Compute a Beats work-proof for spawn or resync authorization.
|
|
625
|
+
*
|
|
626
|
+
* Computes `beatsNeeded` sequential SHA-256 beats at `difficulty`, weaving in
|
|
627
|
+
* the given anchor hash, then submits to the Beats service and returns a signed receipt.
|
|
628
|
+
*
|
|
629
|
+
* @param opts.beatsNeeded Minimum beats required (from spawn/resync response.required_beats)
|
|
630
|
+
* @param opts.anchorHash Current global anchor hash (from syncGlobal or getAnchor)
|
|
631
|
+
* @param opts.anchorIndex Current global anchor index
|
|
632
|
+
* @param opts.difficulty Beat difficulty (default: agent's current difficulty)
|
|
633
|
+
*/
|
|
634
|
+
async computeWorkProof(opts) {
|
|
635
|
+
const { beatsNeeded, anchorHash, anchorIndex } = opts;
|
|
636
|
+
const difficulty = opts.difficulty ?? this.difficulty;
|
|
637
|
+
if (!Number.isInteger(beatsNeeded) || beatsNeeded < 0) {
|
|
638
|
+
return { ok: false, error: "beatsNeeded must be a non-negative integer" };
|
|
639
|
+
}
|
|
640
|
+
const t0 = Date.now();
|
|
641
|
+
const genesisHash = (0, import_crypto.createHash)("sha256").update(`provenonce:work-proof-genesis:${this.config.apiKey.slice(0, 16)}:${Date.now()}`).digest("hex");
|
|
642
|
+
const beats = Math.max(beatsNeeded, 1);
|
|
643
|
+
let prevHash = genesisHash;
|
|
644
|
+
const spotCheckCount = Math.min(5, Math.max(1, Math.floor(beats / 100)));
|
|
645
|
+
const spotInterval = Math.max(1, Math.floor(beats / (spotCheckCount + 1)));
|
|
646
|
+
const spotChecks = [];
|
|
647
|
+
for (let i = 1; i <= beats; i++) {
|
|
648
|
+
const beat = computeBeat(prevHash, i, difficulty, void 0, anchorHash);
|
|
649
|
+
prevHash = beat.hash;
|
|
650
|
+
if (i % spotInterval === 0 && spotChecks.length < spotCheckCount) {
|
|
651
|
+
spotChecks.push({ index: beat.index, hash: beat.hash, prev: beat.prev });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const toHash = prevHash;
|
|
655
|
+
const elapsed_ms = Date.now() - t0;
|
|
656
|
+
this.log(`Work proof computed locally: ${beats} beats in ${elapsed_ms}ms`);
|
|
657
|
+
const beatsUrl = this.config.beatsUrl || "https://beats.provenonce.dev";
|
|
658
|
+
const controller = new AbortController();
|
|
659
|
+
const timeout = setTimeout(() => controller.abort(), 12e4);
|
|
660
|
+
try {
|
|
661
|
+
const res = await fetch(`${beatsUrl}/api/v1/beat/work-proof`, {
|
|
662
|
+
method: "POST",
|
|
663
|
+
headers: { "Content-Type": "application/json" },
|
|
664
|
+
body: JSON.stringify({
|
|
665
|
+
work_proof: {
|
|
666
|
+
from_hash: genesisHash,
|
|
667
|
+
to_hash: toHash,
|
|
668
|
+
beats_computed: beats,
|
|
669
|
+
difficulty,
|
|
670
|
+
anchor_index: anchorIndex,
|
|
671
|
+
anchor_hash: anchorHash,
|
|
672
|
+
spot_checks: spotChecks
|
|
673
|
+
}
|
|
674
|
+
}),
|
|
675
|
+
signal: controller.signal
|
|
676
|
+
});
|
|
677
|
+
let data;
|
|
678
|
+
try {
|
|
679
|
+
data = await res.json();
|
|
680
|
+
} catch {
|
|
681
|
+
return { ok: false, error: `Beats service returned non-JSON (status ${res.status})` };
|
|
682
|
+
}
|
|
683
|
+
if (!data.valid || !data.receipt) {
|
|
684
|
+
return { ok: false, error: data.reason || data.error || "Work proof rejected by Beats service" };
|
|
685
|
+
}
|
|
686
|
+
this.log(`Work proof receipt issued by Beats: ${data.receipt.beats_verified} beats verified`);
|
|
687
|
+
return { ok: true, receipt: data.receipt, beats_computed: beats, elapsed_ms };
|
|
688
|
+
} catch (err) {
|
|
689
|
+
if (err.name === "AbortError") {
|
|
690
|
+
return { ok: false, error: "Work proof submission timed out" };
|
|
691
|
+
}
|
|
692
|
+
return { ok: false, error: err.message };
|
|
693
|
+
} finally {
|
|
694
|
+
clearTimeout(timeout);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Compute a Beats work-proof and use it to request spawn authorization.
|
|
699
|
+
*
|
|
700
|
+
* Probes the spawn endpoint to determine required beats, computes the proof,
|
|
701
|
+
* and returns the spawn_authorization token. The caller still needs to:
|
|
702
|
+
* 1. Register the child via POST /api/v1/register with spawn_authorization
|
|
703
|
+
* 2. Finalize via POST /api/v1/agent/spawn with child_hash
|
|
704
|
+
*
|
|
705
|
+
* @param opts.childName Optional name for the child agent
|
|
706
|
+
* @param opts.beatsNeeded Override the required beats (default: auto-probed)
|
|
707
|
+
*/
|
|
708
|
+
async requestSpawnWithBeatsProof(opts) {
|
|
709
|
+
try {
|
|
710
|
+
await this.syncGlobal();
|
|
711
|
+
let requiredBeats = opts?.beatsNeeded;
|
|
712
|
+
if (requiredBeats === void 0) {
|
|
713
|
+
const controller = new AbortController();
|
|
714
|
+
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
715
|
+
try {
|
|
716
|
+
const probeRes = await fetch(`${this.config.registryUrl}/api/v1/agent/spawn`, {
|
|
717
|
+
method: "POST",
|
|
718
|
+
headers: {
|
|
719
|
+
"Content-Type": "application/json",
|
|
720
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
721
|
+
},
|
|
722
|
+
body: JSON.stringify({ child_name: opts?.childName }),
|
|
723
|
+
signal: controller.signal
|
|
724
|
+
});
|
|
725
|
+
const probeData = await probeRes.json();
|
|
726
|
+
if (probeData.spawn_authorization && probeData.eligible) {
|
|
727
|
+
return probeData;
|
|
728
|
+
}
|
|
729
|
+
requiredBeats = probeData.required_beats ?? 1e3;
|
|
730
|
+
} finally {
|
|
731
|
+
clearTimeout(timeout);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
const proofResult = await this.computeWorkProof({
|
|
735
|
+
beatsNeeded: requiredBeats,
|
|
736
|
+
anchorHash: this.globalAnchorHash,
|
|
737
|
+
anchorIndex: this.globalBeat
|
|
738
|
+
});
|
|
739
|
+
if (!proofResult.ok || !proofResult.receipt) {
|
|
740
|
+
return { ok: false, eligible: false, error: proofResult.error || "Failed to compute work proof" };
|
|
741
|
+
}
|
|
742
|
+
this.log(`Submitting spawn with work proof (${proofResult.beats_computed} beats)`);
|
|
743
|
+
return this.requestSpawn(opts?.childName, void 0, proofResult.receipt);
|
|
744
|
+
} catch (err) {
|
|
745
|
+
this.config.onError(err, "requestSpawnWithBeatsProof");
|
|
746
|
+
throw err;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
777
749
|
/**
|
|
778
750
|
* Purchase a SIGIL (cryptographic identity) for this agent.
|
|
779
751
|
* SIGILs gate heartbeating, lineage proofs, and offline verification.
|
|
@@ -1115,7 +1087,6 @@ function computeBeatsLite(startHash, startIndex, count, difficulty = 1e3, anchor
|
|
|
1115
1087
|
ValidationError,
|
|
1116
1088
|
computeBeat,
|
|
1117
1089
|
computeBeatsLite,
|
|
1118
|
-
generateWalletKeypair,
|
|
1119
1090
|
register,
|
|
1120
1091
|
verifyAnchorHash
|
|
1121
1092
|
});
|