@oma3/omatrust 0.1.0-alpha.1 → 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -7
- package/dist/identity/index.cjs +1 -1
- package/dist/identity/index.cjs.map +1 -1
- package/dist/identity/index.js +1 -1
- package/dist/identity/index.js.map +1 -1
- package/dist/index-B9KW02US.d.cts +119 -0
- package/dist/index-DXrwBex9.d.ts +119 -0
- package/dist/index.cjs +656 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +657 -92
- package/dist/index.js.map +1 -1
- package/dist/reputation/index.browser.cjs +2133 -0
- package/dist/reputation/index.browser.cjs.map +1 -0
- package/dist/reputation/index.browser.d.cts +14 -0
- package/dist/reputation/index.browser.d.ts +14 -0
- package/dist/reputation/index.browser.js +2071 -0
- package/dist/reputation/index.browser.js.map +1 -0
- package/dist/reputation/index.cjs +675 -90
- package/dist/reputation/index.cjs.map +1 -1
- package/dist/reputation/index.d.cts +2 -1
- package/dist/reputation/index.d.ts +2 -1
- package/dist/reputation/index.js +669 -92
- package/dist/reputation/index.js.map +1 -1
- package/dist/subject-ownership-CXvzEjpH.d.cts +434 -0
- package/dist/subject-ownership-CXvzEjpH.d.ts +434 -0
- package/dist/widgets/index.cjs +195 -0
- package/dist/widgets/index.cjs.map +1 -0
- package/dist/widgets/index.d.cts +134 -0
- package/dist/widgets/index.d.ts +134 -0
- package/dist/widgets/index.js +185 -0
- package/dist/widgets/index.js.map +1 -0
- package/package.json +33 -6
- package/dist/index-ChbJxwOA.d.cts +0 -415
- package/dist/index-ChbJxwOA.d.ts +0 -415
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { keccak256, toUtf8Bytes, isAddress, getAddress, sha256, ZeroAddress, Signature,
|
|
1
|
+
import { keccak256, toUtf8Bytes, isAddress, getAddress, sha256, ZeroAddress, Signature, formatUnits, verifyTypedData, hexlify, randomBytes, id, Contract, Interface } from 'ethers';
|
|
2
2
|
import canonicalize from 'canonicalize';
|
|
3
3
|
import { SchemaEncoder, EAS } from '@ethereum-attestation-service/eas-sdk';
|
|
4
4
|
import { resolveTxt } from 'dns/promises';
|
|
@@ -152,7 +152,7 @@ function extractDidIdentifier(did) {
|
|
|
152
152
|
}
|
|
153
153
|
function normalizeDomain(domain) {
|
|
154
154
|
assertString(domain, "domain", "INVALID_DID");
|
|
155
|
-
return domain.trim().toLowerCase().replace(/\.$/, "");
|
|
155
|
+
return domain.trim().toLowerCase().replace(/\.$/, "").replace(/^www\./, "");
|
|
156
156
|
}
|
|
157
157
|
function normalizeDidWeb(input) {
|
|
158
158
|
assertString(input, "input", "INVALID_DID");
|
|
@@ -355,6 +355,7 @@ function hashCanonicalizedJson(obj, algorithm) {
|
|
|
355
355
|
var reputation_exports = {};
|
|
356
356
|
__export(reputation_exports, {
|
|
357
357
|
buildDelegatedAttestationTypedData: () => buildDelegatedAttestationTypedData,
|
|
358
|
+
buildDelegatedTypedDataFromEncoded: () => buildDelegatedTypedDataFromEncoded,
|
|
358
359
|
buildDnsTxtRecord: () => buildDnsTxtRecord,
|
|
359
360
|
buildEip712Domain: () => buildEip712Domain,
|
|
360
361
|
calculateAverageUserReviewRating: () => calculateAverageUserReviewRating,
|
|
@@ -378,6 +379,7 @@ __export(reputation_exports, {
|
|
|
378
379
|
formatSchemaUid: () => formatSchemaUid,
|
|
379
380
|
formatTransferAmount: () => formatTransferAmount,
|
|
380
381
|
getAttestation: () => getAttestation,
|
|
382
|
+
getAttestationsByAttester: () => getAttestationsByAttester,
|
|
381
383
|
getAttestationsForDid: () => getAttestationsForDid,
|
|
382
384
|
getChainConstants: () => getChainConstants,
|
|
383
385
|
getExplorerAddressUrl: () => getExplorerAddressUrl,
|
|
@@ -393,16 +395,22 @@ __export(reputation_exports, {
|
|
|
393
395
|
normalizeSchema: () => normalizeSchema,
|
|
394
396
|
parseDnsTxtRecord: () => parseDnsTxtRecord,
|
|
395
397
|
prepareDelegatedAttestation: () => prepareDelegatedAttestation,
|
|
398
|
+
revokeAttestation: () => revokeAttestation,
|
|
396
399
|
schemaToString: () => schemaToString,
|
|
397
400
|
splitSignature: () => splitSignature,
|
|
398
401
|
submitAttestation: () => submitAttestation,
|
|
399
402
|
submitDelegatedAttestation: () => submitDelegatedAttestation,
|
|
403
|
+
validateAttestationData: () => validateAttestationData,
|
|
400
404
|
verifyAttestation: () => verifyAttestation,
|
|
401
405
|
verifyDidDocumentControllerDid: () => verifyDidDocumentControllerDid,
|
|
406
|
+
verifyDidJsonControllerDid: () => verifyDidJsonControllerDid,
|
|
407
|
+
verifyDidPkhOwnership: () => verifyDidPkhOwnership,
|
|
408
|
+
verifyDidWebOwnership: () => verifyDidWebOwnership,
|
|
402
409
|
verifyDnsTxtControllerDid: () => verifyDnsTxtControllerDid,
|
|
403
410
|
verifyEip712Signature: () => verifyEip712Signature,
|
|
404
411
|
verifyProof: () => verifyProof,
|
|
405
|
-
verifySchemaExists: () => verifySchemaExists
|
|
412
|
+
verifySchemaExists: () => verifySchemaExists,
|
|
413
|
+
verifySubjectOwnership: () => verifySubjectOwnership
|
|
406
414
|
});
|
|
407
415
|
function normalizeSchema(schema) {
|
|
408
416
|
if (Array.isArray(schema)) {
|
|
@@ -438,6 +446,13 @@ function encodeAttestationData(schema, data) {
|
|
|
438
446
|
if (!data || typeof data !== "object") {
|
|
439
447
|
throw new OmaTrustError("INVALID_INPUT", "data must be an object");
|
|
440
448
|
}
|
|
449
|
+
const validationErrors = validateAttestationData(schema, data);
|
|
450
|
+
if (validationErrors.length > 0) {
|
|
451
|
+
const summary = validationErrors.map((error) => `Field "${error.schemaFieldName}" (${error.expectedType}) got ${error.providedType}`).join("; ");
|
|
452
|
+
throw new OmaTrustError("INVALID_INPUT", `Attestation data validation failed: ${summary}`, {
|
|
453
|
+
errors: validationErrors
|
|
454
|
+
});
|
|
455
|
+
}
|
|
441
456
|
const fields = normalizeSchema(schema);
|
|
442
457
|
const schemaString = schemaToString(fields);
|
|
443
458
|
const encoder = new SchemaEncoder(schemaString);
|
|
@@ -450,6 +465,99 @@ function encodeAttestationData(schema, data) {
|
|
|
450
465
|
);
|
|
451
466
|
return encoded;
|
|
452
467
|
}
|
|
468
|
+
function getActualType(value) {
|
|
469
|
+
if (value === null) {
|
|
470
|
+
return "null";
|
|
471
|
+
}
|
|
472
|
+
if (value === void 0) {
|
|
473
|
+
return "undefined";
|
|
474
|
+
}
|
|
475
|
+
if (Array.isArray(value)) {
|
|
476
|
+
return "array";
|
|
477
|
+
}
|
|
478
|
+
if (typeof value === "number") {
|
|
479
|
+
return Number.isFinite(value) ? "number" : "non-finite-number";
|
|
480
|
+
}
|
|
481
|
+
return typeof value;
|
|
482
|
+
}
|
|
483
|
+
function isNumericValue(value, allowNegative) {
|
|
484
|
+
if (typeof value === "bigint") {
|
|
485
|
+
return allowNegative || value >= 0n;
|
|
486
|
+
}
|
|
487
|
+
if (typeof value === "number") {
|
|
488
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
return allowNegative || value >= 0;
|
|
492
|
+
}
|
|
493
|
+
if (typeof value === "string") {
|
|
494
|
+
if (value.length === 0) {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
const pattern = allowNegative ? /^-?\d+$/ : /^\d+$/;
|
|
498
|
+
return pattern.test(value);
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
function isHex(value) {
|
|
503
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]*$/.test(value);
|
|
504
|
+
}
|
|
505
|
+
function validateFieldValue(type, value) {
|
|
506
|
+
const normalizedType = type.trim().toLowerCase();
|
|
507
|
+
const bytesNMatch = normalizedType.match(/^bytes([1-9]|[12]\d|3[0-2])$/);
|
|
508
|
+
if (/^uint\d*$/.test(normalizedType)) {
|
|
509
|
+
return isNumericValue(value, false);
|
|
510
|
+
}
|
|
511
|
+
if (/^int\d*$/.test(normalizedType)) {
|
|
512
|
+
return isNumericValue(value, true);
|
|
513
|
+
}
|
|
514
|
+
if (normalizedType === "string") {
|
|
515
|
+
return typeof value === "string";
|
|
516
|
+
}
|
|
517
|
+
if (normalizedType === "string[]") {
|
|
518
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
519
|
+
}
|
|
520
|
+
if (normalizedType === "bool") {
|
|
521
|
+
return typeof value === "boolean";
|
|
522
|
+
}
|
|
523
|
+
if (normalizedType === "address") {
|
|
524
|
+
return typeof value === "string" && isAddress(value);
|
|
525
|
+
}
|
|
526
|
+
if (normalizedType === "bytes") {
|
|
527
|
+
return isHex(value) && (value.length - 2) % 2 === 0;
|
|
528
|
+
}
|
|
529
|
+
if (bytesNMatch) {
|
|
530
|
+
const expectedBytes = Number(bytesNMatch[1]);
|
|
531
|
+
return isHex(value) && value.length === 2 + expectedBytes * 2;
|
|
532
|
+
}
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
function validateAttestationData(schema, data) {
|
|
536
|
+
if (!data || typeof data !== "object") {
|
|
537
|
+
return [{
|
|
538
|
+
schemaFieldName: "data",
|
|
539
|
+
expectedType: "object",
|
|
540
|
+
providedType: getActualType(data),
|
|
541
|
+
providedValue: data
|
|
542
|
+
}];
|
|
543
|
+
}
|
|
544
|
+
const fields = normalizeSchema(schema);
|
|
545
|
+
const errors = [];
|
|
546
|
+
for (const field of fields) {
|
|
547
|
+
const value = data[field.name];
|
|
548
|
+
const isValid = validateFieldValue(field.type, value);
|
|
549
|
+
if (isValid) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
errors.push({
|
|
553
|
+
schemaFieldName: field.name,
|
|
554
|
+
expectedType: field.type,
|
|
555
|
+
providedType: getActualType(value),
|
|
556
|
+
providedValue: value
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return errors;
|
|
560
|
+
}
|
|
453
561
|
function decodeAttestationData(schema, encodedData) {
|
|
454
562
|
if (typeof encodedData !== "string" || !encodedData.startsWith("0x")) {
|
|
455
563
|
throw new OmaTrustError("INVALID_INPUT", "encodedData must be a hex string");
|
|
@@ -470,22 +578,11 @@ function decodeAttestationData(schema, encodedData) {
|
|
|
470
578
|
return result;
|
|
471
579
|
}
|
|
472
580
|
function extractExpirationTime(data) {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
if (typeof value === "bigint") {
|
|
480
|
-
return value;
|
|
481
|
-
}
|
|
482
|
-
if (typeof value === "number") {
|
|
483
|
-
return value;
|
|
484
|
-
}
|
|
485
|
-
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
486
|
-
return BigInt(value);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
581
|
+
const value = data["expiresAt"];
|
|
582
|
+
if (value === void 0 || value === null) return void 0;
|
|
583
|
+
if (typeof value === "bigint") return value;
|
|
584
|
+
if (typeof value === "number") return value;
|
|
585
|
+
if (typeof value === "string" && /^\d+$/.test(value)) return BigInt(value);
|
|
489
586
|
return void 0;
|
|
490
587
|
}
|
|
491
588
|
var ZERO_UID = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
@@ -526,6 +623,53 @@ function resolveRecipientAddress(data) {
|
|
|
526
623
|
return ZeroAddress;
|
|
527
624
|
}
|
|
528
625
|
|
|
626
|
+
// src/reputation/eas-adapter.ts
|
|
627
|
+
function isRecord(value) {
|
|
628
|
+
return Boolean(value) && typeof value === "object";
|
|
629
|
+
}
|
|
630
|
+
function isHex32(value) {
|
|
631
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
632
|
+
}
|
|
633
|
+
function getEasTransactionReceipt(tx) {
|
|
634
|
+
if (!isRecord(tx)) {
|
|
635
|
+
return void 0;
|
|
636
|
+
}
|
|
637
|
+
const candidates = [tx.receipt, tx.tx, tx.transaction];
|
|
638
|
+
for (const candidate of candidates) {
|
|
639
|
+
if (isRecord(candidate)) {
|
|
640
|
+
return candidate;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return void 0;
|
|
644
|
+
}
|
|
645
|
+
function getEasTransactionHash(tx) {
|
|
646
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
647
|
+
if (receipt) {
|
|
648
|
+
const receiptHash = receipt.hash;
|
|
649
|
+
if (isHex32(receiptHash)) {
|
|
650
|
+
return receiptHash;
|
|
651
|
+
}
|
|
652
|
+
const transactionHash = receipt.transactionHash;
|
|
653
|
+
if (isHex32(transactionHash)) {
|
|
654
|
+
return transactionHash;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (isRecord(tx) && isHex32(tx.hash)) {
|
|
658
|
+
return tx.hash;
|
|
659
|
+
}
|
|
660
|
+
return void 0;
|
|
661
|
+
}
|
|
662
|
+
function isEasSchemaNotFoundError(err) {
|
|
663
|
+
if (!isRecord(err)) {
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
const message = err.message;
|
|
667
|
+
if (typeof message !== "string" || message.length === 0) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
return /schema not found/i.test(message);
|
|
671
|
+
}
|
|
672
|
+
|
|
529
673
|
// src/reputation/submit.ts
|
|
530
674
|
async function submitAttestation(params) {
|
|
531
675
|
if (!params || typeof params !== "object") {
|
|
@@ -556,25 +700,46 @@ async function submitAttestation(params) {
|
|
|
556
700
|
}
|
|
557
701
|
});
|
|
558
702
|
const uid = await tx.wait();
|
|
559
|
-
const
|
|
560
|
-
const
|
|
703
|
+
const txHash = getEasTransactionHash(tx) ?? ZERO_UID;
|
|
704
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
561
705
|
return {
|
|
562
706
|
uid,
|
|
563
707
|
txHash,
|
|
564
|
-
receipt
|
|
708
|
+
receipt
|
|
565
709
|
};
|
|
566
710
|
} catch (err) {
|
|
567
711
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to submit attestation", { err });
|
|
568
712
|
}
|
|
569
713
|
}
|
|
570
|
-
function
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
);
|
|
714
|
+
async function revokeAttestation(params) {
|
|
715
|
+
if (!params || typeof params !== "object") {
|
|
716
|
+
throw new OmaTrustError("INVALID_INPUT", "params must be provided");
|
|
717
|
+
}
|
|
718
|
+
if (!params.signer) {
|
|
719
|
+
throw new OmaTrustError("INVALID_INPUT", "signer is required");
|
|
720
|
+
}
|
|
721
|
+
const eas = new EAS(params.easContractAddress);
|
|
722
|
+
eas.connect(params.signer);
|
|
723
|
+
try {
|
|
724
|
+
const tx = await eas.revoke({
|
|
725
|
+
schema: params.schemaUid,
|
|
726
|
+
data: {
|
|
727
|
+
uid: params.uid,
|
|
728
|
+
value: toBigIntOrDefault(params.value, 0n)
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
await tx.wait();
|
|
732
|
+
const txHash = getEasTransactionHash(tx) ?? ZERO_UID;
|
|
733
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
734
|
+
return {
|
|
735
|
+
txHash,
|
|
736
|
+
receipt
|
|
737
|
+
};
|
|
738
|
+
} catch (err) {
|
|
739
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to revoke attestation", { err });
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function buildDelegatedTypedData(params) {
|
|
578
743
|
return {
|
|
579
744
|
domain: {
|
|
580
745
|
name: "EAS",
|
|
@@ -599,17 +764,52 @@ function buildDelegatedAttestationTypedData(params) {
|
|
|
599
764
|
message: {
|
|
600
765
|
attester: params.attester,
|
|
601
766
|
schema: params.schemaUid,
|
|
602
|
-
recipient,
|
|
603
|
-
expirationTime:
|
|
767
|
+
recipient: params.recipient,
|
|
768
|
+
expirationTime: toBigIntOrDefault(params.expirationTime, 0n),
|
|
604
769
|
revocable: params.revocable ?? true,
|
|
605
770
|
refUID: params.refUid ?? ZERO_UID,
|
|
606
|
-
data: encodedData,
|
|
771
|
+
data: params.encodedData,
|
|
607
772
|
value: toBigIntOrDefault(params.value, 0n),
|
|
608
773
|
nonce: toBigIntOrDefault(params.nonce, 0n),
|
|
609
774
|
deadline: toBigIntOrDefault(params.deadline, BigInt(Math.floor(Date.now() / 1e3) + 600))
|
|
610
775
|
}
|
|
611
776
|
};
|
|
612
777
|
}
|
|
778
|
+
function buildDelegatedAttestationTypedData(params) {
|
|
779
|
+
const dataWithHash = withAutoSubjectDidHash(params.schema, params.data);
|
|
780
|
+
const encodedData = encodeAttestationData(params.schema, dataWithHash);
|
|
781
|
+
const recipient = resolveRecipientAddress(dataWithHash);
|
|
782
|
+
return buildDelegatedTypedData({
|
|
783
|
+
chainId: params.chainId,
|
|
784
|
+
easContractAddress: params.easContractAddress,
|
|
785
|
+
schemaUid: params.schemaUid,
|
|
786
|
+
encodedData,
|
|
787
|
+
recipient,
|
|
788
|
+
attester: params.attester,
|
|
789
|
+
nonce: params.nonce,
|
|
790
|
+
revocable: params.revocable,
|
|
791
|
+
expirationTime: params.expirationTime ?? extractExpirationTime(dataWithHash),
|
|
792
|
+
refUid: params.refUid,
|
|
793
|
+
value: params.value,
|
|
794
|
+
deadline: params.deadline
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
function buildDelegatedTypedDataFromEncoded(params) {
|
|
798
|
+
return buildDelegatedTypedData({
|
|
799
|
+
chainId: params.chainId,
|
|
800
|
+
easContractAddress: params.easContractAddress,
|
|
801
|
+
schemaUid: params.schemaUid,
|
|
802
|
+
encodedData: params.encodedData,
|
|
803
|
+
recipient: params.recipient,
|
|
804
|
+
attester: params.attester,
|
|
805
|
+
nonce: params.nonce,
|
|
806
|
+
revocable: params.revocable,
|
|
807
|
+
expirationTime: params.expirationTime,
|
|
808
|
+
refUid: params.refUid,
|
|
809
|
+
value: params.value,
|
|
810
|
+
deadline: params.deadline
|
|
811
|
+
});
|
|
812
|
+
}
|
|
613
813
|
function splitSignature(signature) {
|
|
614
814
|
try {
|
|
615
815
|
const parsed = Signature.from(signature);
|
|
@@ -686,7 +886,14 @@ async function submitDelegatedAttestation(params) {
|
|
|
686
886
|
var EAS_EVENT_ABI = [
|
|
687
887
|
"event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
|
|
688
888
|
];
|
|
689
|
-
function
|
|
889
|
+
function parseEventTxHash(event) {
|
|
890
|
+
const txHash = event.transactionHash;
|
|
891
|
+
if (typeof txHash !== "string") {
|
|
892
|
+
return void 0;
|
|
893
|
+
}
|
|
894
|
+
return /^0x[0-9a-fA-F]{64}$/.test(txHash) ? txHash : void 0;
|
|
895
|
+
}
|
|
896
|
+
function parseAttestation(attestation, schema, txHash) {
|
|
690
897
|
const rawData = attestation.data ?? "0x";
|
|
691
898
|
const decoded = schema ? decodeAttestationData(schema, rawData) : {};
|
|
692
899
|
const toBigIntSafe = (value) => {
|
|
@@ -706,6 +913,7 @@ function parseAttestation(attestation, schema) {
|
|
|
706
913
|
schema: attestation.schema,
|
|
707
914
|
attester: attestation.attester,
|
|
708
915
|
recipient: attestation.recipient,
|
|
916
|
+
txHash,
|
|
709
917
|
revocable: Boolean(attestation.revocable),
|
|
710
918
|
revocationTime: toBigIntSafe(attestation.revocationTime),
|
|
711
919
|
expirationTime: toBigIntSafe(attestation.expirationTime),
|
|
@@ -731,21 +939,18 @@ async function getAttestation(params) {
|
|
|
731
939
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to read attestation", { err });
|
|
732
940
|
}
|
|
733
941
|
}
|
|
734
|
-
async function
|
|
735
|
-
const
|
|
736
|
-
const
|
|
737
|
-
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
738
|
-
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
739
|
-
const filter = contract.filters.Attested(didToAddress(params.did));
|
|
942
|
+
async function queryAttestationEvents(easContractAddress, provider, fromBlock, toBlock, options) {
|
|
943
|
+
const contract = new Contract(easContractAddress, EAS_EVENT_ABI, provider);
|
|
944
|
+
const filter = contract.filters.Attested(options?.recipient ?? null, options?.attester ?? null);
|
|
740
945
|
let events;
|
|
741
946
|
try {
|
|
742
947
|
events = await contract.queryFilter(filter, fromBlock, toBlock);
|
|
743
948
|
} catch (err) {
|
|
744
949
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to query attestation events", { err });
|
|
745
950
|
}
|
|
746
|
-
const eas = new EAS(
|
|
951
|
+
const eas = new EAS(easContractAddress);
|
|
747
952
|
eas.connect(provider);
|
|
748
|
-
const schemaFilter =
|
|
953
|
+
const schemaFilter = options?.schemas?.map((s) => s.toLowerCase());
|
|
749
954
|
const results = [];
|
|
750
955
|
for (const event of events) {
|
|
751
956
|
if (!("args" in event && Array.isArray(event.args))) {
|
|
@@ -764,11 +969,39 @@ async function getAttestationsForDid(params) {
|
|
|
764
969
|
if (!attestation || !attestation.uid) {
|
|
765
970
|
continue;
|
|
766
971
|
}
|
|
767
|
-
results.push(parseAttestation(attestation));
|
|
972
|
+
results.push(parseAttestation(attestation, void 0, parseEventTxHash(event)));
|
|
768
973
|
}
|
|
769
974
|
results.sort((a, b) => Number(b.time - a.time));
|
|
770
975
|
return results;
|
|
771
976
|
}
|
|
977
|
+
async function getAttestationsForDid(params) {
|
|
978
|
+
const provider = params.provider;
|
|
979
|
+
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
980
|
+
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
981
|
+
return queryAttestationEvents(
|
|
982
|
+
params.easContractAddress,
|
|
983
|
+
provider,
|
|
984
|
+
fromBlock,
|
|
985
|
+
toBlock,
|
|
986
|
+
{ recipient: didToAddress(params.did), schemas: params.schemas }
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
async function getAttestationsByAttester(params) {
|
|
990
|
+
const provider = params.provider;
|
|
991
|
+
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
992
|
+
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
993
|
+
if (params.limit !== void 0 && params.limit <= 0) {
|
|
994
|
+
return [];
|
|
995
|
+
}
|
|
996
|
+
const results = await queryAttestationEvents(
|
|
997
|
+
params.easContractAddress,
|
|
998
|
+
provider,
|
|
999
|
+
fromBlock,
|
|
1000
|
+
toBlock,
|
|
1001
|
+
{ attester: params.attester, schemas: params.schemas }
|
|
1002
|
+
);
|
|
1003
|
+
return params.limit !== void 0 ? results.slice(0, params.limit) : results;
|
|
1004
|
+
}
|
|
772
1005
|
async function listAttestations(params) {
|
|
773
1006
|
const limit = params.limit ?? 20;
|
|
774
1007
|
if (limit <= 0) {
|
|
@@ -779,34 +1012,15 @@ async function listAttestations(params) {
|
|
|
779
1012
|
}
|
|
780
1013
|
async function getLatestAttestations(params) {
|
|
781
1014
|
const provider = params.provider;
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if (!("args" in event && Array.isArray(event.args))) {
|
|
792
|
-
continue;
|
|
793
|
-
}
|
|
794
|
-
const args = event.args;
|
|
795
|
-
const uid = args?.[2];
|
|
796
|
-
const schemaUid = args?.[3];
|
|
797
|
-
if (!uid || !schemaUid) {
|
|
798
|
-
continue;
|
|
799
|
-
}
|
|
800
|
-
if (schemaFilter && !schemaFilter.includes(schemaUid.toLowerCase())) {
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
const attestation = await eas.getAttestation(uid);
|
|
804
|
-
if (!attestation || !attestation.uid) {
|
|
805
|
-
continue;
|
|
806
|
-
}
|
|
807
|
-
results.push(parseAttestation(attestation));
|
|
808
|
-
}
|
|
809
|
-
results.sort((a, b) => Number(b.time - a.time));
|
|
1015
|
+
const toBlock = await provider.getBlockNumber();
|
|
1016
|
+
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
1017
|
+
const results = await queryAttestationEvents(
|
|
1018
|
+
params.easContractAddress,
|
|
1019
|
+
provider,
|
|
1020
|
+
fromBlock,
|
|
1021
|
+
toBlock,
|
|
1022
|
+
{ schemas: params.schemas }
|
|
1023
|
+
);
|
|
810
1024
|
return results.slice(0, params.limit ?? 20);
|
|
811
1025
|
}
|
|
812
1026
|
function deduplicateReviews(attestations) {
|
|
@@ -1068,6 +1282,13 @@ function verifyDidDocumentControllerDid(didDocument, expectedControllerDid) {
|
|
|
1068
1282
|
reason: `No matching address found in DID document (expected ${expectedAddress})`
|
|
1069
1283
|
};
|
|
1070
1284
|
}
|
|
1285
|
+
async function verifyDidJsonControllerDid(domain, expectedControllerDid, options = {}) {
|
|
1286
|
+
if (!domain || typeof domain !== "string") {
|
|
1287
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1288
|
+
}
|
|
1289
|
+
const didDocument = options.fetchDidDocument ? await options.fetchDidDocument(domain) : await fetchDidDocument(domain);
|
|
1290
|
+
return verifyDidDocumentControllerDid(didDocument, expectedControllerDid);
|
|
1291
|
+
}
|
|
1071
1292
|
function buildEip712Domain(name, version, chainId, verifyingContract) {
|
|
1072
1293
|
return { name, version, chainId, verifyingContract };
|
|
1073
1294
|
}
|
|
@@ -1100,6 +1321,8 @@ function verifyEip712Signature(typedData, signature) {
|
|
|
1100
1321
|
throw new OmaTrustError("INVALID_INPUT", "Failed to verify EIP-712 signature", { err });
|
|
1101
1322
|
}
|
|
1102
1323
|
}
|
|
1324
|
+
|
|
1325
|
+
// src/reputation/proof/dns-txt-record.ts
|
|
1103
1326
|
function parseDnsTxtRecord(record) {
|
|
1104
1327
|
if (!record || typeof record !== "string") {
|
|
1105
1328
|
throw new OmaTrustError("INVALID_INPUT", "record must be a non-empty string", { record });
|
|
@@ -1127,27 +1350,6 @@ function buildDnsTxtRecord(controllerDid) {
|
|
|
1127
1350
|
const normalized = normalizeDid(controllerDid);
|
|
1128
1351
|
return `v=1;controller=${normalized}`;
|
|
1129
1352
|
}
|
|
1130
|
-
async function verifyDnsTxtControllerDid(domain, expectedControllerDid) {
|
|
1131
|
-
if (!domain || typeof domain !== "string") {
|
|
1132
|
-
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1133
|
-
}
|
|
1134
|
-
const expected = normalizeDid(expectedControllerDid);
|
|
1135
|
-
const host = `_omatrust.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1136
|
-
let records;
|
|
1137
|
-
try {
|
|
1138
|
-
records = await resolveTxt(host);
|
|
1139
|
-
} catch (err) {
|
|
1140
|
-
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1141
|
-
}
|
|
1142
|
-
for (const recordParts of records) {
|
|
1143
|
-
const record = recordParts.join("");
|
|
1144
|
-
const parsed = parseDnsTxtRecord(record);
|
|
1145
|
-
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1146
|
-
return { valid: true, record };
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
1150
|
-
}
|
|
1151
1353
|
|
|
1152
1354
|
// src/reputation/verify.ts
|
|
1153
1355
|
function parseChainId(input) {
|
|
@@ -1428,6 +1630,9 @@ async function getSchemaDetails(schemaRegistry, schemaUid) {
|
|
|
1428
1630
|
revocable: Boolean(details.revocable)
|
|
1429
1631
|
};
|
|
1430
1632
|
} catch (err) {
|
|
1633
|
+
if (isEasSchemaNotFoundError(err)) {
|
|
1634
|
+
throw new OmaTrustError("SCHEMA_NOT_FOUND", "Schema was not found", { schemaUid });
|
|
1635
|
+
}
|
|
1431
1636
|
if (err instanceof OmaTrustError) {
|
|
1432
1637
|
throw err;
|
|
1433
1638
|
}
|
|
@@ -1611,6 +1816,366 @@ function createEvidencePointerProof(url) {
|
|
|
1611
1816
|
issuedAt: Math.floor(Date.now() / 1e3)
|
|
1612
1817
|
};
|
|
1613
1818
|
}
|
|
1819
|
+
async function verifyDnsTxtControllerDid(domain, expectedControllerDid, options = {}) {
|
|
1820
|
+
if (!domain || typeof domain !== "string") {
|
|
1821
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1822
|
+
}
|
|
1823
|
+
const expected = normalizeDid(expectedControllerDid);
|
|
1824
|
+
const prefix = options.recordPrefix ?? "_controllers";
|
|
1825
|
+
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1826
|
+
let records;
|
|
1827
|
+
try {
|
|
1828
|
+
records = await (options.resolveTxt ?? resolveTxt)(host);
|
|
1829
|
+
} catch (err) {
|
|
1830
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1831
|
+
}
|
|
1832
|
+
for (const recordParts of records) {
|
|
1833
|
+
const record = recordParts.join("");
|
|
1834
|
+
const parsed = parseDnsTxtRecord(record);
|
|
1835
|
+
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1836
|
+
return { valid: true, record };
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// src/reputation/proof/dns-txt-shared.ts
|
|
1843
|
+
async function verifyDnsTxtControllerDid2(domain, expectedControllerDid, options = {}) {
|
|
1844
|
+
if (!domain || typeof domain !== "string") {
|
|
1845
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1846
|
+
}
|
|
1847
|
+
if (!options.resolveTxt) {
|
|
1848
|
+
throw new OmaTrustError("NETWORK_ERROR", "No DNS TXT resolver was provided", { domain });
|
|
1849
|
+
}
|
|
1850
|
+
const expected = normalizeDid(expectedControllerDid);
|
|
1851
|
+
const prefix = options.recordPrefix ?? "_controllers";
|
|
1852
|
+
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1853
|
+
let records;
|
|
1854
|
+
try {
|
|
1855
|
+
records = await options.resolveTxt(host);
|
|
1856
|
+
} catch (err) {
|
|
1857
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1858
|
+
}
|
|
1859
|
+
for (const recordParts of records) {
|
|
1860
|
+
const record = recordParts.join("");
|
|
1861
|
+
const parsed = parseDnsTxtRecord(record);
|
|
1862
|
+
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1863
|
+
return { valid: true, record };
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
// src/reputation/proof/subject-ownership.ts
|
|
1870
|
+
var EIP1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
|
|
1871
|
+
var OWNERSHIP_PATTERNS = [
|
|
1872
|
+
{ method: "owner", signature: "function owner() view returns (address)" },
|
|
1873
|
+
{ method: "admin", signature: "function admin() view returns (address)" },
|
|
1874
|
+
{ method: "getOwner", signature: "function getOwner() view returns (address)" }
|
|
1875
|
+
];
|
|
1876
|
+
function assertConnectedWalletDid(input) {
|
|
1877
|
+
const normalized = normalizeDid(input);
|
|
1878
|
+
if (extractDidMethod(normalized) !== "pkh") {
|
|
1879
|
+
throw new OmaTrustError("INVALID_INPUT", "connectedWalletDid must be a did:pkh DID", {
|
|
1880
|
+
connectedWalletDid: input
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
return normalized;
|
|
1884
|
+
}
|
|
1885
|
+
function normalizeSubjectDid(input) {
|
|
1886
|
+
return normalizeDid(input);
|
|
1887
|
+
}
|
|
1888
|
+
async function readAddressFromContract(provider, contractAddress, signature, method) {
|
|
1889
|
+
try {
|
|
1890
|
+
const iface = new Interface([signature]);
|
|
1891
|
+
const data = iface.encodeFunctionData(method, []);
|
|
1892
|
+
const result = await provider.call({ to: contractAddress, data });
|
|
1893
|
+
const [value] = iface.decodeFunctionResult(method, result);
|
|
1894
|
+
if (typeof value === "string" && isAddress(value) && value !== ZeroAddress) {
|
|
1895
|
+
return getAddress(value);
|
|
1896
|
+
}
|
|
1897
|
+
} catch {
|
|
1898
|
+
}
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
async function discoverControllingWallet(provider, contractAddress, chainId) {
|
|
1902
|
+
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
1903
|
+
const address = await readAddressFromContract(
|
|
1904
|
+
provider,
|
|
1905
|
+
contractAddress,
|
|
1906
|
+
pattern.signature,
|
|
1907
|
+
pattern.method
|
|
1908
|
+
);
|
|
1909
|
+
if (address) {
|
|
1910
|
+
return buildEvmDidPkh(chainId, address);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
try {
|
|
1914
|
+
const adminValue = await provider.getStorage(contractAddress, EIP1967_ADMIN_SLOT);
|
|
1915
|
+
if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
1916
|
+
const adminAddress = getAddress(`0x${adminValue.slice(-40)}`);
|
|
1917
|
+
if (adminAddress !== ZeroAddress) {
|
|
1918
|
+
return buildEvmDidPkh(chainId, adminAddress);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
} catch {
|
|
1922
|
+
}
|
|
1923
|
+
return null;
|
|
1924
|
+
}
|
|
1925
|
+
async function verifyDidWebOwnership(params) {
|
|
1926
|
+
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
1927
|
+
const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
|
|
1928
|
+
const domain = getDomainFromDidWeb(subjectDid);
|
|
1929
|
+
if (!domain) {
|
|
1930
|
+
throw new OmaTrustError("INVALID_INPUT", "subjectDid must be a did:web DID", {
|
|
1931
|
+
subjectDid: params.subjectDid
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
let dnsReason;
|
|
1935
|
+
try {
|
|
1936
|
+
const dnsResult = await verifyDnsTxtControllerDid2(domain, connectedWalletDid, {
|
|
1937
|
+
resolveTxt: params.resolveTxt,
|
|
1938
|
+
recordPrefix: params.recordPrefix
|
|
1939
|
+
});
|
|
1940
|
+
if (dnsResult.valid) {
|
|
1941
|
+
return {
|
|
1942
|
+
valid: true,
|
|
1943
|
+
method: "dns",
|
|
1944
|
+
details: `Verified via DNS TXT record at ${params.recordPrefix ?? "_controllers"}.${domain}`,
|
|
1945
|
+
subjectDid,
|
|
1946
|
+
connectedWalletDid
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
dnsReason = dnsResult.reason;
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
dnsReason = error instanceof Error ? error.message : "DNS TXT verification failed";
|
|
1952
|
+
}
|
|
1953
|
+
let didDocReason;
|
|
1954
|
+
try {
|
|
1955
|
+
const didDocumentResult = await verifyDidJsonControllerDid(domain, connectedWalletDid, {
|
|
1956
|
+
fetchDidDocument: params.fetchDidDocument
|
|
1957
|
+
});
|
|
1958
|
+
if (didDocumentResult.valid) {
|
|
1959
|
+
return {
|
|
1960
|
+
valid: true,
|
|
1961
|
+
method: "did-document",
|
|
1962
|
+
details: `Verified via DID document at https://${domain}/.well-known/did.json`,
|
|
1963
|
+
subjectDid,
|
|
1964
|
+
connectedWalletDid
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
didDocReason = didDocumentResult.reason;
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
didDocReason = error instanceof Error ? error.message : "DID document verification failed";
|
|
1970
|
+
}
|
|
1971
|
+
return {
|
|
1972
|
+
valid: false,
|
|
1973
|
+
reason: "DID ownership verification failed",
|
|
1974
|
+
details: `DNS check: ${dnsReason ?? "failed"}. DID document check: ${didDocReason ?? "failed"}.`,
|
|
1975
|
+
subjectDid,
|
|
1976
|
+
connectedWalletDid
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
async function verifyDidPkhOwnership(params) {
|
|
1980
|
+
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
1981
|
+
const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
|
|
1982
|
+
if (!isEvmDidPkh(subjectDid)) {
|
|
1983
|
+
throw new OmaTrustError("INVALID_INPUT", "subjectDid must be an EVM did:pkh DID", {
|
|
1984
|
+
subjectDid: params.subjectDid
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
const subjectAddress = getAddressFromDidPkh(subjectDid);
|
|
1988
|
+
const connectedWalletAddress = getAddressFromDidPkh(connectedWalletDid);
|
|
1989
|
+
const chainIdRaw = getChainIdFromDidPkh(subjectDid);
|
|
1990
|
+
if (!subjectAddress || !connectedWalletAddress || !chainIdRaw) {
|
|
1991
|
+
throw new OmaTrustError("INVALID_INPUT", "Could not parse did:pkh ownership inputs", {
|
|
1992
|
+
subjectDid: params.subjectDid,
|
|
1993
|
+
connectedWalletDid: params.connectedWalletDid
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
const chainId = Number(chainIdRaw);
|
|
1997
|
+
if (!Number.isFinite(chainId)) {
|
|
1998
|
+
throw new OmaTrustError("INVALID_INPUT", "Invalid chain id in subjectDid", {
|
|
1999
|
+
subjectDid: params.subjectDid
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
const code = await params.provider.getCode(subjectAddress);
|
|
2003
|
+
const isContract = code !== "0x" && code !== "0x0";
|
|
2004
|
+
if (!isContract) {
|
|
2005
|
+
if (getAddress(subjectAddress) === getAddress(connectedWalletAddress)) {
|
|
2006
|
+
return {
|
|
2007
|
+
valid: true,
|
|
2008
|
+
method: "wallet",
|
|
2009
|
+
details: "Verified direct wallet ownership from matching did:pkh subject and connected wallet",
|
|
2010
|
+
subjectDid,
|
|
2011
|
+
connectedWalletDid
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
return {
|
|
2015
|
+
valid: false,
|
|
2016
|
+
reason: "EOA did:pkh subject does not match connected wallet",
|
|
2017
|
+
details: "For direct wallet did:pkh subjects, connectedWalletDid must match the subject DID.",
|
|
2018
|
+
subjectDid,
|
|
2019
|
+
connectedWalletDid
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
const controllingWalletDid = await discoverControllingWallet(params.provider, subjectAddress, chainId);
|
|
2023
|
+
if (params.txHash) {
|
|
2024
|
+
if (!controllingWalletDid) {
|
|
2025
|
+
return {
|
|
2026
|
+
valid: false,
|
|
2027
|
+
reason: "Could not discover controlling wallet",
|
|
2028
|
+
details: "Contract does not expose owner/admin/getOwner or a readable EIP-1967 admin slot for transfer verification.",
|
|
2029
|
+
subjectDid,
|
|
2030
|
+
connectedWalletDid
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
const tx = await params.provider.getTransaction(params.txHash);
|
|
2034
|
+
if (!tx) {
|
|
2035
|
+
return {
|
|
2036
|
+
valid: false,
|
|
2037
|
+
reason: "Transaction not found",
|
|
2038
|
+
details: `Transaction ${params.txHash} was not found on chain ${chainId}.`,
|
|
2039
|
+
subjectDid,
|
|
2040
|
+
connectedWalletDid,
|
|
2041
|
+
controllingWalletDid
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
const receipt = await params.provider.getTransactionReceipt(params.txHash);
|
|
2045
|
+
if (!receipt) {
|
|
2046
|
+
return {
|
|
2047
|
+
valid: false,
|
|
2048
|
+
reason: "Transaction not confirmed",
|
|
2049
|
+
details: "Transaction exists but is not yet confirmed.",
|
|
2050
|
+
subjectDid,
|
|
2051
|
+
connectedWalletDid,
|
|
2052
|
+
controllingWalletDid
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
const controllingWalletAddress = getAddressFromDidPkh(controllingWalletDid);
|
|
2056
|
+
if (!tx.from || !controllingWalletAddress || getAddress(tx.from) !== getAddress(controllingWalletAddress)) {
|
|
2057
|
+
return {
|
|
2058
|
+
valid: false,
|
|
2059
|
+
reason: "Wrong sender",
|
|
2060
|
+
details: `Transfer must originate from controlling wallet ${controllingWalletDid}.`,
|
|
2061
|
+
subjectDid,
|
|
2062
|
+
connectedWalletDid,
|
|
2063
|
+
controllingWalletDid
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
if (!tx.to || getAddress(tx.to) !== getAddress(connectedWalletAddress)) {
|
|
2067
|
+
return {
|
|
2068
|
+
valid: false,
|
|
2069
|
+
reason: "Wrong recipient",
|
|
2070
|
+
details: `Transfer must be sent to connected wallet ${connectedWalletDid}.`,
|
|
2071
|
+
subjectDid,
|
|
2072
|
+
connectedWalletDid,
|
|
2073
|
+
controllingWalletDid
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
const expectedAmount = calculateTransferAmount(
|
|
2077
|
+
subjectDid,
|
|
2078
|
+
connectedWalletDid,
|
|
2079
|
+
chainId,
|
|
2080
|
+
"shared-control"
|
|
2081
|
+
);
|
|
2082
|
+
const actualValue = BigInt(tx.value ?? 0n);
|
|
2083
|
+
if (actualValue !== expectedAmount) {
|
|
2084
|
+
return {
|
|
2085
|
+
valid: false,
|
|
2086
|
+
reason: "Wrong amount",
|
|
2087
|
+
details: `Transfer amount ${actualValue.toString()} does not match expected proof amount ${expectedAmount.toString()}.`,
|
|
2088
|
+
subjectDid,
|
|
2089
|
+
connectedWalletDid,
|
|
2090
|
+
controllingWalletDid
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
await params.provider.getBlockNumber();
|
|
2094
|
+
await params.provider.getBlock(receipt.blockNumber);
|
|
2095
|
+
return {
|
|
2096
|
+
valid: true,
|
|
2097
|
+
method: "transfer",
|
|
2098
|
+
details: `Verified via transfer proof ${params.txHash}.`,
|
|
2099
|
+
subjectDid,
|
|
2100
|
+
connectedWalletDid,
|
|
2101
|
+
controllingWalletDid
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
2105
|
+
const ownerAddress = await readAddressFromContract(
|
|
2106
|
+
params.provider,
|
|
2107
|
+
subjectAddress,
|
|
2108
|
+
pattern.signature,
|
|
2109
|
+
pattern.method
|
|
2110
|
+
);
|
|
2111
|
+
if (ownerAddress && getAddress(ownerAddress) === getAddress(connectedWalletAddress)) {
|
|
2112
|
+
return {
|
|
2113
|
+
valid: true,
|
|
2114
|
+
method: "contract",
|
|
2115
|
+
details: `Verified via ${pattern.method}() ownership check.`,
|
|
2116
|
+
subjectDid,
|
|
2117
|
+
connectedWalletDid,
|
|
2118
|
+
controllingWalletDid: controllingWalletDid ?? void 0
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
try {
|
|
2123
|
+
const adminValue = await params.provider.getStorage(subjectAddress, EIP1967_ADMIN_SLOT);
|
|
2124
|
+
if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
2125
|
+
const adminAddress = getAddress(`0x${adminValue.slice(-40)}`);
|
|
2126
|
+
if (adminAddress === getAddress(connectedWalletAddress)) {
|
|
2127
|
+
return {
|
|
2128
|
+
valid: true,
|
|
2129
|
+
method: "contract",
|
|
2130
|
+
details: "Verified via EIP-1967 admin slot.",
|
|
2131
|
+
subjectDid,
|
|
2132
|
+
connectedWalletDid,
|
|
2133
|
+
controllingWalletDid: controllingWalletDid ?? buildEvmDidPkh(chainId, adminAddress)
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
} catch {
|
|
2138
|
+
}
|
|
2139
|
+
if (getAddress(subjectAddress) === getAddress(connectedWalletAddress) && controllingWalletDid) {
|
|
2140
|
+
return {
|
|
2141
|
+
valid: true,
|
|
2142
|
+
method: "minting-wallet",
|
|
2143
|
+
details: `Verified because connected wallet matches the contract DID address. Controlling wallet is ${controllingWalletDid}.`,
|
|
2144
|
+
subjectDid,
|
|
2145
|
+
connectedWalletDid,
|
|
2146
|
+
controllingWalletDid
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
return {
|
|
2150
|
+
valid: false,
|
|
2151
|
+
reason: "Contract ownership verification failed",
|
|
2152
|
+
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}.`,
|
|
2153
|
+
subjectDid,
|
|
2154
|
+
connectedWalletDid,
|
|
2155
|
+
controllingWalletDid: controllingWalletDid ?? void 0
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
async function verifySubjectOwnership(params) {
|
|
2159
|
+
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
2160
|
+
const method = extractDidMethod(subjectDid);
|
|
2161
|
+
if (method === "web") {
|
|
2162
|
+
return verifyDidWebOwnership(params);
|
|
2163
|
+
}
|
|
2164
|
+
if (method === "pkh") {
|
|
2165
|
+
const didPkhParams = params;
|
|
2166
|
+
if (!didPkhParams.provider) {
|
|
2167
|
+
throw new OmaTrustError(
|
|
2168
|
+
"INVALID_INPUT",
|
|
2169
|
+
"provider is required for did:pkh ownership verification",
|
|
2170
|
+
{ subjectDid }
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
return verifyDidPkhOwnership(didPkhParams);
|
|
2174
|
+
}
|
|
2175
|
+
throw new OmaTrustError("INVALID_INPUT", "Unsupported DID type for ownership verification", {
|
|
2176
|
+
subjectDid
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
1614
2179
|
|
|
1615
2180
|
// src/app-registry/index.ts
|
|
1616
2181
|
var app_registry_exports = {};
|