@oma3/omatrust 0.1.0-alpha.1 → 0.1.0-alpha.11

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.
Files changed (44) hide show
  1. package/README.md +21 -7
  2. package/dist/identity/index.cjs +544 -1
  3. package/dist/identity/index.cjs.map +1 -1
  4. package/dist/identity/index.d.cts +2 -1
  5. package/dist/identity/index.d.ts +2 -1
  6. package/dist/identity/index.js +528 -2
  7. package/dist/identity/index.js.map +1 -1
  8. package/dist/index-BOvk-7Ku.d.cts +235 -0
  9. package/dist/index-C2w5EvFH.d.ts +329 -0
  10. package/dist/index-QueRiudB.d.cts +329 -0
  11. package/dist/index-R78TpAhN.d.ts +235 -0
  12. package/dist/index.cjs +2020 -145
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +4 -2
  15. package/dist/index.d.ts +4 -2
  16. package/dist/index.js +2021 -146
  17. package/dist/index.js.map +1 -1
  18. package/dist/reputation/index.browser.cjs +3010 -0
  19. package/dist/reputation/index.browser.cjs.map +1 -0
  20. package/dist/reputation/index.browser.d.cts +14 -0
  21. package/dist/reputation/index.browser.d.ts +14 -0
  22. package/dist/reputation/index.browser.js +2946 -0
  23. package/dist/reputation/index.browser.js.map +1 -0
  24. package/dist/reputation/index.cjs +1884 -123
  25. package/dist/reputation/index.cjs.map +1 -1
  26. package/dist/reputation/index.d.cts +3 -1
  27. package/dist/reputation/index.d.ts +3 -1
  28. package/dist/reputation/index.js +1861 -123
  29. package/dist/reputation/index.js.map +1 -1
  30. package/dist/subject-ownership-B7cFlm8c.d.cts +664 -0
  31. package/dist/subject-ownership-B7cFlm8c.d.ts +664 -0
  32. package/dist/types-dpYxRq8N.d.cts +281 -0
  33. package/dist/types-dpYxRq8N.d.ts +281 -0
  34. package/dist/widgets/index.cjs +238 -0
  35. package/dist/widgets/index.cjs.map +1 -0
  36. package/dist/widgets/index.d.cts +158 -0
  37. package/dist/widgets/index.d.ts +158 -0
  38. package/dist/widgets/index.js +226 -0
  39. package/dist/widgets/index.js.map +1 -0
  40. package/package.json +35 -7
  41. package/dist/index-ChbJxwOA.d.cts +0 -415
  42. package/dist/index-ChbJxwOA.d.ts +0 -415
  43. package/dist/index-QZDExA4I.d.cts +0 -90
  44. package/dist/index-QZDExA4I.d.ts +0 -90
package/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var ethers = require('ethers');
4
+ var jose = require('jose');
4
5
  var canonicalize = require('canonicalize');
5
6
  var easSdk = require('@ethereum-attestation-service/eas-sdk');
6
7
  var promises = require('dns/promises');
@@ -56,6 +57,7 @@ function asError(err) {
56
57
  // src/identity/index.ts
57
58
  var identity_exports = {};
58
59
  __export(identity_exports, {
60
+ assertBareDid: () => assertBareDid,
59
61
  buildCaip10: () => buildCaip10,
60
62
  buildCaip2: () => buildCaip2,
61
63
  buildDidPkh: () => buildDidPkh,
@@ -66,27 +68,43 @@ __export(identity_exports, {
66
68
  canonicalizeJson: () => canonicalizeJson,
67
69
  computeDidAddress: () => computeDidAddress,
68
70
  computeDidHash: () => computeDidHash,
71
+ computeJwkThumbprint: () => computeJwkThumbprint,
72
+ didJwkToJwk: () => didJwkToJwk,
69
73
  didToAddress: () => didToAddress,
70
74
  extractAddressFromDid: () => extractAddressFromDid,
75
+ extractAuthorizationMetadata: () => extractAuthorizationMetadata,
76
+ extractControllerEvmAddress: () => extractControllerEvmAddress,
71
77
  extractDidIdentifier: () => extractDidIdentifier,
72
78
  extractDidMethod: () => extractDidMethod,
79
+ formatJktValue: () => formatJktValue,
73
80
  getAddressFromDidPkh: () => getAddressFromDidPkh,
74
81
  getChainIdFromDidPkh: () => getChainIdFromDidPkh,
75
82
  getDomainFromDidWeb: () => getDomainFromDidWeb,
76
83
  getNamespaceFromDidPkh: () => getNamespaceFromDidPkh,
77
84
  hashCanonicalizedJson: () => hashCanonicalizedJson,
85
+ isDidUrl: () => isDidUrl,
78
86
  isEvmDidPkh: () => isEvmDidPkh,
87
+ isPrivateKeyDid: () => isPrivateKeyDid,
88
+ isSameControllerId: () => isSameControllerId,
79
89
  isValidDid: () => isValidDid,
90
+ jwkToDidJwk: () => jwkToDidJwk,
80
91
  normalizeCaip10: () => normalizeCaip10,
81
92
  normalizeDid: () => normalizeDid,
82
93
  normalizeDidHandle: () => normalizeDidHandle,
94
+ normalizeDidJwk: () => normalizeDidJwk,
83
95
  normalizeDidKey: () => normalizeDidKey,
84
96
  normalizeDidPkh: () => normalizeDidPkh,
85
97
  normalizeDidWeb: () => normalizeDidWeb,
86
98
  normalizeDomain: () => normalizeDomain,
87
99
  parseCaip10: () => parseCaip10,
88
100
  parseCaip2: () => parseCaip2,
89
- validateDidAddress: () => validateDidAddress
101
+ parseDidUrl: () => parseDidUrl,
102
+ publicJwkEquals: () => publicJwkEquals,
103
+ resolveDidUrlToControllerDid: () => resolveDidUrlToControllerDid,
104
+ resolveDidUrlToPublicKey: () => resolveDidUrlToPublicKey,
105
+ validateDidAddress: () => validateDidAddress,
106
+ validatePrivateKeyDid: () => validatePrivateKeyDid,
107
+ validatePublicJwk: () => validatePublicJwk
90
108
  });
91
109
 
92
110
  // src/identity/caip.ts
@@ -158,7 +176,7 @@ function extractDidIdentifier(did) {
158
176
  }
159
177
  function normalizeDomain(domain) {
160
178
  assertString(domain, "domain", "INVALID_DID");
161
- return domain.trim().toLowerCase().replace(/\.$/, "");
179
+ return domain.trim().toLowerCase().replace(/\.$/, "").replace(/^www\./, "");
162
180
  }
163
181
  function normalizeDidWeb(input) {
164
182
  assertString(input, "input", "INVALID_DID");
@@ -234,6 +252,8 @@ function normalizeDid(input) {
234
252
  return normalizeDidHandle(trimmed);
235
253
  case "key":
236
254
  return normalizeDidKey(trimmed);
255
+ case "jwk":
256
+ return normalizeDidJwk(trimmed);
237
257
  default:
238
258
  return trimmed;
239
259
  }
@@ -337,6 +357,524 @@ function extractAddressFromDid(identifier) {
337
357
  }
338
358
  throw new OmaTrustError("INVALID_DID", "Unsupported identifier format", { identifier });
339
359
  }
360
+ var CAIP2_NAMESPACE_REGEX = /^[a-z0-9-]{3,8}$/;
361
+ var BASE64URL_REGEX = /^[A-Za-z0-9_-]+$/;
362
+ var VALID_JWK_KTY = /* @__PURE__ */ new Set(["EC", "OKP", "RSA"]);
363
+ function isPrivateKeyDid(did) {
364
+ return validatePrivateKeyDid(did).valid;
365
+ }
366
+ function validatePrivateKeyDid(did) {
367
+ if (typeof did !== "string" || did.trim().length === 0) {
368
+ return { valid: false, method: null, error: "DID must be a non-empty string" };
369
+ }
370
+ const trimmed = did.trim();
371
+ const method = extractDidMethod(trimmed);
372
+ switch (method) {
373
+ case "pkh":
374
+ return validateDidPkh(trimmed);
375
+ case "jwk":
376
+ return validateDidJwk(trimmed);
377
+ default:
378
+ return {
379
+ valid: false,
380
+ method: null,
381
+ error: method ? `DID method "${method}" is not a recognized private-key method` : "Invalid DID format"
382
+ };
383
+ }
384
+ }
385
+ function validateDidPkh(did) {
386
+ const parts = did.split(":");
387
+ if (parts.length !== 5) {
388
+ return {
389
+ valid: false,
390
+ method: "pkh",
391
+ error: `did:pkh must have exactly 5 colon-separated parts, got ${parts.length}`
392
+ };
393
+ }
394
+ const [, , namespace, chainId, address] = parts;
395
+ if (!namespace) {
396
+ return { valid: false, method: "pkh", error: "Missing namespace" };
397
+ }
398
+ if (!CAIP2_NAMESPACE_REGEX.test(namespace)) {
399
+ return {
400
+ valid: false,
401
+ method: "pkh",
402
+ error: `Invalid CAIP-2 namespace "${namespace}" (must be 3-8 lowercase alphanumeric/hyphen chars)`
403
+ };
404
+ }
405
+ if (!chainId) {
406
+ return { valid: false, method: "pkh", error: "Missing chain ID (reference)" };
407
+ }
408
+ if (!address) {
409
+ return { valid: false, method: "pkh", error: "Missing address" };
410
+ }
411
+ if (namespace === "eip155") {
412
+ if (!/^\d+$/.test(chainId)) {
413
+ return {
414
+ valid: false,
415
+ method: "pkh",
416
+ error: `Invalid eip155 chain ID "${chainId}" (must be numeric)`
417
+ };
418
+ }
419
+ if (!ethers.isAddress(address)) {
420
+ return {
421
+ valid: false,
422
+ method: "pkh",
423
+ error: `Invalid EVM address "${address}" (must be 0x + 40 hex chars)`
424
+ };
425
+ }
426
+ }
427
+ return { valid: true, method: "pkh" };
428
+ }
429
+ function validateDidJwk(did) {
430
+ const parts = did.split(":");
431
+ if (parts.length !== 3) {
432
+ return {
433
+ valid: false,
434
+ method: "jwk",
435
+ error: `did:jwk must have exactly 3 colon-separated parts, got ${parts.length}`
436
+ };
437
+ }
438
+ const [, , encoded] = parts;
439
+ if (!encoded || encoded.length === 0) {
440
+ return { valid: false, method: "jwk", error: "Missing base64url-encoded JWK identifier" };
441
+ }
442
+ if (!BASE64URL_REGEX.test(encoded)) {
443
+ return {
444
+ valid: false,
445
+ method: "jwk",
446
+ error: "Identifier contains invalid base64url characters"
447
+ };
448
+ }
449
+ let decoded;
450
+ try {
451
+ const bytes = jose.base64url.decode(encoded);
452
+ decoded = new TextDecoder().decode(bytes);
453
+ } catch {
454
+ return { valid: false, method: "jwk", error: "Failed to base64url-decode identifier" };
455
+ }
456
+ let jwk;
457
+ try {
458
+ jwk = JSON.parse(decoded);
459
+ } catch {
460
+ return { valid: false, method: "jwk", error: "Decoded identifier is not valid JSON" };
461
+ }
462
+ if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
463
+ return { valid: false, method: "jwk", error: "Decoded JWK must be a JSON object" };
464
+ }
465
+ const kty = jwk.kty;
466
+ if (typeof kty !== "string" || !VALID_JWK_KTY.has(kty)) {
467
+ return {
468
+ valid: false,
469
+ method: "jwk",
470
+ error: `Invalid or missing kty field (must be one of: EC, OKP, RSA)`
471
+ };
472
+ }
473
+ if ("d" in jwk) {
474
+ return {
475
+ valid: false,
476
+ method: "jwk",
477
+ error: "DID must reference a public key \u2014 private key component (d) is not allowed"
478
+ };
479
+ }
480
+ return { valid: true, method: "jwk" };
481
+ }
482
+ function normalizeDidJwk(input) {
483
+ assertString(input, "input", "INVALID_DID");
484
+ const trimmed = input.trim();
485
+ if (!trimmed.startsWith("did:jwk:")) {
486
+ throw new OmaTrustError("INVALID_DID", "Expected did:jwk DID", { input });
487
+ }
488
+ const result = validateDidJwk(trimmed);
489
+ if (!result.valid) {
490
+ throw new OmaTrustError("INVALID_DID", result.error ?? "Invalid did:jwk", { input });
491
+ }
492
+ return trimmed;
493
+ }
494
+
495
+ // src/identity/did-url.ts
496
+ var DID_URL_REGEX = /^did:[a-z0-9]+:.+$/i;
497
+ function parseDidUrl(input) {
498
+ assertString(input, "input", "INVALID_DID_URL");
499
+ const trimmed = input.trim();
500
+ const hashIndex = trimmed.indexOf("#");
501
+ let did;
502
+ let fragment;
503
+ if (hashIndex === -1) {
504
+ did = trimmed;
505
+ fragment = null;
506
+ } else {
507
+ did = trimmed.slice(0, hashIndex);
508
+ const rawFragment = trimmed.slice(hashIndex + 1);
509
+ if (rawFragment.length === 0) {
510
+ throw new OmaTrustError(
511
+ "INVALID_DID_URL",
512
+ "DID URL has empty fragment (trailing '#' with no identifier)",
513
+ { input }
514
+ );
515
+ }
516
+ fragment = rawFragment;
517
+ }
518
+ if (!DID_URL_REGEX.test(did)) {
519
+ throw new OmaTrustError(
520
+ "INVALID_DID_URL",
521
+ "Invalid DID URL: base DID portion is malformed",
522
+ { input, did }
523
+ );
524
+ }
525
+ return {
526
+ didUrl: trimmed,
527
+ did,
528
+ fragment
529
+ };
530
+ }
531
+ function isDidUrl(input) {
532
+ if (typeof input !== "string") return false;
533
+ const trimmed = input.trim();
534
+ return trimmed.includes("#") && DID_URL_REGEX.test(trimmed.split("#")[0]);
535
+ }
536
+ function assertBareDid(input, paramName = "did") {
537
+ assertString(input, paramName, "INVALID_DID");
538
+ if (isDidUrl(input)) {
539
+ throw new OmaTrustError(
540
+ "INVALID_DID",
541
+ `Expected a bare DID but received a DID URL with a fragment. Use parseDidUrl() to handle DID URLs.`,
542
+ { input }
543
+ );
544
+ }
545
+ }
546
+ var VALID_KTY = /* @__PURE__ */ new Set(["EC", "OKP", "RSA"]);
547
+ var PRIVATE_KEY_FIELDS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth"]);
548
+ var REQUIRED_PUBLIC_FIELDS = {
549
+ EC: ["crv", "x", "y"],
550
+ OKP: ["crv", "x"],
551
+ RSA: ["n", "e"]
552
+ };
553
+ function validatePublicJwk(jwk) {
554
+ if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
555
+ return { valid: false, error: "JWK must be a non-null object" };
556
+ }
557
+ const obj = jwk;
558
+ const kty = obj.kty;
559
+ if (typeof kty !== "string" || !VALID_KTY.has(kty)) {
560
+ return {
561
+ valid: false,
562
+ error: `Invalid or missing kty (must be one of: ${[...VALID_KTY].join(", ")})`
563
+ };
564
+ }
565
+ for (const field of PRIVATE_KEY_FIELDS) {
566
+ if (field in obj) {
567
+ return {
568
+ valid: false,
569
+ error: `JWK contains private key field "${field}" \u2014 only public keys are allowed`
570
+ };
571
+ }
572
+ }
573
+ const required = REQUIRED_PUBLIC_FIELDS[kty];
574
+ if (required) {
575
+ for (const field of required) {
576
+ if (!(field in obj) || obj[field] === void 0 || obj[field] === null || obj[field] === "") {
577
+ return {
578
+ valid: false,
579
+ error: `Missing required public key field "${field}" for kty="${kty}"`
580
+ };
581
+ }
582
+ }
583
+ }
584
+ return { valid: true };
585
+ }
586
+ function canonicalizeJwkJson(jwk) {
587
+ const sorted = Object.keys(jwk).sort();
588
+ const obj = {};
589
+ for (const key of sorted) {
590
+ obj[key] = jwk[key];
591
+ }
592
+ return JSON.stringify(obj);
593
+ }
594
+ function jwkToDidJwk(jwk) {
595
+ assertObject(jwk, "jwk", "INVALID_JWK");
596
+ const validation = validatePublicJwk(jwk);
597
+ if (!validation.valid) {
598
+ throw new OmaTrustError("INVALID_JWK", validation.error ?? "Invalid public JWK", { jwk });
599
+ }
600
+ const canonical = canonicalizeJwkJson(jwk);
601
+ const encoded = jose.base64url.encode(new TextEncoder().encode(canonical));
602
+ return `did:jwk:${encoded}`;
603
+ }
604
+ function didJwkToJwk(didJwk) {
605
+ if (typeof didJwk !== "string" || !didJwk.startsWith("did:jwk:")) {
606
+ throw new OmaTrustError("INVALID_DID", "Expected a did:jwk DID", { input: didJwk });
607
+ }
608
+ const parts = didJwk.split(":");
609
+ if (parts.length !== 3) {
610
+ throw new OmaTrustError("INVALID_DID", "did:jwk must have exactly 3 colon-separated parts", {
611
+ input: didJwk
612
+ });
613
+ }
614
+ const encoded = parts[2];
615
+ if (!encoded || encoded.length === 0) {
616
+ throw new OmaTrustError("INVALID_DID", "Missing base64url-encoded JWK identifier", {
617
+ input: didJwk
618
+ });
619
+ }
620
+ let decoded;
621
+ try {
622
+ const bytes = jose.base64url.decode(encoded);
623
+ decoded = new TextDecoder().decode(bytes);
624
+ } catch {
625
+ throw new OmaTrustError("INVALID_DID", "Failed to base64url-decode did:jwk identifier", {
626
+ input: didJwk
627
+ });
628
+ }
629
+ let jwk;
630
+ try {
631
+ jwk = JSON.parse(decoded);
632
+ } catch {
633
+ throw new OmaTrustError("INVALID_DID", "Decoded did:jwk identifier is not valid JSON", {
634
+ input: didJwk
635
+ });
636
+ }
637
+ if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
638
+ throw new OmaTrustError("INVALID_DID", "Decoded did:jwk must be a JSON object", {
639
+ input: didJwk
640
+ });
641
+ }
642
+ const validation = validatePublicJwk(jwk);
643
+ if (!validation.valid) {
644
+ throw new OmaTrustError("INVALID_DID", validation.error ?? "Invalid public JWK in did:jwk", {
645
+ input: didJwk
646
+ });
647
+ }
648
+ return jwk;
649
+ }
650
+ function extractPublicKeyFields(jwk) {
651
+ const result = {};
652
+ const metadataFields = /* @__PURE__ */ new Set(["kid", "use", "key_ops", "alg", "ext"]);
653
+ for (const [key, value] of Object.entries(jwk)) {
654
+ if (PRIVATE_KEY_FIELDS.has(key)) continue;
655
+ if (metadataFields.has(key)) continue;
656
+ result[key] = value;
657
+ }
658
+ return result;
659
+ }
660
+ function publicJwkEquals(a, b) {
661
+ assertObject(a, "a", "INVALID_JWK");
662
+ assertObject(b, "b", "INVALID_JWK");
663
+ const aObj = a;
664
+ const bObj = b;
665
+ for (const field of PRIVATE_KEY_FIELDS) {
666
+ if (field in aObj) {
667
+ throw new OmaTrustError(
668
+ "INVALID_JWK",
669
+ `First JWK contains private key field "${field}"`,
670
+ { field }
671
+ );
672
+ }
673
+ if (field in bObj) {
674
+ throw new OmaTrustError(
675
+ "INVALID_JWK",
676
+ `Second JWK contains private key field "${field}"`,
677
+ { field }
678
+ );
679
+ }
680
+ }
681
+ const aPublic = extractPublicKeyFields(aObj);
682
+ const bPublic = extractPublicKeyFields(bObj);
683
+ return canonicalizeJwkJson(aPublic) === canonicalizeJwkJson(bPublic);
684
+ }
685
+ async function computeJwkThumbprint(jwk, digestAlgorithm = "sha256") {
686
+ assertObject(jwk, "jwk", "INVALID_JWK");
687
+ const validation = validatePublicJwk(jwk);
688
+ if (!validation.valid) {
689
+ throw new OmaTrustError("INVALID_JWK", validation.error ?? "Invalid public JWK", { jwk });
690
+ }
691
+ const alg = digestAlgorithm === "sha256" ? "sha256" : digestAlgorithm === "sha384" ? "sha384" : "sha512";
692
+ return jose.calculateJwkThumbprint(jwk, alg);
693
+ }
694
+ async function formatJktValue(jwk) {
695
+ const thumbprint = await computeJwkThumbprint(jwk, "sha256");
696
+ return `jkt=S256:${thumbprint}`;
697
+ }
698
+
699
+ // src/shared/did-document.ts
700
+ async function fetchDidDocument(domain) {
701
+ const normalized = domain.toLowerCase().replace(/\.$/, "");
702
+ const url = `https://${normalized}/.well-known/did.json`;
703
+ let response;
704
+ try {
705
+ response = await fetch(url, { headers: { Accept: "application/json" } });
706
+ } catch (err) {
707
+ throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch DID document", { domain, err });
708
+ }
709
+ if (!response.ok) {
710
+ throw new OmaTrustError("NETWORK_ERROR", `DID document fetch failed: ${response.status}`, {
711
+ domain,
712
+ status: response.status
713
+ });
714
+ }
715
+ return await response.json();
716
+ }
717
+
718
+ // src/identity/resolve-key.ts
719
+ function findVerificationMethod(didDocument, didUrl, fragment) {
720
+ const methods = didDocument.verificationMethod;
721
+ if (!Array.isArray(methods)) {
722
+ return null;
723
+ }
724
+ for (const method of methods) {
725
+ if (!method || typeof method !== "object") continue;
726
+ const id2 = method.id;
727
+ if (typeof id2 !== "string") continue;
728
+ if (id2 === didUrl) return method;
729
+ if (fragment && (id2 === `#${fragment}` || id2.endsWith(`#${fragment}`))) {
730
+ return method;
731
+ }
732
+ }
733
+ return null;
734
+ }
735
+ function extractMethodIdsFromDidDocument(didDocument) {
736
+ const methods = didDocument.verificationMethod;
737
+ if (!Array.isArray(methods)) return [];
738
+ return methods.filter((m) => m && typeof m.id === "string").map((m) => m.id);
739
+ }
740
+ async function resolveDidUrlToPublicKey(didUrl, options) {
741
+ assertString(didUrl, "didUrl", "INVALID_DID_URL");
742
+ const parsed = parseDidUrl(didUrl);
743
+ const method = extractDidMethod(parsed.did);
744
+ if (!method) {
745
+ throw new OmaTrustError("INVALID_DID_URL", "Cannot extract DID method from DID URL", {
746
+ didUrl,
747
+ did: parsed.did
748
+ });
749
+ }
750
+ switch (method) {
751
+ case "web":
752
+ return resolveDidUrlKey(parsed.didUrl, parsed.did, parsed.fragment, options);
753
+ default:
754
+ throw new OmaTrustError(
755
+ "UNSUPPORTED_DID_METHOD",
756
+ `DID URL key resolution is not supported for method "${method}"`,
757
+ { didUrl, method }
758
+ );
759
+ }
760
+ }
761
+ async function resolveDidUrlToControllerDid(didUrl, options) {
762
+ const resolved = await resolveDidUrlToPublicKey(didUrl, options);
763
+ const controllerDid = jwkToDidJwk(resolved.publicKeyJwk);
764
+ return {
765
+ ...resolved,
766
+ controllerDid
767
+ };
768
+ }
769
+ async function resolveDidUrlKey(didUrl, did, fragment, options) {
770
+ const domain = getDomainFromDidWeb(did);
771
+ if (!domain) {
772
+ throw new OmaTrustError("INVALID_DID_URL", "Cannot extract domain from did:web DID", {
773
+ didUrl,
774
+ did
775
+ });
776
+ }
777
+ const fetchFn = options?.fetchDidDocument ?? fetchDidDocument;
778
+ const didDocument = await fetchFn(domain);
779
+ const method = findVerificationMethod(didDocument, didUrl, fragment);
780
+ if (!method) {
781
+ throw new OmaTrustError(
782
+ "KEY_NOT_FOUND",
783
+ `No verification method found matching "${fragment ? `#${fragment}` : didUrl}"`,
784
+ { didUrl, fragment, availableMethods: extractMethodIdsFromDidDocument(didDocument) }
785
+ );
786
+ }
787
+ const publicKeyJwk = method.publicKeyJwk;
788
+ if (!publicKeyJwk || typeof publicKeyJwk !== "object") {
789
+ throw new OmaTrustError(
790
+ "KEY_NOT_FOUND",
791
+ `Verification method "${method.id}" does not contain publicKeyJwk`,
792
+ { didUrl, verificationMethodId: method.id }
793
+ );
794
+ }
795
+ const validation = validatePublicJwk(publicKeyJwk);
796
+ if (!validation.valid) {
797
+ throw new OmaTrustError(
798
+ "INVALID_JWK",
799
+ `publicKeyJwk in verification method "${method.id}" is invalid: ${validation.error}`,
800
+ { didUrl, verificationMethodId: method.id }
801
+ );
802
+ }
803
+ return {
804
+ didUrl,
805
+ did,
806
+ fragment,
807
+ publicKeyJwk,
808
+ verificationMethodId: method.id
809
+ };
810
+ }
811
+
812
+ // src/identity/controller-id.ts
813
+ function isSameControllerId(a, b) {
814
+ let normalizedA = null;
815
+ let normalizedB = null;
816
+ try {
817
+ normalizedA = normalizeDid(a);
818
+ } catch {
819
+ }
820
+ try {
821
+ normalizedB = normalizeDid(b);
822
+ } catch {
823
+ }
824
+ if (normalizedA && normalizedB && normalizedA === normalizedB) {
825
+ return true;
826
+ }
827
+ const evmA = extractControllerEvmAddress(a);
828
+ const evmB = extractControllerEvmAddress(b);
829
+ if (evmA && evmB && evmA.toLowerCase() === evmB.toLowerCase()) {
830
+ return true;
831
+ }
832
+ const methodA = extractDidMethod(a);
833
+ const methodB = extractDidMethod(b);
834
+ if (methodA === "jwk" && methodB === "jwk") {
835
+ try {
836
+ const jwkA = didJwkToJwk(a);
837
+ const jwkB = didJwkToJwk(b);
838
+ return publicJwkEquals(jwkA, jwkB);
839
+ } catch {
840
+ return false;
841
+ }
842
+ }
843
+ return false;
844
+ }
845
+ function extractControllerEvmAddress(controllerDid) {
846
+ try {
847
+ const method = extractDidMethod(controllerDid);
848
+ if (method === "pkh" && controllerDid.includes("eip155")) {
849
+ return extractAddressFromDid(controllerDid);
850
+ }
851
+ } catch {
852
+ }
853
+ return null;
854
+ }
855
+
856
+ // src/identity/types.ts
857
+ function extractAuthorizationMetadata(result) {
858
+ const payload = result.payload;
859
+ const resourceUrl = typeof payload.resourceUrl === "string" ? payload.resourceUrl : null;
860
+ const issuedAt = typeof payload.issuedAt === "string" ? payload.issuedAt : null;
861
+ let subjectDid = null;
862
+ if (resourceUrl) {
863
+ try {
864
+ const url = new URL(resourceUrl);
865
+ subjectDid = `did:web:${url.hostname}`;
866
+ } catch {
867
+ }
868
+ }
869
+ return {
870
+ controllerDid: result.publicKeyDid,
871
+ subjectDid,
872
+ resourceUrl,
873
+ issuedAt,
874
+ kid: result.kid,
875
+ publicKeyJwk: result.publicKeyJwk
876
+ };
877
+ }
340
878
  function canonicalizeJson(obj) {
341
879
  const jcs = canonicalize__default.default(obj);
342
880
  if (!jcs) {
@@ -360,7 +898,10 @@ function hashCanonicalizedJson(obj, algorithm) {
360
898
  // src/reputation/index.ts
361
899
  var reputation_exports = {};
362
900
  __export(reputation_exports, {
901
+ EIP1967_ADMIN_SLOT: () => EIP1967_ADMIN_SLOT,
902
+ OWNERSHIP_PATTERNS: () => OWNERSHIP_PATTERNS,
363
903
  buildDelegatedAttestationTypedData: () => buildDelegatedAttestationTypedData,
904
+ buildDelegatedTypedDataFromEncoded: () => buildDelegatedTypedDataFromEncoded,
364
905
  buildDnsTxtRecord: () => buildDnsTxtRecord,
365
906
  buildEip712Domain: () => buildEip712Domain,
366
907
  calculateAverageUserReviewRating: () => calculateAverageUserReviewRating,
@@ -377,15 +918,20 @@ __export(reputation_exports, {
377
918
  createX402ReceiptProof: () => createX402ReceiptProof,
378
919
  decodeAttestationData: () => decodeAttestationData,
379
920
  deduplicateReviews: () => deduplicateReviews,
921
+ discoverContractOwner: () => discoverContractOwner,
922
+ discoverControllingWalletDid: () => discoverControllingWalletDid,
380
923
  encodeAttestationData: () => encodeAttestationData,
381
- extractAddressesFromDidDocument: () => extractAddressesFromDidDocument,
924
+ extractEvmAddressesFromDidDocument: () => extractEvmAddressesFromDidDocument,
382
925
  extractExpirationTime: () => extractExpirationTime,
926
+ extractJwksFromDidDocument: () => extractJwksFromDidDocument,
383
927
  fetchDidDocument: () => fetchDidDocument,
384
928
  formatSchemaUid: () => formatSchemaUid,
385
929
  formatTransferAmount: () => formatTransferAmount,
386
930
  getAttestation: () => getAttestation,
931
+ getAttestationsByAttester: () => getAttestationsByAttester,
387
932
  getAttestationsForDid: () => getAttestationsForDid,
388
933
  getChainConstants: () => getChainConstants,
934
+ getControllerAuthorization: () => getControllerAuthorization,
389
935
  getExplorerAddressUrl: () => getExplorerAddressUrl,
390
936
  getExplorerTxUrl: () => getExplorerTxUrl,
391
937
  getLatestAttestations: () => getLatestAttestations,
@@ -399,16 +945,31 @@ __export(reputation_exports, {
399
945
  normalizeSchema: () => normalizeSchema,
400
946
  parseDnsTxtRecord: () => parseDnsTxtRecord,
401
947
  prepareDelegatedAttestation: () => prepareDelegatedAttestation,
948
+ readOwnerFromContract: () => readOwnerFromContract,
949
+ requestControllerWitness: () => requestControllerWitness,
950
+ revokeAttestation: () => revokeAttestation,
402
951
  schemaToString: () => schemaToString,
403
952
  splitSignature: () => splitSignature,
404
953
  submitAttestation: () => submitAttestation,
405
954
  submitDelegatedAttestation: () => submitDelegatedAttestation,
955
+ validateAttestationData: () => validateAttestationData,
406
956
  verifyAttestation: () => verifyAttestation,
407
957
  verifyDidDocumentControllerDid: () => verifyDidDocumentControllerDid,
408
- verifyDnsTxtControllerDid: () => verifyDnsTxtControllerDid,
958
+ verifyDidJsonControllerDid: () => verifyDidJsonControllerDid,
959
+ verifyDidPkhOwnership: () => verifyDidPkhOwnership,
960
+ verifyDidWebOwnership: () => verifyDidWebOwnership,
961
+ verifyDnsTxtControllerDid: () => verifyDnsTxtControllerDid2,
409
962
  verifyEip712Signature: () => verifyEip712Signature,
410
963
  verifyProof: () => verifyProof,
411
- verifySchemaExists: () => verifySchemaExists
964
+ verifySchemaExists: () => verifySchemaExists,
965
+ verifySubjectOwnership: () => verifySubjectOwnership,
966
+ verifyTransferProof: () => verifyTransferProof,
967
+ verifyX402Eip712Artifact: () => verifyX402Eip712Artifact,
968
+ verifyX402Eip712Offer: () => verifyX402Eip712Offer,
969
+ verifyX402Eip712Receipt: () => verifyX402Eip712Receipt,
970
+ verifyX402JwsArtifact: () => verifyX402JwsArtifact,
971
+ verifyX402JwsOffer: () => verifyX402JwsOffer,
972
+ verifyX402JwsReceipt: () => verifyX402JwsReceipt
412
973
  });
413
974
  function normalizeSchema(schema) {
414
975
  if (Array.isArray(schema)) {
@@ -444,6 +1005,13 @@ function encodeAttestationData(schema, data) {
444
1005
  if (!data || typeof data !== "object") {
445
1006
  throw new OmaTrustError("INVALID_INPUT", "data must be an object");
446
1007
  }
1008
+ const validationErrors = validateAttestationData(schema, data);
1009
+ if (validationErrors.length > 0) {
1010
+ const summary = validationErrors.map((error) => `Field "${error.schemaFieldName}" (${error.expectedType}) got ${error.providedType}`).join("; ");
1011
+ throw new OmaTrustError("INVALID_INPUT", `Attestation data validation failed: ${summary}`, {
1012
+ errors: validationErrors
1013
+ });
1014
+ }
447
1015
  const fields = normalizeSchema(schema);
448
1016
  const schemaString = schemaToString(fields);
449
1017
  const encoder = new easSdk.SchemaEncoder(schemaString);
@@ -456,6 +1024,99 @@ function encodeAttestationData(schema, data) {
456
1024
  );
457
1025
  return encoded;
458
1026
  }
1027
+ function getActualType(value) {
1028
+ if (value === null) {
1029
+ return "null";
1030
+ }
1031
+ if (value === void 0) {
1032
+ return "undefined";
1033
+ }
1034
+ if (Array.isArray(value)) {
1035
+ return "array";
1036
+ }
1037
+ if (typeof value === "number") {
1038
+ return Number.isFinite(value) ? "number" : "non-finite-number";
1039
+ }
1040
+ return typeof value;
1041
+ }
1042
+ function isNumericValue(value, allowNegative) {
1043
+ if (typeof value === "bigint") {
1044
+ return allowNegative || value >= 0n;
1045
+ }
1046
+ if (typeof value === "number") {
1047
+ if (!Number.isFinite(value) || !Number.isInteger(value)) {
1048
+ return false;
1049
+ }
1050
+ return allowNegative || value >= 0;
1051
+ }
1052
+ if (typeof value === "string") {
1053
+ if (value.length === 0) {
1054
+ return false;
1055
+ }
1056
+ const pattern = allowNegative ? /^-?\d+$/ : /^\d+$/;
1057
+ return pattern.test(value);
1058
+ }
1059
+ return false;
1060
+ }
1061
+ function isHex(value) {
1062
+ return typeof value === "string" && /^0x[0-9a-fA-F]*$/.test(value);
1063
+ }
1064
+ function validateFieldValue(type, value) {
1065
+ const normalizedType = type.trim().toLowerCase();
1066
+ const bytesNMatch = normalizedType.match(/^bytes([1-9]|[12]\d|3[0-2])$/);
1067
+ if (/^uint\d*$/.test(normalizedType)) {
1068
+ return isNumericValue(value, false);
1069
+ }
1070
+ if (/^int\d*$/.test(normalizedType)) {
1071
+ return isNumericValue(value, true);
1072
+ }
1073
+ if (normalizedType === "string") {
1074
+ return typeof value === "string";
1075
+ }
1076
+ if (normalizedType === "string[]") {
1077
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
1078
+ }
1079
+ if (normalizedType === "bool") {
1080
+ return typeof value === "boolean";
1081
+ }
1082
+ if (normalizedType === "address") {
1083
+ return typeof value === "string" && ethers.isAddress(value);
1084
+ }
1085
+ if (normalizedType === "bytes") {
1086
+ return isHex(value) && (value.length - 2) % 2 === 0;
1087
+ }
1088
+ if (bytesNMatch) {
1089
+ const expectedBytes = Number(bytesNMatch[1]);
1090
+ return isHex(value) && value.length === 2 + expectedBytes * 2;
1091
+ }
1092
+ return true;
1093
+ }
1094
+ function validateAttestationData(schema, data) {
1095
+ if (!data || typeof data !== "object") {
1096
+ return [{
1097
+ schemaFieldName: "data",
1098
+ expectedType: "object",
1099
+ providedType: getActualType(data),
1100
+ providedValue: data
1101
+ }];
1102
+ }
1103
+ const fields = normalizeSchema(schema);
1104
+ const errors = [];
1105
+ for (const field of fields) {
1106
+ const value = data[field.name];
1107
+ const isValid = validateFieldValue(field.type, value);
1108
+ if (isValid) {
1109
+ continue;
1110
+ }
1111
+ errors.push({
1112
+ schemaFieldName: field.name,
1113
+ expectedType: field.type,
1114
+ providedType: getActualType(value),
1115
+ providedValue: value
1116
+ });
1117
+ }
1118
+ return errors;
1119
+ }
459
1120
  function decodeAttestationData(schema, encodedData) {
460
1121
  if (typeof encodedData !== "string" || !encodedData.startsWith("0x")) {
461
1122
  throw new OmaTrustError("INVALID_INPUT", "encodedData must be a hex string");
@@ -476,22 +1137,11 @@ function decodeAttestationData(schema, encodedData) {
476
1137
  return result;
477
1138
  }
478
1139
  function extractExpirationTime(data) {
479
- const keys = ["expirationTime", "expiration", "validUntil", "expiresAt", "expires"];
480
- for (const key of keys) {
481
- const value = data[key];
482
- if (value === void 0 || value === null) {
483
- continue;
484
- }
485
- if (typeof value === "bigint") {
486
- return value;
487
- }
488
- if (typeof value === "number") {
489
- return value;
490
- }
491
- if (typeof value === "string" && /^\d+$/.test(value)) {
492
- return BigInt(value);
493
- }
494
- }
1140
+ const value = data["expiresAt"];
1141
+ if (value === void 0 || value === null) return void 0;
1142
+ if (typeof value === "bigint") return value;
1143
+ if (typeof value === "number") return value;
1144
+ if (typeof value === "string" && /^\d+$/.test(value)) return BigInt(value);
495
1145
  return void 0;
496
1146
  }
497
1147
  var ZERO_UID = "0x0000000000000000000000000000000000000000000000000000000000000000";
@@ -532,6 +1182,53 @@ function resolveRecipientAddress(data) {
532
1182
  return ethers.ZeroAddress;
533
1183
  }
534
1184
 
1185
+ // src/reputation/eas-adapter.ts
1186
+ function isRecord(value) {
1187
+ return Boolean(value) && typeof value === "object";
1188
+ }
1189
+ function isHex32(value) {
1190
+ return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
1191
+ }
1192
+ function getEasTransactionReceipt(tx) {
1193
+ if (!isRecord(tx)) {
1194
+ return void 0;
1195
+ }
1196
+ const candidates = [tx.receipt, tx.tx, tx.transaction];
1197
+ for (const candidate of candidates) {
1198
+ if (isRecord(candidate)) {
1199
+ return candidate;
1200
+ }
1201
+ }
1202
+ return void 0;
1203
+ }
1204
+ function getEasTransactionHash(tx) {
1205
+ const receipt = getEasTransactionReceipt(tx);
1206
+ if (receipt) {
1207
+ const receiptHash = receipt.hash;
1208
+ if (isHex32(receiptHash)) {
1209
+ return receiptHash;
1210
+ }
1211
+ const transactionHash = receipt.transactionHash;
1212
+ if (isHex32(transactionHash)) {
1213
+ return transactionHash;
1214
+ }
1215
+ }
1216
+ if (isRecord(tx) && isHex32(tx.hash)) {
1217
+ return tx.hash;
1218
+ }
1219
+ return void 0;
1220
+ }
1221
+ function isEasSchemaNotFoundError(err) {
1222
+ if (!isRecord(err)) {
1223
+ return false;
1224
+ }
1225
+ const message = err.message;
1226
+ if (typeof message !== "string" || message.length === 0) {
1227
+ return false;
1228
+ }
1229
+ return /schema not found/i.test(message);
1230
+ }
1231
+
535
1232
  // src/reputation/submit.ts
536
1233
  async function submitAttestation(params) {
537
1234
  if (!params || typeof params !== "object") {
@@ -562,25 +1259,46 @@ async function submitAttestation(params) {
562
1259
  }
563
1260
  });
564
1261
  const uid = await tx.wait();
565
- const txAny = tx;
566
- const txHash = txAny.tx?.hash ?? txAny.hash ?? txAny.transaction?.hash ?? ZERO_UID;
1262
+ const txHash = getEasTransactionHash(tx) ?? ZERO_UID;
1263
+ const receipt = getEasTransactionReceipt(tx);
567
1264
  return {
568
1265
  uid,
569
1266
  txHash,
570
- receipt: txAny.tx
1267
+ receipt
571
1268
  };
572
1269
  } catch (err) {
573
1270
  throw new OmaTrustError("NETWORK_ERROR", "Failed to submit attestation", { err });
574
1271
  }
575
1272
  }
576
- function buildDelegatedAttestationTypedData(params) {
577
- const dataWithHash = withAutoSubjectDidHash(params.schema, params.data);
578
- const encodedData = encodeAttestationData(params.schema, dataWithHash);
579
- const recipient = resolveRecipientAddress(dataWithHash);
580
- const expiration = toBigIntOrDefault(
581
- params.expirationTime ?? extractExpirationTime(dataWithHash),
582
- 0n
583
- );
1273
+ async function revokeAttestation(params) {
1274
+ if (!params || typeof params !== "object") {
1275
+ throw new OmaTrustError("INVALID_INPUT", "params must be provided");
1276
+ }
1277
+ if (!params.signer) {
1278
+ throw new OmaTrustError("INVALID_INPUT", "signer is required");
1279
+ }
1280
+ const eas = new easSdk.EAS(params.easContractAddress);
1281
+ eas.connect(params.signer);
1282
+ try {
1283
+ const tx = await eas.revoke({
1284
+ schema: params.schemaUid,
1285
+ data: {
1286
+ uid: params.uid,
1287
+ value: toBigIntOrDefault(params.value, 0n)
1288
+ }
1289
+ });
1290
+ await tx.wait();
1291
+ const txHash = getEasTransactionHash(tx) ?? ZERO_UID;
1292
+ const receipt = getEasTransactionReceipt(tx);
1293
+ return {
1294
+ txHash,
1295
+ receipt
1296
+ };
1297
+ } catch (err) {
1298
+ throw new OmaTrustError("NETWORK_ERROR", "Failed to revoke attestation", { err });
1299
+ }
1300
+ }
1301
+ function buildDelegatedTypedData(params) {
584
1302
  return {
585
1303
  domain: {
586
1304
  name: "EAS",
@@ -605,17 +1323,52 @@ function buildDelegatedAttestationTypedData(params) {
605
1323
  message: {
606
1324
  attester: params.attester,
607
1325
  schema: params.schemaUid,
608
- recipient,
609
- expirationTime: expiration,
1326
+ recipient: params.recipient,
1327
+ expirationTime: toBigIntOrDefault(params.expirationTime, 0n),
610
1328
  revocable: params.revocable ?? true,
611
1329
  refUID: params.refUid ?? ZERO_UID,
612
- data: encodedData,
1330
+ data: params.encodedData,
613
1331
  value: toBigIntOrDefault(params.value, 0n),
614
1332
  nonce: toBigIntOrDefault(params.nonce, 0n),
615
1333
  deadline: toBigIntOrDefault(params.deadline, BigInt(Math.floor(Date.now() / 1e3) + 600))
616
1334
  }
617
1335
  };
618
1336
  }
1337
+ function buildDelegatedAttestationTypedData(params) {
1338
+ const dataWithHash = withAutoSubjectDidHash(params.schema, params.data);
1339
+ const encodedData = encodeAttestationData(params.schema, dataWithHash);
1340
+ const recipient = resolveRecipientAddress(dataWithHash);
1341
+ return buildDelegatedTypedData({
1342
+ chainId: params.chainId,
1343
+ easContractAddress: params.easContractAddress,
1344
+ schemaUid: params.schemaUid,
1345
+ encodedData,
1346
+ recipient,
1347
+ attester: params.attester,
1348
+ nonce: params.nonce,
1349
+ revocable: params.revocable,
1350
+ expirationTime: params.expirationTime ?? extractExpirationTime(dataWithHash),
1351
+ refUid: params.refUid,
1352
+ value: params.value,
1353
+ deadline: params.deadline
1354
+ });
1355
+ }
1356
+ function buildDelegatedTypedDataFromEncoded(params) {
1357
+ return buildDelegatedTypedData({
1358
+ chainId: params.chainId,
1359
+ easContractAddress: params.easContractAddress,
1360
+ schemaUid: params.schemaUid,
1361
+ encodedData: params.encodedData,
1362
+ recipient: params.recipient,
1363
+ attester: params.attester,
1364
+ nonce: params.nonce,
1365
+ revocable: params.revocable,
1366
+ expirationTime: params.expirationTime,
1367
+ refUid: params.refUid,
1368
+ value: params.value,
1369
+ deadline: params.deadline
1370
+ });
1371
+ }
619
1372
  function splitSignature(signature) {
620
1373
  try {
621
1374
  const parsed = ethers.Signature.from(signature);
@@ -675,24 +1428,44 @@ async function submitDelegatedAttestation(params) {
675
1428
  payload = {};
676
1429
  }
677
1430
  if (!response.ok) {
678
- throw new OmaTrustError("NETWORK_ERROR", "Relay submission failed", {
679
- status: response.status,
1431
+ const relayError = {
1432
+ httpStatus: response.status,
1433
+ code: payload.code,
1434
+ error: payload.error ?? payload.message,
680
1435
  payload
681
- });
1436
+ };
1437
+ throw new OmaTrustError("RELAY_ERROR", `Relay submission failed (HTTP ${response.status})`, relayError);
682
1438
  }
683
1439
  const uid = payload.uid ?? ZERO_UID;
684
1440
  const txHash = payload.txHash;
685
- const status = payload.status ?? "submitted";
686
- return {
687
- uid,
688
- txHash,
689
- status
690
- };
1441
+ const hasConfirmationSignals = payload.status === "confirmed" || payload.success === true || typeof payload.blockNumber === "number";
1442
+ const status = hasConfirmationSignals ? "confirmed" : "submitted";
1443
+ const relay = {};
1444
+ if (typeof payload.blockNumber === "number") relay.blockNumber = payload.blockNumber;
1445
+ if (typeof payload.chain === "string") relay.chain = payload.chain;
1446
+ if (typeof payload.success === "boolean") relay.success = payload.success;
1447
+ for (const key of Object.keys(payload)) {
1448
+ if (!["uid", "txHash", "status", "blockNumber", "chain", "success"].includes(key)) {
1449
+ relay[key] = payload[key];
1450
+ }
1451
+ }
1452
+ const result = { uid, txHash, status };
1453
+ if (Object.keys(relay).length > 0) {
1454
+ result.relay = relay;
1455
+ }
1456
+ return result;
691
1457
  }
692
1458
  var EAS_EVENT_ABI = [
693
1459
  "event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
694
1460
  ];
695
- function parseAttestation(attestation, schema) {
1461
+ function parseEventTxHash(event) {
1462
+ const txHash = event.transactionHash;
1463
+ if (typeof txHash !== "string") {
1464
+ return void 0;
1465
+ }
1466
+ return /^0x[0-9a-fA-F]{64}$/.test(txHash) ? txHash : void 0;
1467
+ }
1468
+ function parseAttestation(attestation, schema, txHash) {
696
1469
  const rawData = attestation.data ?? "0x";
697
1470
  const decoded = schema ? decodeAttestationData(schema, rawData) : {};
698
1471
  const toBigIntSafe = (value) => {
@@ -712,6 +1485,7 @@ function parseAttestation(attestation, schema) {
712
1485
  schema: attestation.schema,
713
1486
  attester: attestation.attester,
714
1487
  recipient: attestation.recipient,
1488
+ txHash,
715
1489
  revocable: Boolean(attestation.revocable),
716
1490
  revocationTime: toBigIntSafe(attestation.revocationTime),
717
1491
  expirationTime: toBigIntSafe(attestation.expirationTime),
@@ -737,21 +1511,18 @@ async function getAttestation(params) {
737
1511
  throw new OmaTrustError("NETWORK_ERROR", "Failed to read attestation", { err });
738
1512
  }
739
1513
  }
740
- async function getAttestationsForDid(params) {
741
- const provider = params.provider;
742
- const contract = new ethers.Contract(params.easContractAddress, EAS_EVENT_ABI, provider);
743
- const toBlock = params.toBlock ?? await provider.getBlockNumber();
744
- const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
745
- const filter = contract.filters.Attested(didToAddress(params.did));
1514
+ async function queryAttestationEvents(easContractAddress, provider, fromBlock, toBlock, options) {
1515
+ const contract = new ethers.Contract(easContractAddress, EAS_EVENT_ABI, provider);
1516
+ const filter = contract.filters.Attested(options?.recipient ?? null, options?.attester ?? null);
746
1517
  let events;
747
1518
  try {
748
1519
  events = await contract.queryFilter(filter, fromBlock, toBlock);
749
1520
  } catch (err) {
750
1521
  throw new OmaTrustError("NETWORK_ERROR", "Failed to query attestation events", { err });
751
1522
  }
752
- const eas = new easSdk.EAS(params.easContractAddress);
1523
+ const eas = new easSdk.EAS(easContractAddress);
753
1524
  eas.connect(provider);
754
- const schemaFilter = params.schemas?.map((schema) => schema.toLowerCase());
1525
+ const schemaFilter = options?.schemas?.map((s) => s.toLowerCase());
755
1526
  const results = [];
756
1527
  for (const event of events) {
757
1528
  if (!("args" in event && Array.isArray(event.args))) {
@@ -770,11 +1541,39 @@ async function getAttestationsForDid(params) {
770
1541
  if (!attestation || !attestation.uid) {
771
1542
  continue;
772
1543
  }
773
- results.push(parseAttestation(attestation));
1544
+ results.push(parseAttestation(attestation, void 0, parseEventTxHash(event)));
774
1545
  }
775
1546
  results.sort((a, b) => Number(b.time - a.time));
776
1547
  return results;
777
1548
  }
1549
+ async function getAttestationsForDid(params) {
1550
+ const provider = params.provider;
1551
+ const toBlock = params.toBlock ?? await provider.getBlockNumber();
1552
+ const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
1553
+ return queryAttestationEvents(
1554
+ params.easContractAddress,
1555
+ provider,
1556
+ fromBlock,
1557
+ toBlock,
1558
+ { recipient: didToAddress(params.subjectDid), schemas: params.schemas }
1559
+ );
1560
+ }
1561
+ async function getAttestationsByAttester(params) {
1562
+ const provider = params.provider;
1563
+ const toBlock = params.toBlock ?? await provider.getBlockNumber();
1564
+ const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
1565
+ if (params.limit !== void 0 && params.limit <= 0) {
1566
+ return [];
1567
+ }
1568
+ const results = await queryAttestationEvents(
1569
+ params.easContractAddress,
1570
+ provider,
1571
+ fromBlock,
1572
+ toBlock,
1573
+ { attester: params.attester, schemas: params.schemas }
1574
+ );
1575
+ return params.limit !== void 0 ? results.slice(0, params.limit) : results;
1576
+ }
778
1577
  async function listAttestations(params) {
779
1578
  const limit = params.limit ?? 20;
780
1579
  if (limit <= 0) {
@@ -785,34 +1584,15 @@ async function listAttestations(params) {
785
1584
  }
786
1585
  async function getLatestAttestations(params) {
787
1586
  const provider = params.provider;
788
- const contract = new ethers.Contract(params.easContractAddress, EAS_EVENT_ABI, provider);
789
- const currentBlock = await provider.getBlockNumber();
790
- const fromBlock = params.fromBlock ?? Math.max(0, currentBlock - 5e4);
791
- const events = await contract.queryFilter(contract.filters.Attested(), fromBlock, currentBlock);
792
- const eas = new easSdk.EAS(params.easContractAddress);
793
- eas.connect(provider);
794
- const schemaFilter = params.schemas?.map((schema) => schema.toLowerCase());
795
- const results = [];
796
- for (const event of events) {
797
- if (!("args" in event && Array.isArray(event.args))) {
798
- continue;
799
- }
800
- const args = event.args;
801
- const uid = args?.[2];
802
- const schemaUid = args?.[3];
803
- if (!uid || !schemaUid) {
804
- continue;
805
- }
806
- if (schemaFilter && !schemaFilter.includes(schemaUid.toLowerCase())) {
807
- continue;
808
- }
809
- const attestation = await eas.getAttestation(uid);
810
- if (!attestation || !attestation.uid) {
811
- continue;
812
- }
813
- results.push(parseAttestation(attestation));
814
- }
815
- results.sort((a, b) => Number(b.time - a.time));
1587
+ const toBlock = await provider.getBlockNumber();
1588
+ const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
1589
+ const results = await queryAttestationEvents(
1590
+ params.easContractAddress,
1591
+ provider,
1592
+ fromBlock,
1593
+ toBlock,
1594
+ { schemas: params.schemas }
1595
+ );
816
1596
  return results.slice(0, params.limit ?? 20);
817
1597
  }
818
1598
  function deduplicateReviews(attestations) {
@@ -1016,25 +1796,7 @@ function getExplorerTxUrl(chainId, txHash) {
1016
1796
  function getExplorerAddressUrl(chainId, address) {
1017
1797
  return `${getConfig(chainId).explorer}/address/${address}`;
1018
1798
  }
1019
- async function fetchDidDocument(domain) {
1020
- const normalized = domain.toLowerCase().replace(/\.$/, "");
1021
- const url = `https://${normalized}/.well-known/did.json`;
1022
- let response;
1023
- try {
1024
- response = await fetch(url, { headers: { Accept: "application/json" } });
1025
- } catch (err) {
1026
- throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch DID document", { domain, err });
1027
- }
1028
- if (!response.ok) {
1029
- throw new OmaTrustError("NETWORK_ERROR", "DID document fetch failed", {
1030
- domain,
1031
- status: response.status
1032
- });
1033
- }
1034
- const body = await response.json();
1035
- return body;
1036
- }
1037
- function extractAddressesFromDidDocument(didDocument) {
1799
+ function extractEvmAddressesFromDidDocument(didDocument) {
1038
1800
  const methods = didDocument.verificationMethod;
1039
1801
  if (!Array.isArray(methods)) {
1040
1802
  return [];
@@ -1058,14 +1820,52 @@ function extractAddressesFromDidDocument(didDocument) {
1058
1820
  }
1059
1821
  return [...addresses];
1060
1822
  }
1823
+ function extractJwksFromDidDocument(didDocument) {
1824
+ const methods = didDocument.verificationMethod;
1825
+ if (!Array.isArray(methods)) {
1826
+ return [];
1827
+ }
1828
+ const jwks = [];
1829
+ for (const method of methods) {
1830
+ const publicKeyJwk = method.publicKeyJwk;
1831
+ if (publicKeyJwk && typeof publicKeyJwk === "object" && !Array.isArray(publicKeyJwk)) {
1832
+ jwks.push(publicKeyJwk);
1833
+ }
1834
+ }
1835
+ return jwks;
1836
+ }
1061
1837
  function verifyDidDocumentControllerDid(didDocument, expectedControllerDid) {
1838
+ const method = extractDidMethod(expectedControllerDid);
1839
+ if (method === "jwk") {
1840
+ try {
1841
+ const expectedJwk = didJwkToJwk(expectedControllerDid);
1842
+ const documentJwks = extractJwksFromDidDocument(didDocument);
1843
+ for (const docJwk of documentJwks) {
1844
+ try {
1845
+ if (publicJwkEquals(docJwk, expectedJwk)) {
1846
+ return { valid: true };
1847
+ }
1848
+ } catch {
1849
+ }
1850
+ }
1851
+ return {
1852
+ valid: false,
1853
+ reason: "No matching publicKeyJwk found in DID document verification methods"
1854
+ };
1855
+ } catch {
1856
+ return {
1857
+ valid: false,
1858
+ reason: "Failed to decode did:jwk controller DID"
1859
+ };
1860
+ }
1861
+ }
1062
1862
  let expectedAddress;
1063
1863
  try {
1064
1864
  expectedAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
1065
1865
  } catch {
1066
- return { valid: false, reason: "Expected controller DID does not resolve to an EVM address" };
1866
+ return { valid: false, reason: "Controller DID does not resolve to an EVM address and is not did:jwk" };
1067
1867
  }
1068
- const addresses = extractAddressesFromDidDocument(didDocument);
1868
+ const addresses = extractEvmAddressesFromDidDocument(didDocument);
1069
1869
  if (addresses.some((address) => address.toLowerCase() === expectedAddress.toLowerCase())) {
1070
1870
  return { valid: true };
1071
1871
  }
@@ -1074,6 +1874,13 @@ function verifyDidDocumentControllerDid(didDocument, expectedControllerDid) {
1074
1874
  reason: `No matching address found in DID document (expected ${expectedAddress})`
1075
1875
  };
1076
1876
  }
1877
+ async function verifyDidJsonControllerDid(domain, expectedControllerDid, options = {}) {
1878
+ if (!domain || typeof domain !== "string") {
1879
+ throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
1880
+ }
1881
+ const didDocument = options.fetchDidDocument ? await options.fetchDidDocument(domain) : await fetchDidDocument(domain);
1882
+ return verifyDidDocumentControllerDid(didDocument, expectedControllerDid);
1883
+ }
1077
1884
  function buildEip712Domain(name, version, chainId, verifyingContract) {
1078
1885
  return { name, version, chainId, verifyingContract };
1079
1886
  }
@@ -1106,53 +1913,360 @@ function verifyEip712Signature(typedData, signature) {
1106
1913
  throw new OmaTrustError("INVALID_INPUT", "Failed to verify EIP-712 signature", { err });
1107
1914
  }
1108
1915
  }
1916
+
1917
+ // src/reputation/proof/dns-txt-record.ts
1109
1918
  function parseDnsTxtRecord(record) {
1110
1919
  if (!record || typeof record !== "string") {
1111
1920
  throw new OmaTrustError("INVALID_INPUT", "record must be a non-empty string", { record });
1112
1921
  }
1113
1922
  const entries = record.split(/[;\s]+/).map((entry) => entry.trim()).filter(Boolean);
1114
1923
  const parsed = {};
1924
+ const controllers = [];
1115
1925
  for (const entry of entries) {
1116
- const [key, ...valueParts] = entry.split("=");
1117
- if (!key) {
1118
- continue;
1926
+ const eqIndex = entry.indexOf("=");
1927
+ if (eqIndex === -1) continue;
1928
+ const key = entry.slice(0, eqIndex).trim();
1929
+ const value = entry.slice(eqIndex + 1).trim();
1930
+ if (!key || !value) continue;
1931
+ if (key === "controller") {
1932
+ controllers.push(value);
1933
+ } else {
1934
+ parsed[key] = value;
1935
+ }
1936
+ }
1937
+ return {
1938
+ version: parsed.v,
1939
+ controller: controllers[0],
1940
+ controllers
1941
+ };
1942
+ }
1943
+ function buildDnsTxtRecord(controllerDid) {
1944
+ const normalized = normalizeDid(controllerDid);
1945
+ return `v=1;controller=${normalized}`;
1946
+ }
1947
+ var REQUIRED_OFFER_FIELDS = [
1948
+ "version",
1949
+ "resourceUrl",
1950
+ "scheme",
1951
+ "network",
1952
+ "asset",
1953
+ "payTo",
1954
+ "amount"
1955
+ ];
1956
+ var REQUIRED_RECEIPT_FIELDS = [
1957
+ "version",
1958
+ "network",
1959
+ "resourceUrl",
1960
+ "payer",
1961
+ "issuedAt"
1962
+ ];
1963
+ function validateOfferPayload(payload) {
1964
+ for (const field of REQUIRED_OFFER_FIELDS) {
1965
+ if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
1966
+ return { valid: false, field };
1967
+ }
1968
+ }
1969
+ return { valid: true };
1970
+ }
1971
+ function validateReceiptPayload(payload) {
1972
+ for (const field of REQUIRED_RECEIPT_FIELDS) {
1973
+ if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
1974
+ return { valid: false, field };
1975
+ }
1976
+ }
1977
+ return { valid: true };
1978
+ }
1979
+ function failure(code, message, partial) {
1980
+ return {
1981
+ valid: false,
1982
+ error: { code, message },
1983
+ ...partial
1984
+ };
1985
+ }
1986
+ async function verifyX402JwsArtifact(artifact, options) {
1987
+ if (!artifact || typeof artifact !== "object") {
1988
+ return failure("INVALID_ARTIFACT", "Artifact must be a non-null object");
1989
+ }
1990
+ if (artifact.format !== "jws") {
1991
+ return failure("INVALID_ARTIFACT", `Expected format "jws", got "${artifact.format}"`);
1992
+ }
1993
+ if (typeof artifact.signature !== "string" || artifact.signature.length === 0) {
1994
+ return failure("INVALID_ARTIFACT", "Artifact signature must be a non-empty string");
1995
+ }
1996
+ const compactJws = artifact.signature;
1997
+ const parts = compactJws.split(".");
1998
+ if (parts.length !== 3) {
1999
+ return failure("MALFORMED_JWS", "Invalid compact JWS format: expected 3 dot-separated parts");
2000
+ }
2001
+ let header;
2002
+ try {
2003
+ header = jose.decodeProtectedHeader(compactJws);
2004
+ } catch {
2005
+ return failure("MALFORMED_JWS", "Failed to decode JWS protected header");
2006
+ }
2007
+ if (!header.alg || typeof header.alg !== "string") {
2008
+ return failure("MISSING_ALG", "JWS header must include alg", { header });
2009
+ }
2010
+ const kid = typeof header.kid === "string" ? header.kid : null;
2011
+ const embeddedJwk = header.jwk;
2012
+ if (!kid && !embeddedJwk) {
2013
+ return failure(
2014
+ "MISSING_KEY_MATERIAL",
2015
+ "JWS header must include at least one of kid or jwk",
2016
+ { header }
2017
+ );
2018
+ }
2019
+ let publicKeyJwk;
2020
+ let publicKeySource;
2021
+ if (embeddedJwk) {
2022
+ const validation = validatePublicJwk(embeddedJwk);
2023
+ if (!validation.valid) {
2024
+ return failure(
2025
+ "INVALID_EMBEDDED_JWK",
2026
+ `Embedded JWK is invalid: ${validation.error}`,
2027
+ { header }
2028
+ );
2029
+ }
2030
+ publicKeyJwk = embeddedJwk;
2031
+ publicKeySource = "embedded-jwk";
2032
+ if (kid) {
2033
+ try {
2034
+ const resolved = await resolveDidUrlToPublicKey(kid, options?.resolveOptions);
2035
+ if (!publicJwkEquals(publicKeyJwk, resolved.publicKeyJwk)) {
2036
+ return failure(
2037
+ "KEY_CONFLICT",
2038
+ "Embedded jwk conflicts with public key resolved from kid",
2039
+ { header, kid }
2040
+ );
2041
+ }
2042
+ } catch (err) {
2043
+ }
2044
+ }
2045
+ } else {
2046
+ publicKeySource = "kid-resolution";
2047
+ try {
2048
+ const resolved = await resolveDidUrlToPublicKey(kid, options?.resolveOptions);
2049
+ publicKeyJwk = resolved.publicKeyJwk;
2050
+ } catch (err) {
2051
+ const message = err instanceof OmaTrustError ? `Failed to resolve kid "${kid}": ${err.message}` : `Failed to resolve kid "${kid}"`;
2052
+ return failure("KID_RESOLUTION_FAILED", message, { header, kid });
2053
+ }
2054
+ }
2055
+ let payload;
2056
+ try {
2057
+ const key = await jose.importJWK(publicKeyJwk, header.alg);
2058
+ const result = await jose.compactVerify(compactJws, key);
2059
+ const payloadText = new TextDecoder().decode(result.payload);
2060
+ payload = JSON.parse(payloadText);
2061
+ } catch (err) {
2062
+ const message = err instanceof Error ? err.message : "Signature verification failed";
2063
+ return failure("SIGNATURE_INVALID", message, { header, kid });
2064
+ }
2065
+ const publicKeyDid = jwkToDidJwk(publicKeyJwk);
2066
+ return {
2067
+ valid: true,
2068
+ header,
2069
+ payload,
2070
+ kid,
2071
+ publicKeyJwk,
2072
+ publicKeySource,
2073
+ publicKeyDid
2074
+ };
2075
+ }
2076
+ async function verifyX402JwsOffer(artifact, options) {
2077
+ const result = await verifyX402JwsArtifact(artifact, options);
2078
+ if (!result.valid) return result;
2079
+ const payloadCheck = validateOfferPayload(result.payload);
2080
+ if (!payloadCheck.valid) {
2081
+ return failure(
2082
+ "INVALID_OFFER_PAYLOAD",
2083
+ `Missing required offer field: ${payloadCheck.field}`,
2084
+ {
2085
+ header: result.header,
2086
+ payload: result.payload,
2087
+ kid: result.kid,
2088
+ publicKeyJwk: result.publicKeyJwk,
2089
+ publicKeySource: result.publicKeySource,
2090
+ publicKeyDid: result.publicKeyDid
2091
+ }
2092
+ );
2093
+ }
2094
+ return result;
2095
+ }
2096
+ async function verifyX402JwsReceipt(artifact, options) {
2097
+ const result = await verifyX402JwsArtifact(artifact, options);
2098
+ if (!result.valid) return result;
2099
+ const payloadCheck = validateReceiptPayload(result.payload);
2100
+ if (!payloadCheck.valid) {
2101
+ return failure(
2102
+ "INVALID_RECEIPT_PAYLOAD",
2103
+ `Missing required receipt field: ${payloadCheck.field}`,
2104
+ {
2105
+ header: result.header,
2106
+ payload: result.payload,
2107
+ kid: result.kid,
2108
+ publicKeyJwk: result.publicKeyJwk,
2109
+ publicKeySource: result.publicKeySource,
2110
+ publicKeyDid: result.publicKeyDid
2111
+ }
2112
+ );
2113
+ }
2114
+ return result;
2115
+ }
2116
+ var OFFER_DOMAIN = {
2117
+ name: "x402 offer",
2118
+ version: "1",
2119
+ chainId: 1
2120
+ };
2121
+ var RECEIPT_DOMAIN = {
2122
+ name: "x402 receipt",
2123
+ version: "1",
2124
+ chainId: 1
2125
+ };
2126
+ var OFFER_TYPES = {
2127
+ Offer: [
2128
+ { name: "version", type: "uint256" },
2129
+ { name: "resourceUrl", type: "string" },
2130
+ { name: "scheme", type: "string" },
2131
+ { name: "network", type: "string" },
2132
+ { name: "asset", type: "string" },
2133
+ { name: "payTo", type: "string" },
2134
+ { name: "amount", type: "string" },
2135
+ { name: "validUntil", type: "uint256" }
2136
+ ]
2137
+ };
2138
+ var RECEIPT_TYPES = {
2139
+ Receipt: [
2140
+ { name: "version", type: "uint256" },
2141
+ { name: "network", type: "string" },
2142
+ { name: "resourceUrl", type: "string" },
2143
+ { name: "payer", type: "string" },
2144
+ { name: "issuedAt", type: "uint256" },
2145
+ { name: "transaction", type: "string" }
2146
+ ]
2147
+ };
2148
+ var REQUIRED_OFFER_FIELDS2 = [
2149
+ "version",
2150
+ "resourceUrl",
2151
+ "scheme",
2152
+ "network",
2153
+ "asset",
2154
+ "payTo",
2155
+ "amount"
2156
+ ];
2157
+ var REQUIRED_RECEIPT_FIELDS2 = [
2158
+ "version",
2159
+ "network",
2160
+ "resourceUrl",
2161
+ "payer",
2162
+ "issuedAt"
2163
+ ];
2164
+ function validateOfferPayload2(payload) {
2165
+ for (const field of REQUIRED_OFFER_FIELDS2) {
2166
+ if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
2167
+ return { valid: false, field };
1119
2168
  }
1120
- const value = valueParts.join("=");
1121
- if (!value) {
1122
- continue;
2169
+ }
2170
+ return { valid: true };
2171
+ }
2172
+ function validateReceiptPayload2(payload) {
2173
+ for (const field of REQUIRED_RECEIPT_FIELDS2) {
2174
+ if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
2175
+ return { valid: false, field };
1123
2176
  }
1124
- parsed[key.trim()] = value.trim();
1125
2177
  }
2178
+ return { valid: true };
2179
+ }
2180
+ function failure2(code, message, partial) {
1126
2181
  return {
1127
- version: parsed.v,
1128
- controller: parsed.controller,
1129
- ...parsed
2182
+ valid: false,
2183
+ error: { code, message },
2184
+ ...partial
1130
2185
  };
1131
2186
  }
1132
- function buildDnsTxtRecord(controllerDid) {
1133
- const normalized = normalizeDid(controllerDid);
1134
- return `v=1;controller=${normalized}`;
2187
+ function prepareOfferMessage(payload) {
2188
+ return {
2189
+ version: payload.version,
2190
+ resourceUrl: payload.resourceUrl,
2191
+ scheme: payload.scheme,
2192
+ network: payload.network,
2193
+ asset: payload.asset,
2194
+ payTo: payload.payTo,
2195
+ amount: payload.amount,
2196
+ validUntil: payload.validUntil ?? 0
2197
+ };
1135
2198
  }
1136
- async function verifyDnsTxtControllerDid(domain, expectedControllerDid) {
1137
- if (!domain || typeof domain !== "string") {
1138
- throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
2199
+ function prepareReceiptMessage(payload) {
2200
+ return {
2201
+ version: payload.version,
2202
+ network: payload.network,
2203
+ resourceUrl: payload.resourceUrl,
2204
+ payer: payload.payer,
2205
+ issuedAt: payload.issuedAt,
2206
+ transaction: payload.transaction ?? ""
2207
+ };
2208
+ }
2209
+ function verifyX402Eip712Artifact(artifact, artifactType) {
2210
+ if (!artifact || typeof artifact !== "object") {
2211
+ return failure2("INVALID_ARTIFACT", "Artifact must be a non-null object");
1139
2212
  }
1140
- const expected = normalizeDid(expectedControllerDid);
1141
- const host = `_omatrust.${domain.toLowerCase().replace(/\.$/, "")}`;
1142
- let records;
1143
- try {
1144
- records = await promises.resolveTxt(host);
1145
- } catch (err) {
1146
- throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
2213
+ if (artifact.format !== "eip712") {
2214
+ return failure2("INVALID_ARTIFACT", `Expected format "eip712", got "${artifact.format}"`);
1147
2215
  }
1148
- for (const recordParts of records) {
1149
- const record = recordParts.join("");
1150
- const parsed = parseDnsTxtRecord(record);
1151
- if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
1152
- return { valid: true, record };
2216
+ if (!artifact.payload || typeof artifact.payload !== "object" || Array.isArray(artifact.payload)) {
2217
+ return failure2("MISSING_PAYLOAD", "EIP-712 artifact must include a payload object");
2218
+ }
2219
+ if (typeof artifact.signature !== "string" || artifact.signature.length === 0) {
2220
+ return failure2("MISSING_SIGNATURE", "EIP-712 artifact must include a non-empty signature");
2221
+ }
2222
+ const payload = artifact.payload;
2223
+ if (artifactType === "offer") {
2224
+ const check = validateOfferPayload2(payload);
2225
+ if (!check.valid) {
2226
+ return failure2(
2227
+ "INVALID_OFFER_PAYLOAD",
2228
+ `Missing required offer field: ${check.field}`,
2229
+ { payload }
2230
+ );
1153
2231
  }
2232
+ } else {
2233
+ const check = validateReceiptPayload2(payload);
2234
+ if (!check.valid) {
2235
+ return failure2(
2236
+ "INVALID_RECEIPT_PAYLOAD",
2237
+ `Missing required receipt field: ${check.field}`,
2238
+ { payload }
2239
+ );
2240
+ }
2241
+ }
2242
+ const domain = artifactType === "offer" ? OFFER_DOMAIN : RECEIPT_DOMAIN;
2243
+ const types = artifactType === "offer" ? OFFER_TYPES : RECEIPT_TYPES;
2244
+ const message = artifactType === "offer" ? prepareOfferMessage(payload) : prepareReceiptMessage(payload);
2245
+ let signer;
2246
+ try {
2247
+ signer = ethers.verifyTypedData(
2248
+ domain,
2249
+ types,
2250
+ message,
2251
+ artifact.signature
2252
+ );
2253
+ signer = ethers.getAddress(signer);
2254
+ } catch (err) {
2255
+ const message2 = err instanceof Error ? err.message : "EIP-712 signature verification failed";
2256
+ return failure2("SIGNATURE_INVALID", message2, { payload });
1154
2257
  }
1155
- return { valid: false, reason: "No TXT record matched expected controller DID" };
2258
+ return {
2259
+ valid: true,
2260
+ payload,
2261
+ signer,
2262
+ artifactType
2263
+ };
2264
+ }
2265
+ function verifyX402Eip712Offer(artifact) {
2266
+ return verifyX402Eip712Artifact(artifact, "offer");
2267
+ }
2268
+ function verifyX402Eip712Receipt(artifact) {
2269
+ return verifyX402Eip712Artifact(artifact, "receipt");
1156
2270
  }
1157
2271
 
1158
2272
  // src/reputation/verify.ts
@@ -1212,18 +2326,18 @@ function decodeJwtPayload(compactJws) {
1212
2326
  return JSON.parse(json);
1213
2327
  }
1214
2328
  async function verifyProof(params) {
1215
- const { proof, provider, expectedSubject, expectedController } = params;
2329
+ const { proof, provider, expectedSubjectDid, expectedControllerDid } = params;
1216
2330
  try {
1217
2331
  switch (proof.proofType) {
1218
2332
  case "tx-encoded-value": {
1219
2333
  if (!provider) {
1220
2334
  return { valid: false, proofType: proof.proofType, reason: "Provider is required" };
1221
2335
  }
1222
- if (!expectedSubject || !expectedController) {
2336
+ if (!expectedSubjectDid || !expectedControllerDid) {
1223
2337
  return {
1224
2338
  valid: false,
1225
2339
  proofType: proof.proofType,
1226
- reason: "expectedSubject and expectedController are required"
2340
+ reason: "expectedSubjectDid and expectedControllerDid are required"
1227
2341
  };
1228
2342
  }
1229
2343
  const proofObject = proof.proofObject;
@@ -1235,13 +2349,13 @@ async function verifyProof(params) {
1235
2349
  return { valid: false, proofType: proof.proofType, reason: "Transaction not found" };
1236
2350
  }
1237
2351
  const expectedAmount = calculateTransferAmount(
1238
- expectedSubject,
1239
- expectedController,
2352
+ expectedSubjectDid,
2353
+ expectedControllerDid,
1240
2354
  chainId,
1241
2355
  getProofPurpose(proof)
1242
2356
  );
1243
- const subjectAddress = ethers.getAddress(extractAddressFromDid(expectedSubject));
1244
- const controllerAddress = ethers.getAddress(extractAddressFromDid(expectedController));
2357
+ const subjectAddress = ethers.getAddress(extractAddressFromDid(expectedSubjectDid));
2358
+ const controllerAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
1245
2359
  if (tx.from && ethers.getAddress(tx.from) !== subjectAddress) {
1246
2360
  return { valid: false, proofType: proof.proofType, reason: "Transaction sender mismatch" };
1247
2361
  }
@@ -1315,6 +2429,38 @@ async function verifyProof(params) {
1315
2429
  if (!proof.proofObject || typeof proof.proofObject !== "object") {
1316
2430
  return { valid: false, proofType: proof.proofType, reason: "Invalid x402 proof object" };
1317
2431
  }
2432
+ const proofObj = proof.proofObject;
2433
+ if (proofObj.format === "jws" && typeof proofObj.signature === "string") {
2434
+ const artifact = {
2435
+ format: "jws",
2436
+ signature: proofObj.signature
2437
+ };
2438
+ const jwsResult = proof.proofType === "x402-offer" ? await verifyX402JwsOffer(artifact) : await verifyX402JwsReceipt(artifact);
2439
+ if (!jwsResult.valid) {
2440
+ return {
2441
+ valid: false,
2442
+ proofType: proof.proofType,
2443
+ reason: jwsResult.error?.message ?? "JWS verification failed"
2444
+ };
2445
+ }
2446
+ return { valid: true, proofType: proof.proofType };
2447
+ }
2448
+ if (proofObj.format === "eip712" && typeof proofObj.signature === "string" && proofObj.payload && typeof proofObj.payload === "object") {
2449
+ const artifact = {
2450
+ format: "eip712",
2451
+ payload: proofObj.payload,
2452
+ signature: proofObj.signature
2453
+ };
2454
+ const eip712Result = proof.proofType === "x402-offer" ? verifyX402Eip712Offer(artifact) : verifyX402Eip712Receipt(artifact);
2455
+ if (!eip712Result.valid) {
2456
+ return {
2457
+ valid: false,
2458
+ proofType: proof.proofType,
2459
+ reason: eip712Result.error?.message ?? "EIP-712 verification failed"
2460
+ };
2461
+ }
2462
+ return { valid: true, proofType: proof.proofType };
2463
+ }
1318
2464
  return { valid: true, proofType: proof.proofType };
1319
2465
  }
1320
2466
  case "evidence-pointer": {
@@ -1326,9 +2472,9 @@ async function verifyProof(params) {
1326
2472
  if (!response.ok) {
1327
2473
  return { valid: false, proofType: proof.proofType, reason: `Evidence fetch failed (${response.status})` };
1328
2474
  }
1329
- if (object.url.endsWith("/.well-known/did.json") && expectedController) {
2475
+ if (object.url.endsWith("/.well-known/did.json") && expectedControllerDid) {
1330
2476
  const didDoc = await response.json();
1331
- const didCheck = verifyDidDocumentControllerDid(didDoc, expectedController);
2477
+ const didCheck = verifyDidDocumentControllerDid(didDoc, expectedControllerDid);
1332
2478
  return {
1333
2479
  valid: didCheck.valid,
1334
2480
  proofType: proof.proofType,
@@ -1336,10 +2482,10 @@ async function verifyProof(params) {
1336
2482
  };
1337
2483
  }
1338
2484
  const body = await response.text();
1339
- if (expectedController && !body.includes(expectedController)) {
2485
+ if (expectedControllerDid && !body.includes(expectedControllerDid)) {
1340
2486
  try {
1341
2487
  const parsed = parseDnsTxtRecord(body);
1342
- if (parsed.controller !== expectedController) {
2488
+ if (!parsed.controllers.includes(expectedControllerDid)) {
1343
2489
  return {
1344
2490
  valid: false,
1345
2491
  proofType: proof.proofType,
@@ -1395,8 +2541,8 @@ async function verifyAttestation(params) {
1395
2541
  const result = await verifyProof({
1396
2542
  proof,
1397
2543
  provider: params.provider,
1398
- expectedSubject: params.context?.subject,
1399
- expectedController: params.context?.controller
2544
+ expectedSubjectDid: params.context?.subjectDid,
2545
+ expectedControllerDid: params.context?.controllerDid
1400
2546
  });
1401
2547
  checks[proof.proofType] = result.valid;
1402
2548
  if (!result.valid) {
@@ -1434,6 +2580,9 @@ async function getSchemaDetails(schemaRegistry, schemaUid) {
1434
2580
  revocable: Boolean(details.revocable)
1435
2581
  };
1436
2582
  } catch (err) {
2583
+ if (isEasSchemaNotFoundError(err)) {
2584
+ throw new OmaTrustError("SCHEMA_NOT_FOUND", "Schema was not found", { schemaUid });
2585
+ }
1437
2586
  if (err instanceof OmaTrustError) {
1438
2587
  throw err;
1439
2588
  }
@@ -1496,6 +2645,460 @@ async function callControllerWitness(params) {
1496
2645
  method: "did-json"
1497
2646
  };
1498
2647
  }
2648
+ var DEFAULT_CONTROLLER_WITNESS_URL = "https://api.omatrust.org/v1/controller-witness";
2649
+ async function requestControllerWitness(params) {
2650
+ const url = params.gatewayUrl ?? DEFAULT_CONTROLLER_WITNESS_URL;
2651
+ const body = {
2652
+ subjectDid: params.subjectDid,
2653
+ controllerDid: params.controllerDid
2654
+ };
2655
+ if (params.chainId !== void 0) {
2656
+ body.chainId = params.chainId;
2657
+ }
2658
+ let response;
2659
+ try {
2660
+ response = await fetch(url, {
2661
+ method: "POST",
2662
+ headers: { "Content-Type": "application/json" },
2663
+ credentials: "include",
2664
+ body: JSON.stringify(body),
2665
+ signal: AbortSignal.timeout(params.timeoutMs ?? 15e3)
2666
+ });
2667
+ } catch (err) {
2668
+ throw new OmaTrustError("NETWORK_ERROR", "Controller witness request failed", { err });
2669
+ }
2670
+ if (!response.ok) {
2671
+ const errorBody = await response.json().catch(() => ({ error: response.statusText }));
2672
+ throw new OmaTrustError(
2673
+ "API_ERROR",
2674
+ errorBody.error ?? `Controller witness failed: ${response.status}`,
2675
+ { status: response.status, ...errorBody }
2676
+ );
2677
+ }
2678
+ return await response.json();
2679
+ }
2680
+ var EIP1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
2681
+ var OWNERSHIP_PATTERNS = [
2682
+ { method: "owner", signature: "function owner() view returns (address)" },
2683
+ { method: "admin", signature: "function admin() view returns (address)" },
2684
+ { method: "getOwner", signature: "function getOwner() view returns (address)" }
2685
+ ];
2686
+ async function readOwnerFromContract(provider, contractAddress, signature, method) {
2687
+ try {
2688
+ const iface = new ethers.Interface([signature]);
2689
+ const data = iface.encodeFunctionData(method, []);
2690
+ const result = await provider.call({ to: contractAddress, data });
2691
+ const [value] = iface.decodeFunctionResult(method, result);
2692
+ if (typeof value === "string" && ethers.isAddress(value) && value !== ethers.ZeroAddress) {
2693
+ return ethers.getAddress(value);
2694
+ }
2695
+ } catch {
2696
+ }
2697
+ return null;
2698
+ }
2699
+ async function discoverContractOwner(provider, contractAddress) {
2700
+ try {
2701
+ const code = await provider.getCode(contractAddress);
2702
+ if (code === "0x" || code === "0x0") {
2703
+ return null;
2704
+ }
2705
+ } catch {
2706
+ return null;
2707
+ }
2708
+ for (const pattern of OWNERSHIP_PATTERNS) {
2709
+ const address = await readOwnerFromContract(
2710
+ provider,
2711
+ contractAddress,
2712
+ pattern.signature,
2713
+ pattern.method
2714
+ );
2715
+ if (address) {
2716
+ return address;
2717
+ }
2718
+ }
2719
+ try {
2720
+ const adminValue = await provider.getStorage(contractAddress, EIP1967_ADMIN_SLOT);
2721
+ if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
2722
+ const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
2723
+ if (adminAddress !== ethers.ZeroAddress) {
2724
+ return adminAddress;
2725
+ }
2726
+ }
2727
+ } catch {
2728
+ }
2729
+ return null;
2730
+ }
2731
+ async function discoverControllingWalletDid(provider, contractAddress, chainId) {
2732
+ const owner = await discoverContractOwner(provider, contractAddress);
2733
+ return owner ? buildEvmDidPkh(chainId, owner) : null;
2734
+ }
2735
+ async function verifyTransferProof(provider, txHash, subjectDid, attesterAddress, chainId) {
2736
+ try {
2737
+ const tx = await provider.getTransaction(txHash);
2738
+ if (!tx || !tx.from || !tx.to) {
2739
+ return false;
2740
+ }
2741
+ if (ethers.getAddress(tx.to) !== ethers.getAddress(attesterAddress)) {
2742
+ return false;
2743
+ }
2744
+ const attesterDid = buildEvmDidPkh(chainId, attesterAddress);
2745
+ const expectedAmount = calculateTransferAmount(
2746
+ subjectDid,
2747
+ attesterDid,
2748
+ chainId,
2749
+ "shared-control"
2750
+ );
2751
+ const actualValue = BigInt(tx.value ?? 0n);
2752
+ return actualValue === expectedAmount;
2753
+ } catch {
2754
+ return false;
2755
+ }
2756
+ }
2757
+
2758
+ // src/shared/trust-anchors.ts
2759
+ var DEFAULT_TRUST_ANCHORS_URL = "https://api.omatrust.org/v1/trust-anchors";
2760
+ var TRUST_ANCHORS_URL = DEFAULT_TRUST_ANCHORS_URL;
2761
+ var cachedAnchors = null;
2762
+ var cacheTimestamp = 0;
2763
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
2764
+ async function fetchTrustAnchors(url) {
2765
+ const now = Date.now();
2766
+ if (cachedAnchors && now - cacheTimestamp < CACHE_TTL_MS) {
2767
+ return cachedAnchors;
2768
+ }
2769
+ let res;
2770
+ try {
2771
+ res = await fetch(url ?? TRUST_ANCHORS_URL);
2772
+ } catch (err) {
2773
+ throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch trust anchors", { err });
2774
+ }
2775
+ if (!res.ok) {
2776
+ throw new OmaTrustError("NETWORK_ERROR", `Failed to fetch trust anchors: ${res.status} ${res.statusText}`);
2777
+ }
2778
+ const anchors = await res.json();
2779
+ if (!anchors.version || !anchors.chains || typeof anchors.chains !== "object") {
2780
+ throw new OmaTrustError("INVALID_INPUT", "Invalid trust anchors format");
2781
+ }
2782
+ cachedAnchors = anchors;
2783
+ cacheTimestamp = now;
2784
+ return anchors;
2785
+ }
2786
+ function getChainAnchors(anchors, caip2) {
2787
+ const chain = anchors.chains[caip2];
2788
+ if (!chain) {
2789
+ throw new OmaTrustError("UNSUPPORTED_CHAIN", `Chain ${caip2} is not in the trust anchors`, {
2790
+ caip2,
2791
+ supportedChains: Object.keys(anchors.chains)
2792
+ });
2793
+ }
2794
+ return chain;
2795
+ }
2796
+
2797
+ // src/reputation/proof/dns-txt-shared.ts
2798
+ async function verifyDnsTxtControllerDid(domain, expectedControllerDid, options = {}) {
2799
+ if (!domain || typeof domain !== "string") {
2800
+ throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
2801
+ }
2802
+ if (!options.resolveTxt) {
2803
+ throw new OmaTrustError("NETWORK_ERROR", "No DNS TXT resolver was provided", { domain });
2804
+ }
2805
+ const prefix = options.recordPrefix ?? "_controllers";
2806
+ const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
2807
+ let records;
2808
+ try {
2809
+ records = await options.resolveTxt(host);
2810
+ } catch (err) {
2811
+ throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
2812
+ }
2813
+ for (const recordParts of records) {
2814
+ const record = recordParts.join("");
2815
+ const parsed = parseDnsTxtRecord(record);
2816
+ if (parsed.version !== "1") continue;
2817
+ for (const recordController of parsed.controllers) {
2818
+ if (isSameControllerId(recordController, expectedControllerDid)) {
2819
+ return { valid: true, record };
2820
+ }
2821
+ }
2822
+ }
2823
+ return { valid: false, reason: "Controller DID not found in DNS TXT records" };
2824
+ }
2825
+
2826
+ // src/reputation/attester-authorization.ts
2827
+ var DEFAULT_CHAIN = "eip155:6623";
2828
+ var DEFAULT_PURPOSES = ["authentication", "assertionMethod"];
2829
+ async function getControllerAuthorization(params) {
2830
+ const chain = params.chain ?? DEFAULT_CHAIN;
2831
+ const purposes = params.purpose && params.purpose.length > 0 ? params.purpose : DEFAULT_PURPOSES;
2832
+ const controllerDid = normalizeDid(params.controllerDid);
2833
+ const controllerMethod = extractDidMethod(controllerDid);
2834
+ const controllerEvmAddress = extractControllerEvmAddress(controllerDid);
2835
+ const anchors = await fetchTrustAnchors();
2836
+ const chainAnchors = getChainAnchors(anchors, chain);
2837
+ const easContractAddress = params.easContractAddress ?? chainAnchors.easContract;
2838
+ const provider = params.provider;
2839
+ const controllerWitnessSchemaUid = chainAnchors.schemas["controller-witness"];
2840
+ const keyBindingSchemaUid = chainAnchors.schemas["key-binding"];
2841
+ const subjectAddress = didToAddress(params.subjectDid);
2842
+ let relevantWitnesses = [];
2843
+ if (controllerWitnessSchemaUid) {
2844
+ const rawWitnesses = await queryByRecipientAndSchema(
2845
+ easContractAddress,
2846
+ provider,
2847
+ subjectAddress,
2848
+ [controllerWitnessSchemaUid],
2849
+ params.fromBlock ?? 0
2850
+ );
2851
+ relevantWitnesses = rawWitnesses.filter((att) => {
2852
+ const witnessController = att.data?.controller;
2853
+ if (typeof witnessController !== "string") return false;
2854
+ return isSameControllerId(witnessController, controllerDid);
2855
+ });
2856
+ relevantWitnesses.sort((a, b) => Number(a.time - b.time));
2857
+ }
2858
+ const controllerWitnesses = relevantWitnesses.map((att) => ({
2859
+ uid: att.uid,
2860
+ issuedAt: att.time,
2861
+ attester: att.attester,
2862
+ method: resolveWitnessMethod(att.data?.method)
2863
+ }));
2864
+ let keyBindingUid = null;
2865
+ let until = null;
2866
+ let keyPurposeStatus = "not-required";
2867
+ if (keyBindingSchemaUid) {
2868
+ const keyBindings = await queryByRecipientAndSchema(
2869
+ easContractAddress,
2870
+ provider,
2871
+ subjectAddress,
2872
+ [keyBindingSchemaUid],
2873
+ params.fromBlock ?? 0
2874
+ );
2875
+ const relevantKeyBindings = keyBindings.filter((att) => controllerMatchesKeyBinding(att, controllerDid, controllerMethod)).sort((a, b) => Number(b.time - a.time));
2876
+ const relevantKeyBinding = relevantKeyBindings[0] ?? null;
2877
+ if (relevantKeyBinding) {
2878
+ keyBindingUid = relevantKeyBinding.uid;
2879
+ if (relevantKeyBinding.revocationTime > 0n) {
2880
+ until = relevantKeyBinding.revocationTime;
2881
+ }
2882
+ const keyPurposes = relevantKeyBinding.data?.keyPurpose;
2883
+ if (!Array.isArray(keyPurposes) || keyPurposes.length === 0) {
2884
+ keyPurposeStatus = "unknown";
2885
+ } else {
2886
+ const allMatched = purposes.every((p) => keyPurposes.includes(p));
2887
+ keyPurposeStatus = allMatched ? "matched" : "mismatch";
2888
+ }
2889
+ }
2890
+ }
2891
+ let currentlyVerified = false;
2892
+ let liveMethod = null;
2893
+ let transferProofVerified = false;
2894
+ let transferProofAnchor = null;
2895
+ const subjectMethod = extractDidMethod(params.subjectDid);
2896
+ if (subjectMethod === "web") {
2897
+ const domain = getDomainFromDidWeb(params.subjectDid);
2898
+ if (domain) {
2899
+ try {
2900
+ const dnsResult = await verifyDnsTxtControllerDid(domain, controllerDid, {
2901
+ resolveTxt: params.resolveTxt
2902
+ });
2903
+ if (dnsResult.valid) {
2904
+ currentlyVerified = true;
2905
+ liveMethod = "dns";
2906
+ }
2907
+ } catch {
2908
+ }
2909
+ if (!currentlyVerified) {
2910
+ try {
2911
+ const fetchDoc = params.fetchDidDocument ?? fetchDidDocument;
2912
+ const didDoc = await fetchDoc(domain);
2913
+ const didJsonResult = verifyDidDocumentControllerDid(didDoc, controllerDid);
2914
+ if (didJsonResult.valid) {
2915
+ currentlyVerified = true;
2916
+ liveMethod = "did-document";
2917
+ }
2918
+ } catch {
2919
+ }
2920
+ }
2921
+ }
2922
+ } else if (subjectMethod === "pkh") {
2923
+ if (isEvmDidPkh(params.subjectDid) && controllerEvmAddress) {
2924
+ const subjectContractAddress = getAddressFromDidPkh(params.subjectDid);
2925
+ const subjectChainId = getChainIdFromDidPkh(params.subjectDid);
2926
+ const chainIdNum = subjectChainId ? Number(subjectChainId) : null;
2927
+ if (subjectContractAddress && chainIdNum) {
2928
+ const ownerAddress = await discoverContractOwner(
2929
+ provider,
2930
+ subjectContractAddress
2931
+ );
2932
+ if (ownerAddress && ethers.getAddress(ownerAddress) === ethers.getAddress(controllerEvmAddress)) {
2933
+ currentlyVerified = true;
2934
+ liveMethod = "contract-ownership";
2935
+ }
2936
+ if (!currentlyVerified && keyBindingUid) {
2937
+ const relevantKeyBinding = await findKeyBindingWithTransferProof(
2938
+ keyBindingSchemaUid,
2939
+ easContractAddress,
2940
+ provider,
2941
+ subjectAddress,
2942
+ controllerDid,
2943
+ controllerMethod,
2944
+ params.fromBlock ?? 0
2945
+ );
2946
+ if (relevantKeyBinding) {
2947
+ const proofTxHash = relevantKeyBinding.data?.proofTxHash;
2948
+ if (proofTxHash && /^0x[0-9a-fA-F]{64}$/.test(proofTxHash)) {
2949
+ const verified = await verifyTransferProof(
2950
+ provider,
2951
+ proofTxHash,
2952
+ params.subjectDid,
2953
+ controllerEvmAddress,
2954
+ chainIdNum
2955
+ );
2956
+ if (verified) {
2957
+ transferProofVerified = true;
2958
+ transferProofAnchor = relevantKeyBinding.time;
2959
+ }
2960
+ }
2961
+ }
2962
+ }
2963
+ }
2964
+ }
2965
+ }
2966
+ const witnessAnchor = relevantWitnesses.length > 0 ? relevantWitnesses[0].time : null;
2967
+ const anchoredFrom = witnessAnchor ?? transferProofAnchor;
2968
+ const purposeBlocks = keyPurposeStatus === "mismatch";
2969
+ const hasOpenWindow = relevantWitnesses.length > 0 && until === null;
2970
+ const hasKeyBindingWithProof = keyBindingUid !== null && transferProofVerified && until === null;
2971
+ const authorized = !purposeBlocks && (hasOpenWindow || currentlyVerified && until === null || hasKeyBindingWithProof);
2972
+ return {
2973
+ authorized,
2974
+ anchoredFrom,
2975
+ until,
2976
+ currentlyVerified,
2977
+ liveMethod,
2978
+ controllerWitnesses,
2979
+ keyBindingUid,
2980
+ keyPurposeStatus,
2981
+ transferProofVerified: transferProofVerified || void 0
2982
+ };
2983
+ }
2984
+ function controllerMatchesKeyBinding(att, controllerDid, controllerMethod) {
2985
+ const keyId = att.data?.keyId;
2986
+ if (typeof keyId === "string") {
2987
+ if (isSameControllerId(keyId, controllerDid)) {
2988
+ return true;
2989
+ }
2990
+ }
2991
+ if (controllerMethod === "jwk") {
2992
+ const publicKeyJwkRaw = att.data?.publicKeyJwk;
2993
+ if (publicKeyJwkRaw) {
2994
+ try {
2995
+ const onChainJwk = typeof publicKeyJwkRaw === "string" ? JSON.parse(publicKeyJwkRaw) : publicKeyJwkRaw;
2996
+ const controllerJwk = didJwkToJwk(controllerDid);
2997
+ return publicJwkEquals(onChainJwk, controllerJwk);
2998
+ } catch {
2999
+ }
3000
+ }
3001
+ }
3002
+ return false;
3003
+ }
3004
+ var EAS_EVENT_ABI2 = [
3005
+ "event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
3006
+ ];
3007
+ function resolveWitnessMethod(value) {
3008
+ if (typeof value !== "string") return void 0;
3009
+ switch (value) {
3010
+ case "dns":
3011
+ case "dns-txt":
3012
+ return "dns";
3013
+ case "did-document":
3014
+ case "did-json":
3015
+ return "did-document";
3016
+ case "manual":
3017
+ return "manual";
3018
+ default:
3019
+ return "other";
3020
+ }
3021
+ }
3022
+ async function queryByRecipientAndSchema(easContractAddress, provider, recipient, schemas, fromBlock) {
3023
+ const contract = new ethers.Contract(easContractAddress, EAS_EVENT_ABI2, provider);
3024
+ const filter = contract.filters.Attested(recipient, null);
3025
+ const toBlock = await provider.getBlockNumber();
3026
+ let events;
3027
+ try {
3028
+ events = await contract.queryFilter(filter, fromBlock, toBlock);
3029
+ } catch (err) {
3030
+ throw new OmaTrustError("NETWORK_ERROR", "Failed to query attestation events", { err });
3031
+ }
3032
+ const eas = new easSdk.EAS(easContractAddress);
3033
+ eas.connect(provider);
3034
+ const schemaFilter = schemas.map((s) => s.toLowerCase());
3035
+ const results = [];
3036
+ for (const event of events) {
3037
+ if (!("args" in event) || !Array.isArray(event.args)) continue;
3038
+ const args = event.args;
3039
+ const uid = args?.[2];
3040
+ const schemaUid = args?.[3];
3041
+ if (!uid || !schemaUid) continue;
3042
+ if (!schemaFilter.includes(schemaUid.toLowerCase())) continue;
3043
+ const attestation = await eas.getAttestation(uid);
3044
+ if (!attestation || !attestation.uid) continue;
3045
+ let data = {};
3046
+ const rawData = attestation.data;
3047
+ if (rawData && rawData !== "0x") {
3048
+ try {
3049
+ data = decodeAttestationData(
3050
+ "string subject, string controller, string method",
3051
+ rawData
3052
+ );
3053
+ } catch {
3054
+ try {
3055
+ data = decodeAttestationData(
3056
+ "string subject, string keyId, string publicKeyJwk, string[] keyPurpose, string[] proofs, uint256 issuedAt, uint256 effectiveAt, uint256 expiresAt",
3057
+ rawData
3058
+ );
3059
+ } catch {
3060
+ }
3061
+ }
3062
+ }
3063
+ const toBigIntSafe = (value) => {
3064
+ if (typeof value === "bigint") return value;
3065
+ if (typeof value === "number") return BigInt(value);
3066
+ if (typeof value === "string" && value.length > 0) return BigInt(value);
3067
+ return 0n;
3068
+ };
3069
+ results.push({
3070
+ uid: attestation.uid,
3071
+ schema: attestation.schema,
3072
+ attester: attestation.attester,
3073
+ recipient: attestation.recipient,
3074
+ revocable: Boolean(attestation.revocable),
3075
+ revocationTime: toBigIntSafe(attestation.revocationTime),
3076
+ expirationTime: toBigIntSafe(attestation.expirationTime),
3077
+ time: toBigIntSafe(attestation.time),
3078
+ refUID: attestation.refUID,
3079
+ data
3080
+ });
3081
+ }
3082
+ return results;
3083
+ }
3084
+ async function findKeyBindingWithTransferProof(keyBindingSchemaUid, easContractAddress, provider, subjectAddress, controllerDid, controllerMethod, fromBlock) {
3085
+ const keyBindings = await queryByRecipientAndSchema(
3086
+ easContractAddress,
3087
+ provider,
3088
+ subjectAddress,
3089
+ [keyBindingSchemaUid],
3090
+ fromBlock
3091
+ );
3092
+ const withProof = keyBindings.filter((att) => {
3093
+ if (!controllerMatchesKeyBinding(att, controllerDid, controllerMethod)) return false;
3094
+ const proofs = att.data?.proofs;
3095
+ if (Array.isArray(proofs)) {
3096
+ return proofs.some((p) => typeof p === "string" && /^0x[0-9a-fA-F]{64}$/.test(p));
3097
+ }
3098
+ return typeof att.data?.proofTxHash === "string";
3099
+ }).sort((a, b) => Number(b.time - a.time));
3100
+ return withProof[0] ?? null;
3101
+ }
1499
3102
 
1500
3103
  // src/reputation/proof/tx-interaction.ts
1501
3104
  function createTxInteractionProof(chainId, txHash) {
@@ -1617,6 +3220,278 @@ function createEvidencePointerProof(url) {
1617
3220
  issuedAt: Math.floor(Date.now() / 1e3)
1618
3221
  };
1619
3222
  }
3223
+ function verifyDnsTxtControllerDid2(domain, expectedControllerDid, options = {}) {
3224
+ return verifyDnsTxtControllerDid(domain, expectedControllerDid, {
3225
+ ...options,
3226
+ resolveTxt: options.resolveTxt ?? promises.resolveTxt
3227
+ });
3228
+ }
3229
+ function assertConnectedWalletDid(input) {
3230
+ const normalized = normalizeDid(input);
3231
+ if (extractDidMethod(normalized) !== "pkh") {
3232
+ throw new OmaTrustError("INVALID_INPUT", "connectedWalletDid must be a did:pkh DID", {
3233
+ connectedWalletDid: input
3234
+ });
3235
+ }
3236
+ return normalized;
3237
+ }
3238
+ function normalizeSubjectDid(input) {
3239
+ return normalizeDid(input);
3240
+ }
3241
+ async function verifyDidWebOwnership(params) {
3242
+ const subjectDid = normalizeSubjectDid(params.subjectDid);
3243
+ const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
3244
+ const domain = getDomainFromDidWeb(subjectDid);
3245
+ if (!domain) {
3246
+ throw new OmaTrustError("INVALID_INPUT", "subjectDid must be a did:web DID", {
3247
+ subjectDid: params.subjectDid
3248
+ });
3249
+ }
3250
+ let dnsReason;
3251
+ try {
3252
+ const dnsResult = await verifyDnsTxtControllerDid(domain, connectedWalletDid, {
3253
+ resolveTxt: params.resolveTxt,
3254
+ recordPrefix: params.recordPrefix
3255
+ });
3256
+ if (dnsResult.valid) {
3257
+ return {
3258
+ valid: true,
3259
+ method: "dns",
3260
+ details: `Verified via DNS TXT record at ${params.recordPrefix ?? "_controllers"}.${domain}`,
3261
+ subjectDid,
3262
+ connectedWalletDid
3263
+ };
3264
+ }
3265
+ dnsReason = dnsResult.reason;
3266
+ } catch (error) {
3267
+ dnsReason = error instanceof Error ? error.message : "DNS TXT verification failed";
3268
+ }
3269
+ let didDocReason;
3270
+ try {
3271
+ const didDocumentResult = await verifyDidJsonControllerDid(domain, connectedWalletDid, {
3272
+ fetchDidDocument: params.fetchDidDocument
3273
+ });
3274
+ if (didDocumentResult.valid) {
3275
+ return {
3276
+ valid: true,
3277
+ method: "did-document",
3278
+ details: `Verified via DID document at https://${domain}/.well-known/did.json`,
3279
+ subjectDid,
3280
+ connectedWalletDid
3281
+ };
3282
+ }
3283
+ didDocReason = didDocumentResult.reason;
3284
+ } catch (error) {
3285
+ didDocReason = error instanceof Error ? error.message : "DID document verification failed";
3286
+ }
3287
+ return {
3288
+ valid: false,
3289
+ reason: "DID ownership verification failed",
3290
+ details: `DNS check: ${dnsReason ?? "failed"}. DID document check: ${didDocReason ?? "failed"}.`,
3291
+ subjectDid,
3292
+ connectedWalletDid
3293
+ };
3294
+ }
3295
+ async function verifyDidPkhOwnership(params) {
3296
+ const subjectDid = normalizeSubjectDid(params.subjectDid);
3297
+ const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
3298
+ if (!isEvmDidPkh(subjectDid)) {
3299
+ throw new OmaTrustError("INVALID_INPUT", "subjectDid must be an EVM did:pkh DID", {
3300
+ subjectDid: params.subjectDid
3301
+ });
3302
+ }
3303
+ const subjectAddress = getAddressFromDidPkh(subjectDid);
3304
+ const connectedWalletAddress = getAddressFromDidPkh(connectedWalletDid);
3305
+ const chainIdRaw = getChainIdFromDidPkh(subjectDid);
3306
+ if (!subjectAddress || !connectedWalletAddress || !chainIdRaw) {
3307
+ throw new OmaTrustError("INVALID_INPUT", "Could not parse did:pkh ownership inputs", {
3308
+ subjectDid: params.subjectDid,
3309
+ connectedWalletDid: params.connectedWalletDid
3310
+ });
3311
+ }
3312
+ const chainId = Number(chainIdRaw);
3313
+ if (!Number.isFinite(chainId)) {
3314
+ throw new OmaTrustError("INVALID_INPUT", "Invalid chain id in subjectDid", {
3315
+ subjectDid: params.subjectDid
3316
+ });
3317
+ }
3318
+ const code = await params.provider.getCode(subjectAddress);
3319
+ const isContract = code !== "0x" && code !== "0x0";
3320
+ if (!isContract) {
3321
+ if (ethers.getAddress(subjectAddress) === ethers.getAddress(connectedWalletAddress)) {
3322
+ return {
3323
+ valid: true,
3324
+ method: "wallet",
3325
+ details: "Verified direct wallet ownership from matching did:pkh subject and connected wallet",
3326
+ subjectDid,
3327
+ connectedWalletDid
3328
+ };
3329
+ }
3330
+ return {
3331
+ valid: false,
3332
+ reason: "EOA did:pkh subject does not match connected wallet",
3333
+ details: "For direct wallet did:pkh subjects, connectedWalletDid must match the subject DID.",
3334
+ subjectDid,
3335
+ connectedWalletDid
3336
+ };
3337
+ }
3338
+ const controllingWalletDid = await discoverControllingWalletDid(params.provider, subjectAddress, chainId);
3339
+ if (params.txHash) {
3340
+ if (!controllingWalletDid) {
3341
+ return {
3342
+ valid: false,
3343
+ reason: "Could not discover controlling wallet",
3344
+ details: "Contract does not expose owner/admin/getOwner or a readable EIP-1967 admin slot for transfer verification.",
3345
+ subjectDid,
3346
+ connectedWalletDid
3347
+ };
3348
+ }
3349
+ const tx = await params.provider.getTransaction(params.txHash);
3350
+ if (!tx) {
3351
+ return {
3352
+ valid: false,
3353
+ reason: "Transaction not found",
3354
+ details: `Transaction ${params.txHash} was not found on chain ${chainId}.`,
3355
+ subjectDid,
3356
+ connectedWalletDid,
3357
+ controllingWalletDid
3358
+ };
3359
+ }
3360
+ const receipt = await params.provider.getTransactionReceipt(params.txHash);
3361
+ if (!receipt) {
3362
+ return {
3363
+ valid: false,
3364
+ reason: "Transaction not confirmed",
3365
+ details: "Transaction exists but is not yet confirmed.",
3366
+ subjectDid,
3367
+ connectedWalletDid,
3368
+ controllingWalletDid
3369
+ };
3370
+ }
3371
+ const controllingWalletAddress = getAddressFromDidPkh(controllingWalletDid);
3372
+ if (!tx.from || !controllingWalletAddress || ethers.getAddress(tx.from) !== ethers.getAddress(controllingWalletAddress)) {
3373
+ return {
3374
+ valid: false,
3375
+ reason: "Wrong sender",
3376
+ details: `Transfer must originate from controlling wallet ${controllingWalletDid}.`,
3377
+ subjectDid,
3378
+ connectedWalletDid,
3379
+ controllingWalletDid
3380
+ };
3381
+ }
3382
+ if (!tx.to || ethers.getAddress(tx.to) !== ethers.getAddress(connectedWalletAddress)) {
3383
+ return {
3384
+ valid: false,
3385
+ reason: "Wrong recipient",
3386
+ details: `Transfer must be sent to connected wallet ${connectedWalletDid}.`,
3387
+ subjectDid,
3388
+ connectedWalletDid,
3389
+ controllingWalletDid
3390
+ };
3391
+ }
3392
+ const expectedAmount = calculateTransferAmount(
3393
+ subjectDid,
3394
+ connectedWalletDid,
3395
+ chainId,
3396
+ "shared-control"
3397
+ );
3398
+ const actualValue = BigInt(tx.value ?? 0n);
3399
+ if (actualValue !== expectedAmount) {
3400
+ return {
3401
+ valid: false,
3402
+ reason: "Wrong amount",
3403
+ details: `Transfer amount ${actualValue.toString()} does not match expected proof amount ${expectedAmount.toString()}.`,
3404
+ subjectDid,
3405
+ connectedWalletDid,
3406
+ controllingWalletDid
3407
+ };
3408
+ }
3409
+ await params.provider.getBlockNumber();
3410
+ await params.provider.getBlock(receipt.blockNumber);
3411
+ return {
3412
+ valid: true,
3413
+ method: "transfer",
3414
+ details: `Verified via transfer proof ${params.txHash}.`,
3415
+ subjectDid,
3416
+ connectedWalletDid,
3417
+ controllingWalletDid
3418
+ };
3419
+ }
3420
+ for (const pattern of OWNERSHIP_PATTERNS) {
3421
+ const ownerAddress = await readOwnerFromContract(
3422
+ params.provider,
3423
+ subjectAddress,
3424
+ pattern.signature,
3425
+ pattern.method
3426
+ );
3427
+ if (ownerAddress && ethers.getAddress(ownerAddress) === ethers.getAddress(connectedWalletAddress)) {
3428
+ return {
3429
+ valid: true,
3430
+ method: "contract",
3431
+ details: `Verified via ${pattern.method}() ownership check.`,
3432
+ subjectDid,
3433
+ connectedWalletDid,
3434
+ controllingWalletDid: controllingWalletDid ?? void 0
3435
+ };
3436
+ }
3437
+ }
3438
+ try {
3439
+ const adminValue = await params.provider.getStorage(subjectAddress, EIP1967_ADMIN_SLOT);
3440
+ if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
3441
+ const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
3442
+ if (adminAddress === ethers.getAddress(connectedWalletAddress)) {
3443
+ return {
3444
+ valid: true,
3445
+ method: "contract",
3446
+ details: "Verified via EIP-1967 admin slot.",
3447
+ subjectDid,
3448
+ connectedWalletDid,
3449
+ controllingWalletDid: controllingWalletDid ?? buildEvmDidPkh(chainId, adminAddress)
3450
+ };
3451
+ }
3452
+ }
3453
+ } catch {
3454
+ }
3455
+ if (ethers.getAddress(subjectAddress) === ethers.getAddress(connectedWalletAddress) && controllingWalletDid) {
3456
+ return {
3457
+ valid: true,
3458
+ method: "minting-wallet",
3459
+ details: `Verified because connected wallet matches the contract DID address. Controlling wallet is ${controllingWalletDid}.`,
3460
+ subjectDid,
3461
+ connectedWalletDid,
3462
+ controllingWalletDid
3463
+ };
3464
+ }
3465
+ return {
3466
+ valid: false,
3467
+ reason: "Contract ownership verification failed",
3468
+ details: controllingWalletDid ? `Connected wallet ${connectedWalletDid} does not match the controlling wallet ${controllingWalletDid} or the subject contract address ${subjectDid}.` : `Could not match connected wallet ${connectedWalletDid} to contract ownership for ${subjectDid}.`,
3469
+ subjectDid,
3470
+ connectedWalletDid,
3471
+ controllingWalletDid: controllingWalletDid ?? void 0
3472
+ };
3473
+ }
3474
+ async function verifySubjectOwnership(params) {
3475
+ const subjectDid = normalizeSubjectDid(params.subjectDid);
3476
+ const method = extractDidMethod(subjectDid);
3477
+ if (method === "web") {
3478
+ return verifyDidWebOwnership(params);
3479
+ }
3480
+ if (method === "pkh") {
3481
+ const didPkhParams = params;
3482
+ if (!didPkhParams.provider) {
3483
+ throw new OmaTrustError(
3484
+ "INVALID_INPUT",
3485
+ "provider is required for did:pkh ownership verification",
3486
+ { subjectDid }
3487
+ );
3488
+ }
3489
+ return verifyDidPkhOwnership(didPkhParams);
3490
+ }
3491
+ throw new OmaTrustError("INVALID_INPUT", "Unsupported DID type for ownership verification", {
3492
+ subjectDid
3493
+ });
3494
+ }
1620
3495
 
1621
3496
  // src/app-registry/index.ts
1622
3497
  var app_registry_exports = {};