@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.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { keccak256, toUtf8Bytes,
|
|
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");
|
|
@@ -241,8 +241,7 @@ function computeDidAddress(didHash) {
|
|
|
241
241
|
if (!/^0x[0-9a-fA-F]{64}$/.test(didHash)) {
|
|
242
242
|
throw new OmaTrustError("INVALID_DID", "didHash must be 32-byte hex", { didHash });
|
|
243
243
|
}
|
|
244
|
-
|
|
245
|
-
return getAddress(truncated);
|
|
244
|
+
return `0x${didHash.slice(-40).toLowerCase()}`;
|
|
246
245
|
}
|
|
247
246
|
function didToAddress(did) {
|
|
248
247
|
return computeDidAddress(computeDidHash(did));
|
|
@@ -356,6 +355,7 @@ function hashCanonicalizedJson(obj, algorithm) {
|
|
|
356
355
|
var reputation_exports = {};
|
|
357
356
|
__export(reputation_exports, {
|
|
358
357
|
buildDelegatedAttestationTypedData: () => buildDelegatedAttestationTypedData,
|
|
358
|
+
buildDelegatedTypedDataFromEncoded: () => buildDelegatedTypedDataFromEncoded,
|
|
359
359
|
buildDnsTxtRecord: () => buildDnsTxtRecord,
|
|
360
360
|
buildEip712Domain: () => buildEip712Domain,
|
|
361
361
|
calculateAverageUserReviewRating: () => calculateAverageUserReviewRating,
|
|
@@ -379,6 +379,7 @@ __export(reputation_exports, {
|
|
|
379
379
|
formatSchemaUid: () => formatSchemaUid,
|
|
380
380
|
formatTransferAmount: () => formatTransferAmount,
|
|
381
381
|
getAttestation: () => getAttestation,
|
|
382
|
+
getAttestationsByAttester: () => getAttestationsByAttester,
|
|
382
383
|
getAttestationsForDid: () => getAttestationsForDid,
|
|
383
384
|
getChainConstants: () => getChainConstants,
|
|
384
385
|
getExplorerAddressUrl: () => getExplorerAddressUrl,
|
|
@@ -394,16 +395,22 @@ __export(reputation_exports, {
|
|
|
394
395
|
normalizeSchema: () => normalizeSchema,
|
|
395
396
|
parseDnsTxtRecord: () => parseDnsTxtRecord,
|
|
396
397
|
prepareDelegatedAttestation: () => prepareDelegatedAttestation,
|
|
398
|
+
revokeAttestation: () => revokeAttestation,
|
|
397
399
|
schemaToString: () => schemaToString,
|
|
398
400
|
splitSignature: () => splitSignature,
|
|
399
401
|
submitAttestation: () => submitAttestation,
|
|
400
402
|
submitDelegatedAttestation: () => submitDelegatedAttestation,
|
|
403
|
+
validateAttestationData: () => validateAttestationData,
|
|
401
404
|
verifyAttestation: () => verifyAttestation,
|
|
402
405
|
verifyDidDocumentControllerDid: () => verifyDidDocumentControllerDid,
|
|
406
|
+
verifyDidJsonControllerDid: () => verifyDidJsonControllerDid,
|
|
407
|
+
verifyDidPkhOwnership: () => verifyDidPkhOwnership,
|
|
408
|
+
verifyDidWebOwnership: () => verifyDidWebOwnership,
|
|
403
409
|
verifyDnsTxtControllerDid: () => verifyDnsTxtControllerDid,
|
|
404
410
|
verifyEip712Signature: () => verifyEip712Signature,
|
|
405
411
|
verifyProof: () => verifyProof,
|
|
406
|
-
verifySchemaExists: () => verifySchemaExists
|
|
412
|
+
verifySchemaExists: () => verifySchemaExists,
|
|
413
|
+
verifySubjectOwnership: () => verifySubjectOwnership
|
|
407
414
|
});
|
|
408
415
|
function normalizeSchema(schema) {
|
|
409
416
|
if (Array.isArray(schema)) {
|
|
@@ -439,6 +446,13 @@ function encodeAttestationData(schema, data) {
|
|
|
439
446
|
if (!data || typeof data !== "object") {
|
|
440
447
|
throw new OmaTrustError("INVALID_INPUT", "data must be an object");
|
|
441
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
|
+
}
|
|
442
456
|
const fields = normalizeSchema(schema);
|
|
443
457
|
const schemaString = schemaToString(fields);
|
|
444
458
|
const encoder = new SchemaEncoder(schemaString);
|
|
@@ -451,6 +465,99 @@ function encodeAttestationData(schema, data) {
|
|
|
451
465
|
);
|
|
452
466
|
return encoded;
|
|
453
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
|
+
}
|
|
454
561
|
function decodeAttestationData(schema, encodedData) {
|
|
455
562
|
if (typeof encodedData !== "string" || !encodedData.startsWith("0x")) {
|
|
456
563
|
throw new OmaTrustError("INVALID_INPUT", "encodedData must be a hex string");
|
|
@@ -471,22 +578,11 @@ function decodeAttestationData(schema, encodedData) {
|
|
|
471
578
|
return result;
|
|
472
579
|
}
|
|
473
580
|
function extractExpirationTime(data) {
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
if (typeof value === "bigint") {
|
|
481
|
-
return value;
|
|
482
|
-
}
|
|
483
|
-
if (typeof value === "number") {
|
|
484
|
-
return value;
|
|
485
|
-
}
|
|
486
|
-
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
487
|
-
return BigInt(value);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
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);
|
|
490
586
|
return void 0;
|
|
491
587
|
}
|
|
492
588
|
var ZERO_UID = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
@@ -527,6 +623,53 @@ function resolveRecipientAddress(data) {
|
|
|
527
623
|
return ZeroAddress;
|
|
528
624
|
}
|
|
529
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
|
+
|
|
530
673
|
// src/reputation/submit.ts
|
|
531
674
|
async function submitAttestation(params) {
|
|
532
675
|
if (!params || typeof params !== "object") {
|
|
@@ -557,25 +700,46 @@ async function submitAttestation(params) {
|
|
|
557
700
|
}
|
|
558
701
|
});
|
|
559
702
|
const uid = await tx.wait();
|
|
560
|
-
const
|
|
561
|
-
const
|
|
703
|
+
const txHash = getEasTransactionHash(tx) ?? ZERO_UID;
|
|
704
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
562
705
|
return {
|
|
563
706
|
uid,
|
|
564
707
|
txHash,
|
|
565
|
-
receipt
|
|
708
|
+
receipt
|
|
566
709
|
};
|
|
567
710
|
} catch (err) {
|
|
568
711
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to submit attestation", { err });
|
|
569
712
|
}
|
|
570
713
|
}
|
|
571
|
-
function
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
);
|
|
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) {
|
|
579
743
|
return {
|
|
580
744
|
domain: {
|
|
581
745
|
name: "EAS",
|
|
@@ -600,17 +764,52 @@ function buildDelegatedAttestationTypedData(params) {
|
|
|
600
764
|
message: {
|
|
601
765
|
attester: params.attester,
|
|
602
766
|
schema: params.schemaUid,
|
|
603
|
-
recipient,
|
|
604
|
-
expirationTime:
|
|
767
|
+
recipient: params.recipient,
|
|
768
|
+
expirationTime: toBigIntOrDefault(params.expirationTime, 0n),
|
|
605
769
|
revocable: params.revocable ?? true,
|
|
606
770
|
refUID: params.refUid ?? ZERO_UID,
|
|
607
|
-
data: encodedData,
|
|
771
|
+
data: params.encodedData,
|
|
608
772
|
value: toBigIntOrDefault(params.value, 0n),
|
|
609
773
|
nonce: toBigIntOrDefault(params.nonce, 0n),
|
|
610
774
|
deadline: toBigIntOrDefault(params.deadline, BigInt(Math.floor(Date.now() / 1e3) + 600))
|
|
611
775
|
}
|
|
612
776
|
};
|
|
613
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
|
+
}
|
|
614
813
|
function splitSignature(signature) {
|
|
615
814
|
try {
|
|
616
815
|
const parsed = Signature.from(signature);
|
|
@@ -687,7 +886,14 @@ async function submitDelegatedAttestation(params) {
|
|
|
687
886
|
var EAS_EVENT_ABI = [
|
|
688
887
|
"event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
|
|
689
888
|
];
|
|
690
|
-
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) {
|
|
691
897
|
const rawData = attestation.data ?? "0x";
|
|
692
898
|
const decoded = schema ? decodeAttestationData(schema, rawData) : {};
|
|
693
899
|
const toBigIntSafe = (value) => {
|
|
@@ -707,6 +913,7 @@ function parseAttestation(attestation, schema) {
|
|
|
707
913
|
schema: attestation.schema,
|
|
708
914
|
attester: attestation.attester,
|
|
709
915
|
recipient: attestation.recipient,
|
|
916
|
+
txHash,
|
|
710
917
|
revocable: Boolean(attestation.revocable),
|
|
711
918
|
revocationTime: toBigIntSafe(attestation.revocationTime),
|
|
712
919
|
expirationTime: toBigIntSafe(attestation.expirationTime),
|
|
@@ -732,21 +939,18 @@ async function getAttestation(params) {
|
|
|
732
939
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to read attestation", { err });
|
|
733
940
|
}
|
|
734
941
|
}
|
|
735
|
-
async function
|
|
736
|
-
const
|
|
737
|
-
const
|
|
738
|
-
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
739
|
-
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
740
|
-
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);
|
|
741
945
|
let events;
|
|
742
946
|
try {
|
|
743
947
|
events = await contract.queryFilter(filter, fromBlock, toBlock);
|
|
744
948
|
} catch (err) {
|
|
745
949
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to query attestation events", { err });
|
|
746
950
|
}
|
|
747
|
-
const eas = new EAS(
|
|
951
|
+
const eas = new EAS(easContractAddress);
|
|
748
952
|
eas.connect(provider);
|
|
749
|
-
const schemaFilter =
|
|
953
|
+
const schemaFilter = options?.schemas?.map((s) => s.toLowerCase());
|
|
750
954
|
const results = [];
|
|
751
955
|
for (const event of events) {
|
|
752
956
|
if (!("args" in event && Array.isArray(event.args))) {
|
|
@@ -765,11 +969,39 @@ async function getAttestationsForDid(params) {
|
|
|
765
969
|
if (!attestation || !attestation.uid) {
|
|
766
970
|
continue;
|
|
767
971
|
}
|
|
768
|
-
results.push(parseAttestation(attestation));
|
|
972
|
+
results.push(parseAttestation(attestation, void 0, parseEventTxHash(event)));
|
|
769
973
|
}
|
|
770
974
|
results.sort((a, b) => Number(b.time - a.time));
|
|
771
975
|
return results;
|
|
772
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
|
+
}
|
|
773
1005
|
async function listAttestations(params) {
|
|
774
1006
|
const limit = params.limit ?? 20;
|
|
775
1007
|
if (limit <= 0) {
|
|
@@ -780,34 +1012,15 @@ async function listAttestations(params) {
|
|
|
780
1012
|
}
|
|
781
1013
|
async function getLatestAttestations(params) {
|
|
782
1014
|
const provider = params.provider;
|
|
783
|
-
const
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (!("args" in event && Array.isArray(event.args))) {
|
|
793
|
-
continue;
|
|
794
|
-
}
|
|
795
|
-
const args = event.args;
|
|
796
|
-
const uid = args?.[2];
|
|
797
|
-
const schemaUid = args?.[3];
|
|
798
|
-
if (!uid || !schemaUid) {
|
|
799
|
-
continue;
|
|
800
|
-
}
|
|
801
|
-
if (schemaFilter && !schemaFilter.includes(schemaUid.toLowerCase())) {
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
const attestation = await eas.getAttestation(uid);
|
|
805
|
-
if (!attestation || !attestation.uid) {
|
|
806
|
-
continue;
|
|
807
|
-
}
|
|
808
|
-
results.push(parseAttestation(attestation));
|
|
809
|
-
}
|
|
810
|
-
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
|
+
);
|
|
811
1024
|
return results.slice(0, params.limit ?? 20);
|
|
812
1025
|
}
|
|
813
1026
|
function deduplicateReviews(attestations) {
|
|
@@ -1069,6 +1282,13 @@ function verifyDidDocumentControllerDid(didDocument, expectedControllerDid) {
|
|
|
1069
1282
|
reason: `No matching address found in DID document (expected ${expectedAddress})`
|
|
1070
1283
|
};
|
|
1071
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
|
+
}
|
|
1072
1292
|
function buildEip712Domain(name, version, chainId, verifyingContract) {
|
|
1073
1293
|
return { name, version, chainId, verifyingContract };
|
|
1074
1294
|
}
|
|
@@ -1101,6 +1321,8 @@ function verifyEip712Signature(typedData, signature) {
|
|
|
1101
1321
|
throw new OmaTrustError("INVALID_INPUT", "Failed to verify EIP-712 signature", { err });
|
|
1102
1322
|
}
|
|
1103
1323
|
}
|
|
1324
|
+
|
|
1325
|
+
// src/reputation/proof/dns-txt-record.ts
|
|
1104
1326
|
function parseDnsTxtRecord(record) {
|
|
1105
1327
|
if (!record || typeof record !== "string") {
|
|
1106
1328
|
throw new OmaTrustError("INVALID_INPUT", "record must be a non-empty string", { record });
|
|
@@ -1128,27 +1350,6 @@ function buildDnsTxtRecord(controllerDid) {
|
|
|
1128
1350
|
const normalized = normalizeDid(controllerDid);
|
|
1129
1351
|
return `v=1;controller=${normalized}`;
|
|
1130
1352
|
}
|
|
1131
|
-
async function verifyDnsTxtControllerDid(domain, expectedControllerDid) {
|
|
1132
|
-
if (!domain || typeof domain !== "string") {
|
|
1133
|
-
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1134
|
-
}
|
|
1135
|
-
const expected = normalizeDid(expectedControllerDid);
|
|
1136
|
-
const host = `_omatrust.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1137
|
-
let records;
|
|
1138
|
-
try {
|
|
1139
|
-
records = await resolveTxt(host);
|
|
1140
|
-
} catch (err) {
|
|
1141
|
-
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1142
|
-
}
|
|
1143
|
-
for (const recordParts of records) {
|
|
1144
|
-
const record = recordParts.join("");
|
|
1145
|
-
const parsed = parseDnsTxtRecord(record);
|
|
1146
|
-
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1147
|
-
return { valid: true, record };
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
1151
|
-
}
|
|
1152
1353
|
|
|
1153
1354
|
// src/reputation/verify.ts
|
|
1154
1355
|
function parseChainId(input) {
|
|
@@ -1429,6 +1630,9 @@ async function getSchemaDetails(schemaRegistry, schemaUid) {
|
|
|
1429
1630
|
revocable: Boolean(details.revocable)
|
|
1430
1631
|
};
|
|
1431
1632
|
} catch (err) {
|
|
1633
|
+
if (isEasSchemaNotFoundError(err)) {
|
|
1634
|
+
throw new OmaTrustError("SCHEMA_NOT_FOUND", "Schema was not found", { schemaUid });
|
|
1635
|
+
}
|
|
1432
1636
|
if (err instanceof OmaTrustError) {
|
|
1433
1637
|
throw err;
|
|
1434
1638
|
}
|
|
@@ -1612,6 +1816,366 @@ function createEvidencePointerProof(url) {
|
|
|
1612
1816
|
issuedAt: Math.floor(Date.now() / 1e3)
|
|
1613
1817
|
};
|
|
1614
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
|
+
}
|
|
1615
2179
|
|
|
1616
2180
|
// src/app-registry/index.ts
|
|
1617
2181
|
var app_registry_exports = {};
|