@oma3/omatrust 0.1.0-alpha.0 → 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 +72 -7
- package/dist/identity/index.cjs +2 -3
- package/dist/identity/index.cjs.map +1 -1
- package/dist/identity/index.js +3 -4
- 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 +657 -93
- 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 +658 -94
- 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 +676 -92
- 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 +670 -94
- 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.cjs
CHANGED
|
@@ -158,7 +158,7 @@ function extractDidIdentifier(did) {
|
|
|
158
158
|
}
|
|
159
159
|
function normalizeDomain(domain) {
|
|
160
160
|
assertString(domain, "domain", "INVALID_DID");
|
|
161
|
-
return domain.trim().toLowerCase().replace(/\.$/, "");
|
|
161
|
+
return domain.trim().toLowerCase().replace(/\.$/, "").replace(/^www\./, "");
|
|
162
162
|
}
|
|
163
163
|
function normalizeDidWeb(input) {
|
|
164
164
|
assertString(input, "input", "INVALID_DID");
|
|
@@ -247,8 +247,7 @@ function computeDidAddress(didHash) {
|
|
|
247
247
|
if (!/^0x[0-9a-fA-F]{64}$/.test(didHash)) {
|
|
248
248
|
throw new OmaTrustError("INVALID_DID", "didHash must be 32-byte hex", { didHash });
|
|
249
249
|
}
|
|
250
|
-
|
|
251
|
-
return ethers.getAddress(truncated);
|
|
250
|
+
return `0x${didHash.slice(-40).toLowerCase()}`;
|
|
252
251
|
}
|
|
253
252
|
function didToAddress(did) {
|
|
254
253
|
return computeDidAddress(computeDidHash(did));
|
|
@@ -362,6 +361,7 @@ function hashCanonicalizedJson(obj, algorithm) {
|
|
|
362
361
|
var reputation_exports = {};
|
|
363
362
|
__export(reputation_exports, {
|
|
364
363
|
buildDelegatedAttestationTypedData: () => buildDelegatedAttestationTypedData,
|
|
364
|
+
buildDelegatedTypedDataFromEncoded: () => buildDelegatedTypedDataFromEncoded,
|
|
365
365
|
buildDnsTxtRecord: () => buildDnsTxtRecord,
|
|
366
366
|
buildEip712Domain: () => buildEip712Domain,
|
|
367
367
|
calculateAverageUserReviewRating: () => calculateAverageUserReviewRating,
|
|
@@ -385,6 +385,7 @@ __export(reputation_exports, {
|
|
|
385
385
|
formatSchemaUid: () => formatSchemaUid,
|
|
386
386
|
formatTransferAmount: () => formatTransferAmount,
|
|
387
387
|
getAttestation: () => getAttestation,
|
|
388
|
+
getAttestationsByAttester: () => getAttestationsByAttester,
|
|
388
389
|
getAttestationsForDid: () => getAttestationsForDid,
|
|
389
390
|
getChainConstants: () => getChainConstants,
|
|
390
391
|
getExplorerAddressUrl: () => getExplorerAddressUrl,
|
|
@@ -400,16 +401,22 @@ __export(reputation_exports, {
|
|
|
400
401
|
normalizeSchema: () => normalizeSchema,
|
|
401
402
|
parseDnsTxtRecord: () => parseDnsTxtRecord,
|
|
402
403
|
prepareDelegatedAttestation: () => prepareDelegatedAttestation,
|
|
404
|
+
revokeAttestation: () => revokeAttestation,
|
|
403
405
|
schemaToString: () => schemaToString,
|
|
404
406
|
splitSignature: () => splitSignature,
|
|
405
407
|
submitAttestation: () => submitAttestation,
|
|
406
408
|
submitDelegatedAttestation: () => submitDelegatedAttestation,
|
|
409
|
+
validateAttestationData: () => validateAttestationData,
|
|
407
410
|
verifyAttestation: () => verifyAttestation,
|
|
408
411
|
verifyDidDocumentControllerDid: () => verifyDidDocumentControllerDid,
|
|
412
|
+
verifyDidJsonControllerDid: () => verifyDidJsonControllerDid,
|
|
413
|
+
verifyDidPkhOwnership: () => verifyDidPkhOwnership,
|
|
414
|
+
verifyDidWebOwnership: () => verifyDidWebOwnership,
|
|
409
415
|
verifyDnsTxtControllerDid: () => verifyDnsTxtControllerDid,
|
|
410
416
|
verifyEip712Signature: () => verifyEip712Signature,
|
|
411
417
|
verifyProof: () => verifyProof,
|
|
412
|
-
verifySchemaExists: () => verifySchemaExists
|
|
418
|
+
verifySchemaExists: () => verifySchemaExists,
|
|
419
|
+
verifySubjectOwnership: () => verifySubjectOwnership
|
|
413
420
|
});
|
|
414
421
|
function normalizeSchema(schema) {
|
|
415
422
|
if (Array.isArray(schema)) {
|
|
@@ -445,6 +452,13 @@ function encodeAttestationData(schema, data) {
|
|
|
445
452
|
if (!data || typeof data !== "object") {
|
|
446
453
|
throw new OmaTrustError("INVALID_INPUT", "data must be an object");
|
|
447
454
|
}
|
|
455
|
+
const validationErrors = validateAttestationData(schema, data);
|
|
456
|
+
if (validationErrors.length > 0) {
|
|
457
|
+
const summary = validationErrors.map((error) => `Field "${error.schemaFieldName}" (${error.expectedType}) got ${error.providedType}`).join("; ");
|
|
458
|
+
throw new OmaTrustError("INVALID_INPUT", `Attestation data validation failed: ${summary}`, {
|
|
459
|
+
errors: validationErrors
|
|
460
|
+
});
|
|
461
|
+
}
|
|
448
462
|
const fields = normalizeSchema(schema);
|
|
449
463
|
const schemaString = schemaToString(fields);
|
|
450
464
|
const encoder = new easSdk.SchemaEncoder(schemaString);
|
|
@@ -457,6 +471,99 @@ function encodeAttestationData(schema, data) {
|
|
|
457
471
|
);
|
|
458
472
|
return encoded;
|
|
459
473
|
}
|
|
474
|
+
function getActualType(value) {
|
|
475
|
+
if (value === null) {
|
|
476
|
+
return "null";
|
|
477
|
+
}
|
|
478
|
+
if (value === void 0) {
|
|
479
|
+
return "undefined";
|
|
480
|
+
}
|
|
481
|
+
if (Array.isArray(value)) {
|
|
482
|
+
return "array";
|
|
483
|
+
}
|
|
484
|
+
if (typeof value === "number") {
|
|
485
|
+
return Number.isFinite(value) ? "number" : "non-finite-number";
|
|
486
|
+
}
|
|
487
|
+
return typeof value;
|
|
488
|
+
}
|
|
489
|
+
function isNumericValue(value, allowNegative) {
|
|
490
|
+
if (typeof value === "bigint") {
|
|
491
|
+
return allowNegative || value >= 0n;
|
|
492
|
+
}
|
|
493
|
+
if (typeof value === "number") {
|
|
494
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
return allowNegative || value >= 0;
|
|
498
|
+
}
|
|
499
|
+
if (typeof value === "string") {
|
|
500
|
+
if (value.length === 0) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
const pattern = allowNegative ? /^-?\d+$/ : /^\d+$/;
|
|
504
|
+
return pattern.test(value);
|
|
505
|
+
}
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
function isHex(value) {
|
|
509
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]*$/.test(value);
|
|
510
|
+
}
|
|
511
|
+
function validateFieldValue(type, value) {
|
|
512
|
+
const normalizedType = type.trim().toLowerCase();
|
|
513
|
+
const bytesNMatch = normalizedType.match(/^bytes([1-9]|[12]\d|3[0-2])$/);
|
|
514
|
+
if (/^uint\d*$/.test(normalizedType)) {
|
|
515
|
+
return isNumericValue(value, false);
|
|
516
|
+
}
|
|
517
|
+
if (/^int\d*$/.test(normalizedType)) {
|
|
518
|
+
return isNumericValue(value, true);
|
|
519
|
+
}
|
|
520
|
+
if (normalizedType === "string") {
|
|
521
|
+
return typeof value === "string";
|
|
522
|
+
}
|
|
523
|
+
if (normalizedType === "string[]") {
|
|
524
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
525
|
+
}
|
|
526
|
+
if (normalizedType === "bool") {
|
|
527
|
+
return typeof value === "boolean";
|
|
528
|
+
}
|
|
529
|
+
if (normalizedType === "address") {
|
|
530
|
+
return typeof value === "string" && ethers.isAddress(value);
|
|
531
|
+
}
|
|
532
|
+
if (normalizedType === "bytes") {
|
|
533
|
+
return isHex(value) && (value.length - 2) % 2 === 0;
|
|
534
|
+
}
|
|
535
|
+
if (bytesNMatch) {
|
|
536
|
+
const expectedBytes = Number(bytesNMatch[1]);
|
|
537
|
+
return isHex(value) && value.length === 2 + expectedBytes * 2;
|
|
538
|
+
}
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
function validateAttestationData(schema, data) {
|
|
542
|
+
if (!data || typeof data !== "object") {
|
|
543
|
+
return [{
|
|
544
|
+
schemaFieldName: "data",
|
|
545
|
+
expectedType: "object",
|
|
546
|
+
providedType: getActualType(data),
|
|
547
|
+
providedValue: data
|
|
548
|
+
}];
|
|
549
|
+
}
|
|
550
|
+
const fields = normalizeSchema(schema);
|
|
551
|
+
const errors = [];
|
|
552
|
+
for (const field of fields) {
|
|
553
|
+
const value = data[field.name];
|
|
554
|
+
const isValid = validateFieldValue(field.type, value);
|
|
555
|
+
if (isValid) {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
errors.push({
|
|
559
|
+
schemaFieldName: field.name,
|
|
560
|
+
expectedType: field.type,
|
|
561
|
+
providedType: getActualType(value),
|
|
562
|
+
providedValue: value
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return errors;
|
|
566
|
+
}
|
|
460
567
|
function decodeAttestationData(schema, encodedData) {
|
|
461
568
|
if (typeof encodedData !== "string" || !encodedData.startsWith("0x")) {
|
|
462
569
|
throw new OmaTrustError("INVALID_INPUT", "encodedData must be a hex string");
|
|
@@ -477,22 +584,11 @@ function decodeAttestationData(schema, encodedData) {
|
|
|
477
584
|
return result;
|
|
478
585
|
}
|
|
479
586
|
function extractExpirationTime(data) {
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
if (typeof value === "bigint") {
|
|
487
|
-
return value;
|
|
488
|
-
}
|
|
489
|
-
if (typeof value === "number") {
|
|
490
|
-
return value;
|
|
491
|
-
}
|
|
492
|
-
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
493
|
-
return BigInt(value);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
587
|
+
const value = data["expiresAt"];
|
|
588
|
+
if (value === void 0 || value === null) return void 0;
|
|
589
|
+
if (typeof value === "bigint") return value;
|
|
590
|
+
if (typeof value === "number") return value;
|
|
591
|
+
if (typeof value === "string" && /^\d+$/.test(value)) return BigInt(value);
|
|
496
592
|
return void 0;
|
|
497
593
|
}
|
|
498
594
|
var ZERO_UID = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
@@ -533,6 +629,53 @@ function resolveRecipientAddress(data) {
|
|
|
533
629
|
return ethers.ZeroAddress;
|
|
534
630
|
}
|
|
535
631
|
|
|
632
|
+
// src/reputation/eas-adapter.ts
|
|
633
|
+
function isRecord(value) {
|
|
634
|
+
return Boolean(value) && typeof value === "object";
|
|
635
|
+
}
|
|
636
|
+
function isHex32(value) {
|
|
637
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
638
|
+
}
|
|
639
|
+
function getEasTransactionReceipt(tx) {
|
|
640
|
+
if (!isRecord(tx)) {
|
|
641
|
+
return void 0;
|
|
642
|
+
}
|
|
643
|
+
const candidates = [tx.receipt, tx.tx, tx.transaction];
|
|
644
|
+
for (const candidate of candidates) {
|
|
645
|
+
if (isRecord(candidate)) {
|
|
646
|
+
return candidate;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return void 0;
|
|
650
|
+
}
|
|
651
|
+
function getEasTransactionHash(tx) {
|
|
652
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
653
|
+
if (receipt) {
|
|
654
|
+
const receiptHash = receipt.hash;
|
|
655
|
+
if (isHex32(receiptHash)) {
|
|
656
|
+
return receiptHash;
|
|
657
|
+
}
|
|
658
|
+
const transactionHash = receipt.transactionHash;
|
|
659
|
+
if (isHex32(transactionHash)) {
|
|
660
|
+
return transactionHash;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (isRecord(tx) && isHex32(tx.hash)) {
|
|
664
|
+
return tx.hash;
|
|
665
|
+
}
|
|
666
|
+
return void 0;
|
|
667
|
+
}
|
|
668
|
+
function isEasSchemaNotFoundError(err) {
|
|
669
|
+
if (!isRecord(err)) {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
const message = err.message;
|
|
673
|
+
if (typeof message !== "string" || message.length === 0) {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
return /schema not found/i.test(message);
|
|
677
|
+
}
|
|
678
|
+
|
|
536
679
|
// src/reputation/submit.ts
|
|
537
680
|
async function submitAttestation(params) {
|
|
538
681
|
if (!params || typeof params !== "object") {
|
|
@@ -563,25 +706,46 @@ async function submitAttestation(params) {
|
|
|
563
706
|
}
|
|
564
707
|
});
|
|
565
708
|
const uid = await tx.wait();
|
|
566
|
-
const
|
|
567
|
-
const
|
|
709
|
+
const txHash = getEasTransactionHash(tx) ?? ZERO_UID;
|
|
710
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
568
711
|
return {
|
|
569
712
|
uid,
|
|
570
713
|
txHash,
|
|
571
|
-
receipt
|
|
714
|
+
receipt
|
|
572
715
|
};
|
|
573
716
|
} catch (err) {
|
|
574
717
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to submit attestation", { err });
|
|
575
718
|
}
|
|
576
719
|
}
|
|
577
|
-
function
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
);
|
|
720
|
+
async function revokeAttestation(params) {
|
|
721
|
+
if (!params || typeof params !== "object") {
|
|
722
|
+
throw new OmaTrustError("INVALID_INPUT", "params must be provided");
|
|
723
|
+
}
|
|
724
|
+
if (!params.signer) {
|
|
725
|
+
throw new OmaTrustError("INVALID_INPUT", "signer is required");
|
|
726
|
+
}
|
|
727
|
+
const eas = new easSdk.EAS(params.easContractAddress);
|
|
728
|
+
eas.connect(params.signer);
|
|
729
|
+
try {
|
|
730
|
+
const tx = await eas.revoke({
|
|
731
|
+
schema: params.schemaUid,
|
|
732
|
+
data: {
|
|
733
|
+
uid: params.uid,
|
|
734
|
+
value: toBigIntOrDefault(params.value, 0n)
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
await tx.wait();
|
|
738
|
+
const txHash = getEasTransactionHash(tx) ?? ZERO_UID;
|
|
739
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
740
|
+
return {
|
|
741
|
+
txHash,
|
|
742
|
+
receipt
|
|
743
|
+
};
|
|
744
|
+
} catch (err) {
|
|
745
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to revoke attestation", { err });
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function buildDelegatedTypedData(params) {
|
|
585
749
|
return {
|
|
586
750
|
domain: {
|
|
587
751
|
name: "EAS",
|
|
@@ -606,17 +770,52 @@ function buildDelegatedAttestationTypedData(params) {
|
|
|
606
770
|
message: {
|
|
607
771
|
attester: params.attester,
|
|
608
772
|
schema: params.schemaUid,
|
|
609
|
-
recipient,
|
|
610
|
-
expirationTime:
|
|
773
|
+
recipient: params.recipient,
|
|
774
|
+
expirationTime: toBigIntOrDefault(params.expirationTime, 0n),
|
|
611
775
|
revocable: params.revocable ?? true,
|
|
612
776
|
refUID: params.refUid ?? ZERO_UID,
|
|
613
|
-
data: encodedData,
|
|
777
|
+
data: params.encodedData,
|
|
614
778
|
value: toBigIntOrDefault(params.value, 0n),
|
|
615
779
|
nonce: toBigIntOrDefault(params.nonce, 0n),
|
|
616
780
|
deadline: toBigIntOrDefault(params.deadline, BigInt(Math.floor(Date.now() / 1e3) + 600))
|
|
617
781
|
}
|
|
618
782
|
};
|
|
619
783
|
}
|
|
784
|
+
function buildDelegatedAttestationTypedData(params) {
|
|
785
|
+
const dataWithHash = withAutoSubjectDidHash(params.schema, params.data);
|
|
786
|
+
const encodedData = encodeAttestationData(params.schema, dataWithHash);
|
|
787
|
+
const recipient = resolveRecipientAddress(dataWithHash);
|
|
788
|
+
return buildDelegatedTypedData({
|
|
789
|
+
chainId: params.chainId,
|
|
790
|
+
easContractAddress: params.easContractAddress,
|
|
791
|
+
schemaUid: params.schemaUid,
|
|
792
|
+
encodedData,
|
|
793
|
+
recipient,
|
|
794
|
+
attester: params.attester,
|
|
795
|
+
nonce: params.nonce,
|
|
796
|
+
revocable: params.revocable,
|
|
797
|
+
expirationTime: params.expirationTime ?? extractExpirationTime(dataWithHash),
|
|
798
|
+
refUid: params.refUid,
|
|
799
|
+
value: params.value,
|
|
800
|
+
deadline: params.deadline
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
function buildDelegatedTypedDataFromEncoded(params) {
|
|
804
|
+
return buildDelegatedTypedData({
|
|
805
|
+
chainId: params.chainId,
|
|
806
|
+
easContractAddress: params.easContractAddress,
|
|
807
|
+
schemaUid: params.schemaUid,
|
|
808
|
+
encodedData: params.encodedData,
|
|
809
|
+
recipient: params.recipient,
|
|
810
|
+
attester: params.attester,
|
|
811
|
+
nonce: params.nonce,
|
|
812
|
+
revocable: params.revocable,
|
|
813
|
+
expirationTime: params.expirationTime,
|
|
814
|
+
refUid: params.refUid,
|
|
815
|
+
value: params.value,
|
|
816
|
+
deadline: params.deadline
|
|
817
|
+
});
|
|
818
|
+
}
|
|
620
819
|
function splitSignature(signature) {
|
|
621
820
|
try {
|
|
622
821
|
const parsed = ethers.Signature.from(signature);
|
|
@@ -693,7 +892,14 @@ async function submitDelegatedAttestation(params) {
|
|
|
693
892
|
var EAS_EVENT_ABI = [
|
|
694
893
|
"event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
|
|
695
894
|
];
|
|
696
|
-
function
|
|
895
|
+
function parseEventTxHash(event) {
|
|
896
|
+
const txHash = event.transactionHash;
|
|
897
|
+
if (typeof txHash !== "string") {
|
|
898
|
+
return void 0;
|
|
899
|
+
}
|
|
900
|
+
return /^0x[0-9a-fA-F]{64}$/.test(txHash) ? txHash : void 0;
|
|
901
|
+
}
|
|
902
|
+
function parseAttestation(attestation, schema, txHash) {
|
|
697
903
|
const rawData = attestation.data ?? "0x";
|
|
698
904
|
const decoded = schema ? decodeAttestationData(schema, rawData) : {};
|
|
699
905
|
const toBigIntSafe = (value) => {
|
|
@@ -713,6 +919,7 @@ function parseAttestation(attestation, schema) {
|
|
|
713
919
|
schema: attestation.schema,
|
|
714
920
|
attester: attestation.attester,
|
|
715
921
|
recipient: attestation.recipient,
|
|
922
|
+
txHash,
|
|
716
923
|
revocable: Boolean(attestation.revocable),
|
|
717
924
|
revocationTime: toBigIntSafe(attestation.revocationTime),
|
|
718
925
|
expirationTime: toBigIntSafe(attestation.expirationTime),
|
|
@@ -738,21 +945,18 @@ async function getAttestation(params) {
|
|
|
738
945
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to read attestation", { err });
|
|
739
946
|
}
|
|
740
947
|
}
|
|
741
|
-
async function
|
|
742
|
-
const
|
|
743
|
-
const
|
|
744
|
-
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
745
|
-
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
746
|
-
const filter = contract.filters.Attested(didToAddress(params.did));
|
|
948
|
+
async function queryAttestationEvents(easContractAddress, provider, fromBlock, toBlock, options) {
|
|
949
|
+
const contract = new ethers.Contract(easContractAddress, EAS_EVENT_ABI, provider);
|
|
950
|
+
const filter = contract.filters.Attested(options?.recipient ?? null, options?.attester ?? null);
|
|
747
951
|
let events;
|
|
748
952
|
try {
|
|
749
953
|
events = await contract.queryFilter(filter, fromBlock, toBlock);
|
|
750
954
|
} catch (err) {
|
|
751
955
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to query attestation events", { err });
|
|
752
956
|
}
|
|
753
|
-
const eas = new easSdk.EAS(
|
|
957
|
+
const eas = new easSdk.EAS(easContractAddress);
|
|
754
958
|
eas.connect(provider);
|
|
755
|
-
const schemaFilter =
|
|
959
|
+
const schemaFilter = options?.schemas?.map((s) => s.toLowerCase());
|
|
756
960
|
const results = [];
|
|
757
961
|
for (const event of events) {
|
|
758
962
|
if (!("args" in event && Array.isArray(event.args))) {
|
|
@@ -771,11 +975,39 @@ async function getAttestationsForDid(params) {
|
|
|
771
975
|
if (!attestation || !attestation.uid) {
|
|
772
976
|
continue;
|
|
773
977
|
}
|
|
774
|
-
results.push(parseAttestation(attestation));
|
|
978
|
+
results.push(parseAttestation(attestation, void 0, parseEventTxHash(event)));
|
|
775
979
|
}
|
|
776
980
|
results.sort((a, b) => Number(b.time - a.time));
|
|
777
981
|
return results;
|
|
778
982
|
}
|
|
983
|
+
async function getAttestationsForDid(params) {
|
|
984
|
+
const provider = params.provider;
|
|
985
|
+
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
986
|
+
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
987
|
+
return queryAttestationEvents(
|
|
988
|
+
params.easContractAddress,
|
|
989
|
+
provider,
|
|
990
|
+
fromBlock,
|
|
991
|
+
toBlock,
|
|
992
|
+
{ recipient: didToAddress(params.did), schemas: params.schemas }
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
async function getAttestationsByAttester(params) {
|
|
996
|
+
const provider = params.provider;
|
|
997
|
+
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
998
|
+
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
999
|
+
if (params.limit !== void 0 && params.limit <= 0) {
|
|
1000
|
+
return [];
|
|
1001
|
+
}
|
|
1002
|
+
const results = await queryAttestationEvents(
|
|
1003
|
+
params.easContractAddress,
|
|
1004
|
+
provider,
|
|
1005
|
+
fromBlock,
|
|
1006
|
+
toBlock,
|
|
1007
|
+
{ attester: params.attester, schemas: params.schemas }
|
|
1008
|
+
);
|
|
1009
|
+
return params.limit !== void 0 ? results.slice(0, params.limit) : results;
|
|
1010
|
+
}
|
|
779
1011
|
async function listAttestations(params) {
|
|
780
1012
|
const limit = params.limit ?? 20;
|
|
781
1013
|
if (limit <= 0) {
|
|
@@ -786,34 +1018,15 @@ async function listAttestations(params) {
|
|
|
786
1018
|
}
|
|
787
1019
|
async function getLatestAttestations(params) {
|
|
788
1020
|
const provider = params.provider;
|
|
789
|
-
const
|
|
790
|
-
const
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
if (!("args" in event && Array.isArray(event.args))) {
|
|
799
|
-
continue;
|
|
800
|
-
}
|
|
801
|
-
const args = event.args;
|
|
802
|
-
const uid = args?.[2];
|
|
803
|
-
const schemaUid = args?.[3];
|
|
804
|
-
if (!uid || !schemaUid) {
|
|
805
|
-
continue;
|
|
806
|
-
}
|
|
807
|
-
if (schemaFilter && !schemaFilter.includes(schemaUid.toLowerCase())) {
|
|
808
|
-
continue;
|
|
809
|
-
}
|
|
810
|
-
const attestation = await eas.getAttestation(uid);
|
|
811
|
-
if (!attestation || !attestation.uid) {
|
|
812
|
-
continue;
|
|
813
|
-
}
|
|
814
|
-
results.push(parseAttestation(attestation));
|
|
815
|
-
}
|
|
816
|
-
results.sort((a, b) => Number(b.time - a.time));
|
|
1021
|
+
const toBlock = await provider.getBlockNumber();
|
|
1022
|
+
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
1023
|
+
const results = await queryAttestationEvents(
|
|
1024
|
+
params.easContractAddress,
|
|
1025
|
+
provider,
|
|
1026
|
+
fromBlock,
|
|
1027
|
+
toBlock,
|
|
1028
|
+
{ schemas: params.schemas }
|
|
1029
|
+
);
|
|
817
1030
|
return results.slice(0, params.limit ?? 20);
|
|
818
1031
|
}
|
|
819
1032
|
function deduplicateReviews(attestations) {
|
|
@@ -1075,6 +1288,13 @@ function verifyDidDocumentControllerDid(didDocument, expectedControllerDid) {
|
|
|
1075
1288
|
reason: `No matching address found in DID document (expected ${expectedAddress})`
|
|
1076
1289
|
};
|
|
1077
1290
|
}
|
|
1291
|
+
async function verifyDidJsonControllerDid(domain, expectedControllerDid, options = {}) {
|
|
1292
|
+
if (!domain || typeof domain !== "string") {
|
|
1293
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1294
|
+
}
|
|
1295
|
+
const didDocument = options.fetchDidDocument ? await options.fetchDidDocument(domain) : await fetchDidDocument(domain);
|
|
1296
|
+
return verifyDidDocumentControllerDid(didDocument, expectedControllerDid);
|
|
1297
|
+
}
|
|
1078
1298
|
function buildEip712Domain(name, version, chainId, verifyingContract) {
|
|
1079
1299
|
return { name, version, chainId, verifyingContract };
|
|
1080
1300
|
}
|
|
@@ -1107,6 +1327,8 @@ function verifyEip712Signature(typedData, signature) {
|
|
|
1107
1327
|
throw new OmaTrustError("INVALID_INPUT", "Failed to verify EIP-712 signature", { err });
|
|
1108
1328
|
}
|
|
1109
1329
|
}
|
|
1330
|
+
|
|
1331
|
+
// src/reputation/proof/dns-txt-record.ts
|
|
1110
1332
|
function parseDnsTxtRecord(record) {
|
|
1111
1333
|
if (!record || typeof record !== "string") {
|
|
1112
1334
|
throw new OmaTrustError("INVALID_INPUT", "record must be a non-empty string", { record });
|
|
@@ -1134,27 +1356,6 @@ function buildDnsTxtRecord(controllerDid) {
|
|
|
1134
1356
|
const normalized = normalizeDid(controllerDid);
|
|
1135
1357
|
return `v=1;controller=${normalized}`;
|
|
1136
1358
|
}
|
|
1137
|
-
async function verifyDnsTxtControllerDid(domain, expectedControllerDid) {
|
|
1138
|
-
if (!domain || typeof domain !== "string") {
|
|
1139
|
-
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1140
|
-
}
|
|
1141
|
-
const expected = normalizeDid(expectedControllerDid);
|
|
1142
|
-
const host = `_omatrust.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1143
|
-
let records;
|
|
1144
|
-
try {
|
|
1145
|
-
records = await promises.resolveTxt(host);
|
|
1146
|
-
} catch (err) {
|
|
1147
|
-
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1148
|
-
}
|
|
1149
|
-
for (const recordParts of records) {
|
|
1150
|
-
const record = recordParts.join("");
|
|
1151
|
-
const parsed = parseDnsTxtRecord(record);
|
|
1152
|
-
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1153
|
-
return { valid: true, record };
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
1157
|
-
}
|
|
1158
1359
|
|
|
1159
1360
|
// src/reputation/verify.ts
|
|
1160
1361
|
function parseChainId(input) {
|
|
@@ -1435,6 +1636,9 @@ async function getSchemaDetails(schemaRegistry, schemaUid) {
|
|
|
1435
1636
|
revocable: Boolean(details.revocable)
|
|
1436
1637
|
};
|
|
1437
1638
|
} catch (err) {
|
|
1639
|
+
if (isEasSchemaNotFoundError(err)) {
|
|
1640
|
+
throw new OmaTrustError("SCHEMA_NOT_FOUND", "Schema was not found", { schemaUid });
|
|
1641
|
+
}
|
|
1438
1642
|
if (err instanceof OmaTrustError) {
|
|
1439
1643
|
throw err;
|
|
1440
1644
|
}
|
|
@@ -1618,6 +1822,366 @@ function createEvidencePointerProof(url) {
|
|
|
1618
1822
|
issuedAt: Math.floor(Date.now() / 1e3)
|
|
1619
1823
|
};
|
|
1620
1824
|
}
|
|
1825
|
+
async function verifyDnsTxtControllerDid(domain, expectedControllerDid, options = {}) {
|
|
1826
|
+
if (!domain || typeof domain !== "string") {
|
|
1827
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1828
|
+
}
|
|
1829
|
+
const expected = normalizeDid(expectedControllerDid);
|
|
1830
|
+
const prefix = options.recordPrefix ?? "_controllers";
|
|
1831
|
+
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1832
|
+
let records;
|
|
1833
|
+
try {
|
|
1834
|
+
records = await (options.resolveTxt ?? promises.resolveTxt)(host);
|
|
1835
|
+
} catch (err) {
|
|
1836
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1837
|
+
}
|
|
1838
|
+
for (const recordParts of records) {
|
|
1839
|
+
const record = recordParts.join("");
|
|
1840
|
+
const parsed = parseDnsTxtRecord(record);
|
|
1841
|
+
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1842
|
+
return { valid: true, record };
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// src/reputation/proof/dns-txt-shared.ts
|
|
1849
|
+
async function verifyDnsTxtControllerDid2(domain, expectedControllerDid, options = {}) {
|
|
1850
|
+
if (!domain || typeof domain !== "string") {
|
|
1851
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1852
|
+
}
|
|
1853
|
+
if (!options.resolveTxt) {
|
|
1854
|
+
throw new OmaTrustError("NETWORK_ERROR", "No DNS TXT resolver was provided", { domain });
|
|
1855
|
+
}
|
|
1856
|
+
const expected = normalizeDid(expectedControllerDid);
|
|
1857
|
+
const prefix = options.recordPrefix ?? "_controllers";
|
|
1858
|
+
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1859
|
+
let records;
|
|
1860
|
+
try {
|
|
1861
|
+
records = await options.resolveTxt(host);
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1864
|
+
}
|
|
1865
|
+
for (const recordParts of records) {
|
|
1866
|
+
const record = recordParts.join("");
|
|
1867
|
+
const parsed = parseDnsTxtRecord(record);
|
|
1868
|
+
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1869
|
+
return { valid: true, record };
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// src/reputation/proof/subject-ownership.ts
|
|
1876
|
+
var EIP1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
|
|
1877
|
+
var OWNERSHIP_PATTERNS = [
|
|
1878
|
+
{ method: "owner", signature: "function owner() view returns (address)" },
|
|
1879
|
+
{ method: "admin", signature: "function admin() view returns (address)" },
|
|
1880
|
+
{ method: "getOwner", signature: "function getOwner() view returns (address)" }
|
|
1881
|
+
];
|
|
1882
|
+
function assertConnectedWalletDid(input) {
|
|
1883
|
+
const normalized = normalizeDid(input);
|
|
1884
|
+
if (extractDidMethod(normalized) !== "pkh") {
|
|
1885
|
+
throw new OmaTrustError("INVALID_INPUT", "connectedWalletDid must be a did:pkh DID", {
|
|
1886
|
+
connectedWalletDid: input
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
return normalized;
|
|
1890
|
+
}
|
|
1891
|
+
function normalizeSubjectDid(input) {
|
|
1892
|
+
return normalizeDid(input);
|
|
1893
|
+
}
|
|
1894
|
+
async function readAddressFromContract(provider, contractAddress, signature, method) {
|
|
1895
|
+
try {
|
|
1896
|
+
const iface = new ethers.Interface([signature]);
|
|
1897
|
+
const data = iface.encodeFunctionData(method, []);
|
|
1898
|
+
const result = await provider.call({ to: contractAddress, data });
|
|
1899
|
+
const [value] = iface.decodeFunctionResult(method, result);
|
|
1900
|
+
if (typeof value === "string" && ethers.isAddress(value) && value !== ethers.ZeroAddress) {
|
|
1901
|
+
return ethers.getAddress(value);
|
|
1902
|
+
}
|
|
1903
|
+
} catch {
|
|
1904
|
+
}
|
|
1905
|
+
return null;
|
|
1906
|
+
}
|
|
1907
|
+
async function discoverControllingWallet(provider, contractAddress, chainId) {
|
|
1908
|
+
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
1909
|
+
const address = await readAddressFromContract(
|
|
1910
|
+
provider,
|
|
1911
|
+
contractAddress,
|
|
1912
|
+
pattern.signature,
|
|
1913
|
+
pattern.method
|
|
1914
|
+
);
|
|
1915
|
+
if (address) {
|
|
1916
|
+
return buildEvmDidPkh(chainId, address);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
try {
|
|
1920
|
+
const adminValue = await provider.getStorage(contractAddress, EIP1967_ADMIN_SLOT);
|
|
1921
|
+
if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
1922
|
+
const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
|
|
1923
|
+
if (adminAddress !== ethers.ZeroAddress) {
|
|
1924
|
+
return buildEvmDidPkh(chainId, adminAddress);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
} catch {
|
|
1928
|
+
}
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
async function verifyDidWebOwnership(params) {
|
|
1932
|
+
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
1933
|
+
const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
|
|
1934
|
+
const domain = getDomainFromDidWeb(subjectDid);
|
|
1935
|
+
if (!domain) {
|
|
1936
|
+
throw new OmaTrustError("INVALID_INPUT", "subjectDid must be a did:web DID", {
|
|
1937
|
+
subjectDid: params.subjectDid
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
let dnsReason;
|
|
1941
|
+
try {
|
|
1942
|
+
const dnsResult = await verifyDnsTxtControllerDid2(domain, connectedWalletDid, {
|
|
1943
|
+
resolveTxt: params.resolveTxt,
|
|
1944
|
+
recordPrefix: params.recordPrefix
|
|
1945
|
+
});
|
|
1946
|
+
if (dnsResult.valid) {
|
|
1947
|
+
return {
|
|
1948
|
+
valid: true,
|
|
1949
|
+
method: "dns",
|
|
1950
|
+
details: `Verified via DNS TXT record at ${params.recordPrefix ?? "_controllers"}.${domain}`,
|
|
1951
|
+
subjectDid,
|
|
1952
|
+
connectedWalletDid
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
dnsReason = dnsResult.reason;
|
|
1956
|
+
} catch (error) {
|
|
1957
|
+
dnsReason = error instanceof Error ? error.message : "DNS TXT verification failed";
|
|
1958
|
+
}
|
|
1959
|
+
let didDocReason;
|
|
1960
|
+
try {
|
|
1961
|
+
const didDocumentResult = await verifyDidJsonControllerDid(domain, connectedWalletDid, {
|
|
1962
|
+
fetchDidDocument: params.fetchDidDocument
|
|
1963
|
+
});
|
|
1964
|
+
if (didDocumentResult.valid) {
|
|
1965
|
+
return {
|
|
1966
|
+
valid: true,
|
|
1967
|
+
method: "did-document",
|
|
1968
|
+
details: `Verified via DID document at https://${domain}/.well-known/did.json`,
|
|
1969
|
+
subjectDid,
|
|
1970
|
+
connectedWalletDid
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
didDocReason = didDocumentResult.reason;
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
didDocReason = error instanceof Error ? error.message : "DID document verification failed";
|
|
1976
|
+
}
|
|
1977
|
+
return {
|
|
1978
|
+
valid: false,
|
|
1979
|
+
reason: "DID ownership verification failed",
|
|
1980
|
+
details: `DNS check: ${dnsReason ?? "failed"}. DID document check: ${didDocReason ?? "failed"}.`,
|
|
1981
|
+
subjectDid,
|
|
1982
|
+
connectedWalletDid
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
async function verifyDidPkhOwnership(params) {
|
|
1986
|
+
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
1987
|
+
const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
|
|
1988
|
+
if (!isEvmDidPkh(subjectDid)) {
|
|
1989
|
+
throw new OmaTrustError("INVALID_INPUT", "subjectDid must be an EVM did:pkh DID", {
|
|
1990
|
+
subjectDid: params.subjectDid
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
const subjectAddress = getAddressFromDidPkh(subjectDid);
|
|
1994
|
+
const connectedWalletAddress = getAddressFromDidPkh(connectedWalletDid);
|
|
1995
|
+
const chainIdRaw = getChainIdFromDidPkh(subjectDid);
|
|
1996
|
+
if (!subjectAddress || !connectedWalletAddress || !chainIdRaw) {
|
|
1997
|
+
throw new OmaTrustError("INVALID_INPUT", "Could not parse did:pkh ownership inputs", {
|
|
1998
|
+
subjectDid: params.subjectDid,
|
|
1999
|
+
connectedWalletDid: params.connectedWalletDid
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
const chainId = Number(chainIdRaw);
|
|
2003
|
+
if (!Number.isFinite(chainId)) {
|
|
2004
|
+
throw new OmaTrustError("INVALID_INPUT", "Invalid chain id in subjectDid", {
|
|
2005
|
+
subjectDid: params.subjectDid
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
const code = await params.provider.getCode(subjectAddress);
|
|
2009
|
+
const isContract = code !== "0x" && code !== "0x0";
|
|
2010
|
+
if (!isContract) {
|
|
2011
|
+
if (ethers.getAddress(subjectAddress) === ethers.getAddress(connectedWalletAddress)) {
|
|
2012
|
+
return {
|
|
2013
|
+
valid: true,
|
|
2014
|
+
method: "wallet",
|
|
2015
|
+
details: "Verified direct wallet ownership from matching did:pkh subject and connected wallet",
|
|
2016
|
+
subjectDid,
|
|
2017
|
+
connectedWalletDid
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
return {
|
|
2021
|
+
valid: false,
|
|
2022
|
+
reason: "EOA did:pkh subject does not match connected wallet",
|
|
2023
|
+
details: "For direct wallet did:pkh subjects, connectedWalletDid must match the subject DID.",
|
|
2024
|
+
subjectDid,
|
|
2025
|
+
connectedWalletDid
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
const controllingWalletDid = await discoverControllingWallet(params.provider, subjectAddress, chainId);
|
|
2029
|
+
if (params.txHash) {
|
|
2030
|
+
if (!controllingWalletDid) {
|
|
2031
|
+
return {
|
|
2032
|
+
valid: false,
|
|
2033
|
+
reason: "Could not discover controlling wallet",
|
|
2034
|
+
details: "Contract does not expose owner/admin/getOwner or a readable EIP-1967 admin slot for transfer verification.",
|
|
2035
|
+
subjectDid,
|
|
2036
|
+
connectedWalletDid
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
const tx = await params.provider.getTransaction(params.txHash);
|
|
2040
|
+
if (!tx) {
|
|
2041
|
+
return {
|
|
2042
|
+
valid: false,
|
|
2043
|
+
reason: "Transaction not found",
|
|
2044
|
+
details: `Transaction ${params.txHash} was not found on chain ${chainId}.`,
|
|
2045
|
+
subjectDid,
|
|
2046
|
+
connectedWalletDid,
|
|
2047
|
+
controllingWalletDid
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
const receipt = await params.provider.getTransactionReceipt(params.txHash);
|
|
2051
|
+
if (!receipt) {
|
|
2052
|
+
return {
|
|
2053
|
+
valid: false,
|
|
2054
|
+
reason: "Transaction not confirmed",
|
|
2055
|
+
details: "Transaction exists but is not yet confirmed.",
|
|
2056
|
+
subjectDid,
|
|
2057
|
+
connectedWalletDid,
|
|
2058
|
+
controllingWalletDid
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
const controllingWalletAddress = getAddressFromDidPkh(controllingWalletDid);
|
|
2062
|
+
if (!tx.from || !controllingWalletAddress || ethers.getAddress(tx.from) !== ethers.getAddress(controllingWalletAddress)) {
|
|
2063
|
+
return {
|
|
2064
|
+
valid: false,
|
|
2065
|
+
reason: "Wrong sender",
|
|
2066
|
+
details: `Transfer must originate from controlling wallet ${controllingWalletDid}.`,
|
|
2067
|
+
subjectDid,
|
|
2068
|
+
connectedWalletDid,
|
|
2069
|
+
controllingWalletDid
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
if (!tx.to || ethers.getAddress(tx.to) !== ethers.getAddress(connectedWalletAddress)) {
|
|
2073
|
+
return {
|
|
2074
|
+
valid: false,
|
|
2075
|
+
reason: "Wrong recipient",
|
|
2076
|
+
details: `Transfer must be sent to connected wallet ${connectedWalletDid}.`,
|
|
2077
|
+
subjectDid,
|
|
2078
|
+
connectedWalletDid,
|
|
2079
|
+
controllingWalletDid
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
const expectedAmount = calculateTransferAmount(
|
|
2083
|
+
subjectDid,
|
|
2084
|
+
connectedWalletDid,
|
|
2085
|
+
chainId,
|
|
2086
|
+
"shared-control"
|
|
2087
|
+
);
|
|
2088
|
+
const actualValue = BigInt(tx.value ?? 0n);
|
|
2089
|
+
if (actualValue !== expectedAmount) {
|
|
2090
|
+
return {
|
|
2091
|
+
valid: false,
|
|
2092
|
+
reason: "Wrong amount",
|
|
2093
|
+
details: `Transfer amount ${actualValue.toString()} does not match expected proof amount ${expectedAmount.toString()}.`,
|
|
2094
|
+
subjectDid,
|
|
2095
|
+
connectedWalletDid,
|
|
2096
|
+
controllingWalletDid
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
await params.provider.getBlockNumber();
|
|
2100
|
+
await params.provider.getBlock(receipt.blockNumber);
|
|
2101
|
+
return {
|
|
2102
|
+
valid: true,
|
|
2103
|
+
method: "transfer",
|
|
2104
|
+
details: `Verified via transfer proof ${params.txHash}.`,
|
|
2105
|
+
subjectDid,
|
|
2106
|
+
connectedWalletDid,
|
|
2107
|
+
controllingWalletDid
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
2111
|
+
const ownerAddress = await readAddressFromContract(
|
|
2112
|
+
params.provider,
|
|
2113
|
+
subjectAddress,
|
|
2114
|
+
pattern.signature,
|
|
2115
|
+
pattern.method
|
|
2116
|
+
);
|
|
2117
|
+
if (ownerAddress && ethers.getAddress(ownerAddress) === ethers.getAddress(connectedWalletAddress)) {
|
|
2118
|
+
return {
|
|
2119
|
+
valid: true,
|
|
2120
|
+
method: "contract",
|
|
2121
|
+
details: `Verified via ${pattern.method}() ownership check.`,
|
|
2122
|
+
subjectDid,
|
|
2123
|
+
connectedWalletDid,
|
|
2124
|
+
controllingWalletDid: controllingWalletDid ?? void 0
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
try {
|
|
2129
|
+
const adminValue = await params.provider.getStorage(subjectAddress, EIP1967_ADMIN_SLOT);
|
|
2130
|
+
if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
2131
|
+
const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
|
|
2132
|
+
if (adminAddress === ethers.getAddress(connectedWalletAddress)) {
|
|
2133
|
+
return {
|
|
2134
|
+
valid: true,
|
|
2135
|
+
method: "contract",
|
|
2136
|
+
details: "Verified via EIP-1967 admin slot.",
|
|
2137
|
+
subjectDid,
|
|
2138
|
+
connectedWalletDid,
|
|
2139
|
+
controllingWalletDid: controllingWalletDid ?? buildEvmDidPkh(chainId, adminAddress)
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
} catch {
|
|
2144
|
+
}
|
|
2145
|
+
if (ethers.getAddress(subjectAddress) === ethers.getAddress(connectedWalletAddress) && controllingWalletDid) {
|
|
2146
|
+
return {
|
|
2147
|
+
valid: true,
|
|
2148
|
+
method: "minting-wallet",
|
|
2149
|
+
details: `Verified because connected wallet matches the contract DID address. Controlling wallet is ${controllingWalletDid}.`,
|
|
2150
|
+
subjectDid,
|
|
2151
|
+
connectedWalletDid,
|
|
2152
|
+
controllingWalletDid
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
return {
|
|
2156
|
+
valid: false,
|
|
2157
|
+
reason: "Contract ownership verification failed",
|
|
2158
|
+
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}.`,
|
|
2159
|
+
subjectDid,
|
|
2160
|
+
connectedWalletDid,
|
|
2161
|
+
controllingWalletDid: controllingWalletDid ?? void 0
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
async function verifySubjectOwnership(params) {
|
|
2165
|
+
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
2166
|
+
const method = extractDidMethod(subjectDid);
|
|
2167
|
+
if (method === "web") {
|
|
2168
|
+
return verifyDidWebOwnership(params);
|
|
2169
|
+
}
|
|
2170
|
+
if (method === "pkh") {
|
|
2171
|
+
const didPkhParams = params;
|
|
2172
|
+
if (!didPkhParams.provider) {
|
|
2173
|
+
throw new OmaTrustError(
|
|
2174
|
+
"INVALID_INPUT",
|
|
2175
|
+
"provider is required for did:pkh ownership verification",
|
|
2176
|
+
{ subjectDid }
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
return verifyDidPkhOwnership(didPkhParams);
|
|
2180
|
+
}
|
|
2181
|
+
throw new OmaTrustError("INVALID_INPUT", "Unsupported DID type for ownership verification", {
|
|
2182
|
+
subjectDid
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
1621
2185
|
|
|
1622
2186
|
// src/app-registry/index.ts
|
|
1623
2187
|
var app_registry_exports = {};
|