@provenonce/sdk 0.14.0 → 0.17.1
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 +16 -15
- package/dist/index.d.mts +177 -29
- package/dist/index.d.ts +177 -29
- package/dist/index.js +289 -40
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +288 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
ProvenonceError: () => ProvenonceError,
|
|
30
30
|
RateLimitError: () => RateLimitError,
|
|
31
31
|
ServerError: () => ServerError,
|
|
32
|
+
SigilRequiredError: () => SigilRequiredError,
|
|
32
33
|
StateError: () => StateError,
|
|
33
34
|
ValidationError: () => ValidationError,
|
|
34
35
|
computeBeat: () => computeBeat,
|
|
@@ -46,6 +47,7 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
|
46
47
|
ErrorCode2["VALIDATION"] = "VALIDATION";
|
|
47
48
|
ErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
|
|
48
49
|
ErrorCode2["AUTH_MISSING"] = "AUTH_MISSING";
|
|
50
|
+
ErrorCode2["SIGIL_REQUIRED"] = "SIGIL_REQUIRED";
|
|
49
51
|
ErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
|
|
50
52
|
ErrorCode2["AGENT_FROZEN"] = "AGENT_FROZEN";
|
|
51
53
|
ErrorCode2["AGENT_NOT_INITIALIZED"] = "AGENT_NOT_INITIALIZED";
|
|
@@ -78,6 +80,13 @@ var AuthError = class extends ProvenonceError {
|
|
|
78
80
|
this.name = "AuthError";
|
|
79
81
|
}
|
|
80
82
|
};
|
|
83
|
+
var SigilRequiredError = class extends ProvenonceError {
|
|
84
|
+
constructor(message = "SIGIL required: purchase a SIGIL before sending heartbeats.") {
|
|
85
|
+
super(message, "SIGIL_REQUIRED" /* SIGIL_REQUIRED */, 403);
|
|
86
|
+
this.name = "SigilRequiredError";
|
|
87
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
81
90
|
var RateLimitError = class extends ProvenonceError {
|
|
82
91
|
constructor(message, statusCode = 429, retryAfterMs) {
|
|
83
92
|
super(message, "RATE_LIMITED" /* RATE_LIMITED */, statusCode);
|
|
@@ -118,7 +127,42 @@ var ServerError = class extends ProvenonceError {
|
|
|
118
127
|
};
|
|
119
128
|
function mapApiError(statusCode, body, path) {
|
|
120
129
|
const msg = typeof body.error === "string" ? body.error : `API error ${statusCode}`;
|
|
130
|
+
const apiCode = typeof body.code === "string" ? body.code : void 0;
|
|
131
|
+
if (apiCode) {
|
|
132
|
+
switch (apiCode) {
|
|
133
|
+
case "SIGIL_REQUIRED":
|
|
134
|
+
return new SigilRequiredError(msg);
|
|
135
|
+
case "AGENT_FROZEN":
|
|
136
|
+
return new FrozenError(msg);
|
|
137
|
+
case "AUTH_MISSING":
|
|
138
|
+
return new AuthError(msg, "AUTH_MISSING" /* AUTH_MISSING */, statusCode);
|
|
139
|
+
case "AUTH_INVALID":
|
|
140
|
+
case "AUTH_FORBIDDEN":
|
|
141
|
+
return new AuthError(msg, "AUTH_INVALID" /* AUTH_INVALID */, statusCode);
|
|
142
|
+
case "RATE_LIMITED":
|
|
143
|
+
case "HEARTBEAT_TOO_SOON":
|
|
144
|
+
case "VOLUME_CAP_REACHED":
|
|
145
|
+
case "SPAWN_LIMIT_REACHED": {
|
|
146
|
+
const retryAfter = typeof body.retry_after_ms === "number" ? body.retry_after_ms : void 0;
|
|
147
|
+
return new RateLimitError(msg, statusCode, retryAfter);
|
|
148
|
+
}
|
|
149
|
+
case "AGENT_NOT_FOUND":
|
|
150
|
+
return new NotFoundError(msg, statusCode);
|
|
151
|
+
case "AGENT_NOT_INITIALIZED":
|
|
152
|
+
return new StateError(msg, "not_initialized", "AGENT_NOT_INITIALIZED" /* AGENT_NOT_INITIALIZED */);
|
|
153
|
+
case "AGENT_WRONG_STATE":
|
|
154
|
+
case "SPAWN_CONFLICT":
|
|
155
|
+
return new StateError(msg, "conflict", "AGENT_WRONG_STATE" /* AGENT_WRONG_STATE */);
|
|
156
|
+
case "SERVER_ERROR":
|
|
157
|
+
case "DB_ERROR":
|
|
158
|
+
case "DB_NOT_CONFIGURED":
|
|
159
|
+
return new ServerError(msg, statusCode);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
121
162
|
if (statusCode === 401 || statusCode === 403) {
|
|
163
|
+
if (statusCode === 403 && body.sigil_required === true) {
|
|
164
|
+
return new SigilRequiredError(msg);
|
|
165
|
+
}
|
|
122
166
|
const code = statusCode === 401 ? "AUTH_MISSING" /* AUTH_MISSING */ : "AUTH_INVALID" /* AUTH_INVALID */;
|
|
123
167
|
return new AuthError(msg, code, statusCode);
|
|
124
168
|
}
|
|
@@ -349,7 +393,7 @@ async function register(name, options) {
|
|
|
349
393
|
if (!res.ok) throw mapApiError(res.status, data, "/api/v1/register");
|
|
350
394
|
return data;
|
|
351
395
|
}
|
|
352
|
-
var BeatAgent = class {
|
|
396
|
+
var BeatAgent = class _BeatAgent {
|
|
353
397
|
constructor(config) {
|
|
354
398
|
this.chain = [];
|
|
355
399
|
this.difficulty = 1e3;
|
|
@@ -361,9 +405,10 @@ var BeatAgent = class {
|
|
|
361
405
|
this.heartbeatInterval = null;
|
|
362
406
|
this.globalBeat = 0;
|
|
363
407
|
this.globalAnchorHash = "";
|
|
408
|
+
this.cachedIdentityClass = "";
|
|
364
409
|
// ── PHASE 2: SIGIL + HEARTBEAT + PROOF ──
|
|
365
|
-
/** Cached
|
|
366
|
-
this.
|
|
410
|
+
/** Cached passport from the most recent heartbeat or SIGIL purchase */
|
|
411
|
+
this.cachedPassport = null;
|
|
367
412
|
if (!config.apiKey || typeof config.apiKey !== "string") {
|
|
368
413
|
throw new ValidationError("BeatAgentConfig.apiKey is required (must be a non-empty string)");
|
|
369
414
|
}
|
|
@@ -474,10 +519,12 @@ var BeatAgent = class {
|
|
|
474
519
|
* Start the autonomous heartbeat loop.
|
|
475
520
|
* Phase 2: Sends paid heartbeats at regular intervals.
|
|
476
521
|
*
|
|
477
|
-
* @param paymentTxFn -
|
|
478
|
-
* If not provided, uses 'devnet-skip' (devnet only).
|
|
522
|
+
* @param paymentTxFn - Function that returns a Solana payment tx signature for each heartbeat (required).
|
|
479
523
|
*/
|
|
480
524
|
startHeartbeat(paymentTxFn) {
|
|
525
|
+
if (!paymentTxFn) {
|
|
526
|
+
throw new ValidationError("paymentTxFn is required. Provide a function that returns a Solana transaction signature.");
|
|
527
|
+
}
|
|
481
528
|
if (this.heartbeatInterval) {
|
|
482
529
|
this.log("Heartbeat already running.");
|
|
483
530
|
return;
|
|
@@ -495,7 +542,7 @@ var BeatAgent = class {
|
|
|
495
542
|
return;
|
|
496
543
|
}
|
|
497
544
|
try {
|
|
498
|
-
const paymentTx =
|
|
545
|
+
const paymentTx = await paymentTxFn();
|
|
499
546
|
await this.heartbeat(paymentTx);
|
|
500
547
|
consecutiveErrors = 0;
|
|
501
548
|
} catch (err) {
|
|
@@ -641,13 +688,16 @@ var BeatAgent = class {
|
|
|
641
688
|
const genesisHash = (0, import_crypto.createHash)("sha256").update(`provenonce:work-proof-genesis:${this.config.apiKey.slice(0, 16)}:${Date.now()}`).digest("hex");
|
|
642
689
|
const beats = Math.max(beatsNeeded, 1);
|
|
643
690
|
let prevHash = genesisHash;
|
|
644
|
-
const spotCheckCount = Math.
|
|
691
|
+
const spotCheckCount = Math.max(
|
|
692
|
+
Math.min(beats, 3),
|
|
693
|
+
Math.min(Math.ceil(beats / 1e3), 25)
|
|
694
|
+
);
|
|
645
695
|
const spotInterval = Math.max(1, Math.floor(beats / (spotCheckCount + 1)));
|
|
646
696
|
const spotChecks = [];
|
|
647
697
|
for (let i = 1; i <= beats; i++) {
|
|
648
698
|
const beat = computeBeat(prevHash, i, difficulty, void 0, anchorHash);
|
|
649
699
|
prevHash = beat.hash;
|
|
650
|
-
if (i % spotInterval === 0 && spotChecks.length < spotCheckCount) {
|
|
700
|
+
if (i === 1 || i === beats || i % spotInterval === 0 && spotChecks.length < spotCheckCount) {
|
|
651
701
|
spotChecks.push({ index: beat.index, hash: beat.hash, prev: beat.prev });
|
|
652
702
|
}
|
|
653
703
|
}
|
|
@@ -790,16 +840,19 @@ var BeatAgent = class {
|
|
|
790
840
|
}
|
|
791
841
|
try {
|
|
792
842
|
const res = await this.api("POST", "/api/v1/sigil", body);
|
|
793
|
-
|
|
794
|
-
|
|
843
|
+
const resPassport = res.passport || res.lineage_proof;
|
|
844
|
+
if (resPassport) {
|
|
845
|
+
this.cachedPassport = resPassport;
|
|
795
846
|
}
|
|
796
847
|
const sigilStr = res.sigil?.sigil || res.sigil?.identity_class || "";
|
|
848
|
+
this.cachedIdentityClass = res.sigil?.identity_class || "";
|
|
797
849
|
this.log(`SIGIL purchased: ${sigilStr}`);
|
|
798
850
|
this.config.onStatusChange("sigil_issued", { sigil: sigilStr });
|
|
799
851
|
return {
|
|
800
852
|
ok: true,
|
|
801
853
|
sigil: res.sigil,
|
|
802
|
-
|
|
854
|
+
passport: resPassport,
|
|
855
|
+
lineage_proof: resPassport,
|
|
803
856
|
fee: res.fee
|
|
804
857
|
};
|
|
805
858
|
} catch (err) {
|
|
@@ -807,6 +860,35 @@ var BeatAgent = class {
|
|
|
807
860
|
return { ok: false, error: err.message };
|
|
808
861
|
}
|
|
809
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* Get a SIGIL attribution token from a partner skill's ref_token (RFC-018).
|
|
865
|
+
*
|
|
866
|
+
* Call this after receiving a skill's ref_token in its response.
|
|
867
|
+
* Returns a pvr_ attribution token to include as `at` in purchaseSigil().
|
|
868
|
+
*
|
|
869
|
+
* This is the agent-side half of the two-sided witnessed proof:
|
|
870
|
+
* the skill's server already called /sigil/check for this agent hash.
|
|
871
|
+
* This call completes the proof — our server witnesses both sides.
|
|
872
|
+
*
|
|
873
|
+
* @param partnerRef - The skill's 16-char ref_token
|
|
874
|
+
*/
|
|
875
|
+
async getSigilAttribution(partnerRef) {
|
|
876
|
+
if (!partnerRef || typeof partnerRef !== "string" || partnerRef.length !== 16) {
|
|
877
|
+
return { ok: false, error: "partnerRef must be a 16-character ref_token from the skill" };
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
const res = await this.api("POST", "/api/v1/sigil/check", { partner_ref: partnerRef });
|
|
881
|
+
return {
|
|
882
|
+
ok: true,
|
|
883
|
+
attribution_token: res.attribution_token,
|
|
884
|
+
signup_url: res.signup_url ?? null,
|
|
885
|
+
already_has_sigil: res.already_has_sigil ?? false
|
|
886
|
+
};
|
|
887
|
+
} catch (err) {
|
|
888
|
+
this.config.onError(err, "getSigilAttribution");
|
|
889
|
+
return { ok: false, error: err.message };
|
|
890
|
+
}
|
|
891
|
+
}
|
|
810
892
|
/**
|
|
811
893
|
* Update mutable SIGIL metadata fields.
|
|
812
894
|
* Requires a SIGIL. Cannot modify immutable fields.
|
|
@@ -836,17 +918,25 @@ var BeatAgent = class {
|
|
|
836
918
|
* Requires a SIGIL. Returns a signed lineage proof.
|
|
837
919
|
* This is the Phase 2 replacement for pulse() + checkin().
|
|
838
920
|
*
|
|
839
|
-
* @param paymentTx - Solana transaction signature
|
|
921
|
+
* @param paymentTx - Solana transaction signature (required).
|
|
840
922
|
* @param globalAnchor - Optional: the global anchor index to reference.
|
|
841
923
|
*/
|
|
842
|
-
async heartbeat(paymentTx, globalAnchor) {
|
|
924
|
+
async heartbeat(paymentTx, globalAnchor, opts) {
|
|
925
|
+
if (!paymentTx) {
|
|
926
|
+
throw new ValidationError("paymentTx is required. Provide a Solana transaction signature.");
|
|
927
|
+
}
|
|
843
928
|
try {
|
|
844
|
-
const
|
|
845
|
-
payment_tx: paymentTx
|
|
929
|
+
const body = {
|
|
930
|
+
payment_tx: paymentTx,
|
|
846
931
|
global_anchor: globalAnchor
|
|
847
|
-
}
|
|
848
|
-
if (
|
|
849
|
-
|
|
932
|
+
};
|
|
933
|
+
if (opts?.sponsoredBy) {
|
|
934
|
+
body.sponsored_by = opts.sponsoredBy;
|
|
935
|
+
}
|
|
936
|
+
const res = await this.api("POST", "/api/v1/agent/heartbeat", body);
|
|
937
|
+
const hbPassport = res.passport || res.lineage_proof;
|
|
938
|
+
if (hbPassport) {
|
|
939
|
+
this.cachedPassport = hbPassport;
|
|
850
940
|
}
|
|
851
941
|
if (res.ok) {
|
|
852
942
|
this.status = "active";
|
|
@@ -856,69 +946,216 @@ var BeatAgent = class {
|
|
|
856
946
|
}
|
|
857
947
|
return {
|
|
858
948
|
ok: res.ok,
|
|
859
|
-
|
|
949
|
+
passport: hbPassport,
|
|
950
|
+
lineage_proof: hbPassport,
|
|
860
951
|
heartbeat_count_epoch: res.heartbeat_count_epoch,
|
|
861
952
|
billing_epoch: res.billing_epoch,
|
|
862
953
|
current_beat: res.current_beat,
|
|
863
|
-
fee: res.fee
|
|
954
|
+
fee: res.fee,
|
|
955
|
+
sponsor: res.sponsor
|
|
864
956
|
};
|
|
865
957
|
} catch (err) {
|
|
958
|
+
if (err instanceof SigilRequiredError) {
|
|
959
|
+
return { ok: false, sigil_required: true, error: err.message };
|
|
960
|
+
}
|
|
866
961
|
this.config.onError(err, "heartbeat");
|
|
867
962
|
return { ok: false, error: err.message };
|
|
868
963
|
}
|
|
869
964
|
}
|
|
870
965
|
/**
|
|
871
|
-
* Reissue a
|
|
966
|
+
* Reissue a passport. "Reprint, not a renewal."
|
|
872
967
|
* Does NOT create a new lineage event.
|
|
873
968
|
*
|
|
874
|
-
* @param paymentTx - Solana transaction signature
|
|
969
|
+
* @param paymentTx - Solana transaction signature (required).
|
|
875
970
|
*/
|
|
876
|
-
async
|
|
971
|
+
async reissuePassport(paymentTx) {
|
|
972
|
+
if (!paymentTx) {
|
|
973
|
+
throw new ValidationError("paymentTx is required. Provide a Solana transaction signature.");
|
|
974
|
+
}
|
|
877
975
|
try {
|
|
878
976
|
const res = await this.api("POST", "/api/v1/agent/reissue-proof", {
|
|
879
|
-
payment_tx: paymentTx
|
|
977
|
+
payment_tx: paymentTx
|
|
880
978
|
});
|
|
881
|
-
|
|
882
|
-
|
|
979
|
+
const rePassport = res.passport || res.lineage_proof;
|
|
980
|
+
if (rePassport) {
|
|
981
|
+
this.cachedPassport = rePassport;
|
|
883
982
|
}
|
|
884
|
-
return { ok: true,
|
|
983
|
+
return { ok: true, passport: rePassport, lineage_proof: rePassport };
|
|
984
|
+
} catch (err) {
|
|
985
|
+
this.config.onError(err, "reissuePassport");
|
|
986
|
+
return { ok: false, error: err.message };
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
/** @deprecated Use `reissuePassport()` instead. Sunset 2026-09-01. */
|
|
990
|
+
async reissueProof(paymentTx) {
|
|
991
|
+
return this.reissuePassport(paymentTx);
|
|
992
|
+
}
|
|
993
|
+
// ============ SPONSORSHIP (RFC-021) ============
|
|
994
|
+
/**
|
|
995
|
+
* Sponsor a child agent's heartbeats.
|
|
996
|
+
* The authenticated agent (this instance) becomes the sponsor, paying heartbeat
|
|
997
|
+
* fees on behalf of the specified child from this agent's operator wallet.
|
|
998
|
+
*
|
|
999
|
+
* @param childHash - Hash of the child agent to sponsor
|
|
1000
|
+
* @param opts.maxHeartbeatsEpoch - Max heartbeats per billing epoch (default: 100, max: 10,000)
|
|
1001
|
+
* @param opts.expiresInHours - Optional TTL for the sponsorship
|
|
1002
|
+
*/
|
|
1003
|
+
async sponsorChild(childHash, opts) {
|
|
1004
|
+
try {
|
|
1005
|
+
const res = await this.api("POST", "/api/v1/agent/sponsor", {
|
|
1006
|
+
child_hash: childHash,
|
|
1007
|
+
max_heartbeats_epoch: opts?.maxHeartbeatsEpoch,
|
|
1008
|
+
expires_in_hours: opts?.expiresInHours
|
|
1009
|
+
});
|
|
1010
|
+
return { ok: true, sponsorship: res.sponsorship };
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
this.config.onError(err, "sponsorChild");
|
|
1013
|
+
return { ok: false, error: err.message };
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Revoke a sponsorship for a child agent.
|
|
1018
|
+
* The child will no longer be able to use this agent's wallet for heartbeat payments.
|
|
1019
|
+
*/
|
|
1020
|
+
async revokeSponsor(childHash) {
|
|
1021
|
+
try {
|
|
1022
|
+
await this.api("DELETE", "/api/v1/agent/sponsor", {
|
|
1023
|
+
child_hash: childHash
|
|
1024
|
+
});
|
|
1025
|
+
return { ok: true };
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
this.config.onError(err, "revokeSponsor");
|
|
1028
|
+
return { ok: false, error: err.message };
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* List all active sponsorships for this agent (as parent).
|
|
1033
|
+
*/
|
|
1034
|
+
async listSponsorships() {
|
|
1035
|
+
try {
|
|
1036
|
+
const res = await this.api("GET", "/api/v1/agent/sponsor");
|
|
1037
|
+
return { ok: true, sponsorships: res.sponsorships };
|
|
885
1038
|
} catch (err) {
|
|
886
|
-
this.config.onError(err, "
|
|
1039
|
+
this.config.onError(err, "listSponsorships");
|
|
887
1040
|
return { ok: false, error: err.message };
|
|
888
1041
|
}
|
|
889
1042
|
}
|
|
890
1043
|
/**
|
|
891
|
-
* Get the latest cached
|
|
892
|
-
* Returns null if no
|
|
1044
|
+
* Get the latest cached passport (no network call).
|
|
1045
|
+
* Returns null if no passport has been obtained yet.
|
|
893
1046
|
*/
|
|
1047
|
+
getLatestPassport() {
|
|
1048
|
+
return this.cachedPassport;
|
|
1049
|
+
}
|
|
1050
|
+
/** @deprecated Use `getLatestPassport()` instead. Sunset 2026-09-01. */
|
|
894
1051
|
getLatestProof() {
|
|
895
|
-
return this.
|
|
1052
|
+
return this.getLatestPassport();
|
|
896
1053
|
}
|
|
897
1054
|
/**
|
|
898
|
-
* Get the agent's passport (alias for
|
|
1055
|
+
* Get the agent's passport (alias for getLatestPassport).
|
|
899
1056
|
* The passport is the agent's portable, offline-verifiable credential.
|
|
900
1057
|
* Returns null if no passport has been issued yet (requires SIGIL + heartbeat).
|
|
901
1058
|
*/
|
|
902
1059
|
getPassport() {
|
|
903
|
-
return this.
|
|
1060
|
+
return this.cachedPassport;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Export this agent's passport in both flat (Passport) and W3C VC envelope formats.
|
|
1064
|
+
* Fetches a freshly signed passport from the server. Free endpoint, rate-limited 10/hr.
|
|
1065
|
+
* Returns null if the request fails.
|
|
1066
|
+
*/
|
|
1067
|
+
async exportPassport() {
|
|
1068
|
+
try {
|
|
1069
|
+
const res = await this.api("GET", "/api/v1/agent/passport");
|
|
1070
|
+
const expPassport = res.passport || res.lineage_proof;
|
|
1071
|
+
if (expPassport) {
|
|
1072
|
+
this.cachedPassport = expPassport;
|
|
1073
|
+
}
|
|
1074
|
+
return { passport: expPassport, passport_vc: res.passport_vc, lineage_proof: expPassport };
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
this.config.onError(err, "exportPassport");
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Wrap a Passport in a W3C Verifiable Credential envelope (client-side).
|
|
1082
|
+
* Does not make any network call. Requires the passport to have authority_key_id.
|
|
1083
|
+
*/
|
|
1084
|
+
static proofToPassportVC(proof, authorityKeyId) {
|
|
1085
|
+
const keyId = proof.authority_key_id || authorityKeyId || "unknown";
|
|
1086
|
+
return {
|
|
1087
|
+
"@context": [
|
|
1088
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
1089
|
+
"https://provenonce.io/.well-known/provenonce-passport-v1.jsonld"
|
|
1090
|
+
],
|
|
1091
|
+
type: ["VerifiableCredential", "ProvenoncePassport"],
|
|
1092
|
+
issuer: {
|
|
1093
|
+
id: "https://provenonce.io",
|
|
1094
|
+
authority_key_id: keyId
|
|
1095
|
+
},
|
|
1096
|
+
issuanceDate: new Date(proof.issued_at).toISOString(),
|
|
1097
|
+
expirationDate: new Date(proof.valid_until).toISOString(),
|
|
1098
|
+
credentialSubject: {
|
|
1099
|
+
id: `provenonce:agent:${proof.agent_hash}`,
|
|
1100
|
+
agent_hash: proof.agent_hash,
|
|
1101
|
+
agent_public_key: proof.agent_public_key,
|
|
1102
|
+
identity_class: proof.identity_class,
|
|
1103
|
+
registered_at_beat: proof.registered_at_beat,
|
|
1104
|
+
sigil_issued_at_beat: proof.sigil_issued_at_beat,
|
|
1105
|
+
last_heartbeat_beat: proof.last_heartbeat_beat,
|
|
1106
|
+
lineage_chain_hash: proof.lineage_chain_hash
|
|
1107
|
+
},
|
|
1108
|
+
proof: {
|
|
1109
|
+
type: "Ed25519Signature2020",
|
|
1110
|
+
created: new Date(proof.issued_at).toISOString(),
|
|
1111
|
+
verificationMethod: `https://provenonce.io/.well-known/provenonce-authority.json#${keyId}`,
|
|
1112
|
+
proofPurpose: "assertionMethod",
|
|
1113
|
+
cryptosuite: "eddsa-provenonce-2026",
|
|
1114
|
+
proofValue: proof.provenonce_signature
|
|
1115
|
+
},
|
|
1116
|
+
format_version: proof.format_version || 1
|
|
1117
|
+
};
|
|
904
1118
|
}
|
|
905
1119
|
/**
|
|
906
|
-
* Verify a
|
|
1120
|
+
* Verify a passport locally using the authority public key.
|
|
907
1121
|
* Offline verification — no API call, no SOL cost.
|
|
908
1122
|
*
|
|
909
1123
|
* Returns a VerificationResult object. The object is truthy when valid,
|
|
910
|
-
* so `if (BeatAgent.
|
|
1124
|
+
* so `if (BeatAgent.verifyPassportLocally(proof, key))` still works.
|
|
911
1125
|
*
|
|
912
|
-
* @param proof - The
|
|
1126
|
+
* @param proof - The Passport to verify
|
|
913
1127
|
* @param authorityPubKeyHex - 32-byte hex-encoded Ed25519 public key from /.well-known/provenonce-authority.json
|
|
914
1128
|
* @param currentBeat - Optional current global beat index (for beatsSinceHeartbeat calculation)
|
|
915
1129
|
*/
|
|
916
|
-
static
|
|
1130
|
+
static verifyPassportLocally(proof, authorityPubKeyHex, currentBeat) {
|
|
917
1131
|
const now = Date.now();
|
|
918
1132
|
const expired = now > proof.valid_until;
|
|
919
1133
|
let signatureValid = false;
|
|
920
1134
|
try {
|
|
921
|
-
const canonical = JSON.stringify({
|
|
1135
|
+
const canonical = proof.format_version ? JSON.stringify({
|
|
1136
|
+
format_version: proof.format_version,
|
|
1137
|
+
agent_hash: proof.agent_hash,
|
|
1138
|
+
agent_public_key: proof.agent_public_key,
|
|
1139
|
+
authority_key_id: proof.authority_key_id,
|
|
1140
|
+
identity_class: proof.identity_class,
|
|
1141
|
+
registered_at_beat: proof.registered_at_beat,
|
|
1142
|
+
sigil_issued_at_beat: proof.sigil_issued_at_beat,
|
|
1143
|
+
last_heartbeat_beat: proof.last_heartbeat_beat,
|
|
1144
|
+
lineage_chain_hash: proof.lineage_chain_hash,
|
|
1145
|
+
issued_at: proof.issued_at,
|
|
1146
|
+
valid_until: proof.valid_until
|
|
1147
|
+
}) : proof.authority_key_id ? JSON.stringify({
|
|
1148
|
+
agent_hash: proof.agent_hash,
|
|
1149
|
+
agent_public_key: proof.agent_public_key,
|
|
1150
|
+
authority_key_id: proof.authority_key_id,
|
|
1151
|
+
identity_class: proof.identity_class,
|
|
1152
|
+
registered_at_beat: proof.registered_at_beat,
|
|
1153
|
+
sigil_issued_at_beat: proof.sigil_issued_at_beat,
|
|
1154
|
+
last_heartbeat_beat: proof.last_heartbeat_beat,
|
|
1155
|
+
lineage_chain_hash: proof.lineage_chain_hash,
|
|
1156
|
+
issued_at: proof.issued_at,
|
|
1157
|
+
valid_until: proof.valid_until
|
|
1158
|
+
}) : JSON.stringify({
|
|
922
1159
|
agent_hash: proof.agent_hash,
|
|
923
1160
|
agent_public_key: proof.agent_public_key,
|
|
924
1161
|
identity_class: proof.identity_class,
|
|
@@ -944,12 +1181,16 @@ var BeatAgent = class {
|
|
|
944
1181
|
const beatsSinceHeartbeat = currentBeat != null ? currentBeat - proof.last_heartbeat_beat : null;
|
|
945
1182
|
let warning;
|
|
946
1183
|
if (expired) {
|
|
947
|
-
warning = "
|
|
1184
|
+
warning = "Passport has expired. Reissue with reissuePassport() or send a heartbeat.";
|
|
948
1185
|
} else if (beatsSinceHeartbeat != null && beatsSinceHeartbeat > 60) {
|
|
949
1186
|
warning = `Agent is ${beatsSinceHeartbeat} beats behind. Heartbeat may be stale.`;
|
|
950
1187
|
}
|
|
951
1188
|
return { valid, signatureValid, expired, lastHeartbeatBeat: proof.last_heartbeat_beat, beatsSinceHeartbeat, warning };
|
|
952
1189
|
}
|
|
1190
|
+
/** @deprecated Use `verifyPassportLocally()` instead. Sunset 2026-09-01. */
|
|
1191
|
+
static verifyProofLocally(proof, authorityPubKeyHex, currentBeat) {
|
|
1192
|
+
return _BeatAgent.verifyPassportLocally(proof, authorityPubKeyHex, currentBeat);
|
|
1193
|
+
}
|
|
953
1194
|
// ── STATUS ──
|
|
954
1195
|
/**
|
|
955
1196
|
* Get this agent's full beat status from the registry.
|
|
@@ -976,6 +1217,14 @@ var BeatAgent = class {
|
|
|
976
1217
|
chainLength: this.chain.length
|
|
977
1218
|
};
|
|
978
1219
|
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Returns true if this agent has purchased a SIGIL in this session,
|
|
1222
|
+
* or if a cached passport contains an identity_class.
|
|
1223
|
+
* For an authoritative check, use GET /api/v1/agent/me.
|
|
1224
|
+
*/
|
|
1225
|
+
hasSigil() {
|
|
1226
|
+
return !!(this.cachedIdentityClass || this.cachedPassport?.identity_class);
|
|
1227
|
+
}
|
|
979
1228
|
// ── INTERNALS ──
|
|
980
1229
|
async syncGlobal() {
|
|
981
1230
|
try {
|
|
@@ -991,7 +1240,6 @@ var BeatAgent = class {
|
|
|
991
1240
|
}
|
|
992
1241
|
this.globalBeat = data.anchor.beat_index;
|
|
993
1242
|
this.globalAnchorHash = data.anchor.hash || "";
|
|
994
|
-
if (data.anchor.difficulty) this.difficulty = data.anchor.difficulty;
|
|
995
1243
|
this.log(`Synced to global beat ${this.globalBeat} (D=${this.difficulty})`);
|
|
996
1244
|
}
|
|
997
1245
|
} catch {
|
|
@@ -1083,6 +1331,7 @@ function computeBeatsLite(startHash, startIndex, count, difficulty = 1e3, anchor
|
|
|
1083
1331
|
ProvenonceError,
|
|
1084
1332
|
RateLimitError,
|
|
1085
1333
|
ServerError,
|
|
1334
|
+
SigilRequiredError,
|
|
1086
1335
|
StateError,
|
|
1087
1336
|
ValidationError,
|
|
1088
1337
|
computeBeat,
|