From 0111cb3e48ac86f0cea715ceb5277d199f139763 Mon Sep 17 00:00:00 2001 From: RishabhS7 <59636880+RishabhS7@users.noreply.github.com> Date: Tue, 17 Jun 2025 09:51:11 +0530 Subject: [PATCH 01/27] fix: add w3c credential status check (#72) * fix: add w3c credential status check * fix: update test * fix: update enum status codes * fix: remove console --- package-lock.json | 8 +-- package.json | 2 +- src/__tests__/core/verify.test.ts | 16 +++--- src/utils/fragment/index.ts | 55 ++++++++++++++++++- .../document-status/w3cCredentialStatus.ts | 41 +++++++++++++- 5 files changed, 107 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index b680742..3f80ffa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@tradetrust-tt/token-registry-v4": "npm:@tradetrust-tt/token-registry@^4.16.0", "@tradetrust-tt/token-registry-v5": "npm:@tradetrust-tt/token-registry@^5.3.0", "@tradetrust-tt/tradetrust": "^6.10.1", - "@tradetrust-tt/tradetrust-utils": "^2.3.1", + "@tradetrust-tt/tradetrust-utils": "^2.3.2", "@tradetrust-tt/tt-verify": "^9.4.0", "@trustvc/w3c-context": "^1.2.13", "@trustvc/w3c-credential-status": "^1.2.12", @@ -5188,9 +5188,9 @@ } }, "node_modules/@tradetrust-tt/tradetrust-utils": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@tradetrust-tt/tradetrust-utils/-/tradetrust-utils-2.3.1.tgz", - "integrity": "sha512-ts0nyqEN62qpUXXSZ59nYBqajcKzMYKOi5cBpwlTYbOS4cRi6PcVZmM273xLmf2XLwNb8BA6agLpAx3ll5IWZw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@tradetrust-tt/tradetrust-utils/-/tradetrust-utils-2.3.2.tgz", + "integrity": "sha512-T3rikZ3v0qqB3xzmq+KM30OEziyesHgN0CRQrhEvLlQw5IULtmcuwLZGHLE8KyaI8Zd7mqbuubJlfyEMNXJZuA==", "dependencies": { "@tradetrust-tt/tt-verify": "^9.4.0", "dotenv": "^16.4.5", diff --git a/package.json b/package.json index 29b336a..fba04a7 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "@tradetrust-tt/token-registry-v4": "npm:@tradetrust-tt/token-registry@^4.16.0", "@tradetrust-tt/token-registry-v5": "npm:@tradetrust-tt/token-registry@^5.3.0", "@tradetrust-tt/tradetrust": "^6.10.1", - "@tradetrust-tt/tradetrust-utils": "^2.3.1", + "@tradetrust-tt/tradetrust-utils": "^2.3.2", "@tradetrust-tt/tt-verify": "^9.4.0", "@trustvc/w3c-context": "^1.2.13", "@trustvc/w3c-credential-status": "^1.2.12", diff --git a/src/__tests__/core/verify.test.ts b/src/__tests__/core/verify.test.ts index 88cda4c..6fb349b 100644 --- a/src/__tests__/core/verify.test.ts +++ b/src/__tests__/core/verify.test.ts @@ -8,6 +8,7 @@ import { WRAPPED_DOCUMENT_DID_TOKEN_REGISTRY_V3, WRAPPED_DOCUMENT_DNS_TXT_V2, } from '../fixtures/fixtures'; +import { W3CCredentialStatusCode } from 'src/verify/fragments/document-status/w3cCredentialStatus'; const providerUrl = 'https://rpc-amoy.polygon.technology'; @@ -98,7 +99,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'W3CSignatureIntegrity', reason: { - code: 0, + code: W3CCredentialStatusCode.SKIPPED, codeString: 'SKIPPED', message: "Document either has no proof or proof.type is not 'BbsBlsSignature2020'.", }, @@ -120,7 +121,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'W3CIssuerIdentity', reason: { - code: 0, + code: W3CCredentialStatusCode.SKIPPED, codeString: 'SKIPPED', message: 'Document has no issuer field.', }, @@ -188,7 +189,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'W3CCredentialStatus', reason: { - code: 0, + code: W3CCredentialStatusCode.SKIPPED, codeString: 'SKIPPED', message: 'Document does not have a valid credentialStatus or type.', }, @@ -237,12 +238,13 @@ describe.concurrent('W3C verify', () => { statusListIndex: '131072', }, }; - expect(await verifyDocument(tampered)).toEqual( expect.arrayContaining([ expect.objectContaining({ name: 'W3CCredentialStatus', reason: { + code: W3CCredentialStatusCode.UNEXPECTED_ERROR, + codeString: 'ERROR', message: 'Invalid statusListIndex: Index out of range: min=0, max=131071', }, status: 'ERROR', @@ -328,7 +330,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'TransferableRecords', reason: { - code: 9, + code: W3CCredentialStatusCode.UNRECOGNIZED_DOCUMENT, codeString: 'UNRECOGNIZED_DOCUMENT', message: "Document's credentialStatus does not have tokenRegistry", }, @@ -358,7 +360,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'TransferableRecords', reason: { - code: 9, + code: W3CCredentialStatusCode.UNRECOGNIZED_DOCUMENT, codeString: 'UNRECOGNIZED_DOCUMENT', message: "Document's credentialStatus does not have tokenNetwork.chainId", }, @@ -384,7 +386,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'TransferableRecords', reason: { - code: 1, + code: W3CCredentialStatusCode.DOCUMENT_NOT_ISSUED, codeString: 'DOCUMENT_NOT_MINTED', message: 'Document has not been issued under token registry', }, diff --git a/src/utils/fragment/index.ts b/src/utils/fragment/index.ts index ed30ae7..0d972bb 100644 --- a/src/utils/fragment/index.ts +++ b/src/utils/fragment/index.ts @@ -1 +1,54 @@ -export { errorMessageHandling, interpretFragments } from '@tradetrust-tt/tradetrust-utils'; +import { + errorMessageHandling as OAErrorMessageHandling, + CONSTANTS, + interpretFragments, +} from '@tradetrust-tt/tradetrust-utils'; +import { InvalidVerificationFragment, utils, VerificationFragment } from '@tradetrust-tt/tt-verify'; +import { W3CCredentialStatusCode } from '../../verify/fragments/document-status/w3cCredentialStatus'; +import { CredentialStatusResult } from '@trustvc/w3c-vc'; + +const getW3CCredentialStatusFragment = + utils.getFragmentByName>( + 'W3CCredentialStatus', + ); + +const w3cCredentialStatusRevoked = (fragments: VerificationFragment[]): boolean => { + const issuedFragment = getW3CCredentialStatusFragment(fragments); + return ( + issuedFragment?.reason?.code === W3CCredentialStatusCode.DOCUMENT_REVOKED || + issuedFragment?.reason?.code === W3CCredentialStatusCode.DOCUMENT_REVOKED_AND_SUSPENDED + ); +}; + +const w3cCredentialStatusSuspended = (fragments: VerificationFragment[]): boolean => { + const issuedFragment = getW3CCredentialStatusFragment(fragments); + //checking for both revoked and suspended + return ( + issuedFragment?.reason?.code === W3CCredentialStatusCode.DOCUMENT_SUSPENDED || + issuedFragment?.reason?.code === W3CCredentialStatusCode.DOCUMENT_REVOKED_AND_SUSPENDED + ); +}; + +const errorMessageHandling = (fragments: VerificationFragment[]): string[] => { + const errors = []; + const isW3cFragments = fragments.some( + (f) => f.name.startsWith('W3C') || f.name === 'TransferableRecords', + ); + if (isW3cFragments) { + if (w3cCredentialStatusRevoked(fragments)) { + errors.push(CONSTANTS.TYPES.REVOKED); + } + if (w3cCredentialStatusSuspended(fragments)) { + errors.push(CONSTANTS.TYPES.SUSPENDED); + } + + return errors; + } else return OAErrorMessageHandling(fragments); +}; + +export { + interpretFragments, + errorMessageHandling, + w3cCredentialStatusRevoked, + w3cCredentialStatusSuspended, +}; diff --git a/src/verify/fragments/document-status/w3cCredentialStatus.ts b/src/verify/fragments/document-status/w3cCredentialStatus.ts index 7fc72eb..69eace2 100644 --- a/src/verify/fragments/document-status/w3cCredentialStatus.ts +++ b/src/verify/fragments/document-status/w3cCredentialStatus.ts @@ -5,14 +5,28 @@ import { } from '@trustvc/w3c-credential-status'; import { CredentialStatus, isSignedDocument, verifyCredentialStatus } from '@trustvc/w3c-vc'; import { SignedVerifiableCredential } from '../../..'; - +//w3cCredentialStatus enums +export enum W3CCredentialStatusCode { + SKIPPED = 0, + DOCUMENT_NOT_ISSUED = 1, + DOCUMENT_REVOKED = 11, + DOCUMENT_SUSPENDED = 12, + DOCUMENT_REVOKED_AND_SUSPENDED = 101, + STATUS_LIST_NOT_FOUND = 404, + INVALID_VALIDATION_METHOD = 8, + UNRECOGNIZED_DOCUMENT = 9, + SERVER_ERROR = 500, + UNEXPECTED_ERROR = 4, + INVALID_ARGUMENT = 6, + INVALID_ISSUERS = 7, +} export const w3cCredentialStatus: Verifier = { skip: async () => { return { type: 'DOCUMENT_STATUS', name: 'W3CCredentialStatus', reason: { - code: 0, + code: W3CCredentialStatusCode.SKIPPED, codeString: 'SKIPPED', message: `Document does not have a valid credentialStatus or type.`, }, @@ -47,12 +61,18 @@ export const w3cCredentialStatus: Verifier = { verifyCredentialStatus(cs, cs?.type as CredentialStatusType, verifierOptions), ), ); + const purposes = verificationResult.map((item) => item.purpose); + const hasRevocation = purposes.includes('revocation'); + const hasSuspension = purposes.includes('suspension'); + const hasRevocationAndSuspension = hasRevocation && hasSuspension; if (verificationResult.some((r) => r.error)) { return { type: 'DOCUMENT_STATUS', name: 'W3CCredentialStatus', reason: { + code: W3CCredentialStatusCode.UNEXPECTED_ERROR, + codeString: 'ERROR', message: verificationResult.map((r) => r.error).join(', '), }, data: verificationResult, @@ -71,6 +91,23 @@ export const w3cCredentialStatus: Verifier = { name: 'W3CCredentialStatus', data: verificationResult, status: 'INVALID', + reason: { + code: hasRevocationAndSuspension + ? W3CCredentialStatusCode.DOCUMENT_REVOKED_AND_SUSPENDED + : hasRevocation + ? W3CCredentialStatusCode.DOCUMENT_REVOKED + : W3CCredentialStatusCode.DOCUMENT_SUSPENDED, + codeString: hasRevocationAndSuspension + ? 'REVOKED_AND_SUSPENDED' + : hasRevocation + ? 'REVOKED' + : 'SUSPENDED', + message: hasRevocationAndSuspension + ? 'Document has been revoked and suspended.' + : hasRevocation + ? 'Document has been revoked.' + : 'Document has been suspended.', + }, }; } }, From 4b2c3d148594deaa126ee02b593c6db3bad438c1 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 17 Jun 2025 04:22:55 +0000 Subject: [PATCH 02/27] chore(release): 1.5.4 [skip ci] ## [1.5.4](https://github.com/TrustVC/trustvc/compare/v1.5.3...v1.5.4) (2025-06-17) ### Bug Fixes * add w3c credential status check ([#72](https://github.com/TrustVC/trustvc/issues/72)) ([0111cb3](https://github.com/TrustVC/trustvc/commit/0111cb3e48ac86f0cea715ceb5277d199f139763)) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b19e8de..46c0dfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.5.4](https://github.com/TrustVC/trustvc/compare/v1.5.3...v1.5.4) (2025-06-17) + + +### Bug Fixes + +* add w3c credential status check ([#72](https://github.com/TrustVC/trustvc/issues/72)) ([0111cb3](https://github.com/TrustVC/trustvc/commit/0111cb3e48ac86f0cea715ceb5277d199f139763)) + ## [1.5.3](https://github.com/TrustVC/trustvc/compare/v1.5.2...v1.5.3) (2025-06-13) diff --git a/package-lock.json b/package-lock.json index 3f80ffa..213cde8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@trustvc/trustvc", - "version": "1.5.3", + "version": "1.5.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@trustvc/trustvc", - "version": "1.5.3", + "version": "1.5.4", "license": "Apache-2.0", "dependencies": { "@tradetrust-tt/dnsprove": "^2.17.0", diff --git a/package.json b/package.json index fba04a7..8479438 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@trustvc/trustvc", - "version": "1.5.3", + "version": "1.5.4", "description": "TrustVC library", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", From 3c6c9c73675ec43e0514d9afe5df4444f6e4400b Mon Sep 17 00:00:00 2001 From: Ng Han Inn <43451336+nghaninn@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:04:15 +0800 Subject: [PATCH 03/27] fix: upgrade package (#73) --- package-lock.json | 305 +++++++++++----------------------------------- package.json | 8 +- 2 files changed, 75 insertions(+), 238 deletions(-) diff --git a/package-lock.json b/package-lock.json index 213cde8..eefe953 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,18 +10,18 @@ "license": "Apache-2.0", "dependencies": { "@tradetrust-tt/dnsprove": "^2.17.0", - "@tradetrust-tt/ethers-aws-kms-signer": "^2.1.3", + "@tradetrust-tt/ethers-aws-kms-signer": "^2.1.4", "@tradetrust-tt/token-registry-v4": "npm:@tradetrust-tt/token-registry@^4.16.0", "@tradetrust-tt/token-registry-v5": "npm:@tradetrust-tt/token-registry@^5.3.0", "@tradetrust-tt/tradetrust": "^6.10.1", "@tradetrust-tt/tradetrust-utils": "^2.3.2", "@tradetrust-tt/tt-verify": "^9.4.0", "@trustvc/w3c-context": "^1.2.13", - "@trustvc/w3c-credential-status": "^1.2.12", - "@trustvc/w3c-issuer": "^1.2.3", + "@trustvc/w3c-credential-status": "^1.2.13", + "@trustvc/w3c-issuer": "^1.2.4", "@trustvc/w3c-vc": "^1.2.17", "ethers": "^5.8.0", - "ethersV6": "npm:ethers@^6.14.3", + "ethersV6": "npm:ethers@^6.14.4", "js-sha3": "^0.9.3", "ts-chacha20": "^1.2.0" }, @@ -214,24 +214,24 @@ } }, "node_modules/@aws-sdk/client-kms": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-kms/-/client-kms-3.826.0.tgz", - "integrity": "sha512-97UFikkyIbeul0USWmn5TE+L41uufkhPt8ASHX6wFrkQ7iWvSXO1JJym0jNJeg5TWBVYUUZARZSl8/a+QiG7Lg==", + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-kms/-/client-kms-3.830.0.tgz", + "integrity": "sha512-y24IvqKn21nF13tkhsKMCKcz0/z6UEePvpV5JAWwZSUmNJj2spagucD/ksykNqBPalbNZIuTicUqtHyBtYAWYQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.826.0", - "@aws-sdk/credential-provider-node": "3.826.0", + "@aws-sdk/credential-provider-node": "3.830.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.826.0", + "@aws-sdk/middleware-user-agent": "3.828.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.826.0", + "@aws-sdk/util-user-agent-node": "3.828.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", @@ -264,9 +264,9 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.826.0.tgz", - "integrity": "sha512-/FEKnUC3xPkLL4RuRydwzx+y4b55HIX6qLPbGnyIs+sNmCUyc/62ijtV1Ml+b++YzEF6jWNBsJOxeyZdgrJ3Ig==", + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.830.0.tgz", + "integrity": "sha512-5zCEpfI+zwX2SIa258L+TItNbBoAvQQ6w74qdFM6YJufQ1F9tvwjTX8T+eSTT9nsFIvfYnUaGalWwJVfmJUgVQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -275,12 +275,12 @@ "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.826.0", + "@aws-sdk/middleware-user-agent": "3.828.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.826.0", + "@aws-sdk/util-user-agent-node": "3.828.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", @@ -376,18 +376,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.826.0.tgz", - "integrity": "sha512-g7n+qSklq/Lzjxe2Ke5QFNCgYn26a3ydZnbFIk8QqYin4pzG+qiunaqJjpV3c/EeHMlfK8bBc7MXAylKzGRccQ==", + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.830.0.tgz", + "integrity": "sha512-zeQenzvh8JRY5nULd8izdjVGoCM1tgsVVsrLSwDkHxZTTW0hW/bmOmXfvdaE0wDdomXW7m2CkQDSmP7XdvNXZg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/credential-provider-env": "3.826.0", "@aws-sdk/credential-provider-http": "3.826.0", "@aws-sdk/credential-provider-process": "3.826.0", - "@aws-sdk/credential-provider-sso": "3.826.0", - "@aws-sdk/credential-provider-web-identity": "3.826.0", - "@aws-sdk/nested-clients": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.830.0", + "@aws-sdk/credential-provider-web-identity": "3.830.0", + "@aws-sdk/nested-clients": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", @@ -400,17 +400,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.826.0.tgz", - "integrity": "sha512-UfIJXxHjmSxH6bea00HBPLkjNI2D04enQA/xNLZvB+4xtzt1/gYdCis1P4/73f5aGVVVB4/zQMobBbnjkrmbQw==", + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.830.0.tgz", + "integrity": "sha512-X/2LrTgwtK1pkWrvofxQBI8VTi6QVLtSMpsKKPPnJQ0vgqC0e4czSIs3ZxiEsOkCBaQ2usXSiKyh0ccsQ6k2OA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "3.826.0", "@aws-sdk/credential-provider-http": "3.826.0", - "@aws-sdk/credential-provider-ini": "3.826.0", + "@aws-sdk/credential-provider-ini": "3.830.0", "@aws-sdk/credential-provider-process": "3.826.0", - "@aws-sdk/credential-provider-sso": "3.826.0", - "@aws-sdk/credential-provider-web-identity": "3.826.0", + "@aws-sdk/credential-provider-sso": "3.830.0", + "@aws-sdk/credential-provider-web-identity": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", @@ -440,14 +440,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.826.0.tgz", - "integrity": "sha512-F19J3zcfoom6OnQ0MyAtvduVKQXPgkz9i5ExSO01J2CzjbyMhCDA99qAjHYe+LwhW+W7P/jzBPd0+uOQ2Nhh9Q==", + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.830.0.tgz", + "integrity": "sha512-+VdRpZmfekzpySqZikAKx6l5ndnLGluioIgUG4ZznrButgFD/iogzFtGmBDFB3ZLViX1l4pMXru0zFwJEZT21Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.826.0", + "@aws-sdk/client-sso": "3.830.0", "@aws-sdk/core": "3.826.0", - "@aws-sdk/token-providers": "3.826.0", + "@aws-sdk/token-providers": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -459,13 +459,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.826.0.tgz", - "integrity": "sha512-o27GZ6Hy7qhuvMFVUL2eFEpBzf33Jaa/x3u3SHwU0nL7ko7jmbpeF0x4+wmagpI9X2IvVlUxIs0VaQ3YayPLEA==", + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.830.0.tgz", + "integrity": "sha512-hPYrKsZeeOdLROJ59T6Y8yZ0iwC/60L3qhZXjapBFjbqBtMaQiMTI645K6xVXBioA6vxXq7B4aLOhYqk6Fy/Ww==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.826.0", - "@aws-sdk/nested-clients": "3.826.0", + "@aws-sdk/nested-clients": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", @@ -520,14 +520,14 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.826.0.tgz", - "integrity": "sha512-j404+EcfBbtTlAhyObjXbdKwwDXO1pCxHvR5Fw8FXNvp/H330j6YnXgs3SJ6d3bZUwUJ/ztPx2S5AlBbLVLDFw==", + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.828.0.tgz", + "integrity": "sha512-nixvI/SETXRdmrVab4D9LvXT3lrXkwAWGWk2GVvQvzlqN1/M/RfClj+o37Sn4FqRkGH9o9g7Fqb1YqZ4mqDAtA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", "@smithy/core": "^3.5.3", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", @@ -538,9 +538,9 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.826.0.tgz", - "integrity": "sha512-p7olPq0uTtHqGuXI1GSc/gzKDvV55PMbLtnmupEDfnY9SoRu+QatbWQ6da9sI1lhOcNmRMgiNQBXFzaUFrG+SQ==", + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.830.0.tgz", + "integrity": "sha512-5N5YTlBr1vtxf7+t+UaIQ625KEAmm7fY9o1e3MgGOi/paBoI0+axr3ud24qLIy0NSzFlAHEaxUSWxcERNjIoZw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -549,12 +549,12 @@ "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", - "@aws-sdk/middleware-user-agent": "3.826.0", + "@aws-sdk/middleware-user-agent": "3.828.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", - "@aws-sdk/util-endpoints": "3.821.0", + "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", - "@aws-sdk/util-user-agent-node": "3.826.0", + "@aws-sdk/util-user-agent-node": "3.828.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", @@ -604,13 +604,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.826.0.tgz", - "integrity": "sha512-iCOcVAqGPSHtQL8ZBXifZMEcHyUl9wJ8HvLZ5l1ohA/3ZNP+dqEPGi7jfhR5jZKs+xyp2jxByFqfil9PjI9c5A==", + "version": "3.830.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.830.0.tgz", + "integrity": "sha512-aJ4guFwj92nV9D+EgJPaCFKK0I3y2uMchiDfh69Zqnmwfxxxfxat6F79VA7PS0BdbjRfhLbn+Ghjftnomu2c1g==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.826.0", - "@aws-sdk/nested-clients": "3.826.0", + "@aws-sdk/nested-clients": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -635,9 +635,9 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.821.0.tgz", - "integrity": "sha512-Uknt/zUZnLE76zaAAPEayOeF5/4IZ2puTFXvcSCWHsi9m3tqbb9UozlnlVqvCZLCRWfQryZQoG2W4XSS3qgk5A==", + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.828.0.tgz", + "integrity": "sha512-RvKch111SblqdkPzg3oCIdlGxlQs+k+P7Etory9FmxPHyPDvsP1j1c74PmgYqtzzMWmoXTjd+c9naUHh9xG8xg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.821.0", @@ -674,12 +674,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.826.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.826.0.tgz", - "integrity": "sha512-wHw6bZQWIMcFF/8r03aY9Itp6JLBYY4absGGhCDK1dc3tPEfi8NVSdb05a/Oz+g4TVaDdxLo0OQ/OKMS1DFRHQ==", + "version": "3.828.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.828.0.tgz", + "integrity": "sha512-LdN6fTBzTlQmc8O8f1wiZN0qF3yBWVGis7NwpWK7FUEzP9bEZRxYfIkV9oV9zpt6iNRze1SedK3JQVB/udxBoA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.826.0", + "@aws-sdk/middleware-user-agent": "3.828.0", "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", @@ -5105,12 +5105,12 @@ } }, "node_modules/@tradetrust-tt/ethers-aws-kms-signer": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@tradetrust-tt/ethers-aws-kms-signer/-/ethers-aws-kms-signer-2.1.3.tgz", - "integrity": "sha512-CdF4jFHDFZPVDBhJMP2wU7nywtM6xmBDfgaWdjmwW/0dQbEZ1AJ6UzJKsrFYEtNybUDFtsDcyxtuja84AQzqqQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@tradetrust-tt/ethers-aws-kms-signer/-/ethers-aws-kms-signer-2.1.4.tgz", + "integrity": "sha512-bwsPF9TOlkXUICwUIt3FJCBQZ8zJTbT3AlPdj+7dGEgPTY6sAH71BzFYFQ1aJnZht1Sww6y8ix14FK4M54lRsQ==", "license": "MIT", "dependencies": { - "@aws-sdk/client-kms": "^3.826.0", + "@aws-sdk/client-kms": "^3.830.0", "@ethersproject/abstract-provider": "^5.8.0", "@ethersproject/abstract-signer": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -5118,9 +5118,8 @@ "@ethersproject/keccak256": "^5.8.0", "@ethersproject/properties": "^5.8.0", "@ethersproject/transactions": "^5.8.0", - "@types/node": "^18.19.111", + "@types/node": "^18.19.112", "asn1.js": "^5.4.1", - "aws-sdk": "^2.1692.0", "bn.js": "^5.2.2", "debug": "^4.4.1" }, @@ -5398,9 +5397,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.19.111", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.111.tgz", - "integrity": "sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw==", + "version": "18.19.112", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.112.tgz", + "integrity": "sha512-i+Vukt9POdS/MBI7YrrkkI5fMfwFtOjphSmt4WXYLfwqsfr6z/HdCx7LqT9M7JktGob8WNgj8nFB4TbGNE4Cog==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -6211,69 +6210,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-sdk": { - "version": "2.1692.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1692.0.tgz", - "integrity": "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "buffer": "4.9.2", - "events": "1.1.1", - "ieee754": "1.1.13", - "jmespath": "0.16.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "util": "^0.12.4", - "uuid": "8.0.0", - "xml2js": "0.6.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aws-sdk/node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "license": "MIT", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/aws-sdk/node_modules/ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "license": "BSD-3-Clause" - }, - "node_modules/aws-sdk/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/aws-sdk/node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/aws-sdk/node_modules/uuid": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", - "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -6314,6 +6250,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -8998,9 +8935,9 @@ }, "node_modules/ethersV6": { "name": "ethers", - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.3.tgz", - "integrity": "sha512-qq7ft/oCJohoTcsNPFaXSQUm457MA5iWqkf1Mb11ujONdg7jBI6sAOrHaTi3j0CBqIGFSCeR/RMc+qwRRub7IA==", + "version": "6.14.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.4.tgz", + "integrity": "sha512-Jm/dzRs2Z9iBrT6e9TvGxyb5YVKAPLlpna7hjxH7KH/++DSh2T/JVmQUv7iHI5E55hDbp/gEVvstWYXVxXFzsA==", "funding": [ { "type": "individual", @@ -9121,15 +9058,6 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true }, - "node_modules/events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", - "license": "MIT", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/execa": { "version": "9.5.2", "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", @@ -10502,22 +10430,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -11120,15 +11032,6 @@ "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, - "node_modules/jmespath": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", - "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -16996,12 +16899,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", - "license": "ISC" - }, "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", @@ -19143,50 +19040,12 @@ "punycode": "^2.1.0" } }, - "node_modules/url": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", - "license": "MIT", - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, "node_modules/url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", - "license": "MIT" - }, - "node_modules/url/node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -20262,28 +20121,6 @@ } } }, - "node_modules/xml2js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", - "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 8479438..869ccaf 100644 --- a/package.json +++ b/package.json @@ -111,18 +111,18 @@ }, "dependencies": { "@tradetrust-tt/dnsprove": "^2.17.0", - "@tradetrust-tt/ethers-aws-kms-signer": "^2.1.3", + "@tradetrust-tt/ethers-aws-kms-signer": "^2.1.4", "@tradetrust-tt/token-registry-v4": "npm:@tradetrust-tt/token-registry@^4.16.0", "@tradetrust-tt/token-registry-v5": "npm:@tradetrust-tt/token-registry@^5.3.0", "@tradetrust-tt/tradetrust": "^6.10.1", "@tradetrust-tt/tradetrust-utils": "^2.3.2", "@tradetrust-tt/tt-verify": "^9.4.0", "@trustvc/w3c-context": "^1.2.13", - "@trustvc/w3c-credential-status": "^1.2.12", - "@trustvc/w3c-issuer": "^1.2.3", + "@trustvc/w3c-credential-status": "^1.2.13", + "@trustvc/w3c-issuer": "^1.2.4", "@trustvc/w3c-vc": "^1.2.17", "ethers": "^5.8.0", - "ethersV6": "npm:ethers@^6.14.3", + "ethersV6": "npm:ethers@^6.14.4", "js-sha3": "^0.9.3", "ts-chacha20": "^1.2.0" }, From c125eb29f432269ea28e34d072d3f54d1bec3c13 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 18 Jun 2025 06:05:51 +0000 Subject: [PATCH 04/27] chore(release): 1.5.5 [skip ci] ## [1.5.5](https://github.com/TrustVC/trustvc/compare/v1.5.4...v1.5.5) (2025-06-18) ### Bug Fixes * upgrade package ([#73](https://github.com/TrustVC/trustvc/issues/73)) ([3c6c9c7](https://github.com/TrustVC/trustvc/commit/3c6c9c73675ec43e0514d9afe5df4444f6e4400b)) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46c0dfa..1ef622b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.5.5](https://github.com/TrustVC/trustvc/compare/v1.5.4...v1.5.5) (2025-06-18) + + +### Bug Fixes + +* upgrade package ([#73](https://github.com/TrustVC/trustvc/issues/73)) ([3c6c9c7](https://github.com/TrustVC/trustvc/commit/3c6c9c73675ec43e0514d9afe5df4444f6e4400b)) + ## [1.5.4](https://github.com/TrustVC/trustvc/compare/v1.5.3...v1.5.4) (2025-06-17) diff --git a/package-lock.json b/package-lock.json index eefe953..b78e0e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@trustvc/trustvc", - "version": "1.5.4", + "version": "1.5.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@trustvc/trustvc", - "version": "1.5.4", + "version": "1.5.5", "license": "Apache-2.0", "dependencies": { "@tradetrust-tt/dnsprove": "^2.17.0", diff --git a/package.json b/package.json index 869ccaf..cc57a0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@trustvc/trustvc", - "version": "1.5.4", + "version": "1.5.5", "description": "TrustVC library", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", From fcb5cb765711833f072270d72622da64a6a938f3 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Mon, 23 Jun 2025 17:24:01 +0530 Subject: [PATCH 05/27] feat: add transfer holder function --- package-lock.json | 75 ++++++++++++++++++++++ src/__tests__/core/verify.test.ts | 2 +- src/index.ts | 2 +- src/token-registry-functions/index.ts | 1 + src/token-registry-functions/transfer.ts | 82 ++++++++++++++++++++++++ 5 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/token-registry-functions/index.ts create mode 100644 src/token-registry-functions/transfer.ts diff --git a/package-lock.json b/package-lock.json index b78e0e4..dacc2c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,81 @@ "ethers": "^5.8.0" } }, + "../token-registry": { + "name": "@tradetrust-tt/token-registry", + "version": "4.0.0", + "extraneous": true, + "license": "Apache-2.0", + "dependencies": { + "@typechain/ethers-v5": "10.2.1" + }, + "devDependencies": { + "@babel/cli": "^7.13.16", + "@babel/core": "^7.14.6", + "@babel/eslint-parser": "^7.18.9", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/preset-env": "^7.14.0", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", + "@commitlint/cli": "^12.1.1", + "@commitlint/config-conventional": "^12.1.1", + "@commitlint/prompt": "^12.1.1", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.6.2", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.2", + "@nomicfoundation/hardhat-network-helpers": "^1.0.3", + "@nomicfoundation/hardhat-verify": "^2.0.8", + "@nomiclabs/hardhat-ethers": "^2.0.2", + "@openzeppelin/contracts": "^4.5.0", + "@openzeppelin/contracts-upgradeable": "^4.5.2", + "@typechain/hardhat": "^6.1.2", + "@types/chai": "^4.2.22", + "@types/chai-as-promised": "^7.1.4", + "@types/faker": "^5.5.8", + "@types/jest": "^26.0.23", + "@types/mocha": "^9.0.0", + "@types/node": "^15.14.9", + "@types/uuid": "^8.3.3", + "@typescript-eslint/eslint-plugin": "^5.30.7", + "@typescript-eslint/parser": "^5.30.7", + "babel-jest": "^26.6.3", + "babel-plugin-module-resolver": "^4.1.0", + "chai": "^4.3.4", + "chai-as-promised": "^7.1.1", + "commitizen": "^4.2.4", + "dotenv": "^8.6.0", + "eslint": "^7.29.0", + "eslint-config-airbnb-base": "^14.2.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-chai-expect": "^2.2.0", + "eslint-plugin-chai-friendly": "^0.6.0", + "eslint-plugin-import": "^2.20.0", + "eslint-plugin-prettier": "^3.1.2", + "ethers": "^5.6.9", + "faker": "^5.5.3", + "git-cz": "^4.7.6", + "hardhat": "^2.10.1", + "hardhat-gas-reporter": "^1.0.8", + "hardhat-watcher": "^2.3.0", + "jest": "^27.2.4", + "prettier": "^2.4.1", + "prettier-plugin-solidity": "^1.0.0-dev.23", + "semantic-release": "^19.0.3", + "solhint": "^3.3.6", + "solhint-plugin-prettier": "^0.0.5", + "solidity-coverage": "^0.7.21", + "solium": "^1.2.5", + "ts-node": "^10.9.1", + "typechain": "^8.1.0", + "typescript": "^4.7.4", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "ethers": ">=5.0.8" + } + }, "node_modules/@adraffy/ens-normalize": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", diff --git a/src/__tests__/core/verify.test.ts b/src/__tests__/core/verify.test.ts index 6fb349b..aa32855 100644 --- a/src/__tests__/core/verify.test.ts +++ b/src/__tests__/core/verify.test.ts @@ -8,7 +8,7 @@ import { WRAPPED_DOCUMENT_DID_TOKEN_REGISTRY_V3, WRAPPED_DOCUMENT_DNS_TXT_V2, } from '../fixtures/fixtures'; -import { W3CCredentialStatusCode } from 'src/verify/fragments/document-status/w3cCredentialStatus'; +import { W3CCredentialStatusCode } from './../../../src/verify/fragments/document-status/w3cCredentialStatus'; const providerUrl = 'https://rpc-amoy.polygon.technology'; diff --git a/src/index.ts b/src/index.ts index 0cec9fa..4728185 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ import { v5ComputeInterfaceId, } from './token-registry-v5'; export type { TypedContractMethod } from './token-registry-v5/typedContractMethod'; - +export * from './token-registry-functions'; export * from './core'; export * from './open-attestation'; export * from './verify'; diff --git a/src/token-registry-functions/index.ts b/src/token-registry-functions/index.ts new file mode 100644 index 0000000..6de95ff --- /dev/null +++ b/src/token-registry-functions/index.ts @@ -0,0 +1 @@ +export * from './transfer'; diff --git a/src/token-registry-functions/transfer.ts b/src/token-registry-functions/transfer.ts new file mode 100644 index 0000000..1663331 --- /dev/null +++ b/src/token-registry-functions/transfer.ts @@ -0,0 +1,82 @@ +import { CHAIN_ID, SUPPORTED_CHAINS } from '@tradetrust-tt/tradetrust-utils'; +import { encrypt } from 'src/core'; +import { v4Contracts } from 'src/token-registry-v4'; +import { v5Contracts, v5SupportInterfaceIds } from 'src/token-registry-v5'; +import { BigNumber, Signer } from 'ethers'; +interface TransferHolderParams { + holderAddress: string; + tokenId: string | number; + remarks?: string; +} + +interface TransferHolderOptions { + chainId?: CHAIN_ID; + isV5TT?: boolean; + maxFeePerGas?: BigNumber | string | number; + maxPriorityFeePerGas?: BigNumber | string | number; + id?: string; +} + +const transferHolder = async ( + titleEscrowAddress: string, + signer: Signer, + params: TransferHolderParams, + options: TransferHolderOptions, +) => { + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + + const { holderAddress, remarks } = params; + let { chainId, isV5TT, maxFeePerGas, maxPriorityFeePerGas } = options; + + // Connect V5 contract by default + let titleEscrowContract: v5Contracts.TitleEscrow | v4Contracts.TitleEscrow = + v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const mintableSupportInterfaceId = v5SupportInterfaceIds.TradeTrustTokenMintable; + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + type TransferHolderPayload = [string] | [string, string]; + let payload: TransferHolderPayload = [holderAddress, encryptedRemarks] as [string, string]; + + // Detect version if not explicitly provided + if (isV5TT === undefined) { + isV5TT = await titleEscrowContract.supportsInterface(mintableSupportInterfaceId); + payload = [holderAddress] as [string]; + } + + // Switch to V4 if needed + if (!isV5TT) { + titleEscrowContract = v4Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + } + + // Check callStatic (dry run) + try { + // @ts-expect-error TS2556: Ignoring spread argument tuple requirement for payload + + (await titleEscrowContract.callStatic.transferHolder(...payload)) as v4Contracts.TitleEscrow; + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for transferHolder failed'); + } + + // If gas values are missing, query gas station if available + if (!maxFeePerGas || !maxPriorityFeePerGas) { + chainId = chainId ?? ((await signer.getChainId()) as unknown as CHAIN_ID); + const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; + + if (gasStation) { + const gasFees = await gasStation(); + maxFeePerGas = gasFees?.maxFeePerGas ?? 0; + maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; + } + } + + const txOptions = + maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; + + // Send the actual transaction + // @ts-expect-error TS2556: Ignoring spread argument tuple requirement for payload + + return await titleEscrowContract.transferHolder(...payload, txOptions); +}; + +export { transferHolder }; From 28682cd19bd401e98a93220656cd0cf0ba0ccc21 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Thu, 26 Jun 2025 23:48:14 +0530 Subject: [PATCH 06/27] feat: add transfer owners beneficiary --- .../transfers.test.ts | 945 ++++++++++++++++++ .../endorsement-chain/useEndorsementChain.ts | 3 +- src/token-registry-functions/transfer.ts | 509 +++++++++- tsconfig.json | 3 +- 4 files changed, 1432 insertions(+), 28 deletions(-) create mode 100644 src/__tests__/token-registry-functions/transfers.test.ts diff --git a/src/__tests__/token-registry-functions/transfers.test.ts b/src/__tests__/token-registry-functions/transfers.test.ts new file mode 100644 index 0000000..4615145 --- /dev/null +++ b/src/__tests__/token-registry-functions/transfers.test.ts @@ -0,0 +1,945 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { ethers as ethersV5, Wallet as WalletV5 } from 'ethers'; +import { + ethers as ethersV6, + JsonRpcProvider as JsonRpcProviderV6, + Network, + Wallet as WalletV6, +} from 'ethersV6'; +import * as coreModule from 'src/core'; +import { encrypt } from 'src/core'; +import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; +import { + transferBeneficiary, + transferHolder, + transferOwners, + nominate, +} from 'src/token-registry-functions'; + +// Mocks + +vi.mock('src/core', () => ({ + encrypt: vi.fn(() => 'encrypted_remarks'), + getTitleEscrowAddress: vi.fn(() => Promise.resolve('0xv5contract')), + isTitleEscrowVersion: vi.fn(() => Promise.resolve(true)), + TitleEscrowInterface: { + V4: '0xTitleEscrowIdV4', + V5: '0xTitleEscrowIdV5', + }, +})); + +vi.mock('src/token-registry-v5', () => { + return { + v5Contracts: { + TitleEscrow__factory: { + connect: vi.fn(() => mockV5TitleEscrowContract), + }, + TradeTrustToken__factory: { + connect: vi.fn(() => mockV5TradeTrustTokenContract), + }, + TitleEscrowFactory__factory: { + connect: vi.fn(() => mockV5TitleEscrowFactoryContract), + }, + }, + v5SupportInterfaceIds: { + TitleEscrow: '0xTitleEscrowIdV5', + TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV5', + }, + }; +}); + +vi.mock('src/token-registry-v4', () => { + return { + v4Contracts: { + TitleEscrow__factory: { + connect: vi.fn(() => mockV4TitleEscrowContract), + }, + TradeTrustToken__factory: { + connect: vi.fn(() => mockV4TradeTrustTokenContract), + }, + TitleEscrowFactory__factory: { + connect: vi.fn(() => mockV4TitleEscrowFactoryContract), + }, + v4SupportInterfaceIds: { + TitleEscrow: '0xTitleEscrowIdV4', + TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV4', + }, + }, + }; +}); + +const mockV5TitleEscrowFactoryContract = { + callStatic: { + getEscrowAddress: vi.fn(), + }, + getEscrowAddress: vi.fn(() => Promise.resolve('0xV5titleescrow')), +}; + +const mockV5TradeTrustTokenContract = { + supportsInterface: vi.fn(), + titleEscrowFactory: vi.fn(() => Promise.resolve('0xV5titleescrowfactory')), +}; + +const mockV5TitleEscrowContract = { + supportsInterface: vi.fn(), + callStatic: { + transferHolder: vi.fn(), + transferBeneficiary: vi.fn(), + transferOwners: vi.fn(), + nominate: vi.fn(), + }, + transferHolder: vi.fn(() => Promise.resolve('v5_transfer_holder_tx_hash')), + transferBeneficiary: vi.fn(() => Promise.resolve('v5_transfer_beneficiary_tx_hash')), + transferOwners: vi.fn(() => Promise.resolve('v5_transfer_owners_tx_hash')), + nominate: vi.fn(() => Promise.resolve('v5_nominate_tx_hash')), + holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), + beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), +}; + +const mockV4TitleEscrowContract = { + callStatic: { + transferHolder: vi.fn(), + transferBeneficiary: vi.fn(), + transferOwners: vi.fn(), + nominate: vi.fn(), + }, + transferHolder: vi.fn(() => Promise.resolve('v4_transfer_holder_tx_hash')), + transferBeneficiary: vi.fn(() => Promise.resolve('v4_transfer_beneficiary_tx_hash')), + transferOwners: vi.fn(() => Promise.resolve('v4_transfer_owners_tx_hash')), + nominate: vi.fn(() => Promise.resolve('v4_nominate_tx_hash')), + holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), + beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), +}; +const mockV4TitleEscrowFactoryContract = { + callStatic: { + getEscrowAddress: vi.fn(), + }, + getAddress: vi.fn(() => Promise.resolve('0xV4titleescrow')), +}; + +const mockV4TradeTrustTokenContract = { + titleEscrowFactory: vi.fn(() => Promise.resolve('0xV4titleescrowfactory')), +}; + +const PRIVATE_KEY = '0x59c6995e998f97a5a004497e5f1ebce0c16828d44b3f8d0bfa3a89d271d5b6b9'; // random local key + +const providerV5 = new ethersV5.providers.JsonRpcProvider(); +const providerV6 = new JsonRpcProviderV6(); + +interface ProviderInfo { + Provider: typeof providerV5 | typeof providerV6; + ethersVersion: 'v5' | 'v6'; + titleEscrowVersion: 'v4' | 'v5'; +} + +const providers: ProviderInfo[] = [ + { + Provider: providerV5, + ethersVersion: 'v5', + titleEscrowVersion: 'v5', + }, + { + Provider: providerV5, + ethersVersion: 'v5', + titleEscrowVersion: 'v4', + }, + { + Provider: providerV6, + ethersVersion: 'v6', + titleEscrowVersion: 'v5', + }, + { + Provider: providerV6, + ethersVersion: 'v6', + titleEscrowVersion: 'v4', + }, +]; +describe.each(providers)( + `transfer holder with TR Version $titleEscrowVersion and ethers version $ethersVersion`, + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + beforeEach(() => { + vi.clearAllMocks(); + }); + let wallet: ethersV5.Wallet | ethersV6.Wallet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; + // wallet = { + // ...wallet, + // address: '0xcurrent_holder', + // getChainId: vi.fn().mockResolvedValue(CHAIN_ID.mainnet as unknown as number), + // } as any; + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.mainnet, + } as unknown as Network); + // vi.spyOn(wallet, 'getAddress').mockResolvedValue('0xcurrent_holder'); + } + const isV5TT = titleEscrowVersion === 'v5'; + const mockTitleEscrowContract = isV5TT ? mockV5TitleEscrowContract : mockV4TitleEscrowContract; + const params = isV5TT + ? { + holderAddress: '0xholder', + remarks: '0xencrypted_remarks', + tokenId: 1, + } + : { + holderAddress: '0xholder', + tokenId: 1, + }; + const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + const txHash = isV5TT ? 'v5_transfer_holder_tx_hash' : 'v4_transfer_holder_tx_hash'; + + it('throws error if titleEscrowAddress is missing ', async () => { + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockImplementation(() => Promise.resolve('')); + + await expect( + transferHolder( + { + titleEscrowAddress: '', + }, + wallet, + params, + { titleEscrowVersion }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('handles both v5 and v4 contracts when TR version is not provided', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.transferHolder.mockResolvedValue(true); + + const tx = await transferHolder( + { + titleEscrowAddress: titleEscrowAddress, + }, + wallet, + params, + { id: 'doc-id', chainId: CHAIN_ID.mainnet }, + ); + if (isV5TT) expect(encrypt).toHaveBeenCalledWith('0xencrypted_remarks', 'doc-id'); + + const resultOptions = isV5TT + ? ['0xholder', '0xencrypted_remarks', undefined] + : ['0xholder', undefined]; + + expect(mockTitleEscrowContract.transferHolder).toHaveBeenCalledWith(...resultOptions); + expect(tx).toBe(txHash); + }); + + it(`detects version automatically via supportsInterface for ${titleEscrowAddress}`, async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.transferHolder.mockResolvedValue(true); + + const tx = await transferHolder( + { + titleEscrowAddress: '0xauto', + }, + wallet, + params, + {}, // no isV5TT provided + ); + + expect(coreModule.isTitleEscrowVersion).toHaveBeenCalledWith({ + provider: wallet.provider, + titleEscrowAddress: '0xauto', + versionInterface: isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4', + }); + expect(tx).toBe(txHash); + }); + + it('calls gas station when gas options are missing', async () => { + const gasStationMock = vi.fn().mockResolvedValue({ + maxFeePerGas: 100, + maxPriorityFeePerGas: 50, + }); + + const mockChainId = CHAIN_ID.mainnet; + + // to allow overriding + const { SUPPORTED_CHAINS: SUPPORTED_CHAINS } = await import( + '@tradetrust-tt/tradetrust-utils' + ); + const originalChainData = SUPPORTED_CHAINS[mockChainId].gasStation; + SUPPORTED_CHAINS[mockChainId] = { + ...SUPPORTED_CHAINS[mockChainId], + gasStation: gasStationMock, + }; + + mockTitleEscrowContract.callStatic.transferHolder.mockResolvedValue(true); + + await transferHolder( + { + titleEscrowAddress: '0xv5contract', + }, + wallet, + params, + { id: 'doc-id', titleEscrowVersion }, + ); + const resultOptions = isV5TT ? ['0xholder', '0xencrypted_remarks'] : ['0xholder']; + + expect(gasStationMock).toHaveBeenCalled(); + expect(mockTitleEscrowContract.transferHolder).toHaveBeenCalledWith(...resultOptions, { + maxFeePerGas: 100, + maxPriorityFeePerGas: 50, + }); + + SUPPORTED_CHAINS[mockChainId] = { + ...SUPPORTED_CHAINS[mockChainId], + gasStation: originalChainData, + }; + }); + + it('throws error when callStatic fails', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.transferHolder.mockRejectedValue( + new Error('Simulated failure'), + ); + + await expect( + transferHolder( + { + titleEscrowAddress: '0xv5contract', + }, + wallet, + params, + { id: 'doc-id', titleEscrowVersion }, + ), + ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + }); + + it('handles both v5 and v4 contracts when tokenId and tokenregistry is provided', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockImplementation(() => + Promise.resolve(titleEscrowAddress), + ); + mockTitleEscrowContract.callStatic.transferHolder.mockResolvedValue(true); + + const tx = await transferHolder( + { + tokenId: 1, + tokenRegistryAddress: '0xtokenregistry', + }, + wallet, + params, + { id: 'doc-id', chainId: CHAIN_ID.mainnet }, + ); + if (isV5TT) expect(encrypt).toHaveBeenCalledWith('0xencrypted_remarks', 'doc-id'); + + const resultOptions = isV5TT + ? ['0xholder', '0xencrypted_remarks', undefined] + : ['0xholder', undefined]; + + expect(mockTitleEscrowContract.transferHolder).toHaveBeenCalledWith(...resultOptions); + expect(tx).toBe(txHash); + }); + }, +); + +describe.each(providers)( + `transfer beneficiary with TR Version $titleEscrowVersion and ethers version $ethersVersion`, + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + beforeEach(() => { + vi.clearAllMocks(); + }); + let wallet: ethersV5.Wallet | ethersV6.Wallet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; + // wallet = { + // ...wallet, + // address: '0xcurrent_holder', + // getChainId: vi.fn().mockResolvedValue(CHAIN_ID.mainnet as unknown as number), + // } as any; + + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.mainnet, + } as unknown as Network); + // vi.spyOn(wallet, 'getAddress').mockResolvedValue('0xcurrent_holder'); + } + + // if (ethersVersion === 'v5') { + // wallet = new WalletV5(PRIVATE_KEY, Provider as any); + // vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); + // } else { + // wallet = new WalletV6(PRIVATE_KEY, Provider as any); + // vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + // chainId: CHAIN_ID.mainnet, + // } as unknown as Network); + // } + const isV5TT = titleEscrowVersion === 'v5'; + const mockTitleEscrowContract = isV5TT ? mockV5TitleEscrowContract : mockV4TitleEscrowContract; + const params = isV5TT + ? { newBeneficiaryAddress: '0xbeneficiary', remarks: '0xencrypted_remarks', tokenId: 1 } + : { newBeneficiaryAddress: '0xbeneficiary', tokenId: 1 }; + const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + const txHash = isV5TT ? 'v5_transfer_beneficiary_tx_hash' : 'v4_transfer_beneficiary_tx_hash'; + + it('throws error if titleEscrowAddress is missing ', async () => { + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockImplementation(() => Promise.resolve('')); + + await expect( + transferBeneficiary( + { + titleEscrowAddress: '', + }, + wallet, + params, + { titleEscrowVersion }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('handles both v5 and v4 contracts when TR version is not provided', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.transferBeneficiary.mockResolvedValue(true); + + const tx = await transferBeneficiary( + { + titleEscrowAddress: titleEscrowAddress, + }, + wallet, + params, + { id: 'doc-id', chainId: CHAIN_ID.mainnet }, + ); + if (isV5TT) expect(encrypt).toHaveBeenCalledWith('0xencrypted_remarks', 'doc-id'); + + const resultOptions = isV5TT + ? ['0xbeneficiary', '0xencrypted_remarks', undefined] + : ['0xbeneficiary', undefined]; + + expect(mockTitleEscrowContract.transferBeneficiary).toHaveBeenCalledWith(...resultOptions); + expect(tx).toBe(txHash); + }); + + it(`detects version automatically via supportsInterface for ${titleEscrowVersion}`, async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.transferBeneficiary.mockResolvedValue(true); + + const tx = await transferBeneficiary( + { + titleEscrowAddress: '0xauto', + }, + wallet, + params, + {}, // no isV5TT provided + ); + + expect(coreModule.isTitleEscrowVersion).toHaveBeenCalledWith({ + provider: wallet.provider, + titleEscrowAddress: '0xauto', + versionInterface: isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4', + }); + expect(tx).toBe(txHash); + }); + + it('calls gas station when gas options are missing', async () => { + const gasStationMock = vi.fn().mockResolvedValue({ + maxFeePerGas: 100, + maxPriorityFeePerGas: 50, + }); + + const mockChainId = CHAIN_ID.mainnet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any); + vi.spyOn(wallet, 'getChainId').mockResolvedValue(mockChainId as unknown as number); + } else { + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: mockChainId, + } as unknown as Network); + } + + // to allow overriding + const { SUPPORTED_CHAINS: SUPPORTED_CHAINS } = await import( + '@tradetrust-tt/tradetrust-utils' + ); + const originalChainData = SUPPORTED_CHAINS[mockChainId].gasStation; + SUPPORTED_CHAINS[mockChainId] = { + ...SUPPORTED_CHAINS[mockChainId], + gasStation: gasStationMock, + }; + + mockTitleEscrowContract.callStatic.transferBeneficiary.mockResolvedValue(true); + + await transferBeneficiary( + { + titleEscrowAddress: '0xv5contract', + }, + wallet, + params, + { id: 'doc-id', titleEscrowVersion }, + ); + const resultOptions = isV5TT ? ['0xbeneficiary', '0xencrypted_remarks'] : ['0xbeneficiary']; + + expect(gasStationMock).toHaveBeenCalled(); + expect(mockTitleEscrowContract.transferBeneficiary).toHaveBeenCalledWith(...resultOptions, { + maxFeePerGas: 100, + maxPriorityFeePerGas: 50, + }); + + SUPPORTED_CHAINS[mockChainId] = { + ...SUPPORTED_CHAINS[mockChainId], + gasStation: originalChainData, + }; + }); + + it('throws error when callStatic fails', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.transferBeneficiary.mockRejectedValue( + new Error('Simulated failure'), + ); + + await expect( + transferBeneficiary( + { + titleEscrowAddress: '0xv5contract', + }, + wallet, + params, + { id: 'doc-id', titleEscrowVersion }, + ), + ).rejects.toThrow('Pre-check (callStatic) for transferBeneficiary failed'); + }); + + it('handles both v5 and v4 contracts when tokenId and tokenregistry is provided', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockImplementation(() => + Promise.resolve(titleEscrowAddress), + ); + mockTitleEscrowContract.callStatic.transferBeneficiary.mockResolvedValue(true); + + const tx = await transferBeneficiary( + { + tokenId: 1, + tokenRegistryAddress: '0xtokenregistry', + }, + wallet, + params, + { id: 'doc-id', chainId: CHAIN_ID.mainnet }, + ); + if (isV5TT) expect(encrypt).toHaveBeenCalledWith('0xencrypted_remarks', 'doc-id'); + + const resultOptions = isV5TT + ? ['0xbeneficiary', '0xencrypted_remarks', undefined] + : ['0xbeneficiary', undefined]; + + expect(mockTitleEscrowContract.transferBeneficiary).toHaveBeenCalledWith(...resultOptions); + expect(tx).toBe(txHash); + }); + }, +); + +describe.each(providers)( + `transfer owners with TR Version $titleEscrowVersion and ethers version $ethersVersion`, + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + beforeEach(() => { + vi.clearAllMocks(); + }); + let wallet: ethersV5.Wallet | ethersV6.Wallet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any); + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.mainnet, + } as unknown as Network); + } + const isV5TT = titleEscrowVersion === 'v5'; + const mockTitleEscrowContract = isV5TT ? mockV5TitleEscrowContract : mockV4TitleEscrowContract; + const params = isV5TT + ? { + newBeneficiaryAddress: '0xbeneficiary', + newHolderAddress: '0xholder', + remarks: '0xencrypted_remarks', + } + : { newBeneficiaryAddress: '0xbeneficiary', newHolderAddress: '0xholder' }; + const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + const txHash = isV5TT ? 'v5_transfer_owners_tx_hash' : 'v4_transfer_owners_tx_hash'; + + it('throws error if titleEscrowAddress is missing ', async () => { + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockImplementation(() => Promise.resolve('')); + + await expect( + transferOwners( + { + titleEscrowAddress: '', + }, + wallet, + params, + { titleEscrowVersion }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('handles both v5 and v4 contracts when TR version is not provided', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockImplementation(() => + Promise.resolve(titleEscrowAddress), + ); + mockTitleEscrowContract.callStatic.transferBeneficiary.mockResolvedValue(true); + + const tx = await transferOwners( + { + titleEscrowAddress: titleEscrowAddress, + }, + wallet, + params, + { id: 'doc-id', chainId: CHAIN_ID.mainnet }, + ); + if (isV5TT) expect(encrypt).toHaveBeenCalledWith('0xencrypted_remarks', 'doc-id'); + + const resultOptions = isV5TT + ? ['0xbeneficiary', '0xholder', '0xencrypted_remarks', undefined] + : ['0xbeneficiary', '0xholder', undefined]; + + expect(mockTitleEscrowContract.transferOwners).toHaveBeenCalledWith(...resultOptions); + expect(tx).toBe(txHash); + }); + + it(`detects version automatically via supportsInterface for ${titleEscrowVersion}`, async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.transferOwners.mockResolvedValue(true); + + const tx = await transferOwners( + { + titleEscrowAddress: '0xauto', + }, + wallet, + params, + {}, // no isV5TT provided + ); + + expect(coreModule.isTitleEscrowVersion).toHaveBeenCalledWith({ + provider: wallet.provider, + titleEscrowAddress: '0xauto', + versionInterface: isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4', + }); + expect(tx).toBe(txHash); + }); + + it('calls gas station when gas options are missing', async () => { + const gasStationMock = vi.fn().mockResolvedValue({ + maxFeePerGas: 100, + maxPriorityFeePerGas: 50, + }); + + const mockChainId = CHAIN_ID.mainnet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any); + vi.spyOn(wallet, 'getChainId').mockResolvedValue(mockChainId as unknown as number); + } else { + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: mockChainId, + } as unknown as Network); + } + + // to allow overriding + const { SUPPORTED_CHAINS: SUPPORTED_CHAINS } = await import( + '@tradetrust-tt/tradetrust-utils' + ); + const originalChainData = SUPPORTED_CHAINS[mockChainId].gasStation; + SUPPORTED_CHAINS[mockChainId] = { + ...SUPPORTED_CHAINS[mockChainId], + gasStation: gasStationMock, + }; + + mockTitleEscrowContract.callStatic.transferOwners.mockResolvedValue(true); + + await transferOwners( + { + titleEscrowAddress: '0xv5contract', + }, + wallet, + params, + { id: 'doc-id', titleEscrowVersion }, + ); + const resultOptions = isV5TT + ? ['0xbeneficiary', '0xholder', '0xencrypted_remarks'] + : ['0xbeneficiary', '0xholder']; + + expect(gasStationMock).toHaveBeenCalled(); + expect(mockTitleEscrowContract.transferOwners).toHaveBeenCalledWith(...resultOptions, { + maxFeePerGas: 100, + maxPriorityFeePerGas: 50, + }); + + SUPPORTED_CHAINS[mockChainId] = { + ...SUPPORTED_CHAINS[mockChainId], + gasStation: originalChainData, + }; + }); + + it('throws error when callStatic fails', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.transferOwners.mockRejectedValue( + new Error('Simulated failure'), + ); + + await expect( + transferOwners( + { + titleEscrowAddress: '0xv5contract', + }, + wallet, + params, + { id: 'doc-id', titleEscrowVersion }, + ), + ).rejects.toThrow('Pre-check (callStatic) for transferOwners failed'); + }); + + it('handles both v5 and v4 contracts when tokenId and tokenregistry is provided', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockImplementation(() => + Promise.resolve(titleEscrowAddress), + ); + mockTitleEscrowContract.callStatic.transferOwners.mockResolvedValue(true); + + const tx = await transferOwners( + { + tokenId: 1, + tokenRegistryAddress: '0xtokenregistry', + }, + wallet, + params, + { id: 'doc-id', chainId: CHAIN_ID.mainnet }, + ); + if (isV5TT) expect(encrypt).toHaveBeenCalledWith('0xencrypted_remarks', 'doc-id'); + + const resultOptions = isV5TT + ? ['0xbeneficiary', '0xholder', '0xencrypted_remarks', undefined] + : ['0xbeneficiary', '0xholder', undefined]; + + expect(mockTitleEscrowContract.transferOwners).toHaveBeenCalledWith(...resultOptions); + expect(tx).toBe(txHash); + }); + }, +); + +describe.each(providers)( + `nominate with TR Version $titleEscrowVersion and ethers version $ethersVersion`, + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + beforeEach(() => { + vi.clearAllMocks(); + }); + let wallet: ethersV5.Wallet | ethersV6.Wallet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any); + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); + // vi.spyOn(wallet, 'getSigner').mockResolvedValue(""); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.mainnet, + } as unknown as Network); + } + const isV5TT = titleEscrowVersion === 'v5'; + const mockTitleEscrowContract = isV5TT ? mockV5TitleEscrowContract : mockV4TitleEscrowContract; + const params = isV5TT + ? { newBeneficiaryAddress: '0xbeneficiary', remarks: '0xencrypted_remarks', tokenId: 1 } + : { newBeneficiaryAddress: '0xbeneficiary', tokenId: 1 }; + const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + const txHash = isV5TT ? 'v5_nominate_tx_hash' : 'v4_nominate_tx_hash'; + + it('throws error if titleEscrowAddress is missing ', async () => { + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockImplementation(() => Promise.resolve('')); + + await expect( + nominate( + { + titleEscrowAddress: '', + }, + wallet, + params, + { titleEscrowVersion }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('handles both v5 and v4 contracts when TR version is not provided', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.nominate.mockResolvedValue(true); + + const tx = await nominate( + { + titleEscrowAddress: titleEscrowAddress, + }, + wallet, + params, + { id: 'doc-id', chainId: CHAIN_ID.mainnet }, + ); + if (isV5TT) expect(encrypt).toHaveBeenCalledWith('0xencrypted_remarks', 'doc-id'); + + const resultOptions = isV5TT + ? ['0xbeneficiary', '0xencrypted_remarks', undefined] + : ['0xbeneficiary', undefined]; + + expect(mockTitleEscrowContract.nominate).toHaveBeenCalledWith(...resultOptions); + expect(tx).toBe(txHash); + }); + + it(`detects version automatically via supportsInterface for ${titleEscrowVersion}`, async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.nominate.mockResolvedValue(true); + + const tx = await nominate( + { + titleEscrowAddress: '0xauto', + }, + wallet, + params, + {}, // no isV5TT provided + ); + + expect(coreModule.isTitleEscrowVersion).toHaveBeenCalledWith({ + provider: wallet.provider, + titleEscrowAddress: '0xauto', + versionInterface: isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4', + }); + expect(tx).toBe(txHash); + }); + + it('calls gas station when gas options are missing', async () => { + const gasStationMock = vi.fn().mockResolvedValue({ + maxFeePerGas: 100, + maxPriorityFeePerGas: 50, + }); + + const mockChainId = CHAIN_ID.mainnet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any); + vi.spyOn(wallet, 'getChainId').mockResolvedValue(mockChainId as unknown as number); + } else { + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: mockChainId, + } as unknown as Network); + } + + // to allow overriding + const { SUPPORTED_CHAINS: SUPPORTED_CHAINS } = await import( + '@tradetrust-tt/tradetrust-utils' + ); + const originalChainData = SUPPORTED_CHAINS[mockChainId].gasStation; + SUPPORTED_CHAINS[mockChainId] = { + ...SUPPORTED_CHAINS[mockChainId], + gasStation: gasStationMock, + }; + + mockTitleEscrowContract.callStatic.nominate.mockResolvedValue(true); + + await nominate( + { + titleEscrowAddress: '0xv5contract', + }, + wallet, + params, + { id: 'doc-id', titleEscrowVersion }, + ); + const resultOptions = isV5TT ? ['0xbeneficiary', '0xencrypted_remarks'] : ['0xbeneficiary']; + + expect(gasStationMock).toHaveBeenCalled(); + expect(mockTitleEscrowContract.nominate).toHaveBeenCalledWith(...resultOptions, { + maxFeePerGas: 100, + maxPriorityFeePerGas: 50, + }); + + SUPPORTED_CHAINS[mockChainId] = { + ...SUPPORTED_CHAINS[mockChainId], + gasStation: originalChainData, + }; + }); + + it('throws error when callStatic fails', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + mockTitleEscrowContract.callStatic.nominate.mockRejectedValue(new Error('Simulated failure')); + + await expect( + nominate( + { + titleEscrowAddress: '0xv5contract', + }, + wallet, + params, + { id: 'doc-id', titleEscrowVersion }, + ), + ).rejects.toThrow('Pre-check (callStatic) for nominate failed'); + }); + + it('handles both v5 and v4 contracts when tokenId and tokenregistry is provided', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => + versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'), + ); + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockImplementation(() => + Promise.resolve(titleEscrowAddress), + ); + mockTitleEscrowContract.callStatic.nominate.mockResolvedValue(true); + + const tx = await nominate( + { + tokenId: 1, + tokenRegistryAddress: '0xtokenregistry', + }, + wallet, + params, + { id: 'doc-id', chainId: CHAIN_ID.mainnet }, + ); + if (isV5TT) expect(encrypt).toHaveBeenCalledWith('0xencrypted_remarks', 'doc-id'); + + const resultOptions = isV5TT + ? ['0xbeneficiary', '0xencrypted_remarks', undefined] + : ['0xbeneficiary', undefined]; + + expect(mockTitleEscrowContract.nominate).toHaveBeenCalledWith(...resultOptions); + expect(tx).toBe(txHash); + }); + }, +); diff --git a/src/core/endorsement-chain/useEndorsementChain.ts b/src/core/endorsement-chain/useEndorsementChain.ts index a6b949c..8d2f8a2 100644 --- a/src/core/endorsement-chain/useEndorsementChain.ts +++ b/src/core/endorsement-chain/useEndorsementChain.ts @@ -109,8 +109,9 @@ export const getTitleEscrowAddress = async ( titleEscrowVersion?: 'v4' | 'v5'; }, ): Promise => { + console.log('i am here 1'); const titleEscrowOwner = await getDocumentOwner(tokenRegistryAddress, tokenId, provider); - + console.log('i am here 2'); const BURN_ADDRESS = '0x000000000000000000000000000000000000dEaD'; const isInactiveEscrow = [BURN_ADDRESS, tokenRegistryAddress] .map((address) => address.toLowerCase()) diff --git a/src/token-registry-functions/transfer.ts b/src/token-registry-functions/transfer.ts index 1663331..8fd2691 100644 --- a/src/token-registry-functions/transfer.ts +++ b/src/token-registry-functions/transfer.ts @@ -1,66 +1,399 @@ import { CHAIN_ID, SUPPORTED_CHAINS } from '@tradetrust-tt/tradetrust-utils'; -import { encrypt } from 'src/core'; +import { + encrypt, + getTitleEscrowAddress, + isTitleEscrowVersion, + TitleEscrowInterface, +} from 'src/core'; import { v4Contracts } from 'src/token-registry-v4'; -import { v5Contracts, v5SupportInterfaceIds } from 'src/token-registry-v5'; +import { v5Contracts } from 'src/token-registry-v5'; +import { BigNumberish, Signer as SignerV6 } from 'ethersV6'; import { BigNumber, Signer } from 'ethers'; +import { isV6EthersProvider } from 'src/utils/ethers'; + interface TransferHolderParams { holderAddress: string; - tokenId: string | number; + remarks?: string; +} +interface TransferBeneficiaryParams { + newBeneficiaryAddress: string; + remarks?: string; +} +interface NominateParams { + newBeneficiaryAddress: string; + remarks?: string; +} +interface TransferOwnersParams { + newHolderAddress: string; + newBeneficiaryAddress: string; remarks?: string; } -interface TransferHolderOptions { +interface TransferOptions { chainId?: CHAIN_ID; - isV5TT?: boolean; - maxFeePerGas?: BigNumber | string | number; - maxPriorityFeePerGas?: BigNumber | string | number; + titleEscrowVersion?: 'v4' | 'v5'; + maxFeePerGas?: BigNumberish | string | number | BigNumber; + maxPriorityFeePerGas?: BigNumberish | string | number | BigNumber; id?: string; } +// 🔍 Handles both Ethers v5 and v6 signer types +const getChainIdSafe = async (signer: SignerV6 | Signer): Promise => { + if (isV6EthersProvider(signer.provider)) { + const network = await (signer as Signer).provider?.getNetwork(); + if (!network?.chainId) throw new Error('Cannot determine chainId: provider is missing'); + return network.chainId; + } + return await (signer as Signer).getChainId(); +}; +// const getSignerAddressSafe = async (signer: SignerV6 | Signer): Promise => { +// if (isV6EthersProvider(signer.provider)) { +// return await (signer as SignerV6).getAddress(); +// } +// return (signer as any).address; +// }; + +type ContractOptions = + | { + titleEscrowAddress: string; // Present — no restrictions on the rest + tokenId?: string | number; + tokenRegistryAddress?: string; + } + | { + titleEscrowAddress?: undefined; // Absent — must provide both below + tokenId: string | number; + tokenRegistryAddress: string; + }; + const transferHolder = async ( - titleEscrowAddress: string, - signer: Signer, + contractOptions: ContractOptions, + signer: Signer | SignerV6, params: TransferHolderParams, - options: TransferHolderOptions, + options: TransferOptions, ) => { - if (!titleEscrowAddress) throw new Error('Token registry address is required'); + const { tokenRegistryAddress, tokenId } = contractOptions; + const { titleEscrowVersion } = options; + let { titleEscrowAddress } = contractOptions; + let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + let isV5TT = titleEscrowVersion === 'v5'; + let isV4TT = titleEscrowVersion === 'v4'; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + { titleEscrowVersion }, + ); + } + + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); const { holderAddress, remarks } = params; - let { chainId, isV5TT, maxFeePerGas, maxPriorityFeePerGas } = options; // Connect V5 contract by default let titleEscrowContract: v5Contracts.TitleEscrow | v4Contracts.TitleEscrow = v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); - const mintableSupportInterfaceId = v5SupportInterfaceIds.TradeTrustTokenMintable; const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; - type TransferHolderPayload = [string] | [string, string]; - let payload: TransferHolderPayload = [holderAddress, encryptedRemarks] as [string, string]; // Detect version if not explicitly provided - if (isV5TT === undefined) { - isV5TT = await titleEscrowContract.supportsInterface(mintableSupportInterfaceId); - payload = [holderAddress] as [string]; + if (titleEscrowVersion === undefined) { + [isV4TT, isV5TT] = await Promise.all([ + isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V4, + provider: signer.provider, + }), + isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }), + ]); + } + if (!isV4TT && !isV5TT) { + throw new Error('Only Token Registry V4/V5 is supported'); + } + + // Switch to V4 if needed + if (isV4TT) { + titleEscrowContract = v4Contracts.TitleEscrow__factory.connect( + titleEscrowAddress, + signer as Signer, + ); + } + // check for current holder and signer + // const currentHolder = await titleEscrowContract.holder(); + + // if (currentHolder.toLowerCase() === holderAddress.toLowerCase()) { + // throw new Error('Cannot transfer to current holder'); + // } + // const signerAddress = await getSignerAddressSafe(signer); + // if (currentHolder.toLowerCase() !== signerAddress.toLowerCase()) { + // throw new Error('Only the current holder can transfer'); + // } + + // Check callStatic (dry run) + try { + if (isV5TT) { + await (titleEscrowContract as v5Contracts.TitleEscrow).callStatic.transferHolder( + holderAddress, + encryptedRemarks, + ); + } else if (isV4TT) { + await (titleEscrowContract as v4Contracts.TitleEscrow).callStatic.transferHolder( + holderAddress, + ); + } + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for transferHolder failed'); + } + + // If gas values are missing, query gas station if available + if (!maxFeePerGas || !maxPriorityFeePerGas) { + chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); + const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; + + if (gasStation) { + const gasFees = await gasStation(); + maxFeePerGas = gasFees?.maxFeePerGas ?? 0; + maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; + } + } + const txOptions = + maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; + // Send the actual transaction + if (isV5TT) { + return await (titleEscrowContract as v5Contracts.TitleEscrow).transferHolder( + holderAddress, + encryptedRemarks, + txOptions, + ); + } else if (isV4TT) { + return await titleEscrowContract.transferHolder(holderAddress, txOptions); + } +}; + +const transferBeneficiary = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: TransferBeneficiaryParams, + options: TransferOptions, +) => { + const { tokenId, tokenRegistryAddress } = contractOptions; + const { titleEscrowVersion } = options; + let { titleEscrowAddress } = contractOptions; + let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + + let isV5TT = titleEscrowVersion === 'v5'; + let isV4TT = titleEscrowVersion === 'v4'; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + { titleEscrowVersion }, + ); + } + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { newBeneficiaryAddress, remarks } = params; + + // Connect V5 contract by default + let titleEscrowContract: v5Contracts.TitleEscrow | v4Contracts.TitleEscrow = + v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + if (titleEscrowVersion === undefined) { + [isV4TT, isV5TT] = await Promise.all([ + isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V4, + provider: signer.provider, + }), + isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }), + ]); + } + if (!isV4TT && !isV5TT) { + throw new Error('Only Token Registry V4/V5 is supported'); } // Switch to V4 if needed if (!isV5TT) { - titleEscrowContract = v4Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + titleEscrowContract = v4Contracts.TitleEscrow__factory.connect( + titleEscrowAddress, + signer as Signer, + ); } + // check for current beneficiary and signer + // const currentHolder = await titleEscrowContract.holder(); + // const currentBeneficiary = await titleEscrowContract.beneficiary(); + // if (currentBeneficiary.toLowerCase() === newBeneficiaryAddress.toLowerCase()) { + // throw new Error('Cannot transfer to current beneficiary'); + // } + // const signerAddress = await signer.getAddress(); + // if (currentHolder.toLowerCase() !== signerAddress.toLowerCase()) { + // throw new Error('Only the current holder can transfer'); + // } + // Check callStatic (dry run) try { - // @ts-expect-error TS2556: Ignoring spread argument tuple requirement for payload + if (isV5TT) { + await (titleEscrowContract as v5Contracts.TitleEscrow).callStatic.transferBeneficiary( + newBeneficiaryAddress, + encryptedRemarks, + ); + } else if (isV4TT) { + await (titleEscrowContract as v4Contracts.TitleEscrow).callStatic.transferBeneficiary( + newBeneficiaryAddress, + ); + } + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for transferBeneficiary failed'); + } + + // If gas values are missing, query gas station if available + if (!maxFeePerGas || !maxPriorityFeePerGas) { + chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); + const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; + + if (gasStation) { + const gasFees = await gasStation(); + maxFeePerGas = gasFees?.maxFeePerGas ?? 0; + maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; + } + } + + const txOptions = + maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; + console.log('txoption', txOptions, isV4TT, isV5TT); + // Send the actual transaction + if (isV5TT) { + const tx = await (titleEscrowContract as v5Contracts.TitleEscrow).transferBeneficiary( + newBeneficiaryAddress, + encryptedRemarks, + txOptions, + ); + console.log('isV5TT', tx); + return tx; + } else if (isV4TT) { + const tx = await (titleEscrowContract as v4Contracts.TitleEscrow).transferBeneficiary( + newBeneficiaryAddress, + txOptions, + ); + console.log('isV4TT', tx); + return tx; + } +}; +const transferOwners = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: TransferOwnersParams, + options: TransferOptions, +) => { + const { tokenId, tokenRegistryAddress } = contractOptions; + const { titleEscrowVersion } = options; + let { titleEscrowAddress } = contractOptions; + let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + let isV5TT = titleEscrowVersion === 'v5'; + let isV4TT = titleEscrowVersion === 'v4'; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + { titleEscrowVersion }, + ); + } + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { newBeneficiaryAddress, newHolderAddress, remarks } = params; + + // Connect V5 contract by default + let titleEscrowContract: v5Contracts.TitleEscrow | v4Contracts.TitleEscrow = + v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + if (titleEscrowVersion === undefined) { + [isV4TT, isV5TT] = await Promise.all([ + isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V4, + provider: signer.provider, + }), + isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }), + ]); + } + if (!isV4TT && !isV5TT) { + throw new Error('Only Token Registry V4/V5 is supported'); + } + + // Switch to V4 if needed + if (!isV5TT) { + titleEscrowContract = v4Contracts.TitleEscrow__factory.connect( + titleEscrowAddress, + signer as Signer, + ); + } + + // check for current beneficiary, holder and signer + // const currentHolder = await titleEscrowContract.holder(); + // const currentBeneficiary = await titleEscrowContract.beneficiary(); + // if (currentBeneficiary.toLowerCase() === newBeneficiaryAddress.toLowerCase()) { + // throw new Error('Cannot transfer to current beneficiary'); + // } + // if (currentHolder.toLowerCase() === newHolderAddress.toLowerCase()) { + // throw new Error('Cannot transfer to current holder'); + // } + // const signerAddress = await signer.getAddress(); + // if ( + // currentHolder.toLowerCase() !== signerAddress.toLowerCase() || + // currentBeneficiary.toLowerCase() !== signerAddress.toLowerCase() + // ) { + // throw new Error('Holder and Beneficiary must be current signer'); + // } - (await titleEscrowContract.callStatic.transferHolder(...payload)) as v4Contracts.TitleEscrow; + // Check callStatic (dry run) + try { + if (isV5TT) { + await (titleEscrowContract as v5Contracts.TitleEscrow).callStatic.transferOwners( + newBeneficiaryAddress, + newHolderAddress, + encryptedRemarks, + ); + } else if (isV4TT) { + await (titleEscrowContract as v4Contracts.TitleEscrow).callStatic.transferOwners( + newBeneficiaryAddress, + newHolderAddress, + ); + } } catch (e) { console.error('callStatic failed:', e); - throw new Error('Pre-check (callStatic) for transferHolder failed'); + throw new Error('Pre-check (callStatic) for transferOwners failed'); } // If gas values are missing, query gas station if available if (!maxFeePerGas || !maxPriorityFeePerGas) { - chainId = chainId ?? ((await signer.getChainId()) as unknown as CHAIN_ID); + chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; if (gasStation) { @@ -72,11 +405,135 @@ const transferHolder = async ( const txOptions = maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; + // Send the actual transaction + + if (isV5TT) { + return await (titleEscrowContract as v5Contracts.TitleEscrow).transferOwners( + newBeneficiaryAddress, + newHolderAddress, + encryptedRemarks, + txOptions, + ); + } else if (isV4TT) { + return await (titleEscrowContract as v4Contracts.TitleEscrow).transferOwners( + newBeneficiaryAddress, + newHolderAddress, + txOptions, + ); + } +}; + +const nominate = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: NominateParams, + options: TransferOptions, +) => { + const { tokenId, tokenRegistryAddress } = contractOptions; + const { titleEscrowVersion } = options; + let { titleEscrowAddress } = contractOptions; + let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + + let isV5TT = titleEscrowVersion === 'v5'; + let isV4TT = titleEscrowVersion === 'v4'; + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + { titleEscrowVersion }, + ); + } + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { newBeneficiaryAddress, remarks } = params; + + // Connect V5 contract by default + let titleEscrowContract: v5Contracts.TitleEscrow | v4Contracts.TitleEscrow = + v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + if (titleEscrowVersion === undefined) { + [isV4TT, isV5TT] = await Promise.all([ + isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V4, + provider: signer.provider, + }), + isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }), + ]); + } + + // Switch to V4 if needed + if (!isV5TT) { + titleEscrowContract = v4Contracts.TitleEscrow__factory.connect( + titleEscrowAddress, + signer as Signer, + ); + } + + // check for current beneficiary and signer + // const currentBeneficiary = await titleEscrowContract.beneficiary(); + // const signerAddress = await signer.getAddress(); + // if (currentBeneficiary.toLowerCase() !== signerAddress.toLowerCase()) { + // throw new Error('Beneficiary must be current signer'); + // } + // if (currentBeneficiary.toLowerCase() === newBeneficiaryAddress.toLowerCase()) { + // throw new Error('Cannot nominate to current beneficiary'); + // } + + // Check callStatic (dry run) + try { + if (isV5TT) { + await (titleEscrowContract as v5Contracts.TitleEscrow).callStatic.nominate( + newBeneficiaryAddress, + encryptedRemarks, + ); + } else if (isV4TT) { + await (titleEscrowContract as v4Contracts.TitleEscrow).callStatic.nominate( + newBeneficiaryAddress, + ); + } + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for nominate failed'); + } + + // If gas values are missing, query gas station if available + if (!maxFeePerGas || !maxPriorityFeePerGas) { + chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); + const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; + + if (gasStation) { + const gasFees = await gasStation(); + maxFeePerGas = gasFees?.maxFeePerGas ?? 0; + maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; + } + } + + const txOptions = + maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; // Send the actual transaction - // @ts-expect-error TS2556: Ignoring spread argument tuple requirement for payload - return await titleEscrowContract.transferHolder(...payload, txOptions); + if (isV5TT) { + return await (titleEscrowContract as v5Contracts.TitleEscrow).nominate( + newBeneficiaryAddress, + encryptedRemarks, + txOptions, + ); + } else if (isV4TT) { + return await (titleEscrowContract as v4Contracts.TitleEscrow).nominate( + newBeneficiaryAddress, + txOptions, + ); + } }; -export { transferHolder }; +export { transferHolder, transferBeneficiary, transferOwners, nominate }; diff --git a/tsconfig.json b/tsconfig.json index 06eebbb..cb5de71 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,8 @@ "skipLibCheck": true, "noUncheckedIndexedAccess": true, "noEmit": true, - "downlevelIteration": true + "downlevelIteration": true, + "types": ["vitest"] }, "buildOptions": { "outDir": "./dist", From ce8e23e6f5a24aebfc2b13aa13b7ab98db76e38e Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Fri, 27 Jun 2025 08:49:52 +0530 Subject: [PATCH 07/27] chore: tests cleanup --- .../transfers.test.ts | 145 +++++------------- 1 file changed, 37 insertions(+), 108 deletions(-) diff --git a/src/__tests__/token-registry-functions/transfers.test.ts b/src/__tests__/token-registry-functions/transfers.test.ts index 4615145..c87447c 100644 --- a/src/__tests__/token-registry-functions/transfers.test.ts +++ b/src/__tests__/token-registry-functions/transfers.test.ts @@ -154,30 +154,31 @@ const providers: ProviderInfo[] = [ titleEscrowVersion: 'v4', }, ]; -describe.each(providers)( - `transfer holder with TR Version $titleEscrowVersion and ethers version $ethersVersion`, - async ({ Provider, ethersVersion, titleEscrowVersion }) => { - beforeEach(() => { - vi.clearAllMocks(); - }); - let wallet: ethersV5.Wallet | ethersV6.Wallet; - if (ethersVersion === 'v5') { - wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; - // wallet = { - // ...wallet, - // address: '0xcurrent_holder', - // getChainId: vi.fn().mockResolvedValue(CHAIN_ID.mainnet as unknown as number), - // } as any; - vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); - } else { - wallet = new WalletV6(PRIVATE_KEY, Provider as any); - vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ - chainId: CHAIN_ID.mainnet, - } as unknown as Network); - // vi.spyOn(wallet, 'getAddress').mockResolvedValue('0xcurrent_holder'); - } - const isV5TT = titleEscrowVersion === 'v5'; - const mockTitleEscrowContract = isV5TT ? mockV5TitleEscrowContract : mockV4TitleEscrowContract; +describe.each(providers)('Transfers', async ({ Provider, ethersVersion, titleEscrowVersion }) => { + beforeEach(() => { + vi.clearAllMocks(); + }); + let wallet: ethersV5.Wallet | ethersV6.Wallet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; + // wallet = { + // ...wallet, + // address: '0xcurrent_holder', + // getChainId: vi.fn().mockResolvedValue(CHAIN_ID.mainnet as unknown as number), + // } as any; + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.mainnet, + } as unknown as Network); + // vi.spyOn(wallet, 'getAddress').mockResolvedValue('0xcurrent_holder'); + } + const isV5TT = titleEscrowVersion === 'v5'; + const mockTitleEscrowContract = isV5TT ? mockV5TitleEscrowContract : mockV4TitleEscrowContract; + const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + + describe(`transfer holder with TR Version $titleEscrowVersion and ethers version $ethersVersion`, () => { const params = isV5TT ? { holderAddress: '0xholder', @@ -188,7 +189,7 @@ describe.each(providers)( holderAddress: '0xholder', tokenId: 1, }; - const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + const txHash = isV5TT ? 'v5_transfer_holder_tx_hash' : 'v4_transfer_holder_tx_hash'; it('throws error if titleEscrowAddress is missing ', async () => { @@ -346,48 +347,13 @@ describe.each(providers)( expect(mockTitleEscrowContract.transferHolder).toHaveBeenCalledWith(...resultOptions); expect(tx).toBe(txHash); }); - }, -); + }); -describe.each(providers)( - `transfer beneficiary with TR Version $titleEscrowVersion and ethers version $ethersVersion`, - async ({ Provider, ethersVersion, titleEscrowVersion }) => { - beforeEach(() => { - vi.clearAllMocks(); - }); - let wallet: ethersV5.Wallet | ethersV6.Wallet; - if (ethersVersion === 'v5') { - wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; - // wallet = { - // ...wallet, - // address: '0xcurrent_holder', - // getChainId: vi.fn().mockResolvedValue(CHAIN_ID.mainnet as unknown as number), - // } as any; - - vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); - } else { - wallet = new WalletV6(PRIVATE_KEY, Provider as any); - vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ - chainId: CHAIN_ID.mainnet, - } as unknown as Network); - // vi.spyOn(wallet, 'getAddress').mockResolvedValue('0xcurrent_holder'); - } - - // if (ethersVersion === 'v5') { - // wallet = new WalletV5(PRIVATE_KEY, Provider as any); - // vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); - // } else { - // wallet = new WalletV6(PRIVATE_KEY, Provider as any); - // vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ - // chainId: CHAIN_ID.mainnet, - // } as unknown as Network); - // } - const isV5TT = titleEscrowVersion === 'v5'; - const mockTitleEscrowContract = isV5TT ? mockV5TitleEscrowContract : mockV4TitleEscrowContract; + describe(`transfer beneficiary with TR Version $titleEscrowVersion and ethers version $ethersVersion`, () => { const params = isV5TT ? { newBeneficiaryAddress: '0xbeneficiary', remarks: '0xencrypted_remarks', tokenId: 1 } : { newBeneficiaryAddress: '0xbeneficiary', tokenId: 1 }; - const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + const txHash = isV5TT ? 'v5_transfer_beneficiary_tx_hash' : 'v4_transfer_beneficiary_tx_hash'; it('throws error if titleEscrowAddress is missing ', async () => { @@ -553,27 +519,9 @@ describe.each(providers)( expect(mockTitleEscrowContract.transferBeneficiary).toHaveBeenCalledWith(...resultOptions); expect(tx).toBe(txHash); }); - }, -); + }); -describe.each(providers)( - `transfer owners with TR Version $titleEscrowVersion and ethers version $ethersVersion`, - async ({ Provider, ethersVersion, titleEscrowVersion }) => { - beforeEach(() => { - vi.clearAllMocks(); - }); - let wallet: ethersV5.Wallet | ethersV6.Wallet; - if (ethersVersion === 'v5') { - wallet = new WalletV5(PRIVATE_KEY, Provider as any); - vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); - } else { - wallet = new WalletV6(PRIVATE_KEY, Provider as any); - vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ - chainId: CHAIN_ID.mainnet, - } as unknown as Network); - } - const isV5TT = titleEscrowVersion === 'v5'; - const mockTitleEscrowContract = isV5TT ? mockV5TitleEscrowContract : mockV4TitleEscrowContract; + describe(`transfer owners with TR Version $titleEscrowVersion and ethers version $ethersVersion`, () => { const params = isV5TT ? { newBeneficiaryAddress: '0xbeneficiary', @@ -581,7 +529,7 @@ describe.each(providers)( remarks: '0xencrypted_remarks', } : { newBeneficiaryAddress: '0xbeneficiary', newHolderAddress: '0xholder' }; - const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + const txHash = isV5TT ? 'v5_transfer_owners_tx_hash' : 'v4_transfer_owners_tx_hash'; it('throws error if titleEscrowAddress is missing ', async () => { @@ -752,32 +700,13 @@ describe.each(providers)( expect(mockTitleEscrowContract.transferOwners).toHaveBeenCalledWith(...resultOptions); expect(tx).toBe(txHash); }); - }, -); + }); -describe.each(providers)( - `nominate with TR Version $titleEscrowVersion and ethers version $ethersVersion`, - async ({ Provider, ethersVersion, titleEscrowVersion }) => { - beforeEach(() => { - vi.clearAllMocks(); - }); - let wallet: ethersV5.Wallet | ethersV6.Wallet; - if (ethersVersion === 'v5') { - wallet = new WalletV5(PRIVATE_KEY, Provider as any); - vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); - // vi.spyOn(wallet, 'getSigner').mockResolvedValue(""); - } else { - wallet = new WalletV6(PRIVATE_KEY, Provider as any); - vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ - chainId: CHAIN_ID.mainnet, - } as unknown as Network); - } - const isV5TT = titleEscrowVersion === 'v5'; - const mockTitleEscrowContract = isV5TT ? mockV5TitleEscrowContract : mockV4TitleEscrowContract; + describe(`nominate with TR Version $titleEscrowVersion and ethers version $ethersVersion`, () => { const params = isV5TT ? { newBeneficiaryAddress: '0xbeneficiary', remarks: '0xencrypted_remarks', tokenId: 1 } : { newBeneficiaryAddress: '0xbeneficiary', tokenId: 1 }; - const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + const txHash = isV5TT ? 'v5_nominate_tx_hash' : 'v4_nominate_tx_hash'; it('throws error if titleEscrowAddress is missing ', async () => { @@ -941,5 +870,5 @@ describe.each(providers)( expect(mockTitleEscrowContract.nominate).toHaveBeenCalledWith(...resultOptions); expect(tx).toBe(txHash); }); - }, -); + }); +}); From 9ed24b5f8c792c2f46fb5d446778951c90ef8918 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Fri, 27 Jun 2025 08:56:20 +0530 Subject: [PATCH 08/27] chore: tests cleanup --- src/core/endorsement-chain/useEndorsementChain.ts | 2 -- tsconfig.json | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/endorsement-chain/useEndorsementChain.ts b/src/core/endorsement-chain/useEndorsementChain.ts index 8d2f8a2..e4e4e34 100644 --- a/src/core/endorsement-chain/useEndorsementChain.ts +++ b/src/core/endorsement-chain/useEndorsementChain.ts @@ -109,9 +109,7 @@ export const getTitleEscrowAddress = async ( titleEscrowVersion?: 'v4' | 'v5'; }, ): Promise => { - console.log('i am here 1'); const titleEscrowOwner = await getDocumentOwner(tokenRegistryAddress, tokenId, provider); - console.log('i am here 2'); const BURN_ADDRESS = '0x000000000000000000000000000000000000dEaD'; const isInactiveEscrow = [BURN_ADDRESS, tokenRegistryAddress] .map((address) => address.toLowerCase()) diff --git a/tsconfig.json b/tsconfig.json index cb5de71..06eebbb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,8 +25,7 @@ "skipLibCheck": true, "noUncheckedIndexedAccess": true, "noEmit": true, - "downlevelIteration": true, - "types": ["vitest"] + "downlevelIteration": true }, "buildOptions": { "outDir": "./dist", From e76df5c530e1e2e7ddde0a2e593a22c0af7d03cb Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Fri, 27 Jun 2025 09:06:24 +0530 Subject: [PATCH 09/27] fix: remove console --- src/__tests__/core/verify.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/core/verify.test.ts b/src/__tests__/core/verify.test.ts index aa32855..6fb349b 100644 --- a/src/__tests__/core/verify.test.ts +++ b/src/__tests__/core/verify.test.ts @@ -8,7 +8,7 @@ import { WRAPPED_DOCUMENT_DID_TOKEN_REGISTRY_V3, WRAPPED_DOCUMENT_DNS_TXT_V2, } from '../fixtures/fixtures'; -import { W3CCredentialStatusCode } from './../../../src/verify/fragments/document-status/w3cCredentialStatus'; +import { W3CCredentialStatusCode } from 'src/verify/fragments/document-status/w3cCredentialStatus'; const providerUrl = 'https://rpc-amoy.polygon.technology'; From 1bcf3ab4d5ba7aef756149de228bf063a9e25f90 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Fri, 27 Jun 2025 09:07:43 +0530 Subject: [PATCH 10/27] fix: rever useendorement chain --- src/core/endorsement-chain/useEndorsementChain.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/endorsement-chain/useEndorsementChain.ts b/src/core/endorsement-chain/useEndorsementChain.ts index e4e4e34..a6b949c 100644 --- a/src/core/endorsement-chain/useEndorsementChain.ts +++ b/src/core/endorsement-chain/useEndorsementChain.ts @@ -110,6 +110,7 @@ export const getTitleEscrowAddress = async ( }, ): Promise => { const titleEscrowOwner = await getDocumentOwner(tokenRegistryAddress, tokenId, provider); + const BURN_ADDRESS = '0x000000000000000000000000000000000000dEaD'; const isInactiveEscrow = [BURN_ADDRESS, tokenRegistryAddress] .map((address) => address.toLowerCase()) From b8e2685a3bded6a25f875b26952b86750fe37d1c Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Fri, 27 Jun 2025 09:28:24 +0530 Subject: [PATCH 11/27] fix: remove console --- src/token-registry-functions/transfer.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/token-registry-functions/transfer.ts b/src/token-registry-functions/transfer.ts index 8fd2691..5247762 100644 --- a/src/token-registry-functions/transfer.ts +++ b/src/token-registry-functions/transfer.ts @@ -278,7 +278,6 @@ const transferBeneficiary = async ( const txOptions = maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; - console.log('txoption', txOptions, isV4TT, isV5TT); // Send the actual transaction if (isV5TT) { const tx = await (titleEscrowContract as v5Contracts.TitleEscrow).transferBeneficiary( @@ -286,14 +285,12 @@ const transferBeneficiary = async ( encryptedRemarks, txOptions, ); - console.log('isV5TT', tx); return tx; } else if (isV4TT) { const tx = await (titleEscrowContract as v4Contracts.TitleEscrow).transferBeneficiary( newBeneficiaryAddress, txOptions, ); - console.log('isV4TT', tx); return tx; } }; From 1d1f38eeb6983ab43f72846e9fcdc6941e29125a Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Mon, 30 Jun 2025 18:11:46 +0530 Subject: [PATCH 12/27] feat: add reject transfer functions --- .../token-registry-functions/fixtures.ts | 117 +++++ .../rejectTransfers.test.ts | 438 ++++++++++++++++++ .../transfers.test.ts | 132 +----- src/token-registry-functions/index.ts | 1 + .../rejectTransfers.ts | 217 +++++++++ src/token-registry-functions/transfer.ts | 220 ++++----- src/token-registry-functions/types.ts | 53 +++ src/token-registry-functions/utils.ts | 44 ++ 8 files changed, 981 insertions(+), 241 deletions(-) create mode 100644 src/__tests__/token-registry-functions/fixtures.ts create mode 100644 src/__tests__/token-registry-functions/rejectTransfers.test.ts create mode 100644 src/token-registry-functions/rejectTransfers.ts create mode 100644 src/token-registry-functions/types.ts create mode 100644 src/token-registry-functions/utils.ts diff --git a/src/__tests__/token-registry-functions/fixtures.ts b/src/__tests__/token-registry-functions/fixtures.ts new file mode 100644 index 0000000..0745a5e --- /dev/null +++ b/src/__tests__/token-registry-functions/fixtures.ts @@ -0,0 +1,117 @@ +import { vi } from 'vitest'; +import { ethers as ethersV5 } from 'ethers'; +import { JsonRpcProvider as JsonRpcProviderV6 } from 'ethersV6'; + +vi.mock('src/core', () => ({ + encrypt: vi.fn(() => 'encrypted_remarks'), + getTitleEscrowAddress: vi.fn(() => Promise.resolve('0xv5contract')), + isTitleEscrowVersion: vi.fn(() => Promise.resolve(true)), + TitleEscrowInterface: { + V4: '0xTitleEscrowIdV4', + V5: '0xTitleEscrowIdV5', + }, +})); + +vi.mock('src/token-registry-v5', () => { + return { + v5Contracts: { + TitleEscrow__factory: { + connect: vi.fn(() => mockV5TitleEscrowContract), + }, + TradeTrustToken__factory: { + connect: vi.fn(() => mockV5TradeTrustTokenContract), + }, + TitleEscrowFactory__factory: { + connect: vi.fn(() => mockV5TitleEscrowFactoryContract), + }, + }, + v5SupportInterfaceIds: { + TitleEscrow: '0xTitleEscrowIdV5', + TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV5', + }, + }; +}); + +vi.mock('src/token-registry-v4', () => { + return { + v4Contracts: { + TitleEscrow__factory: { + connect: vi.fn(() => mockV4TitleEscrowContract), + }, + TradeTrustToken__factory: { + connect: vi.fn(() => mockV4TradeTrustTokenContract), + }, + TitleEscrowFactory__factory: { + connect: vi.fn(() => mockV4TitleEscrowFactoryContract), + }, + v4SupportInterfaceIds: { + TitleEscrow: '0xTitleEscrowIdV4', + TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV4', + }, + }, + }; +}); + +export const mockV5TitleEscrowFactoryContract = { + callStatic: { + getEscrowAddress: vi.fn(), + }, + getEscrowAddress: vi.fn(() => Promise.resolve('0xV5titleescrow')), +}; + +export const mockV5TradeTrustTokenContract = { + supportsInterface: vi.fn(), + titleEscrowFactory: vi.fn(() => Promise.resolve('0xV5titleescrowfactory')), +}; + +export const mockV5TitleEscrowContract = { + supportsInterface: vi.fn(), + callStatic: { + transferHolder: vi.fn(), + transferBeneficiary: vi.fn(), + transferOwners: vi.fn(), + nominate: vi.fn(), + rejectTransferHolder: vi.fn(), + rejectTransferBeneficiary: vi.fn(), + rejectTransferOwners: vi.fn(), + }, + transferHolder: vi.fn(() => Promise.resolve('v5_transfer_holder_tx_hash')), + transferBeneficiary: vi.fn(() => Promise.resolve('v5_transfer_beneficiary_tx_hash')), + transferOwners: vi.fn(() => Promise.resolve('v5_transfer_owners_tx_hash')), + nominate: vi.fn(() => Promise.resolve('v5_nominate_tx_hash')), + holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), + beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), + rejectTransferHolder: vi.fn(() => Promise.resolve('v5_reject_transfer_holder_tx_hash')), + rejectTransferBeneficiary: vi.fn(() => Promise.resolve('v5_reject_transfer_beneficiary_tx_hash')), + rejectTransferOwners: vi.fn(() => Promise.resolve('v5_reject_transfer_owners_tx_hash')), +}; + +export const mockV4TitleEscrowContract = { + callStatic: { + transferHolder: vi.fn(), + transferBeneficiary: vi.fn(), + transferOwners: vi.fn(), + nominate: vi.fn(), + }, + transferHolder: vi.fn(() => Promise.resolve('v4_transfer_holder_tx_hash')), + transferBeneficiary: vi.fn(() => Promise.resolve('v4_transfer_beneficiary_tx_hash')), + transferOwners: vi.fn(() => Promise.resolve('v4_transfer_owners_tx_hash')), + nominate: vi.fn(() => Promise.resolve('v4_nominate_tx_hash')), + holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), + beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), +}; +export const mockV4TitleEscrowFactoryContract = { + callStatic: { + getEscrowAddress: vi.fn(), + }, + getAddress: vi.fn(() => Promise.resolve('0xV4titleescrow')), +}; + +export const mockV4TradeTrustTokenContract = { + titleEscrowFactory: vi.fn(() => Promise.resolve('0xV4titleescrowfactory')), +}; + +export const PRIVATE_KEY = '0x59c6995e998f97a5a004497e5f1ebce0c16828d44b3f8d0bfa3a89d271d5b6b9'; // random local key + +export const providerV5 = new ethersV5.providers.JsonRpcProvider(); +export const providerV6 = new JsonRpcProviderV6(); diff --git a/src/__tests__/token-registry-functions/rejectTransfers.test.ts b/src/__tests__/token-registry-functions/rejectTransfers.test.ts new file mode 100644 index 0000000..9af0fcc --- /dev/null +++ b/src/__tests__/token-registry-functions/rejectTransfers.test.ts @@ -0,0 +1,438 @@ +import './fixtures.js'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { ethers as ethersV5, Wallet as WalletV5 } from 'ethers'; +import { ethers as ethersV6, Network, Wallet as WalletV6 } from 'ethersV6'; +import * as coreModule from 'src/core'; +import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; +import { + rejectTransferBeneficiary, + rejectTransferHolder, + rejectTransferOwners, +} from 'src/token-registry-functions/rejectTransfers'; +import { mockV5TitleEscrowContract, PRIVATE_KEY, providerV5, providerV6 } from './fixtures'; +import { ProviderInfo } from 'src/token-registry-functions/types.js'; + +const providers: ProviderInfo[] = [ + { + Provider: providerV5, + ethersVersion: 'v5', + titleEscrowVersion: 'v5', + }, + { + Provider: providerV6, + ethersVersion: 'v6', + titleEscrowVersion: 'v5', + }, +]; + +describe.each(providers)( + 'Reject Transfers', + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + const mockTokenRegistryAddress = '0xTokenRegistry'; + const mockTokenId = '0xTokenId'; + const mockTitleEscrowAddress = '0xTitleEscrow'; + const mockRemarks = 'Rejection remarks'; + const mockChainId = CHAIN_ID.local; + const mockEncryptedRemarks = '0xencryptedRemarks'; + + let wallet: ethersV5.Wallet | ethersV6.Wallet; + beforeEach(() => { + // Reset all mocks before each test + vi.clearAllMocks(); + + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; + // wallet = { + // ...wallet, + // address: '0xcurrent_holder', + // getChainId: vi.fn().mockResolvedValue(CHAIN_ID.mainnet as unknown as number), + // } as any; + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.mainnet, + } as unknown as Network); + // vi.spyOn(wallet, 'getAddress').mockResolvedValue('0xcurrent_holder'); + } + + // Mock core functions + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockResolvedValue(mockTitleEscrowAddress); + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockResolvedValue(true); + vi.spyOn(coreModule, 'encrypt').mockReturnValue(mockEncryptedRemarks.slice(2)); + + // Mock contract calls + }); + describe(`Reject Transfers Holder with ethers version ${ethersVersion}`, () => { + it('should reject transfer holder with signer and all required parameters', async () => { + const result = await rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_holder_tx_hash'); + }); + + it('should reject transfer holder when titleEscrowAddress is provided', async () => { + const result = await rejectTransferHolder( + { + titleEscrowAddress: mockTitleEscrowAddress, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_holder_tx_hash'); + expect(coreModule.getTitleEscrowAddress).not.toHaveBeenCalled(); + }); + + it('should reject transfer holder without remarks', async () => { + const result = await rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + {}, + { chainId: mockChainId }, + ); + + expect(result).toEqual('v5_reject_transfer_holder_tx_hash'); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw error when tokenRegistryAddress is missing', async () => { + vi.mocked(coreModule.getTitleEscrowAddress).mockResolvedValue(undefined); + await expect( + rejectTransferHolder( + { + tokenId: mockTokenId, + } as any, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('should throw error when provider is missing', async () => { + const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1')); + + await expect( + rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + signerWithoutProvider, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw error when title escrow is not V5', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockResolvedValue(false); + + await expect( + rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Only Token Registry V5 is supported'); + }); + + it('should throw error when callStatic fails', async () => { + mockV5TitleEscrowContract.callStatic.rejectTransferHolder.mockRejectedValue( + new Error('Simulated failure'), + ); + + await expect( + rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + mockV5TitleEscrowContract.callStatic.rejectTransferHolder = vi.fn(); + }); + + it('should use explicit titleEscrowVersion when provided', async () => { + await rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(coreModule.isTitleEscrowVersion).not.toHaveBeenCalled(); + }); + }); + + describe(`Reject Transfers Beneficiary with ethers version ${ethersVersion}`, () => { + it('should reject transfer beneficiary with signer and all required parameters', async () => { + const result = await rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_beneficiary_tx_hash'); + }); + + it('should reject transfer beneficiary when titleEscrowAddress is provided', async () => { + const result = await rejectTransferBeneficiary( + { + titleEscrowAddress: mockTitleEscrowAddress, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_beneficiary_tx_hash'); + expect(coreModule.getTitleEscrowAddress).not.toHaveBeenCalled(); + }); + + it('should reject transfer beneficiary without remarks', async () => { + const result = await rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + {}, + { chainId: mockChainId }, + ); + + expect(result).toEqual('v5_reject_transfer_beneficiary_tx_hash'); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw error when tokenRegistryAddress is missing', async () => { + vi.mocked(coreModule.getTitleEscrowAddress).mockResolvedValue(undefined); + await expect( + rejectTransferBeneficiary( + { + tokenId: mockTokenId, + } as any, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('should throw error when provider is missing', async () => { + const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1')); + + await expect( + rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + signerWithoutProvider, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw error when title escrow is not V5', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockResolvedValue(false); + + await expect( + rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Only Token Registry V5 is supported'); + }); + + it('should throw error when callStatic fails', async () => { + mockV5TitleEscrowContract.callStatic.rejectTransferBeneficiary.mockRejectedValue( + new Error('Simulated failure'), + ); + + await expect( + rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + mockV5TitleEscrowContract.callStatic.rejectTransferBeneficiary = vi.fn(); + }); + + it('should use explicit titleEscrowVersion when provided', async () => { + await rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(coreModule.isTitleEscrowVersion).not.toHaveBeenCalled(); + }); + }); + + describe(`Reject Transfers Owners with ethers version ${ethersVersion}`, () => { + it('should reject transfer beneficiary with signer and all required parameters', async () => { + const result = await rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_owners_tx_hash'); + }); + + it('should reject transfer beneficiary when titleEscrowAddress is provided', async () => { + const result = await rejectTransferOwners( + { + titleEscrowAddress: mockTitleEscrowAddress, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_owners_tx_hash'); + expect(coreModule.getTitleEscrowAddress).not.toHaveBeenCalled(); + }); + + it('should reject transfer beneficiary without remarks', async () => { + const result = await rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + {}, + { chainId: mockChainId }, + ); + + expect(result).toEqual('v5_reject_transfer_owners_tx_hash'); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw error when tokenRegistryAddress is missing', async () => { + vi.mocked(coreModule.getTitleEscrowAddress).mockResolvedValue(undefined); + await expect( + rejectTransferOwners( + { + tokenId: mockTokenId, + } as any, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('should throw error when provider is missing', async () => { + const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1')); + + await expect( + rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + signerWithoutProvider, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw error when title escrow is not V5', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockResolvedValue(false); + + await expect( + rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Only Token Registry V5 is supported'); + }); + + it('should throw error when callStatic fails', async () => { + mockV5TitleEscrowContract.callStatic.rejectTransferOwners.mockRejectedValue( + new Error('Simulated failure'), + ); + + await expect( + rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + mockV5TitleEscrowContract.callStatic.rejectTransferOwners = vi.fn(); + }); + + it('should use explicit titleEscrowVersion when provided', async () => { + await rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(coreModule.isTitleEscrowVersion).not.toHaveBeenCalled(); + }); + }); + }, +); diff --git a/src/__tests__/token-registry-functions/transfers.test.ts b/src/__tests__/token-registry-functions/transfers.test.ts index c87447c..7e24e0a 100644 --- a/src/__tests__/token-registry-functions/transfers.test.ts +++ b/src/__tests__/token-registry-functions/transfers.test.ts @@ -1,11 +1,7 @@ +import './fixtures.js'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { ethers as ethersV5, Wallet as WalletV5 } from 'ethers'; -import { - ethers as ethersV6, - JsonRpcProvider as JsonRpcProviderV6, - Network, - Wallet as WalletV6, -} from 'ethersV6'; +import { ethers as ethersV6, Network, Wallet as WalletV6 } from 'ethersV6'; import * as coreModule from 'src/core'; import { encrypt } from 'src/core'; import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; @@ -15,122 +11,14 @@ import { transferOwners, nominate, } from 'src/token-registry-functions'; - -// Mocks - -vi.mock('src/core', () => ({ - encrypt: vi.fn(() => 'encrypted_remarks'), - getTitleEscrowAddress: vi.fn(() => Promise.resolve('0xv5contract')), - isTitleEscrowVersion: vi.fn(() => Promise.resolve(true)), - TitleEscrowInterface: { - V4: '0xTitleEscrowIdV4', - V5: '0xTitleEscrowIdV5', - }, -})); - -vi.mock('src/token-registry-v5', () => { - return { - v5Contracts: { - TitleEscrow__factory: { - connect: vi.fn(() => mockV5TitleEscrowContract), - }, - TradeTrustToken__factory: { - connect: vi.fn(() => mockV5TradeTrustTokenContract), - }, - TitleEscrowFactory__factory: { - connect: vi.fn(() => mockV5TitleEscrowFactoryContract), - }, - }, - v5SupportInterfaceIds: { - TitleEscrow: '0xTitleEscrowIdV5', - TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV5', - }, - }; -}); - -vi.mock('src/token-registry-v4', () => { - return { - v4Contracts: { - TitleEscrow__factory: { - connect: vi.fn(() => mockV4TitleEscrowContract), - }, - TradeTrustToken__factory: { - connect: vi.fn(() => mockV4TradeTrustTokenContract), - }, - TitleEscrowFactory__factory: { - connect: vi.fn(() => mockV4TitleEscrowFactoryContract), - }, - v4SupportInterfaceIds: { - TitleEscrow: '0xTitleEscrowIdV4', - TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV4', - }, - }, - }; -}); - -const mockV5TitleEscrowFactoryContract = { - callStatic: { - getEscrowAddress: vi.fn(), - }, - getEscrowAddress: vi.fn(() => Promise.resolve('0xV5titleescrow')), -}; - -const mockV5TradeTrustTokenContract = { - supportsInterface: vi.fn(), - titleEscrowFactory: vi.fn(() => Promise.resolve('0xV5titleescrowfactory')), -}; - -const mockV5TitleEscrowContract = { - supportsInterface: vi.fn(), - callStatic: { - transferHolder: vi.fn(), - transferBeneficiary: vi.fn(), - transferOwners: vi.fn(), - nominate: vi.fn(), - }, - transferHolder: vi.fn(() => Promise.resolve('v5_transfer_holder_tx_hash')), - transferBeneficiary: vi.fn(() => Promise.resolve('v5_transfer_beneficiary_tx_hash')), - transferOwners: vi.fn(() => Promise.resolve('v5_transfer_owners_tx_hash')), - nominate: vi.fn(() => Promise.resolve('v5_nominate_tx_hash')), - holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), - beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), -}; - -const mockV4TitleEscrowContract = { - callStatic: { - transferHolder: vi.fn(), - transferBeneficiary: vi.fn(), - transferOwners: vi.fn(), - nominate: vi.fn(), - }, - transferHolder: vi.fn(() => Promise.resolve('v4_transfer_holder_tx_hash')), - transferBeneficiary: vi.fn(() => Promise.resolve('v4_transfer_beneficiary_tx_hash')), - transferOwners: vi.fn(() => Promise.resolve('v4_transfer_owners_tx_hash')), - nominate: vi.fn(() => Promise.resolve('v4_nominate_tx_hash')), - holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), - beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), -}; -const mockV4TitleEscrowFactoryContract = { - callStatic: { - getEscrowAddress: vi.fn(), - }, - getAddress: vi.fn(() => Promise.resolve('0xV4titleescrow')), -}; - -const mockV4TradeTrustTokenContract = { - titleEscrowFactory: vi.fn(() => Promise.resolve('0xV4titleescrowfactory')), -}; - -const PRIVATE_KEY = '0x59c6995e998f97a5a004497e5f1ebce0c16828d44b3f8d0bfa3a89d271d5b6b9'; // random local key - -const providerV5 = new ethersV5.providers.JsonRpcProvider(); -const providerV6 = new JsonRpcProviderV6(); - -interface ProviderInfo { - Provider: typeof providerV5 | typeof providerV6; - ethersVersion: 'v5' | 'v6'; - titleEscrowVersion: 'v4' | 'v5'; -} +import { ProviderInfo } from 'src/token-registry-functions/types'; +import { + mockV4TitleEscrowContract, + mockV5TitleEscrowContract, + PRIVATE_KEY, + providerV5, + providerV6, +} from './fixtures'; const providers: ProviderInfo[] = [ { diff --git a/src/token-registry-functions/index.ts b/src/token-registry-functions/index.ts index 6de95ff..927e270 100644 --- a/src/token-registry-functions/index.ts +++ b/src/token-registry-functions/index.ts @@ -1 +1,2 @@ export * from './transfer'; +export * from './rejectTransfers'; diff --git a/src/token-registry-functions/rejectTransfers.ts b/src/token-registry-functions/rejectTransfers.ts new file mode 100644 index 0000000..a32ef4a --- /dev/null +++ b/src/token-registry-functions/rejectTransfers.ts @@ -0,0 +1,217 @@ +import { + encrypt, + getTitleEscrowAddress, + isTitleEscrowVersion, + TitleEscrowInterface, +} from 'src/core'; +import { v5Contracts } from 'src/token-registry-v5'; +import { Signer as SignerV6 } from 'ethersV6'; +import { ContractTransaction, Signer } from 'ethers'; +import { getTxOptions } from './utils'; +import { ContractOptions, RejectTransferParams, TransactionOptions } from './types'; + +/** + * Rejects the transfer of holder for a title escrow contract. + * @param {ContractOptions} contractOptions - Contract-related options including the token registry address, and optionally, token ID and the title escrow address. + * @param {Signer | SignerV6} signer - Ethers signer (V5 or V6) used to sign and send the transaction. + * @param {RejectTransferParams} params - Contains the `remarks` field which is an optional string that will be encrypted and sent with the transaction. + * @param {TransactionOptions} options - Transfer options including optional `chainId`, `titleEscrowVersion`, `maxFeePerGas`, `maxPriorityFeePerGas`, and an `id` used for encryption. + * @throws error if the title escrow address or signer provider is missing. + * @throws if the version is not V5 compatible. + * @throws if the dry-run (`callStatic`) fails. + * @returns {Promise} The transaction response of the rejectTransferHolder call. + */ +const rejectTransferHolder = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: RejectTransferParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress, tokenId } = contractOptions; + let { titleEscrowAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + {}, + ); + } + + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { remarks } = params; + + // Connect V5 contract by default + const titleEscrowContract = v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + let isV5TT = titleEscrowVersion === 'v5'; + if (titleEscrowVersion === undefined) { + isV5TT = await isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }); + } + + if (!isV5TT) { + throw new Error('Only Token Registry V5 is supported'); + } + + // Check callStatic (dry run) + try { + await titleEscrowContract.callStatic.rejectTransferHolder(encryptedRemarks); + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for transferHolder failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + + return await titleEscrowContract.rejectTransferHolder(encryptedRemarks, txOptions); +}; + +/** + * Rejects the transfer of beneficiary for a title escrow contract. + * @param {ContractOptions} contractOptions - Contract-related options including the token registry address, and optionally, token ID and the title escrow address. + * @param {Signer | SignerV6} signer - Ethers signer (V5 or V6) used to sign and send the transaction. + * @param {RejectTransferParams} params - Contains the `remarks` field which is an optional string that will be encrypted and sent with the transaction. + * @param {TransactionOptions} options - Transfer options including optional `chainId`, `titleEscrowVersion`, `maxFeePerGas`, `maxPriorityFeePerGas`, and an `id` used for encryption. + * @throws error if the title escrow address or signer provider is missing. + * @throws error if the version is not V5 compatible. + * @throws error if the dry-run (`callStatic`) fails. + * @returns {Promise} The transaction response of the rejectTransferBeneficiary call. + */ +const rejectTransferBeneficiary = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: RejectTransferParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress, tokenId } = contractOptions; + let { titleEscrowAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + {}, + ); + } + + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { remarks } = params; + + // Connect V5 contract by default + const titleEscrowContract = v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + let isV5TT = titleEscrowVersion === 'v5'; + if (titleEscrowVersion === undefined) { + isV5TT = await isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }); + } + + if (!isV5TT) { + throw new Error('Only Token Registry V5 is supported'); + } + + // Check callStatic (dry run) + try { + await titleEscrowContract.callStatic.rejectTransferBeneficiary(encryptedRemarks); + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for transferHolder failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + + return await titleEscrowContract.rejectTransferBeneficiary(encryptedRemarks, txOptions); +}; + +/** + * Rejects the transfer of ownership for a title escrow contract. + * @param {ContractOptions} contractOptions - Contract-related options including the token registry address, and optionally, token ID and the title escrow address. + * @param {Signer | SignerV6} signer - Ethers signer (V5 or V6) used to sign and send the transaction. + * @param {RejectTransferParams} params - Contains the `remarks` field which is an optional string that will be encrypted and sent with the transaction. + * @param {TransactionOptions} options - Transfer options including optional `chainId`, `titleEscrowVersion`, `maxFeePerGas`, `maxPriorityFeePerGas`, and an `id` used for encryption. + * @throws error if the title escrow address or signer provider is missing. + * @throws an error if the version is not V5 compatible. + * @throws an error if the dry-run (`callStatic`) fails. + * @returns {Promise} The transaction response of the rejectTransferOwners call. + */ +const rejectTransferOwners = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: RejectTransferParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress, tokenId } = contractOptions; + let { titleEscrowAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + {}, + ); + } + + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { remarks } = params; + + // Connect V5 contract by default + const titleEscrowContract = v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + let isV5TT = titleEscrowVersion === 'v5'; + if (titleEscrowVersion === undefined) { + isV5TT = await isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }); + } + + if (!isV5TT) { + throw new Error('Only Token Registry V5 is supported'); + } + + // Check callStatic (dry run) + try { + await titleEscrowContract.callStatic.rejectTransferOwners(encryptedRemarks); + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for transferHolder failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + + return await titleEscrowContract.rejectTransferOwners(encryptedRemarks, txOptions); +}; + +export { rejectTransferHolder, rejectTransferBeneficiary, rejectTransferOwners }; diff --git a/src/token-registry-functions/transfer.ts b/src/token-registry-functions/transfer.ts index 5247762..7c944d3 100644 --- a/src/token-registry-functions/transfer.ts +++ b/src/token-registry-functions/transfer.ts @@ -1,4 +1,3 @@ -import { CHAIN_ID, SUPPORTED_CHAINS } from '@tradetrust-tt/tradetrust-utils'; import { encrypt, getTitleEscrowAddress, @@ -7,74 +6,44 @@ import { } from 'src/core'; import { v4Contracts } from 'src/token-registry-v4'; import { v5Contracts } from 'src/token-registry-v5'; -import { BigNumberish, Signer as SignerV6 } from 'ethersV6'; -import { BigNumber, Signer } from 'ethers'; -import { isV6EthersProvider } from 'src/utils/ethers'; - -interface TransferHolderParams { - holderAddress: string; - remarks?: string; -} -interface TransferBeneficiaryParams { - newBeneficiaryAddress: string; - remarks?: string; -} -interface NominateParams { - newBeneficiaryAddress: string; - remarks?: string; -} -interface TransferOwnersParams { - newHolderAddress: string; - newBeneficiaryAddress: string; - remarks?: string; -} - -interface TransferOptions { - chainId?: CHAIN_ID; - titleEscrowVersion?: 'v4' | 'v5'; - maxFeePerGas?: BigNumberish | string | number | BigNumber; - maxPriorityFeePerGas?: BigNumberish | string | number | BigNumber; - id?: string; -} - -// 🔍 Handles both Ethers v5 and v6 signer types -const getChainIdSafe = async (signer: SignerV6 | Signer): Promise => { - if (isV6EthersProvider(signer.provider)) { - const network = await (signer as Signer).provider?.getNetwork(); - if (!network?.chainId) throw new Error('Cannot determine chainId: provider is missing'); - return network.chainId; - } - return await (signer as Signer).getChainId(); -}; -// const getSignerAddressSafe = async (signer: SignerV6 | Signer): Promise => { -// if (isV6EthersProvider(signer.provider)) { -// return await (signer as SignerV6).getAddress(); -// } -// return (signer as any).address; -// }; - -type ContractOptions = - | { - titleEscrowAddress: string; // Present — no restrictions on the rest - tokenId?: string | number; - tokenRegistryAddress?: string; - } - | { - titleEscrowAddress?: undefined; // Absent — must provide both below - tokenId: string | number; - tokenRegistryAddress: string; - }; - +import { Signer as SignerV6 } from 'ethersV6'; +import { ContractTransaction, Signer } from 'ethers'; +import { + ContractOptions, + NominateParams, + TransactionOptions, + TransferBeneficiaryParams, + TransferHolderParams, + TransferOwnersParams, +} from './types'; +import { getTxOptions } from './utils'; + +/** + * Transfers holder role of a Title Escrow contract to a new address. + * The caller of this function must be the current holder. + * @param {ContractOptions} contractOptions - Contains `tokenRegistryAddress` and optionally `tokenId` and `titleEscrowAddress`. + * @param {Signer | SignerV6} signer - The signer (ethers v5 or v6) who initiates the transaction. + * @param {TransferHolderParams} params - Object containing `holderAddress` (address to transfer to) and optional `remarks`. + * @param {TransactionOptions} options - Transaction options including: + * - `titleEscrowVersion` (optional): Either "v4" or "v5" + * - `chainId` (optional): Used for gas station lookup + * - `maxFeePerGas` (optional), `maxPriorityFeePerGas` (optional): EIP-1559 gas fee configuration + * - `id` (optional): ID used for encrypting remarks + * @throws If required fields like `titleEscrowAddress` or `signer.provider` are missing. + * @throws If the version is unsupported (neither v4 nor v5). + * @throws If the dry-run via `callStatic` fails. + * @returns {Promise} The transaction response for `transferHolder`. + */ const transferHolder = async ( contractOptions: ContractOptions, signer: Signer | SignerV6, params: TransferHolderParams, - options: TransferOptions, -) => { + options: TransactionOptions, +): Promise => { const { tokenRegistryAddress, tokenId } = contractOptions; const { titleEscrowVersion } = options; let { titleEscrowAddress } = contractOptions; - let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + const { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; let isV5TT = titleEscrowVersion === 'v5'; let isV4TT = titleEscrowVersion === 'v4'; @@ -153,18 +122,8 @@ const transferHolder = async ( } // If gas values are missing, query gas station if available - if (!maxFeePerGas || !maxPriorityFeePerGas) { - chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); - const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; - - if (gasStation) { - const gasFees = await gasStation(); - maxFeePerGas = gasFees?.maxFeePerGas ?? 0; - maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; - } - } - const txOptions = - maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + // Send the actual transaction if (isV5TT) { return await (titleEscrowContract as v5Contracts.TitleEscrow).transferHolder( @@ -177,16 +136,34 @@ const transferHolder = async ( } }; +/** + * Transfers the beneficiary role of a Title Escrow contract to a new beneficiary address. + * The caller of this function must be the current holder. + * @param {ContractOptions} contractOptions - Contains `tokenRegistryAddress` and optionally `tokenId` and `titleEscrowAddress`. + * @param {Signer | SignerV6} signer - The signer (ethers v5 or v6) who initiates and signs the transaction. + * @param {TransferBeneficiaryParams} params - Object containing: + * - `newBeneficiaryAddress`: Address to which the beneficiary role is being transferred. + * - `remarks` (optional): Optional encrypted message attached with the transaction. + * @param {TransactionOptions} options - Transaction configuration options: + * - `titleEscrowVersion` (optional): Token registry version, either `'v4'` or `'v5'`. + * - `chainId` (optional): Used to query gas station info if gas fee values are missing. + * - `maxFeePerGas`(optional), `maxPriorityFeePerGas`(optional): EIP-1559 gas fee parameters. + * - `id`(optional): Used for encryption of remarks. + * @throws If required values like `titleEscrowAddress` or `signer.provider` are missing. + * @throws If the version is unsupported (neither v4 nor v5). + * @throws If the dry-run `callStatic` fails for pre-checking the transaction. + * @returns {Promise} The transaction response for the `transferBeneficiary` call. + */ const transferBeneficiary = async ( contractOptions: ContractOptions, signer: Signer | SignerV6, params: TransferBeneficiaryParams, - options: TransferOptions, -) => { + options: TransactionOptions, +): Promise => { const { tokenId, tokenRegistryAddress } = contractOptions; const { titleEscrowVersion } = options; let { titleEscrowAddress } = contractOptions; - let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + const { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; let isV5TT = titleEscrowVersion === 'v5'; let isV4TT = titleEscrowVersion === 'v4'; @@ -265,19 +242,8 @@ const transferBeneficiary = async ( } // If gas values are missing, query gas station if available - if (!maxFeePerGas || !maxPriorityFeePerGas) { - chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); - const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; - - if (gasStation) { - const gasFees = await gasStation(); - maxFeePerGas = gasFees?.maxFeePerGas ?? 0; - maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; - } - } + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); - const txOptions = - maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; // Send the actual transaction if (isV5TT) { const tx = await (titleEscrowContract as v5Contracts.TitleEscrow).transferBeneficiary( @@ -294,16 +260,36 @@ const transferBeneficiary = async ( return tx; } }; + +/** + * Transfers both the holder and beneficiary roles of a Title Escrow contract to new addresses. + * The caller of this function must be the current holder and beneficiary both. + * @param {ContractOptions} contractOptions - Contains `tokenRegistryAddress` and optionally `tokenId` and `titleEscrowAddress`. + * @param {Signer | SignerV6} signer - The signer (ethers v5 or v6) who initiates and signs the transaction. + * @param {TransferOwnersParams} params - Object containing: + * - `newBeneficiaryAddress`: The new beneficiary address. + * - `newHolderAddress`: The new holder address. + * - `remarks` (optional): Optional remarks that will be encrypted and included with the transaction. + * @param {TransactionOptions} options - Transaction configuration options: + * - `titleEscrowVersion` (optional): Token registry version, either `'v4'` or `'v5'`. + * - `chainId` (optional): Used for gas station lookup if gas fee values are not provided. + * - `maxFeePerGas`(optional), `maxPriorityFeePerGas`(optional): EIP-1559 gas fee parameters. + * - `id`(optional): Used for encrypting remarks. + * @throws If required fields like `titleEscrowAddress` or `signer.provider` are missing. + * @throws If the title escrow version is unsupported. + * @throws If the pre-check `callStatic.transferOwners` fails. + * @returns {Promise} The transaction response from the `transferOwners` call. + */ const transferOwners = async ( contractOptions: ContractOptions, signer: Signer | SignerV6, params: TransferOwnersParams, - options: TransferOptions, -) => { + options: TransactionOptions, +): Promise => { const { tokenId, tokenRegistryAddress } = contractOptions; const { titleEscrowVersion } = options; let { titleEscrowAddress } = contractOptions; - let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + const { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; let isV5TT = titleEscrowVersion === 'v5'; let isV4TT = titleEscrowVersion === 'v4'; @@ -389,19 +375,8 @@ const transferOwners = async ( } // If gas values are missing, query gas station if available - if (!maxFeePerGas || !maxPriorityFeePerGas) { - chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); - const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; - - if (gasStation) { - const gasFees = await gasStation(); - maxFeePerGas = gasFees?.maxFeePerGas ?? 0; - maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; - } - } + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); - const txOptions = - maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; // Send the actual transaction if (isV5TT) { @@ -420,16 +395,34 @@ const transferOwners = async ( } }; +/** + * Nominates a new beneficiary on the Title Escrow contract. + * The caller of this function must be the current beneficiary. + * @param {ContractOptions} contractOptions - Contains `tokenRegistryAddress` and optionally `tokenId` and `titleEscrowAddress`. + * @param {Signer | SignerV6} signer - The signer (ethers v5 or v6) who will sign and send the transaction. + * @param {NominateParams} params - Nomination parameters: + * - `newBeneficiaryAddress`: The Ethereum address to nominate as the new beneficiary. + * - `remarks` (optional): Remarks to include with the transaction (will be encrypted). + * @param {TransactionOptions} options - Transaction-level configuration: + * - `titleEscrowVersion` (optional): Specifies token registry version, either `'v4'` or `'v5'`. + * - `chainId` (optional): Chain ID used for querying gas stations if fees are not set. + * - `maxFeePerGas`(optional), `maxPriorityFeePerGas`(optional): EIP-1559-compatible gas fee settings. + * - `id`(optional): Used for encrypting the remarks string. + * @throws If required inputs like `titleEscrowAddress` or `signer.provider` are missing. + * @throws If token registry version is unsupported. + * @throws If the dry-run `callStatic.nominate()` fails. + * @returns {Promise} The transaction response from the `nominate` method. + */ const nominate = async ( contractOptions: ContractOptions, signer: Signer | SignerV6, params: NominateParams, - options: TransferOptions, -) => { + options: TransactionOptions, +): Promise => { const { tokenId, tokenRegistryAddress } = contractOptions; const { titleEscrowVersion } = options; let { titleEscrowAddress } = contractOptions; - let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + const { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; let isV5TT = titleEscrowVersion === 'v5'; let isV4TT = titleEscrowVersion === 'v4'; @@ -504,19 +497,8 @@ const nominate = async ( } // If gas values are missing, query gas station if available - if (!maxFeePerGas || !maxPriorityFeePerGas) { - chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); - const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; - - if (gasStation) { - const gasFees = await gasStation(); - maxFeePerGas = gasFees?.maxFeePerGas ?? 0; - maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; - } - } + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); - const txOptions = - maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; // Send the actual transaction if (isV5TT) { diff --git a/src/token-registry-functions/types.ts b/src/token-registry-functions/types.ts new file mode 100644 index 0000000..22b0951 --- /dev/null +++ b/src/token-registry-functions/types.ts @@ -0,0 +1,53 @@ +import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; +import { BigNumber } from 'ethers'; +import { BigNumberish } from 'ethersV6'; +import { providerV5, providerV6 } from 'src/__tests__/token-registry-functions/fixtures'; + +export type GasValue = BigNumber | BigNumberish | string | number; + +export interface RejectTransferParams { + remarks?: string; +} + +export interface TransactionOptions { + chainId?: CHAIN_ID; + titleEscrowVersion?: 'v4' | 'v5'; + maxFeePerGas?: BigNumberish | string | number | BigNumber; + maxPriorityFeePerGas?: BigNumberish | string | number | BigNumber; + id?: string; +} + +export type ContractOptions = + | { + titleEscrowAddress: string; // Present — no restrictions on the rest + tokenId?: string | number; + tokenRegistryAddress?: string; + } + | { + titleEscrowAddress?: undefined; // Absent — must provide both below + tokenId: string | number; + tokenRegistryAddress: string; + }; + +export interface TransferHolderParams { + holderAddress: string; + remarks?: string; +} +export interface TransferBeneficiaryParams { + newBeneficiaryAddress: string; + remarks?: string; +} +export interface NominateParams { + newBeneficiaryAddress: string; + remarks?: string; +} +export interface TransferOwnersParams { + newHolderAddress: string; + newBeneficiaryAddress: string; + remarks?: string; +} +export interface ProviderInfo { + Provider: typeof providerV5 | typeof providerV6; + ethersVersion: 'v5' | 'v6'; + titleEscrowVersion: 'v4' | 'v5'; +} diff --git a/src/token-registry-functions/utils.ts b/src/token-registry-functions/utils.ts new file mode 100644 index 0000000..d426481 --- /dev/null +++ b/src/token-registry-functions/utils.ts @@ -0,0 +1,44 @@ +import { isV6EthersProvider } from 'src/utils/ethers'; +import { GasValue } from './types'; +import { CHAIN_ID, SUPPORTED_CHAINS } from '@tradetrust-tt/tradetrust-utils'; +import { Signer } from 'ethers'; +import { Signer as SignerV6 } from 'ethersV6'; + +const getTxOptions = async ( + signer: SignerV6 | Signer, + chainId: CHAIN_ID, + maxFeePerGas: GasValue, + maxPriorityFeePerGas: GasValue, +) => { + // If gas values are missing, query gas station if available + if (!maxFeePerGas || !maxPriorityFeePerGas) { + chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); + const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; + + if (gasStation) { + const gasFees = await gasStation(); + maxFeePerGas = gasFees?.maxFeePerGas ?? 0; + maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; + } + } + return maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; +}; + +// 🔍 Handles both Ethers v5 and v6 signer types +const getChainIdSafe = async (signer: SignerV6 | Signer): Promise => { + if (isV6EthersProvider(signer.provider)) { + const network = await (signer as Signer).provider?.getNetwork(); + if (!network?.chainId) throw new Error('Cannot determine chainId: provider is missing'); + return network.chainId; + } + return await (signer as Signer).getChainId(); +}; + +const getSignerAddressSafe = async (signer: SignerV6 | Signer): Promise => { + if (isV6EthersProvider(signer.provider)) { + return await (signer as SignerV6).getAddress(); + } + return await (signer as unknown as Signer).getAddress(); +}; + +export { getChainIdSafe, getTxOptions, getSignerAddressSafe }; From 76aa936579fa4cdc656389d44fe38a587252ef44 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Tue, 1 Jul 2025 00:55:46 +0530 Subject: [PATCH 13/27] chore: trigger rebuild after rebase From 3103e265feab296a7a4e1ae82bab37008c174d5f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 17 Jun 2025 04:22:55 +0000 Subject: [PATCH 14/27] chore(release): 1.5.4 [skip ci] * add w3c credential status check ([#72](https://github.com/TrustVC/trustvc/issues/72)) ([0111cb3](https://github.com/TrustVC/trustvc/commit/0111cb3e48ac86f0cea715ceb5277d199f139763)) --- CHANGELOG.md | 148 +++++++++++++++++---------------------------------- 1 file changed, 50 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecfde22..52f40db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,330 +1,282 @@ ## [1.6.0-alpha.1](https://github.com/TrustVC/trustvc/compare/v1.5.3...v1.6.0-alpha.1) (2025-06-30) - ### Features -* token registry functions ([#74](https://github.com/TrustVC/trustvc/issues/74)) ([5690fcd](https://github.com/TrustVC/trustvc/commit/5690fcda798192609b0060fab0a3f3f77dca8012)) - +- token registry functions ([#74](https://github.com/TrustVC/trustvc/issues/74)) ([5690fcd](https://github.com/TrustVC/trustvc/commit/5690fcda798192609b0060fab0a3f3f77dca8012)) ### Miscellaneous Chores -* back merge ([#75](https://github.com/TrustVC/trustvc/issues/75)) ([7cc1891](https://github.com/TrustVC/trustvc/commit/7cc1891ffebceb4eebf1421d3bc348926efa3f10)), closes [#72](https://github.com/TrustVC/trustvc/issues/72) [#72](https://github.com/TrustVC/trustvc/issues/72) [#73](https://github.com/TrustVC/trustvc/issues/73) [#73](https://github.com/TrustVC/trustvc/issues/73) +- back merge ([#75](https://github.com/TrustVC/trustvc/issues/75)) ([7cc1891](https://github.com/TrustVC/trustvc/commit/7cc1891ffebceb4eebf1421d3bc348926efa3f10)), closes [#72](https://github.com/TrustVC/trustvc/issues/72) [#72](https://github.com/TrustVC/trustvc/issues/72) [#73](https://github.com/TrustVC/trustvc/issues/73) [#73](https://github.com/TrustVC/trustvc/issues/73) ## [1.5.5](https://github.com/TrustVC/trustvc/compare/v1.5.4...v1.5.5) (2025-06-18) - ### Bug Fixes -* upgrade package ([#73](https://github.com/TrustVC/trustvc/issues/73)) ([3c6c9c7](https://github.com/TrustVC/trustvc/commit/3c6c9c73675ec43e0514d9afe5df4444f6e4400b)) +- upgrade package ([#73](https://github.com/TrustVC/trustvc/issues/73)) ([3c6c9c7](https://github.com/TrustVC/trustvc/commit/3c6c9c73675ec43e0514d9afe5df4444f6e4400b)) ## [1.5.4](https://github.com/TrustVC/trustvc/compare/v1.5.3...v1.5.4) (2025-06-17) - ### Bug Fixes -* add w3c credential status check ([#72](https://github.com/TrustVC/trustvc/issues/72)) ([0111cb3](https://github.com/TrustVC/trustvc/commit/0111cb3e48ac86f0cea715ceb5277d199f139763)) +- add w3c credential status check ([#72](https://github.com/TrustVC/trustvc/issues/72)) ([0111cb3](https://github.com/TrustVC/trustvc/commit/0111cb3e48ac86f0cea715ceb5277d199f139763)) ## [1.5.3](https://github.com/TrustVC/trustvc/compare/v1.5.2...v1.5.3) (2025-06-13) - ### Bug Fixes -* redacted document check ([#71](https://github.com/TrustVC/trustvc/issues/71)) ([413e295](https://github.com/TrustVC/trustvc/commit/413e295e5b53cace1bd86f8ec88ece0df265fce6)) +- redacted document check ([#71](https://github.com/TrustVC/trustvc/issues/71)) ([413e295](https://github.com/TrustVC/trustvc/commit/413e295e5b53cace1bd86f8ec88ece0df265fce6)) ## [1.5.2](https://github.com/TrustVC/trustvc/compare/v1.5.1...v1.5.2) (2025-06-10) - ### Bug Fixes -* useEndorsementChain fetch log error ([#70](https://github.com/TrustVC/trustvc/issues/70)) ([3d9871a](https://github.com/TrustVC/trustvc/commit/3d9871a601b05beab88f79814c9b1b4fca4b4145)) +- useEndorsementChain fetch log error ([#70](https://github.com/TrustVC/trustvc/issues/70)) ([3d9871a](https://github.com/TrustVC/trustvc/commit/3d9871a601b05beab88f79814c9b1b4fca4b4145)) ## [1.5.1](https://github.com/TrustVC/trustvc/compare/v1.5.0...v1.5.1) (2025-06-09) - ### Bug Fixes -* expose contracts ([#69](https://github.com/TrustVC/trustvc/issues/69)) ([c4e191d](https://github.com/TrustVC/trustvc/commit/c4e191de42d2a53430cd8d44896b3b6ad7a8a0c5)) +- expose contracts ([#69](https://github.com/TrustVC/trustvc/issues/69)) ([c4e191d](https://github.com/TrustVC/trustvc/commit/c4e191de42d2a53430cd8d44896b3b6ad7a8a0c5)) ## [1.5.0](https://github.com/TrustVC/trustvc/compare/v1.4.12...v1.5.0) (2025-06-09) - ### Features -* add aws kms signer ([#68](https://github.com/TrustVC/trustvc/issues/68)) ([975f82e](https://github.com/TrustVC/trustvc/commit/975f82e19ea1376f9a3b1bd77ca44cf843adbecd)) +- add aws kms signer ([#68](https://github.com/TrustVC/trustvc/issues/68)) ([975f82e](https://github.com/TrustVC/trustvc/commit/975f82e19ea1376f9a3b1bd77ca44cf843adbecd)) ## [1.4.12](https://github.com/TrustVC/trustvc/compare/v1.4.11...v1.4.12) (2025-05-30) - ### Bug Fixes -* resolve import and add provider ([#65](https://github.com/TrustVC/trustvc/issues/65)) ([6b3dae3](https://github.com/TrustVC/trustvc/commit/6b3dae3a630edb5a259b04a3bc5f39ac3a9a4fa4)) +- resolve import and add provider ([#65](https://github.com/TrustVC/trustvc/issues/65)) ([6b3dae3](https://github.com/TrustVC/trustvc/commit/6b3dae3a630edb5a259b04a3bc5f39ac3a9a4fa4)) ## [1.4.11](https://github.com/TrustVC/trustvc/compare/v1.4.10...v1.4.11) (2025-05-29) - ### Bug Fixes -* update tt utils version ([#64](https://github.com/TrustVC/trustvc/issues/64)) ([86ff7fc](https://github.com/TrustVC/trustvc/commit/86ff7fc9ac7b7d43e3e5851a0eecd94e1a5e4e15)) +- update tt utils version ([#64](https://github.com/TrustVC/trustvc/issues/64)) ([86ff7fc](https://github.com/TrustVC/trustvc/commit/86ff7fc9ac7b7d43e3e5851a0eecd94e1a5e4e15)) ## [1.4.10](https://github.com/TrustVC/trustvc/compare/v1.4.9...v1.4.10) (2025-05-21) - ### Bug Fixes -* update amoy rpc for test case ([#63](https://github.com/TrustVC/trustvc/issues/63)) ([9ddba30](https://github.com/TrustVC/trustvc/commit/9ddba300f4f240d9ec2508528bee791e481d8c04)) -* update package ([#62](https://github.com/TrustVC/trustvc/issues/62)) ([f4f3942](https://github.com/TrustVC/trustvc/commit/f4f3942e84325e69c37e652002fa41e970a09ad0)) +- update amoy rpc for test case ([#63](https://github.com/TrustVC/trustvc/issues/63)) ([9ddba30](https://github.com/TrustVC/trustvc/commit/9ddba300f4f240d9ec2508528bee791e481d8c04)) +- update package ([#62](https://github.com/TrustVC/trustvc/issues/62)) ([f4f3942](https://github.com/TrustVC/trustvc/commit/f4f3942e84325e69c37e652002fa41e970a09ad0)) ## [1.4.9](https://github.com/TrustVC/trustvc/compare/v1.4.8...v1.4.9) (2025-05-15) - ### Bug Fixes -* version bump ([#61](https://github.com/TrustVC/trustvc/issues/61)) ([92c7b0b](https://github.com/TrustVC/trustvc/commit/92c7b0ba2d72fc11e16cd6c2f8d7883e83f73d9e)) - +- version bump ([#61](https://github.com/TrustVC/trustvc/issues/61)) ([92c7b0b](https://github.com/TrustVC/trustvc/commit/92c7b0ba2d72fc11e16cd6c2f8d7883e83f73d9e)) ### Miscellaneous Chores -* export gas station function from tt-utils ([#60](https://github.com/TrustVC/trustvc/issues/60)) ([5e88b9c](https://github.com/TrustVC/trustvc/commit/5e88b9cde81068a7ca183a1498bd284f928a092a)) +- export gas station function from tt-utils ([#60](https://github.com/TrustVC/trustvc/issues/60)) ([5e88b9c](https://github.com/TrustVC/trustvc/commit/5e88b9cde81068a7ca183a1498bd284f928a092a)) ## [1.4.8](https://github.com/TrustVC/trustvc/compare/v1.4.7...v1.4.8) (2025-05-14) - ### Bug Fixes -* add qrcode functionality to document builder ([#59](https://github.com/TrustVC/trustvc/issues/59)) ([b217e85](https://github.com/TrustVC/trustvc/commit/b217e85e45a2879800cbf44b1de01d9f5f20ebce)) - +- add qrcode functionality to document builder ([#59](https://github.com/TrustVC/trustvc/issues/59)) ([b217e85](https://github.com/TrustVC/trustvc/commit/b217e85e45a2879800cbf44b1de01d9f5f20ebce)) ### Miscellaneous Chores -* add astrontestnet testcase ([#58](https://github.com/TrustVC/trustvc/issues/58)) ([48e82cb](https://github.com/TrustVC/trustvc/commit/48e82cbb62c6188e8dda86e35d431b308647a03c)) +- add astrontestnet testcase ([#58](https://github.com/TrustVC/trustvc/issues/58)) ([48e82cb](https://github.com/TrustVC/trustvc/commit/48e82cbb62c6188e8dda86e35d431b308647a03c)) ## [1.4.7](https://github.com/TrustVC/trustvc/compare/v1.4.6...v1.4.7) (2025-04-22) - ### Bug Fixes -* update checkDidWebResolve function to use queryDidDocument ([#57](https://github.com/TrustVC/trustvc/issues/57)) ([da975ed](https://github.com/TrustVC/trustvc/commit/da975eda1475390ed41a72926b24308c1fb59c6c)) +- update checkDidWebResolve function to use queryDidDocument ([#57](https://github.com/TrustVC/trustvc/issues/57)) ([da975ed](https://github.com/TrustVC/trustvc/commit/da975eda1475390ed41a72926b24308c1fb59c6c)) ## [1.4.6](https://github.com/TrustVC/trustvc/compare/v1.4.5...v1.4.6) (2025-04-22) - ### Bug Fixes -* endorsement chain initial event remarks error ([#56](https://github.com/TrustVC/trustvc/issues/56)) ([1fd5b4c](https://github.com/TrustVC/trustvc/commit/1fd5b4c1e2794e5cd6748c26ab73f4099b110ffd)) +- endorsement chain initial event remarks error ([#56](https://github.com/TrustVC/trustvc/issues/56)) ([1fd5b4c](https://github.com/TrustVC/trustvc/commit/1fd5b4c1e2794e5cd6748c26ab73f4099b110ffd)) ## [1.4.5](https://github.com/TrustVC/trustvc/compare/v1.4.4...v1.4.5) (2025-04-10) - ### Bug Fixes -* prevent comments from being removed, for jsdocs ([#55](https://github.com/TrustVC/trustvc/issues/55)) ([82a4208](https://github.com/TrustVC/trustvc/commit/82a4208b38487c5c54093e65f0726cc14efcd308)) +- prevent comments from being removed, for jsdocs ([#55](https://github.com/TrustVC/trustvc/issues/55)) ([82a4208](https://github.com/TrustVC/trustvc/commit/82a4208b38487c5c54093e65f0726cc14efcd308)) ## [1.4.4](https://github.com/TrustVC/trustvc/compare/v1.4.3...v1.4.4) (2025-04-10) - ### Bug Fixes -* update w3cIssuerIdentity to use documentLoader ([#54](https://github.com/TrustVC/trustvc/issues/54)) ([8477bfc](https://github.com/TrustVC/trustvc/commit/8477bfc48214523de057cc9d047abe15886e1321)) +- update w3cIssuerIdentity to use documentLoader ([#54](https://github.com/TrustVC/trustvc/issues/54)) ([8477bfc](https://github.com/TrustVC/trustvc/commit/8477bfc48214523de057cc9d047abe15886e1321)) ## [1.4.3](https://github.com/TrustVC/trustvc/compare/v1.4.2...v1.4.3) (2025-04-09) - ### Bug Fixes -* allow getDocumentLoader for verifyDocument ([#53](https://github.com/TrustVC/trustvc/issues/53)) ([8a4afc4](https://github.com/TrustVC/trustvc/commit/8a4afc44bcf3ca45a9bc82d7a6838e7df432200c)) +- allow getDocumentLoader for verifyDocument ([#53](https://github.com/TrustVC/trustvc/issues/53)) ([8a4afc4](https://github.com/TrustVC/trustvc/commit/8a4afc44bcf3ca45a9bc82d7a6838e7df432200c)) ## [1.4.2](https://github.com/TrustVC/trustvc/compare/v1.4.1...v1.4.2) (2025-04-08) - ### Bug Fixes -* add getDocumentOwner ([#52](https://github.com/TrustVC/trustvc/issues/52)) ([ac37a05](https://github.com/TrustVC/trustvc/commit/ac37a053f381128cd180b2d11adfd38c3f24014c)) +- add getDocumentOwner ([#52](https://github.com/TrustVC/trustvc/issues/52)) ([ac37a05](https://github.com/TrustVC/trustvc/commit/ac37a053f381128cd180b2d11adfd38c3f24014c)) ## [1.4.1](https://github.com/TrustVC/trustvc/compare/v1.4.0...v1.4.1) (2025-04-08) - ### Bug Fixes -* upgrade package and update getTitleEscrowAddress ([#51](https://github.com/TrustVC/trustvc/issues/51)) ([f0693a1](https://github.com/TrustVC/trustvc/commit/f0693a14d1bcea22d50ab904d45bbf1151c1ceb8)) +- upgrade package and update getTitleEscrowAddress ([#51](https://github.com/TrustVC/trustvc/issues/51)) ([f0693a1](https://github.com/TrustVC/trustvc/commit/f0693a14d1bcea22d50ab904d45bbf1151c1ceb8)) ## [1.4.0](https://github.com/TrustVC/trustvc/compare/v1.3.0...v1.4.0) (2025-04-04) - ### Features -* document builder update ([#50](https://github.com/TrustVC/trustvc/issues/50)) ([a6e2e46](https://github.com/TrustVC/trustvc/commit/a6e2e4617773e3d426d5429f48f1dce8938b2270)) +- document builder update ([#50](https://github.com/TrustVC/trustvc/issues/50)) ([a6e2e46](https://github.com/TrustVC/trustvc/commit/a6e2e4617773e3d426d5429f48f1dce8938b2270)) ## [1.3.0](https://github.com/TrustVC/trustvc/compare/v1.2.11...v1.3.0) (2025-03-27) - ### Features -* document builder for w3c vc ([#47](https://github.com/TrustVC/trustvc/issues/47)) ([fab9c82](https://github.com/TrustVC/trustvc/commit/fab9c8237a072f541474fa778cf9974de6c0769c)) +- document builder for w3c vc ([#47](https://github.com/TrustVC/trustvc/issues/47)) ([fab9c82](https://github.com/TrustVC/trustvc/commit/fab9c8237a072f541474fa778cf9974de6c0769c)) ## [1.2.11](https://github.com/TrustVC/trustvc/compare/v1.2.10...v1.2.11) (2025-03-17) - ### Bug Fixes -* update package ([#46](https://github.com/TrustVC/trustvc/issues/46)) ([c0a5d1c](https://github.com/TrustVC/trustvc/commit/c0a5d1cb8d5988fa803814efdab4b8b90e0760b2)) +- update package ([#46](https://github.com/TrustVC/trustvc/issues/46)) ([c0a5d1c](https://github.com/TrustVC/trustvc/commit/c0a5d1cb8d5988fa803814efdab4b8b90e0760b2)) ## [1.2.10](https://github.com/TrustVC/trustvc/compare/v1.2.9...v1.2.10) (2025-03-13) - ### Bug Fixes -* update exports ([#45](https://github.com/TrustVC/trustvc/issues/45)) ([bf1e165](https://github.com/TrustVC/trustvc/commit/bf1e165e275bb256046e19a5c6796c773856ce37)) +- update exports ([#45](https://github.com/TrustVC/trustvc/issues/45)) ([bf1e165](https://github.com/TrustVC/trustvc/commit/bf1e165e275bb256046e19a5c6796c773856ce37)) ## [1.2.9](https://github.com/TrustVC/trustvc/compare/v1.2.8...v1.2.9) (2025-03-13) - ### Bug Fixes -* update tradetrust utils ([#44](https://github.com/TrustVC/trustvc/issues/44)) ([3058c16](https://github.com/TrustVC/trustvc/commit/3058c16df61047f6ef721533a237769c588a7557)) +- update tradetrust utils ([#44](https://github.com/TrustVC/trustvc/issues/44)) ([3058c16](https://github.com/TrustVC/trustvc/commit/3058c16df61047f6ef721533a237769c588a7557)) ## [1.2.8](https://github.com/TrustVC/trustvc/compare/v1.2.7...v1.2.8) (2025-03-05) - ### Bug Fixes -* supports web3provider for endorsement chain ([#43](https://github.com/TrustVC/trustvc/issues/43)) ([2559f44](https://github.com/TrustVC/trustvc/commit/2559f44f1e432fd6c1784c87dfae00d5bc18e1e0)) +- supports web3provider for endorsement chain ([#43](https://github.com/TrustVC/trustvc/issues/43)) ([2559f44](https://github.com/TrustVC/trustvc/commit/2559f44f1e432fd6c1784c87dfae00d5bc18e1e0)) ## [1.2.7](https://github.com/TrustVC/trustvc/compare/v1.2.6...v1.2.7) (2025-02-12) - ### Bug Fixes -* package ([#42](https://github.com/TrustVC/trustvc/issues/42)) ([7b4d49e](https://github.com/TrustVC/trustvc/commit/7b4d49e71ed5fa8f5733e0d34611cb817ff2c101)) +- package ([#42](https://github.com/TrustVC/trustvc/issues/42)) ([7b4d49e](https://github.com/TrustVC/trustvc/commit/7b4d49e71ed5fa8f5733e0d34611cb817ff2c101)) ## [1.2.6](https://github.com/TrustVC/trustvc/compare/v1.2.5...v1.2.6) (2025-02-11) - ### Bug Fixes -* upgrade pkgs ([#41](https://github.com/TrustVC/trustvc/issues/41)) ([faa0ddc](https://github.com/TrustVC/trustvc/commit/faa0ddccbb6729e6ac43d75826434db597c7e63b)) +- upgrade pkgs ([#41](https://github.com/TrustVC/trustvc/issues/41)) ([faa0ddc](https://github.com/TrustVC/trustvc/commit/faa0ddccbb6729e6ac43d75826434db597c7e63b)) ## [1.2.5](https://github.com/TrustVC/trustvc/compare/v1.2.4...v1.2.5) (2025-02-11) - ### Bug Fixes -* ethers error ([#40](https://github.com/TrustVC/trustvc/issues/40)) ([f41bebc](https://github.com/TrustVC/trustvc/commit/f41bebc1604e693ec72a727fa587332ee7c0e522)) +- ethers error ([#40](https://github.com/TrustVC/trustvc/issues/40)) ([f41bebc](https://github.com/TrustVC/trustvc/commit/f41bebc1604e693ec72a727fa587332ee7c0e522)) ## [1.2.4](https://github.com/TrustVC/trustvc/compare/v1.2.3...v1.2.4) (2025-02-05) - ### Bug Fixes -* upgrade packages ([#37](https://github.com/TrustVC/trustvc/issues/37)) ([35f26c7](https://github.com/TrustVC/trustvc/commit/35f26c71f029a73cb9498c7fb2201d96c19f432e)) +- upgrade packages ([#37](https://github.com/TrustVC/trustvc/issues/37)) ([35f26c7](https://github.com/TrustVC/trustvc/commit/35f26c71f029a73cb9498c7fb2201d96c19f432e)) ## [1.2.3](https://github.com/TrustVC/trustvc/compare/v1.2.2...v1.2.3) (2025-02-04) - ### Bug Fixes -* upgrade pkgs and migration function from website ([#36](https://github.com/TrustVC/trustvc/issues/36)) ([6936e71](https://github.com/TrustVC/trustvc/commit/6936e719d9878957f52764af3e4528273afb7bd3)) +- upgrade pkgs and migration function from website ([#36](https://github.com/TrustVC/trustvc/issues/36)) ([6936e71](https://github.com/TrustVC/trustvc/commit/6936e719d9878957f52764af3e4528273afb7bd3)) ## [1.2.2](https://github.com/TrustVC/trustvc/compare/v1.2.1...v1.2.2) (2025-01-28) - ### Bug Fixes -* upgrade pkgs ([#35](https://github.com/TrustVC/trustvc/issues/35)) ([bf4d412](https://github.com/TrustVC/trustvc/commit/bf4d4129d756bfd3a406688117fa8a60d3bc7cd4)) +- upgrade pkgs ([#35](https://github.com/TrustVC/trustvc/issues/35)) ([bf4d412](https://github.com/TrustVC/trustvc/commit/bf4d4129d756bfd3a406688117fa8a60d3bc7cd4)) ## [1.2.1](https://github.com/TrustVC/trustvc/compare/v1.2.0...v1.2.1) (2025-01-09) - ### Bug Fixes -* import error ([#34](https://github.com/TrustVC/trustvc/issues/34)) ([1450333](https://github.com/TrustVC/trustvc/commit/145033337e40d1c9fa54933829a095eb83bbe465)) +- import error ([#34](https://github.com/TrustVC/trustvc/issues/34)) ([1450333](https://github.com/TrustVC/trustvc/commit/145033337e40d1c9fa54933829a095eb83bbe465)) ## [1.2.0](https://github.com/TrustVC/trustvc/compare/v1.1.3...v1.2.0) (2025-01-09) - ### Features -* add w3cEmptyCredentialStatus fragment ([#33](https://github.com/TrustVC/trustvc/issues/33)) ([76a3098](https://github.com/TrustVC/trustvc/commit/76a3098427cd00dc4102c3850475ce4828adaed2)) +- add w3cEmptyCredentialStatus fragment ([#33](https://github.com/TrustVC/trustvc/issues/33)) ([76a3098](https://github.com/TrustVC/trustvc/commit/76a3098427cd00dc4102c3850475ce4828adaed2)) ## [1.1.3](https://github.com/TrustVC/trustvc/compare/v1.1.2...v1.1.3) (2025-01-07) - ### Bug Fixes -* upgrade package ([#32](https://github.com/TrustVC/trustvc/issues/32)) ([4cb0ac0](https://github.com/TrustVC/trustvc/commit/4cb0ac0ac0cd4aaa2d16ba639f56001143ab45e9)) +- upgrade package ([#32](https://github.com/TrustVC/trustvc/issues/32)) ([4cb0ac0](https://github.com/TrustVC/trustvc/commit/4cb0ac0ac0cd4aaa2d16ba639f56001143ab45e9)) ## [1.1.2](https://github.com/TrustVC/trustvc/compare/v1.1.1...v1.1.2) (2025-01-03) - ### Bug Fixes -* exports and package upgrade ([#31](https://github.com/TrustVC/trustvc/issues/31)) ([b64b375](https://github.com/TrustVC/trustvc/commit/b64b375a45c662ee841dc9d20ee69d5ebc6cae21)) +- exports and package upgrade ([#31](https://github.com/TrustVC/trustvc/issues/31)) ([b64b375](https://github.com/TrustVC/trustvc/commit/b64b375a45c662ee841dc9d20ee69d5ebc6cae21)) ## [1.1.1](https://github.com/TrustVC/trustvc/compare/v1.1.0...v1.1.1) (2024-12-23) - ### Bug Fixes -* imports ([#29](https://github.com/TrustVC/trustvc/issues/29)) ([6cb78d5](https://github.com/TrustVC/trustvc/commit/6cb78d5fbb521ab2b514a0da0ad7ed1efd224d74)) +- imports ([#29](https://github.com/TrustVC/trustvc/issues/29)) ([6cb78d5](https://github.com/TrustVC/trustvc/commit/6cb78d5fbb521ab2b514a0da0ad7ed1efd224d74)) ## [1.1.0](https://github.com/TrustVC/trustvc/compare/v1.0.5...v1.1.0) (2024-12-19) - ### Features -* endorsement chain ([#27](https://github.com/TrustVC/trustvc/issues/27)) ([58c184a](https://github.com/TrustVC/trustvc/commit/58c184a2d1694978e6dd8a55c85c86aa7a9b1212)) +- endorsement chain ([#27](https://github.com/TrustVC/trustvc/issues/27)) ([58c184a](https://github.com/TrustVC/trustvc/commit/58c184a2d1694978e6dd8a55c85c86aa7a9b1212)) ## [1.0.5](https://github.com/TrustVC/trustvc/compare/v1.0.4...v1.0.5) (2024-12-17) - ### Bug Fixes -* credential status to verify set ([#26](https://github.com/TrustVC/trustvc/issues/26)) ([b869b9e](https://github.com/TrustVC/trustvc/commit/b869b9ed66e7f41e00c17efabf6990796ee9121f)) +- credential status to verify set ([#26](https://github.com/TrustVC/trustvc/issues/26)) ([b869b9e](https://github.com/TrustVC/trustvc/commit/b869b9ed66e7f41e00c17efabf6990796ee9121f)) ## [1.0.4](https://github.com/TrustVC/trustvc/compare/v1.0.3...v1.0.4) (2024-12-13) - ### Bug Fixes -* add imports ([#25](https://github.com/TrustVC/trustvc/issues/25)) ([3ea8b9f](https://github.com/TrustVC/trustvc/commit/3ea8b9f610ce93f9d86792a4951c09d3a2269450)) +- add imports ([#25](https://github.com/TrustVC/trustvc/issues/25)) ([3ea8b9f](https://github.com/TrustVC/trustvc/commit/3ea8b9f610ce93f9d86792a4951c09d3a2269450)) ## [1.0.3](https://github.com/TrustVC/trustvc/compare/v1.0.2...v1.0.3) (2024-12-06) - ### Bug Fixes -* update w3c package ([#24](https://github.com/TrustVC/trustvc/issues/24)) ([26d4371](https://github.com/TrustVC/trustvc/commit/26d4371fe0dbb477ed112240be2db7fa13c39a95)) +- update w3c package ([#24](https://github.com/TrustVC/trustvc/issues/24)) ([26d4371](https://github.com/TrustVC/trustvc/commit/26d4371fe0dbb477ed112240be2db7fa13c39a95)) ## [1.0.2](https://github.com/TrustVC/trustvc/compare/v1.0.1...v1.0.2) (2024-12-06) - ### Bug Fixes -* update imports ([#23](https://github.com/TrustVC/trustvc/issues/23)) ([cd5edd1](https://github.com/TrustVC/trustvc/commit/cd5edd1d1718c78273d8c314f542f1ada3e73cd2)) +- update imports ([#23](https://github.com/TrustVC/trustvc/issues/23)) ([cd5edd1](https://github.com/TrustVC/trustvc/commit/cd5edd1d1718c78273d8c314f542f1ada3e73cd2)) ## [1.0.1](https://github.com/TrustVC/trustvc/compare/v1.0.0...v1.0.1) (2024-12-06) - ### Bug Fixes -* update package dependencies ([#22](https://github.com/TrustVC/trustvc/issues/22)) ([92a9795](https://github.com/TrustVC/trustvc/commit/92a97958f06ce862f8e74138d22de60b2f85b2f6)) - +- update package dependencies ([#22](https://github.com/TrustVC/trustvc/issues/22)) ([92a9795](https://github.com/TrustVC/trustvc/commit/92a97958f06ce862f8e74138d22de60b2f85b2f6)) ### Miscellaneous Chores -* update readme ([#21](https://github.com/TrustVC/trustvc/issues/21)) ([b6f0eb3](https://github.com/TrustVC/trustvc/commit/b6f0eb305f26843307592d0efa26a22d05091022)) +- update readme ([#21](https://github.com/TrustVC/trustvc/issues/21)) ([b6f0eb3](https://github.com/TrustVC/trustvc/commit/b6f0eb305f26843307592d0efa26a22d05091022)) ## [1.0.0](https://github.com/TrustVC/trustvc/compare/v0.0.0...v1.0.0) (2024-12-02) - ### ⚠ BREAKING CHANGES -* v1 release +- v1 release ### Features -* breaking change: v1 release ([#19](https://github.com/TrustVC/trustvc/issues/19)) ([2b563e9](https://github.com/TrustVC/trustvc/commit/2b563e9766598aec12127b36260e2a8488932c3a)) - +- breaking change: v1 release ([#19](https://github.com/TrustVC/trustvc/issues/19)) ([2b563e9](https://github.com/TrustVC/trustvc/commit/2b563e9766598aec12127b36260e2a8488932c3a)) ### Miscellaneous Chores -* update ci ([6bcd602](https://github.com/TrustVC/trustvc/commit/6bcd60227e99ab170eed90ab6b3cccca34ed38aa)) +- update ci ([6bcd602](https://github.com/TrustVC/trustvc/commit/6bcd60227e99ab170eed90ab6b3cccca34ed38aa)) From 9037983c3e3e4bf9c3b5dde83dfeefcb66b67ebb Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Mon, 23 Jun 2025 17:24:01 +0530 Subject: [PATCH 15/27] feat: add transfer holder function --- src/__tests__/core/verify.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/core/verify.test.ts b/src/__tests__/core/verify.test.ts index 6fb349b..aa32855 100644 --- a/src/__tests__/core/verify.test.ts +++ b/src/__tests__/core/verify.test.ts @@ -8,7 +8,7 @@ import { WRAPPED_DOCUMENT_DID_TOKEN_REGISTRY_V3, WRAPPED_DOCUMENT_DNS_TXT_V2, } from '../fixtures/fixtures'; -import { W3CCredentialStatusCode } from 'src/verify/fragments/document-status/w3cCredentialStatus'; +import { W3CCredentialStatusCode } from './../../../src/verify/fragments/document-status/w3cCredentialStatus'; const providerUrl = 'https://rpc-amoy.polygon.technology'; From fd265f789c4d9a6c41ac6976e911bc255a7ed4cd Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Thu, 26 Jun 2025 23:48:14 +0530 Subject: [PATCH 16/27] feat: add transfer owners beneficiary --- src/__tests__/token-registry-functions/transfers.test.ts | 2 -- src/core/endorsement-chain/useEndorsementChain.ts | 3 ++- tsconfig.json | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/__tests__/token-registry-functions/transfers.test.ts b/src/__tests__/token-registry-functions/transfers.test.ts index c87447c..3b5eb8b 100644 --- a/src/__tests__/token-registry-functions/transfers.test.ts +++ b/src/__tests__/token-registry-functions/transfers.test.ts @@ -189,7 +189,6 @@ describe.each(providers)('Transfers', async ({ Provider, ethersVersion, titleEsc holderAddress: '0xholder', tokenId: 1, }; - const txHash = isV5TT ? 'v5_transfer_holder_tx_hash' : 'v4_transfer_holder_tx_hash'; it('throws error if titleEscrowAddress is missing ', async () => { @@ -529,7 +528,6 @@ describe.each(providers)('Transfers', async ({ Provider, ethersVersion, titleEsc remarks: '0xencrypted_remarks', } : { newBeneficiaryAddress: '0xbeneficiary', newHolderAddress: '0xholder' }; - const txHash = isV5TT ? 'v5_transfer_owners_tx_hash' : 'v4_transfer_owners_tx_hash'; it('throws error if titleEscrowAddress is missing ', async () => { diff --git a/src/core/endorsement-chain/useEndorsementChain.ts b/src/core/endorsement-chain/useEndorsementChain.ts index a6b949c..8d2f8a2 100644 --- a/src/core/endorsement-chain/useEndorsementChain.ts +++ b/src/core/endorsement-chain/useEndorsementChain.ts @@ -109,8 +109,9 @@ export const getTitleEscrowAddress = async ( titleEscrowVersion?: 'v4' | 'v5'; }, ): Promise => { + console.log('i am here 1'); const titleEscrowOwner = await getDocumentOwner(tokenRegistryAddress, tokenId, provider); - + console.log('i am here 2'); const BURN_ADDRESS = '0x000000000000000000000000000000000000dEaD'; const isInactiveEscrow = [BURN_ADDRESS, tokenRegistryAddress] .map((address) => address.toLowerCase()) diff --git a/tsconfig.json b/tsconfig.json index 06eebbb..cb5de71 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,8 @@ "skipLibCheck": true, "noUncheckedIndexedAccess": true, "noEmit": true, - "downlevelIteration": true + "downlevelIteration": true, + "types": ["vitest"] }, "buildOptions": { "outDir": "./dist", From 5cd7b26e7956885841b5ca965aeec7f384d538c8 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Fri, 27 Jun 2025 08:56:20 +0530 Subject: [PATCH 17/27] chore: tests cleanup --- src/core/endorsement-chain/useEndorsementChain.ts | 2 -- tsconfig.json | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/endorsement-chain/useEndorsementChain.ts b/src/core/endorsement-chain/useEndorsementChain.ts index 8d2f8a2..e4e4e34 100644 --- a/src/core/endorsement-chain/useEndorsementChain.ts +++ b/src/core/endorsement-chain/useEndorsementChain.ts @@ -109,9 +109,7 @@ export const getTitleEscrowAddress = async ( titleEscrowVersion?: 'v4' | 'v5'; }, ): Promise => { - console.log('i am here 1'); const titleEscrowOwner = await getDocumentOwner(tokenRegistryAddress, tokenId, provider); - console.log('i am here 2'); const BURN_ADDRESS = '0x000000000000000000000000000000000000dEaD'; const isInactiveEscrow = [BURN_ADDRESS, tokenRegistryAddress] .map((address) => address.toLowerCase()) diff --git a/tsconfig.json b/tsconfig.json index cb5de71..06eebbb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,8 +25,7 @@ "skipLibCheck": true, "noUncheckedIndexedAccess": true, "noEmit": true, - "downlevelIteration": true, - "types": ["vitest"] + "downlevelIteration": true }, "buildOptions": { "outDir": "./dist", From f8a0c8bb23b11a7730501bc478de51be3114f090 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Fri, 27 Jun 2025 09:06:24 +0530 Subject: [PATCH 18/27] fix: remove console --- src/__tests__/core/verify.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/core/verify.test.ts b/src/__tests__/core/verify.test.ts index aa32855..6fb349b 100644 --- a/src/__tests__/core/verify.test.ts +++ b/src/__tests__/core/verify.test.ts @@ -8,7 +8,7 @@ import { WRAPPED_DOCUMENT_DID_TOKEN_REGISTRY_V3, WRAPPED_DOCUMENT_DNS_TXT_V2, } from '../fixtures/fixtures'; -import { W3CCredentialStatusCode } from './../../../src/verify/fragments/document-status/w3cCredentialStatus'; +import { W3CCredentialStatusCode } from 'src/verify/fragments/document-status/w3cCredentialStatus'; const providerUrl = 'https://rpc-amoy.polygon.technology'; From 082224f4b2678e47baa2fa3dc91ad68ddaa696fa Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Fri, 27 Jun 2025 09:07:43 +0530 Subject: [PATCH 19/27] fix: rever useendorement chain --- src/core/endorsement-chain/useEndorsementChain.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/endorsement-chain/useEndorsementChain.ts b/src/core/endorsement-chain/useEndorsementChain.ts index e4e4e34..a6b949c 100644 --- a/src/core/endorsement-chain/useEndorsementChain.ts +++ b/src/core/endorsement-chain/useEndorsementChain.ts @@ -110,6 +110,7 @@ export const getTitleEscrowAddress = async ( }, ): Promise => { const titleEscrowOwner = await getDocumentOwner(tokenRegistryAddress, tokenId, provider); + const BURN_ADDRESS = '0x000000000000000000000000000000000000dEaD'; const isInactiveEscrow = [BURN_ADDRESS, tokenRegistryAddress] .map((address) => address.toLowerCase()) From f4b3d39d8a428040cdc2d3ec19c2da407bc4ba39 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Mon, 30 Jun 2025 18:11:46 +0530 Subject: [PATCH 20/27] feat: add reject transfer functions --- .../token-registry-functions/fixtures.ts | 117 +++++ .../rejectTransfers.test.ts | 438 ++++++++++++++++++ .../transfers.test.ts | 132 +----- src/token-registry-functions/index.ts | 1 + .../rejectTransfers.ts | 217 +++++++++ src/token-registry-functions/transfer.ts | 220 ++++----- src/token-registry-functions/types.ts | 53 +++ src/token-registry-functions/utils.ts | 44 ++ 8 files changed, 981 insertions(+), 241 deletions(-) create mode 100644 src/__tests__/token-registry-functions/fixtures.ts create mode 100644 src/__tests__/token-registry-functions/rejectTransfers.test.ts create mode 100644 src/token-registry-functions/rejectTransfers.ts create mode 100644 src/token-registry-functions/types.ts create mode 100644 src/token-registry-functions/utils.ts diff --git a/src/__tests__/token-registry-functions/fixtures.ts b/src/__tests__/token-registry-functions/fixtures.ts new file mode 100644 index 0000000..0745a5e --- /dev/null +++ b/src/__tests__/token-registry-functions/fixtures.ts @@ -0,0 +1,117 @@ +import { vi } from 'vitest'; +import { ethers as ethersV5 } from 'ethers'; +import { JsonRpcProvider as JsonRpcProviderV6 } from 'ethersV6'; + +vi.mock('src/core', () => ({ + encrypt: vi.fn(() => 'encrypted_remarks'), + getTitleEscrowAddress: vi.fn(() => Promise.resolve('0xv5contract')), + isTitleEscrowVersion: vi.fn(() => Promise.resolve(true)), + TitleEscrowInterface: { + V4: '0xTitleEscrowIdV4', + V5: '0xTitleEscrowIdV5', + }, +})); + +vi.mock('src/token-registry-v5', () => { + return { + v5Contracts: { + TitleEscrow__factory: { + connect: vi.fn(() => mockV5TitleEscrowContract), + }, + TradeTrustToken__factory: { + connect: vi.fn(() => mockV5TradeTrustTokenContract), + }, + TitleEscrowFactory__factory: { + connect: vi.fn(() => mockV5TitleEscrowFactoryContract), + }, + }, + v5SupportInterfaceIds: { + TitleEscrow: '0xTitleEscrowIdV5', + TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV5', + }, + }; +}); + +vi.mock('src/token-registry-v4', () => { + return { + v4Contracts: { + TitleEscrow__factory: { + connect: vi.fn(() => mockV4TitleEscrowContract), + }, + TradeTrustToken__factory: { + connect: vi.fn(() => mockV4TradeTrustTokenContract), + }, + TitleEscrowFactory__factory: { + connect: vi.fn(() => mockV4TitleEscrowFactoryContract), + }, + v4SupportInterfaceIds: { + TitleEscrow: '0xTitleEscrowIdV4', + TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV4', + }, + }, + }; +}); + +export const mockV5TitleEscrowFactoryContract = { + callStatic: { + getEscrowAddress: vi.fn(), + }, + getEscrowAddress: vi.fn(() => Promise.resolve('0xV5titleescrow')), +}; + +export const mockV5TradeTrustTokenContract = { + supportsInterface: vi.fn(), + titleEscrowFactory: vi.fn(() => Promise.resolve('0xV5titleescrowfactory')), +}; + +export const mockV5TitleEscrowContract = { + supportsInterface: vi.fn(), + callStatic: { + transferHolder: vi.fn(), + transferBeneficiary: vi.fn(), + transferOwners: vi.fn(), + nominate: vi.fn(), + rejectTransferHolder: vi.fn(), + rejectTransferBeneficiary: vi.fn(), + rejectTransferOwners: vi.fn(), + }, + transferHolder: vi.fn(() => Promise.resolve('v5_transfer_holder_tx_hash')), + transferBeneficiary: vi.fn(() => Promise.resolve('v5_transfer_beneficiary_tx_hash')), + transferOwners: vi.fn(() => Promise.resolve('v5_transfer_owners_tx_hash')), + nominate: vi.fn(() => Promise.resolve('v5_nominate_tx_hash')), + holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), + beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), + rejectTransferHolder: vi.fn(() => Promise.resolve('v5_reject_transfer_holder_tx_hash')), + rejectTransferBeneficiary: vi.fn(() => Promise.resolve('v5_reject_transfer_beneficiary_tx_hash')), + rejectTransferOwners: vi.fn(() => Promise.resolve('v5_reject_transfer_owners_tx_hash')), +}; + +export const mockV4TitleEscrowContract = { + callStatic: { + transferHolder: vi.fn(), + transferBeneficiary: vi.fn(), + transferOwners: vi.fn(), + nominate: vi.fn(), + }, + transferHolder: vi.fn(() => Promise.resolve('v4_transfer_holder_tx_hash')), + transferBeneficiary: vi.fn(() => Promise.resolve('v4_transfer_beneficiary_tx_hash')), + transferOwners: vi.fn(() => Promise.resolve('v4_transfer_owners_tx_hash')), + nominate: vi.fn(() => Promise.resolve('v4_nominate_tx_hash')), + holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), + beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), +}; +export const mockV4TitleEscrowFactoryContract = { + callStatic: { + getEscrowAddress: vi.fn(), + }, + getAddress: vi.fn(() => Promise.resolve('0xV4titleescrow')), +}; + +export const mockV4TradeTrustTokenContract = { + titleEscrowFactory: vi.fn(() => Promise.resolve('0xV4titleescrowfactory')), +}; + +export const PRIVATE_KEY = '0x59c6995e998f97a5a004497e5f1ebce0c16828d44b3f8d0bfa3a89d271d5b6b9'; // random local key + +export const providerV5 = new ethersV5.providers.JsonRpcProvider(); +export const providerV6 = new JsonRpcProviderV6(); diff --git a/src/__tests__/token-registry-functions/rejectTransfers.test.ts b/src/__tests__/token-registry-functions/rejectTransfers.test.ts new file mode 100644 index 0000000..9af0fcc --- /dev/null +++ b/src/__tests__/token-registry-functions/rejectTransfers.test.ts @@ -0,0 +1,438 @@ +import './fixtures.js'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { ethers as ethersV5, Wallet as WalletV5 } from 'ethers'; +import { ethers as ethersV6, Network, Wallet as WalletV6 } from 'ethersV6'; +import * as coreModule from 'src/core'; +import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; +import { + rejectTransferBeneficiary, + rejectTransferHolder, + rejectTransferOwners, +} from 'src/token-registry-functions/rejectTransfers'; +import { mockV5TitleEscrowContract, PRIVATE_KEY, providerV5, providerV6 } from './fixtures'; +import { ProviderInfo } from 'src/token-registry-functions/types.js'; + +const providers: ProviderInfo[] = [ + { + Provider: providerV5, + ethersVersion: 'v5', + titleEscrowVersion: 'v5', + }, + { + Provider: providerV6, + ethersVersion: 'v6', + titleEscrowVersion: 'v5', + }, +]; + +describe.each(providers)( + 'Reject Transfers', + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + const mockTokenRegistryAddress = '0xTokenRegistry'; + const mockTokenId = '0xTokenId'; + const mockTitleEscrowAddress = '0xTitleEscrow'; + const mockRemarks = 'Rejection remarks'; + const mockChainId = CHAIN_ID.local; + const mockEncryptedRemarks = '0xencryptedRemarks'; + + let wallet: ethersV5.Wallet | ethersV6.Wallet; + beforeEach(() => { + // Reset all mocks before each test + vi.clearAllMocks(); + + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; + // wallet = { + // ...wallet, + // address: '0xcurrent_holder', + // getChainId: vi.fn().mockResolvedValue(CHAIN_ID.mainnet as unknown as number), + // } as any; + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.mainnet as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.mainnet, + } as unknown as Network); + // vi.spyOn(wallet, 'getAddress').mockResolvedValue('0xcurrent_holder'); + } + + // Mock core functions + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockResolvedValue(mockTitleEscrowAddress); + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockResolvedValue(true); + vi.spyOn(coreModule, 'encrypt').mockReturnValue(mockEncryptedRemarks.slice(2)); + + // Mock contract calls + }); + describe(`Reject Transfers Holder with ethers version ${ethersVersion}`, () => { + it('should reject transfer holder with signer and all required parameters', async () => { + const result = await rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_holder_tx_hash'); + }); + + it('should reject transfer holder when titleEscrowAddress is provided', async () => { + const result = await rejectTransferHolder( + { + titleEscrowAddress: mockTitleEscrowAddress, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_holder_tx_hash'); + expect(coreModule.getTitleEscrowAddress).not.toHaveBeenCalled(); + }); + + it('should reject transfer holder without remarks', async () => { + const result = await rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + {}, + { chainId: mockChainId }, + ); + + expect(result).toEqual('v5_reject_transfer_holder_tx_hash'); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw error when tokenRegistryAddress is missing', async () => { + vi.mocked(coreModule.getTitleEscrowAddress).mockResolvedValue(undefined); + await expect( + rejectTransferHolder( + { + tokenId: mockTokenId, + } as any, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('should throw error when provider is missing', async () => { + const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1')); + + await expect( + rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + signerWithoutProvider, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw error when title escrow is not V5', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockResolvedValue(false); + + await expect( + rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Only Token Registry V5 is supported'); + }); + + it('should throw error when callStatic fails', async () => { + mockV5TitleEscrowContract.callStatic.rejectTransferHolder.mockRejectedValue( + new Error('Simulated failure'), + ); + + await expect( + rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + mockV5TitleEscrowContract.callStatic.rejectTransferHolder = vi.fn(); + }); + + it('should use explicit titleEscrowVersion when provided', async () => { + await rejectTransferHolder( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(coreModule.isTitleEscrowVersion).not.toHaveBeenCalled(); + }); + }); + + describe(`Reject Transfers Beneficiary with ethers version ${ethersVersion}`, () => { + it('should reject transfer beneficiary with signer and all required parameters', async () => { + const result = await rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_beneficiary_tx_hash'); + }); + + it('should reject transfer beneficiary when titleEscrowAddress is provided', async () => { + const result = await rejectTransferBeneficiary( + { + titleEscrowAddress: mockTitleEscrowAddress, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_beneficiary_tx_hash'); + expect(coreModule.getTitleEscrowAddress).not.toHaveBeenCalled(); + }); + + it('should reject transfer beneficiary without remarks', async () => { + const result = await rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + {}, + { chainId: mockChainId }, + ); + + expect(result).toEqual('v5_reject_transfer_beneficiary_tx_hash'); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw error when tokenRegistryAddress is missing', async () => { + vi.mocked(coreModule.getTitleEscrowAddress).mockResolvedValue(undefined); + await expect( + rejectTransferBeneficiary( + { + tokenId: mockTokenId, + } as any, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('should throw error when provider is missing', async () => { + const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1')); + + await expect( + rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + signerWithoutProvider, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw error when title escrow is not V5', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockResolvedValue(false); + + await expect( + rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Only Token Registry V5 is supported'); + }); + + it('should throw error when callStatic fails', async () => { + mockV5TitleEscrowContract.callStatic.rejectTransferBeneficiary.mockRejectedValue( + new Error('Simulated failure'), + ); + + await expect( + rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + mockV5TitleEscrowContract.callStatic.rejectTransferBeneficiary = vi.fn(); + }); + + it('should use explicit titleEscrowVersion when provided', async () => { + await rejectTransferBeneficiary( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(coreModule.isTitleEscrowVersion).not.toHaveBeenCalled(); + }); + }); + + describe(`Reject Transfers Owners with ethers version ${ethersVersion}`, () => { + it('should reject transfer beneficiary with signer and all required parameters', async () => { + const result = await rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_owners_tx_hash'); + }); + + it('should reject transfer beneficiary when titleEscrowAddress is provided', async () => { + const result = await rejectTransferOwners( + { + titleEscrowAddress: mockTitleEscrowAddress, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual('v5_reject_transfer_owners_tx_hash'); + expect(coreModule.getTitleEscrowAddress).not.toHaveBeenCalled(); + }); + + it('should reject transfer beneficiary without remarks', async () => { + const result = await rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + {}, + { chainId: mockChainId }, + ); + + expect(result).toEqual('v5_reject_transfer_owners_tx_hash'); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw error when tokenRegistryAddress is missing', async () => { + vi.mocked(coreModule.getTitleEscrowAddress).mockResolvedValue(undefined); + await expect( + rejectTransferOwners( + { + tokenId: mockTokenId, + } as any, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('should throw error when provider is missing', async () => { + const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1')); + + await expect( + rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + signerWithoutProvider, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw error when title escrow is not V5', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockResolvedValue(false); + + await expect( + rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Only Token Registry V5 is supported'); + }); + + it('should throw error when callStatic fails', async () => { + mockV5TitleEscrowContract.callStatic.rejectTransferOwners.mockRejectedValue( + new Error('Simulated failure'), + ); + + await expect( + rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + mockV5TitleEscrowContract.callStatic.rejectTransferOwners = vi.fn(); + }); + + it('should use explicit titleEscrowVersion when provided', async () => { + await rejectTransferOwners( + { + tokenRegistryAddress: mockTokenRegistryAddress, + tokenId: mockTokenId, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(coreModule.isTitleEscrowVersion).not.toHaveBeenCalled(); + }); + }); + }, +); diff --git a/src/__tests__/token-registry-functions/transfers.test.ts b/src/__tests__/token-registry-functions/transfers.test.ts index 3b5eb8b..5f1d6f4 100644 --- a/src/__tests__/token-registry-functions/transfers.test.ts +++ b/src/__tests__/token-registry-functions/transfers.test.ts @@ -1,11 +1,7 @@ +import './fixtures.js'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { ethers as ethersV5, Wallet as WalletV5 } from 'ethers'; -import { - ethers as ethersV6, - JsonRpcProvider as JsonRpcProviderV6, - Network, - Wallet as WalletV6, -} from 'ethersV6'; +import { ethers as ethersV6, Network, Wallet as WalletV6 } from 'ethersV6'; import * as coreModule from 'src/core'; import { encrypt } from 'src/core'; import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; @@ -15,122 +11,14 @@ import { transferOwners, nominate, } from 'src/token-registry-functions'; - -// Mocks - -vi.mock('src/core', () => ({ - encrypt: vi.fn(() => 'encrypted_remarks'), - getTitleEscrowAddress: vi.fn(() => Promise.resolve('0xv5contract')), - isTitleEscrowVersion: vi.fn(() => Promise.resolve(true)), - TitleEscrowInterface: { - V4: '0xTitleEscrowIdV4', - V5: '0xTitleEscrowIdV5', - }, -})); - -vi.mock('src/token-registry-v5', () => { - return { - v5Contracts: { - TitleEscrow__factory: { - connect: vi.fn(() => mockV5TitleEscrowContract), - }, - TradeTrustToken__factory: { - connect: vi.fn(() => mockV5TradeTrustTokenContract), - }, - TitleEscrowFactory__factory: { - connect: vi.fn(() => mockV5TitleEscrowFactoryContract), - }, - }, - v5SupportInterfaceIds: { - TitleEscrow: '0xTitleEscrowIdV5', - TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV5', - }, - }; -}); - -vi.mock('src/token-registry-v4', () => { - return { - v4Contracts: { - TitleEscrow__factory: { - connect: vi.fn(() => mockV4TitleEscrowContract), - }, - TradeTrustToken__factory: { - connect: vi.fn(() => mockV4TradeTrustTokenContract), - }, - TitleEscrowFactory__factory: { - connect: vi.fn(() => mockV4TitleEscrowFactoryContract), - }, - v4SupportInterfaceIds: { - TitleEscrow: '0xTitleEscrowIdV4', - TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV4', - }, - }, - }; -}); - -const mockV5TitleEscrowFactoryContract = { - callStatic: { - getEscrowAddress: vi.fn(), - }, - getEscrowAddress: vi.fn(() => Promise.resolve('0xV5titleescrow')), -}; - -const mockV5TradeTrustTokenContract = { - supportsInterface: vi.fn(), - titleEscrowFactory: vi.fn(() => Promise.resolve('0xV5titleescrowfactory')), -}; - -const mockV5TitleEscrowContract = { - supportsInterface: vi.fn(), - callStatic: { - transferHolder: vi.fn(), - transferBeneficiary: vi.fn(), - transferOwners: vi.fn(), - nominate: vi.fn(), - }, - transferHolder: vi.fn(() => Promise.resolve('v5_transfer_holder_tx_hash')), - transferBeneficiary: vi.fn(() => Promise.resolve('v5_transfer_beneficiary_tx_hash')), - transferOwners: vi.fn(() => Promise.resolve('v5_transfer_owners_tx_hash')), - nominate: vi.fn(() => Promise.resolve('v5_nominate_tx_hash')), - holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), - beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), -}; - -const mockV4TitleEscrowContract = { - callStatic: { - transferHolder: vi.fn(), - transferBeneficiary: vi.fn(), - transferOwners: vi.fn(), - nominate: vi.fn(), - }, - transferHolder: vi.fn(() => Promise.resolve('v4_transfer_holder_tx_hash')), - transferBeneficiary: vi.fn(() => Promise.resolve('v4_transfer_beneficiary_tx_hash')), - transferOwners: vi.fn(() => Promise.resolve('v4_transfer_owners_tx_hash')), - nominate: vi.fn(() => Promise.resolve('v4_nominate_tx_hash')), - holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), - beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), -}; -const mockV4TitleEscrowFactoryContract = { - callStatic: { - getEscrowAddress: vi.fn(), - }, - getAddress: vi.fn(() => Promise.resolve('0xV4titleescrow')), -}; - -const mockV4TradeTrustTokenContract = { - titleEscrowFactory: vi.fn(() => Promise.resolve('0xV4titleescrowfactory')), -}; - -const PRIVATE_KEY = '0x59c6995e998f97a5a004497e5f1ebce0c16828d44b3f8d0bfa3a89d271d5b6b9'; // random local key - -const providerV5 = new ethersV5.providers.JsonRpcProvider(); -const providerV6 = new JsonRpcProviderV6(); - -interface ProviderInfo { - Provider: typeof providerV5 | typeof providerV6; - ethersVersion: 'v5' | 'v6'; - titleEscrowVersion: 'v4' | 'v5'; -} +import { ProviderInfo } from 'src/token-registry-functions/types'; +import { + mockV4TitleEscrowContract, + mockV5TitleEscrowContract, + PRIVATE_KEY, + providerV5, + providerV6, +} from './fixtures'; const providers: ProviderInfo[] = [ { diff --git a/src/token-registry-functions/index.ts b/src/token-registry-functions/index.ts index 6de95ff..927e270 100644 --- a/src/token-registry-functions/index.ts +++ b/src/token-registry-functions/index.ts @@ -1 +1,2 @@ export * from './transfer'; +export * from './rejectTransfers'; diff --git a/src/token-registry-functions/rejectTransfers.ts b/src/token-registry-functions/rejectTransfers.ts new file mode 100644 index 0000000..a32ef4a --- /dev/null +++ b/src/token-registry-functions/rejectTransfers.ts @@ -0,0 +1,217 @@ +import { + encrypt, + getTitleEscrowAddress, + isTitleEscrowVersion, + TitleEscrowInterface, +} from 'src/core'; +import { v5Contracts } from 'src/token-registry-v5'; +import { Signer as SignerV6 } from 'ethersV6'; +import { ContractTransaction, Signer } from 'ethers'; +import { getTxOptions } from './utils'; +import { ContractOptions, RejectTransferParams, TransactionOptions } from './types'; + +/** + * Rejects the transfer of holder for a title escrow contract. + * @param {ContractOptions} contractOptions - Contract-related options including the token registry address, and optionally, token ID and the title escrow address. + * @param {Signer | SignerV6} signer - Ethers signer (V5 or V6) used to sign and send the transaction. + * @param {RejectTransferParams} params - Contains the `remarks` field which is an optional string that will be encrypted and sent with the transaction. + * @param {TransactionOptions} options - Transfer options including optional `chainId`, `titleEscrowVersion`, `maxFeePerGas`, `maxPriorityFeePerGas`, and an `id` used for encryption. + * @throws error if the title escrow address or signer provider is missing. + * @throws if the version is not V5 compatible. + * @throws if the dry-run (`callStatic`) fails. + * @returns {Promise} The transaction response of the rejectTransferHolder call. + */ +const rejectTransferHolder = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: RejectTransferParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress, tokenId } = contractOptions; + let { titleEscrowAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + {}, + ); + } + + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { remarks } = params; + + // Connect V5 contract by default + const titleEscrowContract = v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + let isV5TT = titleEscrowVersion === 'v5'; + if (titleEscrowVersion === undefined) { + isV5TT = await isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }); + } + + if (!isV5TT) { + throw new Error('Only Token Registry V5 is supported'); + } + + // Check callStatic (dry run) + try { + await titleEscrowContract.callStatic.rejectTransferHolder(encryptedRemarks); + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for transferHolder failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + + return await titleEscrowContract.rejectTransferHolder(encryptedRemarks, txOptions); +}; + +/** + * Rejects the transfer of beneficiary for a title escrow contract. + * @param {ContractOptions} contractOptions - Contract-related options including the token registry address, and optionally, token ID and the title escrow address. + * @param {Signer | SignerV6} signer - Ethers signer (V5 or V6) used to sign and send the transaction. + * @param {RejectTransferParams} params - Contains the `remarks` field which is an optional string that will be encrypted and sent with the transaction. + * @param {TransactionOptions} options - Transfer options including optional `chainId`, `titleEscrowVersion`, `maxFeePerGas`, `maxPriorityFeePerGas`, and an `id` used for encryption. + * @throws error if the title escrow address or signer provider is missing. + * @throws error if the version is not V5 compatible. + * @throws error if the dry-run (`callStatic`) fails. + * @returns {Promise} The transaction response of the rejectTransferBeneficiary call. + */ +const rejectTransferBeneficiary = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: RejectTransferParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress, tokenId } = contractOptions; + let { titleEscrowAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + {}, + ); + } + + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { remarks } = params; + + // Connect V5 contract by default + const titleEscrowContract = v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + let isV5TT = titleEscrowVersion === 'v5'; + if (titleEscrowVersion === undefined) { + isV5TT = await isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }); + } + + if (!isV5TT) { + throw new Error('Only Token Registry V5 is supported'); + } + + // Check callStatic (dry run) + try { + await titleEscrowContract.callStatic.rejectTransferBeneficiary(encryptedRemarks); + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for transferHolder failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + + return await titleEscrowContract.rejectTransferBeneficiary(encryptedRemarks, txOptions); +}; + +/** + * Rejects the transfer of ownership for a title escrow contract. + * @param {ContractOptions} contractOptions - Contract-related options including the token registry address, and optionally, token ID and the title escrow address. + * @param {Signer | SignerV6} signer - Ethers signer (V5 or V6) used to sign and send the transaction. + * @param {RejectTransferParams} params - Contains the `remarks` field which is an optional string that will be encrypted and sent with the transaction. + * @param {TransactionOptions} options - Transfer options including optional `chainId`, `titleEscrowVersion`, `maxFeePerGas`, `maxPriorityFeePerGas`, and an `id` used for encryption. + * @throws error if the title escrow address or signer provider is missing. + * @throws an error if the version is not V5 compatible. + * @throws an error if the dry-run (`callStatic`) fails. + * @returns {Promise} The transaction response of the rejectTransferOwners call. + */ +const rejectTransferOwners = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: RejectTransferParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress, tokenId } = contractOptions; + let { titleEscrowAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + {}, + ); + } + + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { remarks } = params; + + // Connect V5 contract by default + const titleEscrowContract = v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + let isV5TT = titleEscrowVersion === 'v5'; + if (titleEscrowVersion === undefined) { + isV5TT = await isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }); + } + + if (!isV5TT) { + throw new Error('Only Token Registry V5 is supported'); + } + + // Check callStatic (dry run) + try { + await titleEscrowContract.callStatic.rejectTransferOwners(encryptedRemarks); + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for transferHolder failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + + return await titleEscrowContract.rejectTransferOwners(encryptedRemarks, txOptions); +}; + +export { rejectTransferHolder, rejectTransferBeneficiary, rejectTransferOwners }; diff --git a/src/token-registry-functions/transfer.ts b/src/token-registry-functions/transfer.ts index 5247762..7c944d3 100644 --- a/src/token-registry-functions/transfer.ts +++ b/src/token-registry-functions/transfer.ts @@ -1,4 +1,3 @@ -import { CHAIN_ID, SUPPORTED_CHAINS } from '@tradetrust-tt/tradetrust-utils'; import { encrypt, getTitleEscrowAddress, @@ -7,74 +6,44 @@ import { } from 'src/core'; import { v4Contracts } from 'src/token-registry-v4'; import { v5Contracts } from 'src/token-registry-v5'; -import { BigNumberish, Signer as SignerV6 } from 'ethersV6'; -import { BigNumber, Signer } from 'ethers'; -import { isV6EthersProvider } from 'src/utils/ethers'; - -interface TransferHolderParams { - holderAddress: string; - remarks?: string; -} -interface TransferBeneficiaryParams { - newBeneficiaryAddress: string; - remarks?: string; -} -interface NominateParams { - newBeneficiaryAddress: string; - remarks?: string; -} -interface TransferOwnersParams { - newHolderAddress: string; - newBeneficiaryAddress: string; - remarks?: string; -} - -interface TransferOptions { - chainId?: CHAIN_ID; - titleEscrowVersion?: 'v4' | 'v5'; - maxFeePerGas?: BigNumberish | string | number | BigNumber; - maxPriorityFeePerGas?: BigNumberish | string | number | BigNumber; - id?: string; -} - -// 🔍 Handles both Ethers v5 and v6 signer types -const getChainIdSafe = async (signer: SignerV6 | Signer): Promise => { - if (isV6EthersProvider(signer.provider)) { - const network = await (signer as Signer).provider?.getNetwork(); - if (!network?.chainId) throw new Error('Cannot determine chainId: provider is missing'); - return network.chainId; - } - return await (signer as Signer).getChainId(); -}; -// const getSignerAddressSafe = async (signer: SignerV6 | Signer): Promise => { -// if (isV6EthersProvider(signer.provider)) { -// return await (signer as SignerV6).getAddress(); -// } -// return (signer as any).address; -// }; - -type ContractOptions = - | { - titleEscrowAddress: string; // Present — no restrictions on the rest - tokenId?: string | number; - tokenRegistryAddress?: string; - } - | { - titleEscrowAddress?: undefined; // Absent — must provide both below - tokenId: string | number; - tokenRegistryAddress: string; - }; - +import { Signer as SignerV6 } from 'ethersV6'; +import { ContractTransaction, Signer } from 'ethers'; +import { + ContractOptions, + NominateParams, + TransactionOptions, + TransferBeneficiaryParams, + TransferHolderParams, + TransferOwnersParams, +} from './types'; +import { getTxOptions } from './utils'; + +/** + * Transfers holder role of a Title Escrow contract to a new address. + * The caller of this function must be the current holder. + * @param {ContractOptions} contractOptions - Contains `tokenRegistryAddress` and optionally `tokenId` and `titleEscrowAddress`. + * @param {Signer | SignerV6} signer - The signer (ethers v5 or v6) who initiates the transaction. + * @param {TransferHolderParams} params - Object containing `holderAddress` (address to transfer to) and optional `remarks`. + * @param {TransactionOptions} options - Transaction options including: + * - `titleEscrowVersion` (optional): Either "v4" or "v5" + * - `chainId` (optional): Used for gas station lookup + * - `maxFeePerGas` (optional), `maxPriorityFeePerGas` (optional): EIP-1559 gas fee configuration + * - `id` (optional): ID used for encrypting remarks + * @throws If required fields like `titleEscrowAddress` or `signer.provider` are missing. + * @throws If the version is unsupported (neither v4 nor v5). + * @throws If the dry-run via `callStatic` fails. + * @returns {Promise} The transaction response for `transferHolder`. + */ const transferHolder = async ( contractOptions: ContractOptions, signer: Signer | SignerV6, params: TransferHolderParams, - options: TransferOptions, -) => { + options: TransactionOptions, +): Promise => { const { tokenRegistryAddress, tokenId } = contractOptions; const { titleEscrowVersion } = options; let { titleEscrowAddress } = contractOptions; - let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + const { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; let isV5TT = titleEscrowVersion === 'v5'; let isV4TT = titleEscrowVersion === 'v4'; @@ -153,18 +122,8 @@ const transferHolder = async ( } // If gas values are missing, query gas station if available - if (!maxFeePerGas || !maxPriorityFeePerGas) { - chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); - const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; - - if (gasStation) { - const gasFees = await gasStation(); - maxFeePerGas = gasFees?.maxFeePerGas ?? 0; - maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; - } - } - const txOptions = - maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + // Send the actual transaction if (isV5TT) { return await (titleEscrowContract as v5Contracts.TitleEscrow).transferHolder( @@ -177,16 +136,34 @@ const transferHolder = async ( } }; +/** + * Transfers the beneficiary role of a Title Escrow contract to a new beneficiary address. + * The caller of this function must be the current holder. + * @param {ContractOptions} contractOptions - Contains `tokenRegistryAddress` and optionally `tokenId` and `titleEscrowAddress`. + * @param {Signer | SignerV6} signer - The signer (ethers v5 or v6) who initiates and signs the transaction. + * @param {TransferBeneficiaryParams} params - Object containing: + * - `newBeneficiaryAddress`: Address to which the beneficiary role is being transferred. + * - `remarks` (optional): Optional encrypted message attached with the transaction. + * @param {TransactionOptions} options - Transaction configuration options: + * - `titleEscrowVersion` (optional): Token registry version, either `'v4'` or `'v5'`. + * - `chainId` (optional): Used to query gas station info if gas fee values are missing. + * - `maxFeePerGas`(optional), `maxPriorityFeePerGas`(optional): EIP-1559 gas fee parameters. + * - `id`(optional): Used for encryption of remarks. + * @throws If required values like `titleEscrowAddress` or `signer.provider` are missing. + * @throws If the version is unsupported (neither v4 nor v5). + * @throws If the dry-run `callStatic` fails for pre-checking the transaction. + * @returns {Promise} The transaction response for the `transferBeneficiary` call. + */ const transferBeneficiary = async ( contractOptions: ContractOptions, signer: Signer | SignerV6, params: TransferBeneficiaryParams, - options: TransferOptions, -) => { + options: TransactionOptions, +): Promise => { const { tokenId, tokenRegistryAddress } = contractOptions; const { titleEscrowVersion } = options; let { titleEscrowAddress } = contractOptions; - let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + const { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; let isV5TT = titleEscrowVersion === 'v5'; let isV4TT = titleEscrowVersion === 'v4'; @@ -265,19 +242,8 @@ const transferBeneficiary = async ( } // If gas values are missing, query gas station if available - if (!maxFeePerGas || !maxPriorityFeePerGas) { - chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); - const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; - - if (gasStation) { - const gasFees = await gasStation(); - maxFeePerGas = gasFees?.maxFeePerGas ?? 0; - maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; - } - } + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); - const txOptions = - maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; // Send the actual transaction if (isV5TT) { const tx = await (titleEscrowContract as v5Contracts.TitleEscrow).transferBeneficiary( @@ -294,16 +260,36 @@ const transferBeneficiary = async ( return tx; } }; + +/** + * Transfers both the holder and beneficiary roles of a Title Escrow contract to new addresses. + * The caller of this function must be the current holder and beneficiary both. + * @param {ContractOptions} contractOptions - Contains `tokenRegistryAddress` and optionally `tokenId` and `titleEscrowAddress`. + * @param {Signer | SignerV6} signer - The signer (ethers v5 or v6) who initiates and signs the transaction. + * @param {TransferOwnersParams} params - Object containing: + * - `newBeneficiaryAddress`: The new beneficiary address. + * - `newHolderAddress`: The new holder address. + * - `remarks` (optional): Optional remarks that will be encrypted and included with the transaction. + * @param {TransactionOptions} options - Transaction configuration options: + * - `titleEscrowVersion` (optional): Token registry version, either `'v4'` or `'v5'`. + * - `chainId` (optional): Used for gas station lookup if gas fee values are not provided. + * - `maxFeePerGas`(optional), `maxPriorityFeePerGas`(optional): EIP-1559 gas fee parameters. + * - `id`(optional): Used for encrypting remarks. + * @throws If required fields like `titleEscrowAddress` or `signer.provider` are missing. + * @throws If the title escrow version is unsupported. + * @throws If the pre-check `callStatic.transferOwners` fails. + * @returns {Promise} The transaction response from the `transferOwners` call. + */ const transferOwners = async ( contractOptions: ContractOptions, signer: Signer | SignerV6, params: TransferOwnersParams, - options: TransferOptions, -) => { + options: TransactionOptions, +): Promise => { const { tokenId, tokenRegistryAddress } = contractOptions; const { titleEscrowVersion } = options; let { titleEscrowAddress } = contractOptions; - let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + const { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; let isV5TT = titleEscrowVersion === 'v5'; let isV4TT = titleEscrowVersion === 'v4'; @@ -389,19 +375,8 @@ const transferOwners = async ( } // If gas values are missing, query gas station if available - if (!maxFeePerGas || !maxPriorityFeePerGas) { - chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); - const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; - - if (gasStation) { - const gasFees = await gasStation(); - maxFeePerGas = gasFees?.maxFeePerGas ?? 0; - maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; - } - } + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); - const txOptions = - maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; // Send the actual transaction if (isV5TT) { @@ -420,16 +395,34 @@ const transferOwners = async ( } }; +/** + * Nominates a new beneficiary on the Title Escrow contract. + * The caller of this function must be the current beneficiary. + * @param {ContractOptions} contractOptions - Contains `tokenRegistryAddress` and optionally `tokenId` and `titleEscrowAddress`. + * @param {Signer | SignerV6} signer - The signer (ethers v5 or v6) who will sign and send the transaction. + * @param {NominateParams} params - Nomination parameters: + * - `newBeneficiaryAddress`: The Ethereum address to nominate as the new beneficiary. + * - `remarks` (optional): Remarks to include with the transaction (will be encrypted). + * @param {TransactionOptions} options - Transaction-level configuration: + * - `titleEscrowVersion` (optional): Specifies token registry version, either `'v4'` or `'v5'`. + * - `chainId` (optional): Chain ID used for querying gas stations if fees are not set. + * - `maxFeePerGas`(optional), `maxPriorityFeePerGas`(optional): EIP-1559-compatible gas fee settings. + * - `id`(optional): Used for encrypting the remarks string. + * @throws If required inputs like `titleEscrowAddress` or `signer.provider` are missing. + * @throws If token registry version is unsupported. + * @throws If the dry-run `callStatic.nominate()` fails. + * @returns {Promise} The transaction response from the `nominate` method. + */ const nominate = async ( contractOptions: ContractOptions, signer: Signer | SignerV6, params: NominateParams, - options: TransferOptions, -) => { + options: TransactionOptions, +): Promise => { const { tokenId, tokenRegistryAddress } = contractOptions; const { titleEscrowVersion } = options; let { titleEscrowAddress } = contractOptions; - let { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; + const { chainId, maxFeePerGas, maxPriorityFeePerGas } = options; let isV5TT = titleEscrowVersion === 'v5'; let isV4TT = titleEscrowVersion === 'v4'; @@ -504,19 +497,8 @@ const nominate = async ( } // If gas values are missing, query gas station if available - if (!maxFeePerGas || !maxPriorityFeePerGas) { - chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); - const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; - - if (gasStation) { - const gasFees = await gasStation(); - maxFeePerGas = gasFees?.maxFeePerGas ?? 0; - maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; - } - } + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); - const txOptions = - maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; // Send the actual transaction if (isV5TT) { diff --git a/src/token-registry-functions/types.ts b/src/token-registry-functions/types.ts new file mode 100644 index 0000000..22b0951 --- /dev/null +++ b/src/token-registry-functions/types.ts @@ -0,0 +1,53 @@ +import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; +import { BigNumber } from 'ethers'; +import { BigNumberish } from 'ethersV6'; +import { providerV5, providerV6 } from 'src/__tests__/token-registry-functions/fixtures'; + +export type GasValue = BigNumber | BigNumberish | string | number; + +export interface RejectTransferParams { + remarks?: string; +} + +export interface TransactionOptions { + chainId?: CHAIN_ID; + titleEscrowVersion?: 'v4' | 'v5'; + maxFeePerGas?: BigNumberish | string | number | BigNumber; + maxPriorityFeePerGas?: BigNumberish | string | number | BigNumber; + id?: string; +} + +export type ContractOptions = + | { + titleEscrowAddress: string; // Present — no restrictions on the rest + tokenId?: string | number; + tokenRegistryAddress?: string; + } + | { + titleEscrowAddress?: undefined; // Absent — must provide both below + tokenId: string | number; + tokenRegistryAddress: string; + }; + +export interface TransferHolderParams { + holderAddress: string; + remarks?: string; +} +export interface TransferBeneficiaryParams { + newBeneficiaryAddress: string; + remarks?: string; +} +export interface NominateParams { + newBeneficiaryAddress: string; + remarks?: string; +} +export interface TransferOwnersParams { + newHolderAddress: string; + newBeneficiaryAddress: string; + remarks?: string; +} +export interface ProviderInfo { + Provider: typeof providerV5 | typeof providerV6; + ethersVersion: 'v5' | 'v6'; + titleEscrowVersion: 'v4' | 'v5'; +} diff --git a/src/token-registry-functions/utils.ts b/src/token-registry-functions/utils.ts new file mode 100644 index 0000000..d426481 --- /dev/null +++ b/src/token-registry-functions/utils.ts @@ -0,0 +1,44 @@ +import { isV6EthersProvider } from 'src/utils/ethers'; +import { GasValue } from './types'; +import { CHAIN_ID, SUPPORTED_CHAINS } from '@tradetrust-tt/tradetrust-utils'; +import { Signer } from 'ethers'; +import { Signer as SignerV6 } from 'ethersV6'; + +const getTxOptions = async ( + signer: SignerV6 | Signer, + chainId: CHAIN_ID, + maxFeePerGas: GasValue, + maxPriorityFeePerGas: GasValue, +) => { + // If gas values are missing, query gas station if available + if (!maxFeePerGas || !maxPriorityFeePerGas) { + chainId = chainId ?? ((await getChainIdSafe(signer)) as unknown as CHAIN_ID); + const gasStation = SUPPORTED_CHAINS[chainId]?.gasStation; + + if (gasStation) { + const gasFees = await gasStation(); + maxFeePerGas = gasFees?.maxFeePerGas ?? 0; + maxPriorityFeePerGas = gasFees?.maxPriorityFeePerGas ?? 0; + } + } + return maxFeePerGas && maxPriorityFeePerGas ? { maxFeePerGas, maxPriorityFeePerGas } : undefined; +}; + +// 🔍 Handles both Ethers v5 and v6 signer types +const getChainIdSafe = async (signer: SignerV6 | Signer): Promise => { + if (isV6EthersProvider(signer.provider)) { + const network = await (signer as Signer).provider?.getNetwork(); + if (!network?.chainId) throw new Error('Cannot determine chainId: provider is missing'); + return network.chainId; + } + return await (signer as Signer).getChainId(); +}; + +const getSignerAddressSafe = async (signer: SignerV6 | Signer): Promise => { + if (isV6EthersProvider(signer.provider)) { + return await (signer as SignerV6).getAddress(); + } + return await (signer as unknown as Signer).getAddress(); +}; + +export { getChainIdSafe, getTxOptions, getSignerAddressSafe }; From bf0227872dd2cee22467d144430b59e610216abe Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Tue, 1 Jul 2025 00:55:46 +0530 Subject: [PATCH 21/27] chore: trigger rebuild after rebase From 4bb486fe932566f70bf015642b2786be6c8e1939 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Tue, 1 Jul 2025 01:00:08 +0530 Subject: [PATCH 22/27] chore: trigger rebuild after rebase From 8b113db57c289224581e7b2e73bf95bc3b40daf0 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 2 Jul 2025 14:45:01 +0530 Subject: [PATCH 23/27] feat: token registry return functions --- .../token-registry-functions/fixtures.ts | 55 ++- .../returnToken.test.ts | 452 ++++++++++++++++++ .../endorsement-chain/useEndorsementChain.ts | 2 +- src/token-registry-functions/returnToken.ts | 306 ++++++++++++ src/token-registry-functions/transfer.ts | 5 +- src/token-registry-functions/types.ts | 19 + 6 files changed, 832 insertions(+), 7 deletions(-) create mode 100644 src/__tests__/token-registry-functions/returnToken.test.ts create mode 100644 src/token-registry-functions/returnToken.ts diff --git a/src/__tests__/token-registry-functions/fixtures.ts b/src/__tests__/token-registry-functions/fixtures.ts index 0745a5e..d1c3e81 100644 --- a/src/__tests__/token-registry-functions/fixtures.ts +++ b/src/__tests__/token-registry-functions/fixtures.ts @@ -1,11 +1,34 @@ import { vi } from 'vitest'; import { ethers as ethersV5 } from 'ethers'; import { JsonRpcProvider as JsonRpcProviderV6 } from 'ethersV6'; +export const MOCK_V5_ADDRESS = '0xV5TokenRegistryContract'; +export const MOCK_V4_ADDRESS = '0xV5TokenRegistryContract'; vi.mock('src/core', () => ({ encrypt: vi.fn(() => 'encrypted_remarks'), - getTitleEscrowAddress: vi.fn(() => Promise.resolve('0xv5contract')), + getTitleEscrowAddress: vi.fn(), isTitleEscrowVersion: vi.fn(() => Promise.resolve(true)), + checkSupportsInterface: vi.fn(), + // .mockImplementation((titleEscrowAddress: string, interfaceId: string) => { + // // For V5 contract + // if (titleEscrowAddress === MOCK_V5_ADDRESS) { + // return Promise.resolve( + // interfaceId === v5SupportInterfaceIds.TradeTrustTokenRestorable || + // interfaceId === v5SupportInterfaceIds.TitleEscrow || + // interfaceId === v5SupportInterfaceIds.TradeTrustTokenMintable, + // ); + // } + // // For V4 contract + // else if (titleEscrowAddress === MOCK_V4_ADDRESS) { + // return Promise.resolve( + // interfaceId === v4SupportInterfaceIds.TradeTrustTokenRestorable || + // interfaceId === v4SupportInterfaceIds.TitleEscrow || + // interfaceId === v4SupportInterfaceIds.TradeTrustTokenMintable, + // ); + // } + // // Default case (not supported) + // return Promise.resolve(false); + // }), TitleEscrowInterface: { V4: '0xTitleEscrowIdV4', V5: '0xTitleEscrowIdV5', @@ -28,6 +51,8 @@ vi.mock('src/token-registry-v5', () => { v5SupportInterfaceIds: { TitleEscrow: '0xTitleEscrowIdV5', TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV5', + TradeTrustTokenRestorable: '0xTradeTrustTokenRestorableIdV5', + TradeTrustTokenBurnable: '0xTradeTrustTokenBurnableIdV5', }, }; }); @@ -44,10 +69,12 @@ vi.mock('src/token-registry-v4', () => { TitleEscrowFactory__factory: { connect: vi.fn(() => mockV4TitleEscrowFactoryContract), }, - v4SupportInterfaceIds: { - TitleEscrow: '0xTitleEscrowIdV4', - TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV4', - }, + }, + v4SupportInterfaceIds: { + TitleEscrow: '0xTitleEscrowIdV4', + TradeTrustTokenMintable: '0xTradeTrustTokenMintableIdV4', + TradeTrustTokenRestorable: '0xTradeTrustTokenRestorableIdV4', + TradeTrustTokenBurnable: '0xTradeTrustTokenBurnableIdV4', }, }; }); @@ -60,8 +87,14 @@ export const mockV5TitleEscrowFactoryContract = { }; export const mockV5TradeTrustTokenContract = { + callStatic: { + burn: vi.fn(), + restore: vi.fn(), + }, supportsInterface: vi.fn(), titleEscrowFactory: vi.fn(() => Promise.resolve('0xV5titleescrowfactory')), + burn: vi.fn(() => Promise.resolve('v5_burn_tx_hash')), + restore: vi.fn(() => Promise.resolve('v5_restore_tx_hash')), }; export const mockV5TitleEscrowContract = { @@ -74,6 +107,7 @@ export const mockV5TitleEscrowContract = { rejectTransferHolder: vi.fn(), rejectTransferBeneficiary: vi.fn(), rejectTransferOwners: vi.fn(), + returnToIssuer: vi.fn(), }, transferHolder: vi.fn(() => Promise.resolve('v5_transfer_holder_tx_hash')), transferBeneficiary: vi.fn(() => Promise.resolve('v5_transfer_beneficiary_tx_hash')), @@ -84,14 +118,17 @@ export const mockV5TitleEscrowContract = { rejectTransferHolder: vi.fn(() => Promise.resolve('v5_reject_transfer_holder_tx_hash')), rejectTransferBeneficiary: vi.fn(() => Promise.resolve('v5_reject_transfer_beneficiary_tx_hash')), rejectTransferOwners: vi.fn(() => Promise.resolve('v5_reject_transfer_owners_tx_hash')), + returnToIssuer: vi.fn(() => Promise.resolve('v5_return_to_issuer_tx_hash')), }; export const mockV4TitleEscrowContract = { + supportsInterface: vi.fn(), callStatic: { transferHolder: vi.fn(), transferBeneficiary: vi.fn(), transferOwners: vi.fn(), nominate: vi.fn(), + surrender: vi.fn(), }, transferHolder: vi.fn(() => Promise.resolve('v4_transfer_holder_tx_hash')), transferBeneficiary: vi.fn(() => Promise.resolve('v4_transfer_beneficiary_tx_hash')), @@ -99,6 +136,7 @@ export const mockV4TitleEscrowContract = { nominate: vi.fn(() => Promise.resolve('v4_nominate_tx_hash')), holder: vi.fn(() => Promise.resolve('0xcurrent_holder')), beneficiary: vi.fn(() => Promise.resolve('0xcurrent_beneficiary')), + surrender: vi.fn(() => Promise.resolve('v4_surrender_tx_hash')), }; export const mockV4TitleEscrowFactoryContract = { callStatic: { @@ -108,7 +146,14 @@ export const mockV4TitleEscrowFactoryContract = { }; export const mockV4TradeTrustTokenContract = { + callStatic: { + burn: vi.fn(), + restore: vi.fn(), + }, titleEscrowFactory: vi.fn(() => Promise.resolve('0xV4titleescrowfactory')), + supportsInterface: vi.fn(), + burn: vi.fn(() => Promise.resolve('v4_burn_tx_hash')), + restore: vi.fn(() => Promise.resolve('v4_restore_tx_hash')), }; export const PRIVATE_KEY = '0x59c6995e998f97a5a004497e5f1ebce0c16828d44b3f8d0bfa3a89d271d5b6b9'; // random local key diff --git a/src/__tests__/token-registry-functions/returnToken.test.ts b/src/__tests__/token-registry-functions/returnToken.test.ts new file mode 100644 index 0000000..57a5fc5 --- /dev/null +++ b/src/__tests__/token-registry-functions/returnToken.test.ts @@ -0,0 +1,452 @@ +import './fixtures.js'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { ethers as ethersV5, Wallet as WalletV5 } from 'ethers'; +import { Wallet as WalletV6, Network, ethers as ethersV6 } from 'ethersV6'; +import * as coreModule from 'src/core'; + +import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; +import { v5Contracts } from 'src/token-registry-v5'; +import { v4Contracts } from 'src/token-registry-v4'; +import { + acceptReturned, + rejectReturned, + returnToIssuer, +} from 'src/token-registry-functions/returnToken'; +import { + MOCK_V4_ADDRESS, + MOCK_V5_ADDRESS, + mockV4TitleEscrowContract, + mockV4TradeTrustTokenContract, + mockV5TitleEscrowContract, + mockV5TradeTrustTokenContract, + PRIVATE_KEY, + providerV5, + providerV6, +} from './fixtures.js'; +import { ProviderInfo } from 'src/token-registry-functions/types.js'; + +const providers: ProviderInfo[] = [ + { + Provider: providerV5, + ethersVersion: 'v5', + titleEscrowVersion: 'v5', + }, + { + Provider: providerV5, + ethersVersion: 'v5', + titleEscrowVersion: 'v4', + }, + { + Provider: providerV6, + ethersVersion: 'v6', + titleEscrowVersion: 'v5', + }, + { + Provider: providerV6, + ethersVersion: 'v6', + titleEscrowVersion: 'v4', + }, +]; +describe('Return Token', () => { + const mockTokenId = '0xTokenId'; + const mockRemarks = 'Return remarks'; + const mockChainId = CHAIN_ID.local; + const mockEncryptedRemarks = '0xencryptedRemarks'; + describe.each(providers)( + 'Return Token with TR version $titleEscrowVersion and ethers version $ethersVersion', + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + const mockTokenRegistryAddress = '0xTokenRegistry'; + const mockTxResponse = + titleEscrowVersion === 'v5' ? 'v5_return_to_issuer_tx_hash' : 'v4_surrender_tx_hash'; + + let wallet: ethersV5.Wallet | ethersV6.Wallet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.local as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.local, + } as unknown as Network); + } + const isV5TT = titleEscrowVersion === 'v5'; + const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + + beforeEach(() => { + vi.clearAllMocks(); + vi.spyOn(coreModule, 'getTitleEscrowAddress').mockResolvedValue(titleEscrowAddress); + vi.spyOn(coreModule, 'encrypt').mockReturnValue(mockEncryptedRemarks.slice(2)); + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockImplementation( + async ({ versionInterface }) => { + return versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'); + }, + ); + mockV5TitleEscrowContract.callStatic.rejectTransferHolder.mockResolvedValue(true); + mockV4TitleEscrowContract.callStatic.surrender.mockResolvedValue(true); + }); + + it('should return to issuer with signer and remarks', async () => { + const result = await returnToIssuer( + { + titleEscrowAddress, + }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual(mockTxResponse); + expect(coreModule.encrypt).toHaveBeenCalledWith(mockRemarks, 'encryption-id'); + expect( + (isV5TT ? v5Contracts : v4Contracts).TitleEscrow__factory.connect, + ).toHaveBeenCalled(); + }); + + it('should return to issuer without remarks', async () => { + const result = await returnToIssuer( + { titleEscrowAddress }, + wallet, + {}, + { chainId: mockChainId }, + ); + + expect(result).toEqual(mockTxResponse); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw when callStatic fails', async () => { + if (isV5TT) { + mockV5TitleEscrowContract.callStatic.returnToIssuer.mockRejectedValue( + new Error('Simulated failure'), + ); + } else { + mockV4TitleEscrowContract.callStatic.surrender.mockRejectedValue( + new Error('Simulated failure'), + ); + } + + await expect( + returnToIssuer( + { tokenRegistryAddress: mockTokenRegistryAddress, tokenId: mockTokenId }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for returnToIssuer failed'); + if (isV5TT) { + mockV5TitleEscrowContract.callStatic.returnToIssuer = vi.fn(); + } else { + mockV4TitleEscrowContract.callStatic.surrender = vi.fn(); + } + }); + it('should throw error when provider is missing', async () => { + const signerWithoutProvider = isV5TT + ? new WalletV5('0x'.padEnd(66, '1')) + : new WalletV6('0x'.padEnd(66, '1')); + + await expect( + returnToIssuer( + { tokenRegistryAddress: mockTokenRegistryAddress, tokenId: mockTokenId }, + signerWithoutProvider, + {}, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw when version is unsupported', async () => { + vi.spyOn(coreModule, 'isTitleEscrowVersion').mockResolvedValue(false); + + await expect( + returnToIssuer( + { tokenRegistryAddress: mockTokenRegistryAddress, tokenId: mockTokenId }, + wallet, + {}, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Only Token Registry V4/V5 is supported'); + }); + + it('should work with explicit version', async () => { + const result = await returnToIssuer( + { tokenRegistryAddress: mockTokenRegistryAddress, tokenId: mockTokenId }, + wallet, + { remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(result).toEqual(mockTxResponse); + expect(coreModule.isTitleEscrowVersion).not.toHaveBeenCalled(); + }); + }, + ); + + describe.each(providers)( + 'Reject Return Token with TR version $titleEscrowVersion and ethers version $ethersVersion', + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + const isV5TT = titleEscrowVersion === 'v5'; + // let mockContract = isV5TT ? mockV5TradeTrustTokenContract : mockV4TradeTrustTokenContract; + const mockTxResponse = + titleEscrowVersion === 'v5' ? 'v5_restore_tx_hash' : 'v4_restore_tx_hash'; + + let wallet: ethersV5.Wallet | ethersV6.Wallet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.local as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.local, + } as unknown as Network); + } + const mockTokenRegistryAddress = isV5TT ? MOCK_V5_ADDRESS : MOCK_V4_ADDRESS; + // const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + beforeEach(() => { + vi.clearAllMocks(); + // vi.spyOn(coreModule, 'encrypt').mockReturnValue(mockEncryptedRemarks.slice(2)); + vi.spyOn(coreModule, 'checkSupportsInterface').mockImplementation( + async (address, interfaceId) => { + return ( + interfaceId === + (isV5TT ? '0xTradeTrustTokenRestorableIdV5' : '0xTradeTrustTokenRestorableIdV4') + ); + }, + ); + mockV5TradeTrustTokenContract.callStatic.restore.mockResolvedValue(true); + mockV4TradeTrustTokenContract.callStatic.restore.mockResolvedValue(true); + }); + + it('should reject returned token with remarks', async () => { + const result = await rejectReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId, remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual(mockTxResponse); + if (isV5TT) expect(coreModule.encrypt).toHaveBeenCalledWith(mockRemarks, 'encryption-id'); + expect( + (isV5TT ? v5Contracts : v4Contracts).TradeTrustToken__factory.connect, + ).toHaveBeenCalled(); + }); + + it('should reject returned token without remarks', async () => { + const result = await rejectReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId }, + { chainId: mockChainId, titleEscrowVersion }, + ); + + expect(result).toEqual(mockTxResponse); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw when callStatic fails', async () => { + const mockError = new Error('callStatic error'); + if (isV5TT) { + mockV5TradeTrustTokenContract.callStatic.restore.mockRejectedValue(mockError); + } else { + mockV4TradeTrustTokenContract.callStatic.restore.mockRejectedValue(mockError); + } + await expect( + rejectReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId, remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for acceptReturned failed'); + if (isV5TT) { + mockV5TradeTrustTokenContract.callStatic.restore = vi.fn(); + } else { + mockV4TradeTrustTokenContract.callStatic.restore = vi.fn(); + } + }); + + it('should throw when token registry address is missing', async () => { + await expect( + rejectReturned( + { tokenRegistryAddress: '' }, + wallet, + { tokenId: mockTokenId }, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('should throw when provider is missing', async () => { + const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1')); + + await expect( + rejectReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + signerWithoutProvider, + { tokenId: mockTokenId }, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw when version is unsupported', async () => { + vi.spyOn(coreModule, 'checkSupportsInterface').mockResolvedValue(false); + + await expect( + rejectReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId }, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Only Token Registry V4/V5 is supported'); + }); + + it('should work with explicit V5/V4 version', async () => { + const result = await rejectReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId, remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(result).toEqual(mockTxResponse); + expect(coreModule.checkSupportsInterface).not.toHaveBeenCalled(); + }); + }, + ); + + describe.each(providers)( + 'Accept Return Token with TR version $titleEscrowVersion and ethers version $ethersVersion', + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + const isV5TT = titleEscrowVersion === 'v5'; + // let mockContract = isV5TT ? mockV5TradeTrustTokenContract : mockV4TradeTrustTokenContract; + const mockTxResponse = titleEscrowVersion === 'v5' ? 'v5_burn_tx_hash' : 'v4_burn_tx_hash'; + + let wallet: ethersV5.Wallet | ethersV6.Wallet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.local as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.local, + } as unknown as Network); + } + const mockTokenRegistryAddress = isV5TT ? MOCK_V5_ADDRESS : MOCK_V4_ADDRESS; + // const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + beforeEach(() => { + vi.clearAllMocks(); + + vi.spyOn(coreModule, 'checkSupportsInterface').mockImplementation( + async (address, interfaceId) => { + return ( + interfaceId === + (isV5TT ? '0xTradeTrustTokenBurnableIdV5' : '0xTradeTrustTokenBurnableIdV4') + ); + }, + ); + mockV5TradeTrustTokenContract.callStatic.burn.mockResolvedValue(true); + mockV4TradeTrustTokenContract.callStatic.burn.mockResolvedValue(true); + }); + + it('should reject returned token with remarks', async () => { + const result = await acceptReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId, remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual(mockTxResponse); + if (isV5TT) expect(coreModule.encrypt).toHaveBeenCalledWith(mockRemarks, 'encryption-id'); + expect( + (isV5TT ? v5Contracts : v4Contracts).TradeTrustToken__factory.connect, + ).toHaveBeenCalled(); + }); + + it('should reject returned token without remarks', async () => { + const result = await acceptReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId }, + { chainId: mockChainId, titleEscrowVersion }, + ); + + expect(result).toEqual(mockTxResponse); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw when callStatic fails', async () => { + const mockError = new Error('callStatic error'); + if (isV5TT) { + mockV5TradeTrustTokenContract.callStatic.burn.mockRejectedValue(mockError); + } else { + mockV4TradeTrustTokenContract.callStatic.burn.mockRejectedValue(mockError); + } + await expect( + acceptReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId, remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for acceptReturned failed'); + if (isV5TT) { + mockV5TradeTrustTokenContract.callStatic.burn = vi.fn(); + } else { + mockV4TradeTrustTokenContract.callStatic.burn = vi.fn(); + } + }); + + it('should throw when token registry address is missing', async () => { + await expect( + acceptReturned( + { tokenRegistryAddress: '' }, + wallet, + { tokenId: mockTokenId }, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('should throw when provider is missing', async () => { + const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1')); + + await expect( + acceptReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + signerWithoutProvider, + { tokenId: mockTokenId }, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw when version is unsupported', async () => { + vi.spyOn(coreModule, 'checkSupportsInterface').mockResolvedValue(false); + + await expect( + acceptReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId }, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Only Token Registry V4/V5 is supported'); + }); + + it('should work with explicit V5/V4 version', async () => { + const result = await acceptReturned( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { tokenId: mockTokenId, remarks: mockRemarks }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(result).toEqual(mockTxResponse); + expect(coreModule.checkSupportsInterface).not.toHaveBeenCalled(); + }); + }, + ); +}); diff --git a/src/core/endorsement-chain/useEndorsementChain.ts b/src/core/endorsement-chain/useEndorsementChain.ts index a6b949c..14104c7 100644 --- a/src/core/endorsement-chain/useEndorsementChain.ts +++ b/src/core/endorsement-chain/useEndorsementChain.ts @@ -145,7 +145,7 @@ export const getDocumentOwner = async ( }; // Check Title Escrow Interface Support -const checkSupportsInterface = async ( +export const checkSupportsInterface = async ( titleEscrowAddress: string, interfaceId: string, provider: Provider | ethersV6.Provider, diff --git a/src/token-registry-functions/returnToken.ts b/src/token-registry-functions/returnToken.ts new file mode 100644 index 0000000..af48dee --- /dev/null +++ b/src/token-registry-functions/returnToken.ts @@ -0,0 +1,306 @@ +import { + checkSupportsInterface, + encrypt, + getTitleEscrowAddress, + isTitleEscrowVersion, + TitleEscrowInterface, +} from 'src/core'; +import { v5Contracts, v5SupportInterfaceIds } from 'src/token-registry-v5'; +import { v4Contracts, v4SupportInterfaceIds } from 'src/token-registry-v4'; +import { Signer as SignerV6 } from 'ethersV6'; +import { ContractTransaction, Signer } from 'ethers'; +import { getTxOptions } from './utils'; +import { + AcceptReturnedOptions, + AcceptReturnedParams, + ContractOptions, + RejectReturnedOptions, + RejectReturnedParams, + ReturnToIssuerParams, + TransactionOptions, +} from './types'; + +/** + * Returns the token to the original issuer from the Title Escrow contract. + * @param {ContractOptions} contractOptions - Options including token ID, registry address, and optionally title escrow address. + * @param {Signer | SignerV6} signer - Signer instance (Ethers v5 or v6) that will execute the transaction. + * @param {ReturnToIssuerParams} params - Contains optional remarks to be encrypted and attached to the transaction. + * @param {TransactionOptions} options - Transaction settings including gas fees, escrow version, chain ID, and optional encryption ID. + * @returns {Promise} Promise that resolves to the transaction response from the `returnToIssuer` function. + * @throws {Error} If title escrow address or provider is not provided or if version is unsupported. + * @throws {Error} If the `callStatic.returnToIssuer` fails as a pre-check. + */ +const returnToIssuer = async ( + contractOptions: ContractOptions, + signer: Signer | SignerV6, + params: ReturnToIssuerParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress, tokenId } = contractOptions; + let { titleEscrowAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!titleEscrowAddress) { + titleEscrowAddress = await getTitleEscrowAddress( + tokenRegistryAddress, + tokenId as string, + signer.provider, + {}, + ); + } + + if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { remarks } = params; + + // Connect V5 contract by default + let titleEscrowContract: v5Contracts.TitleEscrow | v4Contracts.TitleEscrow = + v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); + + const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Detect version if not explicitly provided + let isV5TT = titleEscrowVersion === 'v5'; + let isV4TT = titleEscrowVersion === 'v4'; + + if (titleEscrowVersion === undefined) { + [isV4TT, isV5TT] = await Promise.all([ + await isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V4, + provider: signer.provider, + }), + await isTitleEscrowVersion({ + titleEscrowAddress, + versionInterface: TitleEscrowInterface.V5, + provider: signer.provider, + }), + ]); + } + + if (!isV5TT && !isV4TT) { + throw new Error('Only Token Registry V4/V5 is supported'); + } + + if (isV4TT) { + titleEscrowContract = v4Contracts.TitleEscrow__factory.connect( + titleEscrowAddress, + signer as Signer, + ); + } + + // Check callStatic (dry run) + try { + if (isV5TT) { + await (titleEscrowContract as v5Contracts.TitleEscrow).callStatic.returnToIssuer( + encryptedRemarks, + ); + } else if (isV4TT) { + await (titleEscrowContract as v4Contracts.TitleEscrow).callStatic.surrender(); + } + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for returnToIssuer failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + if (isV5TT) { + return await (titleEscrowContract as v5Contracts.TitleEscrow).returnToIssuer( + encryptedRemarks, + txOptions, + ); + } else if (isV4TT) { + return await (titleEscrowContract as v4Contracts.TitleEscrow).surrender(txOptions); + } +}; + +/** + * Rejects a previously returned token by restoring it back to the token registry. + * This is only supported on Token Registry V5 contracts with the `restore` functionality. + * @param {AcceptReturnedOptions} contractOptions - Contains the `tokenRegistryAddress` used to locate the TradeTrustToken contract. + * @param {Signer | SignerV6} signer - Signer instance (v5 or v6) used to authorize the transaction. + * @param {AcceptReturnedParams} params - Includes the `tokenId` to restore and optional `remarks` to encrypt. + * @param {TransactionOptions} options - Configuration for the transaction including version, gas fees, and optional `id` used for encryption. + * @returns {Promise} A promise that resolves to the transaction result of the `restore` call. + * @throws {Error} If the token registry address or provider is missing. + * @throws {Error} If the token registry version is unsupported. + * @throws {Error} If the callStatic pre-check fails. + */ +const rejectReturned = async ( + contractOptions: AcceptReturnedOptions, + signer: Signer | SignerV6, + params: RejectReturnedParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!tokenRegistryAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { tokenId, remarks } = params; + + // Detect version if not explicitly provided checkSupportsInterface + let isV5TT = titleEscrowVersion === 'v5'; + let isV4TT = titleEscrowVersion === 'v4'; + + if (titleEscrowVersion === undefined) { + [isV4TT, isV5TT] = await Promise.all([ + checkSupportsInterface( + tokenRegistryAddress, + v4SupportInterfaceIds.TradeTrustTokenRestorable, + signer.provider, + ), + checkSupportsInterface( + tokenRegistryAddress, + v5SupportInterfaceIds.TradeTrustTokenRestorable, + signer.provider, + ), + ]); + } + + if (!isV4TT && !isV5TT) { + throw new Error('Only Token Registry V4/V5 is supported'); + } + // Connect V5 contract by default + let tradeTrustTokenContract: v5Contracts.TradeTrustToken | v4Contracts.TradeTrustToken; + if (isV5TT) { + tradeTrustTokenContract = v5Contracts.TradeTrustToken__factory.connect( + tokenRegistryAddress, + signer, + ); + } else if (isV4TT) { + tradeTrustTokenContract = v4Contracts.TradeTrustToken__factory.connect( + tokenRegistryAddress, + signer as Signer, + ); + } + + const encryptedRemarks = remarks && isV5TT ? `0x${encrypt(remarks, options.id!)}` : '0x'; + // Check callStatic (dry run) + try { + if (isV5TT) { + await (tradeTrustTokenContract as v5Contracts.TradeTrustToken).callStatic.restore( + tokenId, + encryptedRemarks, + ); + } else if (isV4TT) { + await (tradeTrustTokenContract as v4Contracts.TradeTrustToken).callStatic.restore(tokenId); + } + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for acceptReturned failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + + if (isV5TT) { + return await (tradeTrustTokenContract as v5Contracts.TradeTrustToken).restore( + tokenId, + encryptedRemarks, + txOptions, + ); + } else if (isV4TT) { + return await (tradeTrustTokenContract as v4Contracts.TradeTrustToken).restore( + tokenId, + txOptions, + ); + } +}; +/** + * Accepts the returned token by burning it from the TradeTrustToken contract. + * Only supported on Token Registry V5 contracts that implement the burnable interface. + * @param {RejectReturnedOptions} contractOptions - Contains the `tokenRegistryAddress` from which the token will be burned. + * @param {Signer | SignerV6} signer - Signer instance (v5 or v6) used to authorize and send the burn transaction. + * @param {AcceptReturnedParams} params - Includes the `tokenId` to burn and optional `remarks` for audit trail. + * @param {TransactionOptions} options - Transaction settings including chain ID, gas fee values, escrow version, and encryption ID for remarks. + * @returns {Promise} A promise resolving to the transaction result of the burn call. + * @throws {Error} If token registry address or signer provider is not provided. + * @throws {Error} If the contract does not support Token Registry V5. + * @throws {Error} If `callStatic.burn` fails as a pre-check. + */ +const acceptReturned = async ( + contractOptions: RejectReturnedOptions, + signer: Signer | SignerV6, + params: AcceptReturnedParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!tokenRegistryAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { tokenId, remarks } = params; + + // Detect version if not explicitly provided checkSupportsInterface + let isV5TT = titleEscrowVersion === 'v5'; + let isV4TT = titleEscrowVersion === 'v4'; + + if (titleEscrowVersion === undefined) { + [isV4TT, isV5TT] = await Promise.all([ + checkSupportsInterface( + tokenRegistryAddress, + v4SupportInterfaceIds.TradeTrustTokenBurnable, + signer.provider, + ), + checkSupportsInterface( + tokenRegistryAddress, + v5SupportInterfaceIds.TradeTrustTokenBurnable, + signer.provider, + ), + ]); + } + + if (!isV4TT && !isV5TT) { + throw new Error('Only Token Registry V4/V5 is supported'); + } + // Connect V5 contract by default + let tradeTrustTokenContract: v5Contracts.TradeTrustToken | v4Contracts.TradeTrustToken; + if (isV5TT) { + tradeTrustTokenContract = v5Contracts.TradeTrustToken__factory.connect( + tokenRegistryAddress, + signer, + ); + } else if (isV4TT) { + tradeTrustTokenContract = v4Contracts.TradeTrustToken__factory.connect( + tokenRegistryAddress, + signer as Signer, + ); + } + + const encryptedRemarks = remarks && isV5TT ? `0x${encrypt(remarks, options.id!)}` : '0x'; + + // Check callStatic (dry run) + try { + if (isV5TT) { + await (tradeTrustTokenContract as v5Contracts.TradeTrustToken).callStatic.burn( + tokenId, + encryptedRemarks, + ); + } else if (isV4TT) { + await (tradeTrustTokenContract as v4Contracts.TradeTrustToken).callStatic.burn(tokenId); + } + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for acceptReturned failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + + if (isV5TT) { + return await (tradeTrustTokenContract as v5Contracts.TradeTrustToken).burn( + tokenId, + encryptedRemarks, + txOptions, + ); + } else if (isV4TT) { + return await (tradeTrustTokenContract as v4Contracts.TradeTrustToken).burn(tokenId, txOptions); + } +}; + +export { returnToIssuer, acceptReturned, rejectReturned }; diff --git a/src/token-registry-functions/transfer.ts b/src/token-registry-functions/transfer.ts index 7c944d3..a4fa004 100644 --- a/src/token-registry-functions/transfer.ts +++ b/src/token-registry-functions/transfer.ts @@ -132,7 +132,10 @@ const transferHolder = async ( txOptions, ); } else if (isV4TT) { - return await titleEscrowContract.transferHolder(holderAddress, txOptions); + return await (titleEscrowContract as v4Contracts.TitleEscrow).transferHolder( + holderAddress, + txOptions, + ); } }; diff --git a/src/token-registry-functions/types.ts b/src/token-registry-functions/types.ts index 22b0951..803db2e 100644 --- a/src/token-registry-functions/types.ts +++ b/src/token-registry-functions/types.ts @@ -8,6 +8,18 @@ export type GasValue = BigNumber | BigNumberish | string | number; export interface RejectTransferParams { remarks?: string; } +export interface ReturnToIssuerParams { + remarks?: string; +} + +export interface AcceptReturnedParams { + tokenId: string | number; + remarks?: string; +} +export interface RejectReturnedParams { + tokenId: string | number; + remarks?: string; +} export interface TransactionOptions { chainId?: CHAIN_ID; @@ -29,6 +41,13 @@ export type ContractOptions = tokenRegistryAddress: string; }; +export type AcceptReturnedOptions = { + tokenRegistryAddress: string; +}; +export type RejectReturnedOptions = { + tokenRegistryAddress: string; +}; + export interface TransferHolderParams { holderAddress: string; remarks?: string; From bb490e4417bd71fb9eddb17403dce87665a48f40 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 2 Jul 2025 15:36:10 +0530 Subject: [PATCH 24/27] feat: add mint function --- .../token-registry-functions/fixtures.ts | 25 +- .../token-registry-functions/mint.test.ts | 216 ++++++++++++++++++ src/token-registry-functions/index.ts | 2 + src/token-registry-functions/mint.ts | 113 +++++++++ src/token-registry-functions/types.ts | 11 + 5 files changed, 347 insertions(+), 20 deletions(-) create mode 100644 src/__tests__/token-registry-functions/mint.test.ts create mode 100644 src/token-registry-functions/mint.ts diff --git a/src/__tests__/token-registry-functions/fixtures.ts b/src/__tests__/token-registry-functions/fixtures.ts index d1c3e81..55f48e0 100644 --- a/src/__tests__/token-registry-functions/fixtures.ts +++ b/src/__tests__/token-registry-functions/fixtures.ts @@ -9,26 +9,7 @@ vi.mock('src/core', () => ({ getTitleEscrowAddress: vi.fn(), isTitleEscrowVersion: vi.fn(() => Promise.resolve(true)), checkSupportsInterface: vi.fn(), - // .mockImplementation((titleEscrowAddress: string, interfaceId: string) => { - // // For V5 contract - // if (titleEscrowAddress === MOCK_V5_ADDRESS) { - // return Promise.resolve( - // interfaceId === v5SupportInterfaceIds.TradeTrustTokenRestorable || - // interfaceId === v5SupportInterfaceIds.TitleEscrow || - // interfaceId === v5SupportInterfaceIds.TradeTrustTokenMintable, - // ); - // } - // // For V4 contract - // else if (titleEscrowAddress === MOCK_V4_ADDRESS) { - // return Promise.resolve( - // interfaceId === v4SupportInterfaceIds.TradeTrustTokenRestorable || - // interfaceId === v4SupportInterfaceIds.TitleEscrow || - // interfaceId === v4SupportInterfaceIds.TradeTrustTokenMintable, - // ); - // } - // // Default case (not supported) - // return Promise.resolve(false); - // }), + TitleEscrowInterface: { V4: '0xTitleEscrowIdV4', V5: '0xTitleEscrowIdV5', @@ -90,11 +71,13 @@ export const mockV5TradeTrustTokenContract = { callStatic: { burn: vi.fn(), restore: vi.fn(), + mint: vi.fn(), }, supportsInterface: vi.fn(), titleEscrowFactory: vi.fn(() => Promise.resolve('0xV5titleescrowfactory')), burn: vi.fn(() => Promise.resolve('v5_burn_tx_hash')), restore: vi.fn(() => Promise.resolve('v5_restore_tx_hash')), + mint: vi.fn(() => Promise.resolve('v5_mint_tx_hash')), }; export const mockV5TitleEscrowContract = { @@ -149,11 +132,13 @@ export const mockV4TradeTrustTokenContract = { callStatic: { burn: vi.fn(), restore: vi.fn(), + mint: vi.fn(), }, titleEscrowFactory: vi.fn(() => Promise.resolve('0xV4titleescrowfactory')), supportsInterface: vi.fn(), burn: vi.fn(() => Promise.resolve('v4_burn_tx_hash')), restore: vi.fn(() => Promise.resolve('v4_restore_tx_hash')), + mint: vi.fn(() => Promise.resolve('v4_mint_tx_hash')), }; export const PRIVATE_KEY = '0x59c6995e998f97a5a004497e5f1ebce0c16828d44b3f8d0bfa3a89d271d5b6b9'; // random local key diff --git a/src/__tests__/token-registry-functions/mint.test.ts b/src/__tests__/token-registry-functions/mint.test.ts new file mode 100644 index 0000000..c70b858 --- /dev/null +++ b/src/__tests__/token-registry-functions/mint.test.ts @@ -0,0 +1,216 @@ +import './fixtures.js'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { ethers as ethersV5, Wallet as WalletV5 } from 'ethers'; +import { Wallet as WalletV6, Network, ethers as ethersV6 } from 'ethersV6'; +import * as coreModule from 'src/core'; + +import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; +import { v5Contracts } from 'src/token-registry-v5'; +import { v4Contracts } from 'src/token-registry-v4'; +import { mint } from 'src/token-registry-functions/mint'; +import { + MOCK_V4_ADDRESS, + MOCK_V5_ADDRESS, + mockV4TradeTrustTokenContract, + mockV5TradeTrustTokenContract, + PRIVATE_KEY, + providerV5, + providerV6, +} from './fixtures.js'; +import { ProviderInfo } from 'src/token-registry-functions/types.js'; + +const providers: ProviderInfo[] = [ + { + Provider: providerV5, + ethersVersion: 'v5', + titleEscrowVersion: 'v5', + }, + { + Provider: providerV5, + ethersVersion: 'v5', + titleEscrowVersion: 'v4', + }, + { + Provider: providerV6, + ethersVersion: 'v6', + titleEscrowVersion: 'v5', + }, + { + Provider: providerV6, + ethersVersion: 'v6', + titleEscrowVersion: 'v4', + }, +]; +describe('Mint Token', () => { + const mockTokenId = '0xTokenId'; + const mockRemarks = 'Return remarks'; + const mockChainId = CHAIN_ID.local; + describe.each(providers)( + 'Mint Token with TR version $titleEscrowVersion and ethers version $ethersVersion', + async ({ Provider, ethersVersion, titleEscrowVersion }) => { + const isV5TT = titleEscrowVersion === 'v5'; + // let mockContract = isV5TT ? mockV5TradeTrustTokenContract : mockV4TradeTrustTokenContract; + const mockTxResponse = titleEscrowVersion === 'v5' ? 'v5_mint_tx_hash' : 'v4_mint_tx_hash'; + + let wallet: ethersV5.Wallet | ethersV6.Wallet; + if (ethersVersion === 'v5') { + wallet = new WalletV5(PRIVATE_KEY, Provider as any) as ethersV5.Wallet; + vi.spyOn(wallet, 'getChainId').mockResolvedValue(CHAIN_ID.local as unknown as number); + } else { + wallet = new WalletV6(PRIVATE_KEY, Provider as any); + vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ + chainId: CHAIN_ID.local, + } as unknown as Network); + } + const mockTokenRegistryAddress = isV5TT ? MOCK_V5_ADDRESS : MOCK_V4_ADDRESS; + const mockBeneficiaryAddress = '0xBeneficiaryAddress'; + const mockHolderAddress = '0xHolderAddress'; + // const titleEscrowAddress = isV5TT ? '0xv5contract' : '0xv4contract'; + beforeEach(() => { + vi.clearAllMocks(); + // vi.spyOn(coreModule, 'encrypt').mockReturnValue(mockEncryptedRemarks.slice(2)); + vi.spyOn(coreModule, 'checkSupportsInterface').mockImplementation( + async (address, interfaceId) => { + return ( + interfaceId === + (isV5TT ? '0xTradeTrustTokenMintableIdV5' : '0xTradeTrustTokenMintableIdV4') + ); + }, + ); + mockV5TradeTrustTokenContract.callStatic.mint.mockResolvedValue(true); + mockV4TradeTrustTokenContract.callStatic.mint.mockResolvedValue(true); + }); + + it('should reject returned token with remarks', async () => { + const result = await mint( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { + beneficiaryAddress: mockBeneficiaryAddress, + holderAddress: mockHolderAddress, + tokenId: mockTokenId, + remarks: mockRemarks, + }, + { chainId: mockChainId, id: 'encryption-id' }, + ); + + expect(result).toEqual(mockTxResponse); + if (isV5TT) expect(coreModule.encrypt).toHaveBeenCalledWith(mockRemarks, 'encryption-id'); + expect( + (isV5TT ? v5Contracts : v4Contracts).TradeTrustToken__factory.connect, + ).toHaveBeenCalled(); + }); + + it('should reject returned token without remarks', async () => { + const result = await mint( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { + beneficiaryAddress: mockBeneficiaryAddress, + holderAddress: mockHolderAddress, + tokenId: mockTokenId, + }, + { chainId: mockChainId, titleEscrowVersion }, + ); + + expect(result).toEqual(mockTxResponse); + expect(coreModule.encrypt).not.toHaveBeenCalled(); + }); + + it('should throw when callStatic fails', async () => { + const mockError = new Error('callStatic error'); + if (isV5TT) { + mockV5TradeTrustTokenContract.callStatic.mint.mockRejectedValue(mockError); + } else { + mockV4TradeTrustTokenContract.callStatic.mint.mockRejectedValue(mockError); + } + await expect( + mint( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { + beneficiaryAddress: mockBeneficiaryAddress, + holderAddress: mockHolderAddress, + tokenId: mockTokenId, + remarks: mockRemarks, + }, + { chainId: mockChainId, id: 'encryption-id' }, + ), + ).rejects.toThrow('Pre-check (callStatic) for acceptReturned failed'); + if (isV5TT) { + mockV5TradeTrustTokenContract.callStatic.mint = vi.fn(); + } else { + mockV4TradeTrustTokenContract.callStatic.mint = vi.fn(); + } + }); + + it('should throw when token registry address is missing', async () => { + await expect( + mint( + { tokenRegistryAddress: '' }, + wallet, + { + beneficiaryAddress: mockBeneficiaryAddress, + holderAddress: mockHolderAddress, + tokenId: mockTokenId, + }, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Token registry address is required'); + }); + + it('should throw when provider is missing', async () => { + const signerWithoutProvider = new WalletV5('0x'.padEnd(66, '1')); + + await expect( + mint( + { tokenRegistryAddress: mockTokenRegistryAddress }, + signerWithoutProvider, + { + beneficiaryAddress: mockBeneficiaryAddress, + holderAddress: mockHolderAddress, + tokenId: mockTokenId, + remarks: mockRemarks, + }, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Provider is required'); + }); + + it('should throw when version is unsupported', async () => { + vi.spyOn(coreModule, 'checkSupportsInterface').mockResolvedValue(false); + + await expect( + mint( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { + beneficiaryAddress: mockBeneficiaryAddress, + holderAddress: mockHolderAddress, + tokenId: mockTokenId, + remarks: mockRemarks, + }, + { chainId: mockChainId }, + ), + ).rejects.toThrow('Only Token Registry V4/V5 is supported'); + }); + + it('should work with explicit V5/V4 version', async () => { + const result = await mint( + { tokenRegistryAddress: mockTokenRegistryAddress }, + wallet, + { + beneficiaryAddress: mockBeneficiaryAddress, + holderAddress: mockHolderAddress, + tokenId: mockTokenId, + remarks: mockRemarks, + }, + { chainId: mockChainId, id: 'encryption-id', titleEscrowVersion }, + ); + + expect(result).toEqual(mockTxResponse); + expect(coreModule.checkSupportsInterface).not.toHaveBeenCalled(); + }); + }, + ); +}); diff --git a/src/token-registry-functions/index.ts b/src/token-registry-functions/index.ts index 927e270..fa484d0 100644 --- a/src/token-registry-functions/index.ts +++ b/src/token-registry-functions/index.ts @@ -1,2 +1,4 @@ export * from './transfer'; export * from './rejectTransfers'; +export * from './returnToken'; +export * from './mint'; diff --git a/src/token-registry-functions/mint.ts b/src/token-registry-functions/mint.ts new file mode 100644 index 0000000..a12dc95 --- /dev/null +++ b/src/token-registry-functions/mint.ts @@ -0,0 +1,113 @@ +import { checkSupportsInterface, encrypt } from 'src/core'; +import { v5Contracts, v5SupportInterfaceIds } from 'src/token-registry-v5'; +import { v4Contracts, v4SupportInterfaceIds } from 'src/token-registry-v4'; +import { Signer as SignerV6 } from 'ethersV6'; +import { ContractTransaction, Signer } from 'ethers'; +import { getTxOptions } from './utils'; +import { MintTokenOptions, MintTokenParams, TransactionOptions } from './types'; + +/** + * Mints a new token into the TradeTrustToken registry with the specified beneficiary and holder. + * Supports both Token Registry V4 and V5 contracts. + * @param {MintTokenOptions} contractOptions - Contains the `tokenRegistryAddress` for the minting contract. + * @param {Signer | SignerV6} signer - Signer instance (Ethers v5 or v6) that authorizes the mint transaction. + * @param {MintTokenParams} params - Parameters for minting, including `beneficiaryAddress`, `holderAddress`, `tokenId`, and optional `remarks`. + * @param {TransactionOptions} options - Transaction metadata including gas values, version detection, chain ID, and optional encryption ID. + * @returns {Promise} A promise resolving to the transaction result from the mint call. + * @throws {Error} If the token registry address or signer provider is not provided. + * @throws {Error} If neither V4 nor V5 interfaces are supported. + * @throws {Error} If the `callStatic.mint` fails as a pre-check. + */ +const mint = async ( + contractOptions: MintTokenOptions, + signer: Signer | SignerV6, + params: MintTokenParams, + options: TransactionOptions, +): Promise => { + const { tokenRegistryAddress } = contractOptions; + const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options; + + if (!tokenRegistryAddress) throw new Error('Token registry address is required'); + if (!signer.provider) throw new Error('Provider is required'); + const { beneficiaryAddress, holderAddress, tokenId, remarks } = params; + + // Detect version if not explicitly provided checkSupportsInterface + let isV5TT = titleEscrowVersion === 'v5'; + let isV4TT = titleEscrowVersion === 'v4'; + + if (titleEscrowVersion === undefined) { + [isV4TT, isV5TT] = await Promise.all([ + checkSupportsInterface( + tokenRegistryAddress, + v4SupportInterfaceIds.TradeTrustTokenMintable, + signer.provider, + ), + checkSupportsInterface( + tokenRegistryAddress, + v5SupportInterfaceIds.TradeTrustTokenMintable, + signer.provider, + ), + ]); + } + + if (!isV4TT && !isV5TT) { + throw new Error('Only Token Registry V4/V5 is supported'); + } + // Connect V5 contract by default + let tradeTrustTokenContract: v5Contracts.TradeTrustToken | v4Contracts.TradeTrustToken; + if (isV5TT) { + tradeTrustTokenContract = v5Contracts.TradeTrustToken__factory.connect( + tokenRegistryAddress, + signer, + ); + } else if (isV4TT) { + tradeTrustTokenContract = v4Contracts.TradeTrustToken__factory.connect( + tokenRegistryAddress, + signer as Signer, + ); + } + + const encryptedRemarks = remarks && isV5TT ? `0x${encrypt(remarks, options.id!)}` : '0x'; + // Check callStatic (dry run) + try { + if (isV5TT) { + await (tradeTrustTokenContract as v5Contracts.TradeTrustToken).callStatic.mint( + beneficiaryAddress, + holderAddress, + tokenId, + encryptedRemarks, + ); + } else if (isV4TT) { + await (tradeTrustTokenContract as v4Contracts.TradeTrustToken).callStatic.mint( + beneficiaryAddress, + holderAddress, + tokenId, + ); + } + } catch (e) { + console.error('callStatic failed:', e); + throw new Error('Pre-check (callStatic) for acceptReturned failed'); + } + + const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); + + // Send the actual transaction + + if (isV5TT) { + return await (tradeTrustTokenContract as v5Contracts.TradeTrustToken).mint( + beneficiaryAddress, + holderAddress, + tokenId, + encryptedRemarks, + txOptions, + ); + } else if (isV4TT) { + return await (tradeTrustTokenContract as v4Contracts.TradeTrustToken).mint( + beneficiaryAddress, + holderAddress, + tokenId, + txOptions, + ); + } +}; +export { mint }; diff --git a/src/token-registry-functions/types.ts b/src/token-registry-functions/types.ts index 803db2e..a659372 100644 --- a/src/token-registry-functions/types.ts +++ b/src/token-registry-functions/types.ts @@ -21,6 +21,13 @@ export interface RejectReturnedParams { remarks?: string; } +export interface MintTokenParams { + beneficiaryAddress: string; + holderAddress: string; + tokenId: string | number; + remarks?: string; +} + export interface TransactionOptions { chainId?: CHAIN_ID; titleEscrowVersion?: 'v4' | 'v5'; @@ -48,6 +55,10 @@ export type RejectReturnedOptions = { tokenRegistryAddress: string; }; +export type MintTokenOptions = { + tokenRegistryAddress: string; +}; + export interface TransferHolderParams { holderAddress: string; remarks?: string; From a06de56005071a45fa112e51b63a5654c591e1af Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Thu, 3 Jul 2025 10:46:38 +0530 Subject: [PATCH 25/27] fix: update fixes --- src/__tests__/token-registry-functions/fixtures.ts | 4 ++-- src/__tests__/token-registry-functions/mint.test.ts | 8 ++++---- .../token-registry-functions/rejectTransfers.test.ts | 6 +++--- .../token-registry-functions/returnToken.test.ts | 4 ++-- src/token-registry-functions/mint.ts | 2 +- src/token-registry-functions/rejectTransfers.ts | 6 +++--- src/token-registry-functions/returnToken.ts | 4 ++-- src/token-registry-functions/types.ts | 8 ++++---- src/token-registry-functions/utils.ts | 2 +- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/__tests__/token-registry-functions/fixtures.ts b/src/__tests__/token-registry-functions/fixtures.ts index 55f48e0..767d804 100644 --- a/src/__tests__/token-registry-functions/fixtures.ts +++ b/src/__tests__/token-registry-functions/fixtures.ts @@ -2,7 +2,7 @@ import { vi } from 'vitest'; import { ethers as ethersV5 } from 'ethers'; import { JsonRpcProvider as JsonRpcProviderV6 } from 'ethersV6'; export const MOCK_V5_ADDRESS = '0xV5TokenRegistryContract'; -export const MOCK_V4_ADDRESS = '0xV5TokenRegistryContract'; +export const MOCK_V4_ADDRESS = '0xV4TokenRegistryContract'; vi.mock('src/core', () => ({ encrypt: vi.fn(() => 'encrypted_remarks'), @@ -125,7 +125,7 @@ export const mockV4TitleEscrowFactoryContract = { callStatic: { getEscrowAddress: vi.fn(), }, - getAddress: vi.fn(() => Promise.resolve('0xV4titleescrow')), + getEscrowAddress: vi.fn(() => Promise.resolve('0xV4titleescrow')), }; export const mockV4TradeTrustTokenContract = { diff --git a/src/__tests__/token-registry-functions/mint.test.ts b/src/__tests__/token-registry-functions/mint.test.ts index c70b858..b07e651 100644 --- a/src/__tests__/token-registry-functions/mint.test.ts +++ b/src/__tests__/token-registry-functions/mint.test.ts @@ -43,7 +43,7 @@ const providers: ProviderInfo[] = [ ]; describe('Mint Token', () => { const mockTokenId = '0xTokenId'; - const mockRemarks = 'Return remarks'; + const mockRemarks = 'Mint remarks'; const mockChainId = CHAIN_ID.local; describe.each(providers)( 'Mint Token with TR version $titleEscrowVersion and ethers version $ethersVersion', @@ -81,7 +81,7 @@ describe('Mint Token', () => { mockV4TradeTrustTokenContract.callStatic.mint.mockResolvedValue(true); }); - it('should reject returned token with remarks', async () => { + it('should Mint token with remarks', async () => { const result = await mint( { tokenRegistryAddress: mockTokenRegistryAddress }, wallet, @@ -101,7 +101,7 @@ describe('Mint Token', () => { ).toHaveBeenCalled(); }); - it('should reject returned token without remarks', async () => { + it('should mint token without remarks', async () => { const result = await mint( { tokenRegistryAddress: mockTokenRegistryAddress }, wallet, @@ -136,7 +136,7 @@ describe('Mint Token', () => { }, { chainId: mockChainId, id: 'encryption-id' }, ), - ).rejects.toThrow('Pre-check (callStatic) for acceptReturned failed'); + ).rejects.toThrow('Pre-check (callStatic) for mint failed'); if (isV5TT) { mockV5TradeTrustTokenContract.callStatic.mint = vi.fn(); } else { diff --git a/src/__tests__/token-registry-functions/rejectTransfers.test.ts b/src/__tests__/token-registry-functions/rejectTransfers.test.ts index 9af0fcc..86e3c16 100644 --- a/src/__tests__/token-registry-functions/rejectTransfers.test.ts +++ b/src/__tests__/token-registry-functions/rejectTransfers.test.ts @@ -168,7 +168,7 @@ describe.each(providers)( { remarks: mockRemarks }, { chainId: mockChainId, id: 'encryption-id' }, ), - ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + ).rejects.toThrow('Pre-check (callStatic) for rejectTransferHolder failed'); mockV5TitleEscrowContract.callStatic.rejectTransferHolder = vi.fn(); }); @@ -292,7 +292,7 @@ describe.each(providers)( { remarks: mockRemarks }, { chainId: mockChainId, id: 'encryption-id' }, ), - ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + ).rejects.toThrow('Pre-check (callStatic) for rejectTransferBeneficiary failed'); mockV5TitleEscrowContract.callStatic.rejectTransferBeneficiary = vi.fn(); }); @@ -416,7 +416,7 @@ describe.each(providers)( { remarks: mockRemarks }, { chainId: mockChainId, id: 'encryption-id' }, ), - ).rejects.toThrow('Pre-check (callStatic) for transferHolder failed'); + ).rejects.toThrow('Pre-check (callStatic) for rejectTransferOwners failed'); mockV5TitleEscrowContract.callStatic.rejectTransferOwners = vi.fn(); }); diff --git a/src/__tests__/token-registry-functions/returnToken.test.ts b/src/__tests__/token-registry-functions/returnToken.test.ts index 57a5fc5..53528d2 100644 --- a/src/__tests__/token-registry-functions/returnToken.test.ts +++ b/src/__tests__/token-registry-functions/returnToken.test.ts @@ -81,7 +81,7 @@ describe('Return Token', () => { return versionInterface === (isV5TT ? '0xTitleEscrowIdV5' : '0xTitleEscrowIdV4'); }, ); - mockV5TitleEscrowContract.callStatic.rejectTransferHolder.mockResolvedValue(true); + mockV5TitleEscrowContract.callStatic.returnToIssuer.mockResolvedValue(true); mockV4TitleEscrowContract.callStatic.surrender.mockResolvedValue(true); }); @@ -257,7 +257,7 @@ describe('Return Token', () => { { tokenId: mockTokenId, remarks: mockRemarks }, { chainId: mockChainId, id: 'encryption-id' }, ), - ).rejects.toThrow('Pre-check (callStatic) for acceptReturned failed'); + ).rejects.toThrow('Pre-check (callStatic) for rejectReturned failed'); if (isV5TT) { mockV5TradeTrustTokenContract.callStatic.restore = vi.fn(); } else { diff --git a/src/token-registry-functions/mint.ts b/src/token-registry-functions/mint.ts index a12dc95..0594464 100644 --- a/src/token-registry-functions/mint.ts +++ b/src/token-registry-functions/mint.ts @@ -86,7 +86,7 @@ const mint = async ( } } catch (e) { console.error('callStatic failed:', e); - throw new Error('Pre-check (callStatic) for acceptReturned failed'); + throw new Error('Pre-check (callStatic) for mint failed'); } const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); diff --git a/src/token-registry-functions/rejectTransfers.ts b/src/token-registry-functions/rejectTransfers.ts index a32ef4a..207480d 100644 --- a/src/token-registry-functions/rejectTransfers.ts +++ b/src/token-registry-functions/rejectTransfers.ts @@ -68,7 +68,7 @@ const rejectTransferHolder = async ( await titleEscrowContract.callStatic.rejectTransferHolder(encryptedRemarks); } catch (e) { console.error('callStatic failed:', e); - throw new Error('Pre-check (callStatic) for transferHolder failed'); + throw new Error('Pre-check (callStatic) for rejectTransferHolder failed'); } const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); @@ -136,7 +136,7 @@ const rejectTransferBeneficiary = async ( await titleEscrowContract.callStatic.rejectTransferBeneficiary(encryptedRemarks); } catch (e) { console.error('callStatic failed:', e); - throw new Error('Pre-check (callStatic) for transferHolder failed'); + throw new Error('Pre-check (callStatic) for rejectTransferBeneficiary failed'); } const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); @@ -204,7 +204,7 @@ const rejectTransferOwners = async ( await titleEscrowContract.callStatic.rejectTransferOwners(encryptedRemarks); } catch (e) { console.error('callStatic failed:', e); - throw new Error('Pre-check (callStatic) for transferHolder failed'); + throw new Error('Pre-check (callStatic) for rejectTransferOwners failed'); } const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); diff --git a/src/token-registry-functions/returnToken.ts b/src/token-registry-functions/returnToken.ts index af48dee..ec9bc73 100644 --- a/src/token-registry-functions/returnToken.ts +++ b/src/token-registry-functions/returnToken.ts @@ -57,7 +57,7 @@ const returnToIssuer = async ( let titleEscrowContract: v5Contracts.TitleEscrow | v4Contracts.TitleEscrow = v5Contracts.TitleEscrow__factory.connect(titleEscrowAddress, signer); - const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x'; + const encryptedRemarks = remarks && options.id ? `0x${encrypt(remarks, options.id)}` : '0x'; // Detect version if not explicitly provided let isV5TT = titleEscrowVersion === 'v5'; @@ -190,7 +190,7 @@ const rejectReturned = async ( } } catch (e) { console.error('callStatic failed:', e); - throw new Error('Pre-check (callStatic) for acceptReturned failed'); + throw new Error('Pre-check (callStatic) for rejectReturned failed'); } const txOptions = await getTxOptions(signer, chainId, maxFeePerGas, maxPriorityFeePerGas); diff --git a/src/token-registry-functions/types.ts b/src/token-registry-functions/types.ts index a659372..82397f4 100644 --- a/src/token-registry-functions/types.ts +++ b/src/token-registry-functions/types.ts @@ -1,7 +1,6 @@ import { CHAIN_ID } from '@tradetrust-tt/tradetrust-utils'; -import { BigNumber } from 'ethers'; -import { BigNumberish } from 'ethersV6'; -import { providerV5, providerV6 } from 'src/__tests__/token-registry-functions/fixtures'; +import { BigNumber, providers as providersV5 } from 'ethers'; +import { BigNumberish, Provider as ProviderV6 } from 'ethersV6'; export type GasValue = BigNumber | BigNumberish | string | number; @@ -76,8 +75,9 @@ export interface TransferOwnersParams { newBeneficiaryAddress: string; remarks?: string; } + export interface ProviderInfo { - Provider: typeof providerV5 | typeof providerV6; + Provider: providersV5.Provider | ProviderV6; ethersVersion: 'v5' | 'v6'; titleEscrowVersion: 'v4' | 'v5'; } diff --git a/src/token-registry-functions/utils.ts b/src/token-registry-functions/utils.ts index d426481..df031e0 100644 --- a/src/token-registry-functions/utils.ts +++ b/src/token-registry-functions/utils.ts @@ -27,7 +27,7 @@ const getTxOptions = async ( // 🔍 Handles both Ethers v5 and v6 signer types const getChainIdSafe = async (signer: SignerV6 | Signer): Promise => { if (isV6EthersProvider(signer.provider)) { - const network = await (signer as Signer).provider?.getNetwork(); + const network = await (signer as SignerV6).provider?.getNetwork(); if (!network?.chainId) throw new Error('Cannot determine chainId: provider is missing'); return network.chainId; } From 684f543d5e622e720e9c5cda112393d90fb9ea60 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Thu, 3 Jul 2025 11:02:01 +0530 Subject: [PATCH 26/27] fix: update tests --- src/__tests__/token-registry-functions/returnToken.test.ts | 4 ++-- src/token-registry-functions/returnToken.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/__tests__/token-registry-functions/returnToken.test.ts b/src/__tests__/token-registry-functions/returnToken.test.ts index 53528d2..9fd461c 100644 --- a/src/__tests__/token-registry-functions/returnToken.test.ts +++ b/src/__tests__/token-registry-functions/returnToken.test.ts @@ -350,7 +350,7 @@ describe('Return Token', () => { mockV4TradeTrustTokenContract.callStatic.burn.mockResolvedValue(true); }); - it('should reject returned token with remarks', async () => { + it('should accept returned token with remarks', async () => { const result = await acceptReturned( { tokenRegistryAddress: mockTokenRegistryAddress }, wallet, @@ -365,7 +365,7 @@ describe('Return Token', () => { ).toHaveBeenCalled(); }); - it('should reject returned token without remarks', async () => { + it('should accept returned token without remarks', async () => { const result = await acceptReturned( { tokenRegistryAddress: mockTokenRegistryAddress }, wallet, diff --git a/src/token-registry-functions/returnToken.ts b/src/token-registry-functions/returnToken.ts index ec9bc73..a7b8d0e 100644 --- a/src/token-registry-functions/returnToken.ts +++ b/src/token-registry-functions/returnToken.ts @@ -49,7 +49,7 @@ const returnToIssuer = async ( ); } - if (!titleEscrowAddress) throw new Error('Token registry address is required'); + if (!titleEscrowAddress) throw new Error('Title Escrow address is required'); if (!signer.provider) throw new Error('Provider is required'); const { remarks } = params; From 1130d6d6ab3cffb0ca07365784d32024397ccd94 Mon Sep 17 00:00:00 2001 From: nghaninn <43451336+nghaninn@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:09:21 +0800 Subject: [PATCH 27/27] revert: revert changes --- CHANGELOG.md | 148 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f40db..ecfde22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,282 +1,330 @@ ## [1.6.0-alpha.1](https://github.com/TrustVC/trustvc/compare/v1.5.3...v1.6.0-alpha.1) (2025-06-30) + ### Features -- token registry functions ([#74](https://github.com/TrustVC/trustvc/issues/74)) ([5690fcd](https://github.com/TrustVC/trustvc/commit/5690fcda798192609b0060fab0a3f3f77dca8012)) +* token registry functions ([#74](https://github.com/TrustVC/trustvc/issues/74)) ([5690fcd](https://github.com/TrustVC/trustvc/commit/5690fcda798192609b0060fab0a3f3f77dca8012)) + ### Miscellaneous Chores -- back merge ([#75](https://github.com/TrustVC/trustvc/issues/75)) ([7cc1891](https://github.com/TrustVC/trustvc/commit/7cc1891ffebceb4eebf1421d3bc348926efa3f10)), closes [#72](https://github.com/TrustVC/trustvc/issues/72) [#72](https://github.com/TrustVC/trustvc/issues/72) [#73](https://github.com/TrustVC/trustvc/issues/73) [#73](https://github.com/TrustVC/trustvc/issues/73) +* back merge ([#75](https://github.com/TrustVC/trustvc/issues/75)) ([7cc1891](https://github.com/TrustVC/trustvc/commit/7cc1891ffebceb4eebf1421d3bc348926efa3f10)), closes [#72](https://github.com/TrustVC/trustvc/issues/72) [#72](https://github.com/TrustVC/trustvc/issues/72) [#73](https://github.com/TrustVC/trustvc/issues/73) [#73](https://github.com/TrustVC/trustvc/issues/73) ## [1.5.5](https://github.com/TrustVC/trustvc/compare/v1.5.4...v1.5.5) (2025-06-18) + ### Bug Fixes -- upgrade package ([#73](https://github.com/TrustVC/trustvc/issues/73)) ([3c6c9c7](https://github.com/TrustVC/trustvc/commit/3c6c9c73675ec43e0514d9afe5df4444f6e4400b)) +* upgrade package ([#73](https://github.com/TrustVC/trustvc/issues/73)) ([3c6c9c7](https://github.com/TrustVC/trustvc/commit/3c6c9c73675ec43e0514d9afe5df4444f6e4400b)) ## [1.5.4](https://github.com/TrustVC/trustvc/compare/v1.5.3...v1.5.4) (2025-06-17) + ### Bug Fixes -- add w3c credential status check ([#72](https://github.com/TrustVC/trustvc/issues/72)) ([0111cb3](https://github.com/TrustVC/trustvc/commit/0111cb3e48ac86f0cea715ceb5277d199f139763)) +* add w3c credential status check ([#72](https://github.com/TrustVC/trustvc/issues/72)) ([0111cb3](https://github.com/TrustVC/trustvc/commit/0111cb3e48ac86f0cea715ceb5277d199f139763)) ## [1.5.3](https://github.com/TrustVC/trustvc/compare/v1.5.2...v1.5.3) (2025-06-13) + ### Bug Fixes -- redacted document check ([#71](https://github.com/TrustVC/trustvc/issues/71)) ([413e295](https://github.com/TrustVC/trustvc/commit/413e295e5b53cace1bd86f8ec88ece0df265fce6)) +* redacted document check ([#71](https://github.com/TrustVC/trustvc/issues/71)) ([413e295](https://github.com/TrustVC/trustvc/commit/413e295e5b53cace1bd86f8ec88ece0df265fce6)) ## [1.5.2](https://github.com/TrustVC/trustvc/compare/v1.5.1...v1.5.2) (2025-06-10) + ### Bug Fixes -- useEndorsementChain fetch log error ([#70](https://github.com/TrustVC/trustvc/issues/70)) ([3d9871a](https://github.com/TrustVC/trustvc/commit/3d9871a601b05beab88f79814c9b1b4fca4b4145)) +* useEndorsementChain fetch log error ([#70](https://github.com/TrustVC/trustvc/issues/70)) ([3d9871a](https://github.com/TrustVC/trustvc/commit/3d9871a601b05beab88f79814c9b1b4fca4b4145)) ## [1.5.1](https://github.com/TrustVC/trustvc/compare/v1.5.0...v1.5.1) (2025-06-09) + ### Bug Fixes -- expose contracts ([#69](https://github.com/TrustVC/trustvc/issues/69)) ([c4e191d](https://github.com/TrustVC/trustvc/commit/c4e191de42d2a53430cd8d44896b3b6ad7a8a0c5)) +* expose contracts ([#69](https://github.com/TrustVC/trustvc/issues/69)) ([c4e191d](https://github.com/TrustVC/trustvc/commit/c4e191de42d2a53430cd8d44896b3b6ad7a8a0c5)) ## [1.5.0](https://github.com/TrustVC/trustvc/compare/v1.4.12...v1.5.0) (2025-06-09) + ### Features -- add aws kms signer ([#68](https://github.com/TrustVC/trustvc/issues/68)) ([975f82e](https://github.com/TrustVC/trustvc/commit/975f82e19ea1376f9a3b1bd77ca44cf843adbecd)) +* add aws kms signer ([#68](https://github.com/TrustVC/trustvc/issues/68)) ([975f82e](https://github.com/TrustVC/trustvc/commit/975f82e19ea1376f9a3b1bd77ca44cf843adbecd)) ## [1.4.12](https://github.com/TrustVC/trustvc/compare/v1.4.11...v1.4.12) (2025-05-30) + ### Bug Fixes -- resolve import and add provider ([#65](https://github.com/TrustVC/trustvc/issues/65)) ([6b3dae3](https://github.com/TrustVC/trustvc/commit/6b3dae3a630edb5a259b04a3bc5f39ac3a9a4fa4)) +* resolve import and add provider ([#65](https://github.com/TrustVC/trustvc/issues/65)) ([6b3dae3](https://github.com/TrustVC/trustvc/commit/6b3dae3a630edb5a259b04a3bc5f39ac3a9a4fa4)) ## [1.4.11](https://github.com/TrustVC/trustvc/compare/v1.4.10...v1.4.11) (2025-05-29) + ### Bug Fixes -- update tt utils version ([#64](https://github.com/TrustVC/trustvc/issues/64)) ([86ff7fc](https://github.com/TrustVC/trustvc/commit/86ff7fc9ac7b7d43e3e5851a0eecd94e1a5e4e15)) +* update tt utils version ([#64](https://github.com/TrustVC/trustvc/issues/64)) ([86ff7fc](https://github.com/TrustVC/trustvc/commit/86ff7fc9ac7b7d43e3e5851a0eecd94e1a5e4e15)) ## [1.4.10](https://github.com/TrustVC/trustvc/compare/v1.4.9...v1.4.10) (2025-05-21) + ### Bug Fixes -- update amoy rpc for test case ([#63](https://github.com/TrustVC/trustvc/issues/63)) ([9ddba30](https://github.com/TrustVC/trustvc/commit/9ddba300f4f240d9ec2508528bee791e481d8c04)) -- update package ([#62](https://github.com/TrustVC/trustvc/issues/62)) ([f4f3942](https://github.com/TrustVC/trustvc/commit/f4f3942e84325e69c37e652002fa41e970a09ad0)) +* update amoy rpc for test case ([#63](https://github.com/TrustVC/trustvc/issues/63)) ([9ddba30](https://github.com/TrustVC/trustvc/commit/9ddba300f4f240d9ec2508528bee791e481d8c04)) +* update package ([#62](https://github.com/TrustVC/trustvc/issues/62)) ([f4f3942](https://github.com/TrustVC/trustvc/commit/f4f3942e84325e69c37e652002fa41e970a09ad0)) ## [1.4.9](https://github.com/TrustVC/trustvc/compare/v1.4.8...v1.4.9) (2025-05-15) + ### Bug Fixes -- version bump ([#61](https://github.com/TrustVC/trustvc/issues/61)) ([92c7b0b](https://github.com/TrustVC/trustvc/commit/92c7b0ba2d72fc11e16cd6c2f8d7883e83f73d9e)) +* version bump ([#61](https://github.com/TrustVC/trustvc/issues/61)) ([92c7b0b](https://github.com/TrustVC/trustvc/commit/92c7b0ba2d72fc11e16cd6c2f8d7883e83f73d9e)) + ### Miscellaneous Chores -- export gas station function from tt-utils ([#60](https://github.com/TrustVC/trustvc/issues/60)) ([5e88b9c](https://github.com/TrustVC/trustvc/commit/5e88b9cde81068a7ca183a1498bd284f928a092a)) +* export gas station function from tt-utils ([#60](https://github.com/TrustVC/trustvc/issues/60)) ([5e88b9c](https://github.com/TrustVC/trustvc/commit/5e88b9cde81068a7ca183a1498bd284f928a092a)) ## [1.4.8](https://github.com/TrustVC/trustvc/compare/v1.4.7...v1.4.8) (2025-05-14) + ### Bug Fixes -- add qrcode functionality to document builder ([#59](https://github.com/TrustVC/trustvc/issues/59)) ([b217e85](https://github.com/TrustVC/trustvc/commit/b217e85e45a2879800cbf44b1de01d9f5f20ebce)) +* add qrcode functionality to document builder ([#59](https://github.com/TrustVC/trustvc/issues/59)) ([b217e85](https://github.com/TrustVC/trustvc/commit/b217e85e45a2879800cbf44b1de01d9f5f20ebce)) + ### Miscellaneous Chores -- add astrontestnet testcase ([#58](https://github.com/TrustVC/trustvc/issues/58)) ([48e82cb](https://github.com/TrustVC/trustvc/commit/48e82cbb62c6188e8dda86e35d431b308647a03c)) +* add astrontestnet testcase ([#58](https://github.com/TrustVC/trustvc/issues/58)) ([48e82cb](https://github.com/TrustVC/trustvc/commit/48e82cbb62c6188e8dda86e35d431b308647a03c)) ## [1.4.7](https://github.com/TrustVC/trustvc/compare/v1.4.6...v1.4.7) (2025-04-22) + ### Bug Fixes -- update checkDidWebResolve function to use queryDidDocument ([#57](https://github.com/TrustVC/trustvc/issues/57)) ([da975ed](https://github.com/TrustVC/trustvc/commit/da975eda1475390ed41a72926b24308c1fb59c6c)) +* update checkDidWebResolve function to use queryDidDocument ([#57](https://github.com/TrustVC/trustvc/issues/57)) ([da975ed](https://github.com/TrustVC/trustvc/commit/da975eda1475390ed41a72926b24308c1fb59c6c)) ## [1.4.6](https://github.com/TrustVC/trustvc/compare/v1.4.5...v1.4.6) (2025-04-22) + ### Bug Fixes -- endorsement chain initial event remarks error ([#56](https://github.com/TrustVC/trustvc/issues/56)) ([1fd5b4c](https://github.com/TrustVC/trustvc/commit/1fd5b4c1e2794e5cd6748c26ab73f4099b110ffd)) +* endorsement chain initial event remarks error ([#56](https://github.com/TrustVC/trustvc/issues/56)) ([1fd5b4c](https://github.com/TrustVC/trustvc/commit/1fd5b4c1e2794e5cd6748c26ab73f4099b110ffd)) ## [1.4.5](https://github.com/TrustVC/trustvc/compare/v1.4.4...v1.4.5) (2025-04-10) + ### Bug Fixes -- prevent comments from being removed, for jsdocs ([#55](https://github.com/TrustVC/trustvc/issues/55)) ([82a4208](https://github.com/TrustVC/trustvc/commit/82a4208b38487c5c54093e65f0726cc14efcd308)) +* prevent comments from being removed, for jsdocs ([#55](https://github.com/TrustVC/trustvc/issues/55)) ([82a4208](https://github.com/TrustVC/trustvc/commit/82a4208b38487c5c54093e65f0726cc14efcd308)) ## [1.4.4](https://github.com/TrustVC/trustvc/compare/v1.4.3...v1.4.4) (2025-04-10) + ### Bug Fixes -- update w3cIssuerIdentity to use documentLoader ([#54](https://github.com/TrustVC/trustvc/issues/54)) ([8477bfc](https://github.com/TrustVC/trustvc/commit/8477bfc48214523de057cc9d047abe15886e1321)) +* update w3cIssuerIdentity to use documentLoader ([#54](https://github.com/TrustVC/trustvc/issues/54)) ([8477bfc](https://github.com/TrustVC/trustvc/commit/8477bfc48214523de057cc9d047abe15886e1321)) ## [1.4.3](https://github.com/TrustVC/trustvc/compare/v1.4.2...v1.4.3) (2025-04-09) + ### Bug Fixes -- allow getDocumentLoader for verifyDocument ([#53](https://github.com/TrustVC/trustvc/issues/53)) ([8a4afc4](https://github.com/TrustVC/trustvc/commit/8a4afc44bcf3ca45a9bc82d7a6838e7df432200c)) +* allow getDocumentLoader for verifyDocument ([#53](https://github.com/TrustVC/trustvc/issues/53)) ([8a4afc4](https://github.com/TrustVC/trustvc/commit/8a4afc44bcf3ca45a9bc82d7a6838e7df432200c)) ## [1.4.2](https://github.com/TrustVC/trustvc/compare/v1.4.1...v1.4.2) (2025-04-08) + ### Bug Fixes -- add getDocumentOwner ([#52](https://github.com/TrustVC/trustvc/issues/52)) ([ac37a05](https://github.com/TrustVC/trustvc/commit/ac37a053f381128cd180b2d11adfd38c3f24014c)) +* add getDocumentOwner ([#52](https://github.com/TrustVC/trustvc/issues/52)) ([ac37a05](https://github.com/TrustVC/trustvc/commit/ac37a053f381128cd180b2d11adfd38c3f24014c)) ## [1.4.1](https://github.com/TrustVC/trustvc/compare/v1.4.0...v1.4.1) (2025-04-08) + ### Bug Fixes -- upgrade package and update getTitleEscrowAddress ([#51](https://github.com/TrustVC/trustvc/issues/51)) ([f0693a1](https://github.com/TrustVC/trustvc/commit/f0693a14d1bcea22d50ab904d45bbf1151c1ceb8)) +* upgrade package and update getTitleEscrowAddress ([#51](https://github.com/TrustVC/trustvc/issues/51)) ([f0693a1](https://github.com/TrustVC/trustvc/commit/f0693a14d1bcea22d50ab904d45bbf1151c1ceb8)) ## [1.4.0](https://github.com/TrustVC/trustvc/compare/v1.3.0...v1.4.0) (2025-04-04) + ### Features -- document builder update ([#50](https://github.com/TrustVC/trustvc/issues/50)) ([a6e2e46](https://github.com/TrustVC/trustvc/commit/a6e2e4617773e3d426d5429f48f1dce8938b2270)) +* document builder update ([#50](https://github.com/TrustVC/trustvc/issues/50)) ([a6e2e46](https://github.com/TrustVC/trustvc/commit/a6e2e4617773e3d426d5429f48f1dce8938b2270)) ## [1.3.0](https://github.com/TrustVC/trustvc/compare/v1.2.11...v1.3.0) (2025-03-27) + ### Features -- document builder for w3c vc ([#47](https://github.com/TrustVC/trustvc/issues/47)) ([fab9c82](https://github.com/TrustVC/trustvc/commit/fab9c8237a072f541474fa778cf9974de6c0769c)) +* document builder for w3c vc ([#47](https://github.com/TrustVC/trustvc/issues/47)) ([fab9c82](https://github.com/TrustVC/trustvc/commit/fab9c8237a072f541474fa778cf9974de6c0769c)) ## [1.2.11](https://github.com/TrustVC/trustvc/compare/v1.2.10...v1.2.11) (2025-03-17) + ### Bug Fixes -- update package ([#46](https://github.com/TrustVC/trustvc/issues/46)) ([c0a5d1c](https://github.com/TrustVC/trustvc/commit/c0a5d1cb8d5988fa803814efdab4b8b90e0760b2)) +* update package ([#46](https://github.com/TrustVC/trustvc/issues/46)) ([c0a5d1c](https://github.com/TrustVC/trustvc/commit/c0a5d1cb8d5988fa803814efdab4b8b90e0760b2)) ## [1.2.10](https://github.com/TrustVC/trustvc/compare/v1.2.9...v1.2.10) (2025-03-13) + ### Bug Fixes -- update exports ([#45](https://github.com/TrustVC/trustvc/issues/45)) ([bf1e165](https://github.com/TrustVC/trustvc/commit/bf1e165e275bb256046e19a5c6796c773856ce37)) +* update exports ([#45](https://github.com/TrustVC/trustvc/issues/45)) ([bf1e165](https://github.com/TrustVC/trustvc/commit/bf1e165e275bb256046e19a5c6796c773856ce37)) ## [1.2.9](https://github.com/TrustVC/trustvc/compare/v1.2.8...v1.2.9) (2025-03-13) + ### Bug Fixes -- update tradetrust utils ([#44](https://github.com/TrustVC/trustvc/issues/44)) ([3058c16](https://github.com/TrustVC/trustvc/commit/3058c16df61047f6ef721533a237769c588a7557)) +* update tradetrust utils ([#44](https://github.com/TrustVC/trustvc/issues/44)) ([3058c16](https://github.com/TrustVC/trustvc/commit/3058c16df61047f6ef721533a237769c588a7557)) ## [1.2.8](https://github.com/TrustVC/trustvc/compare/v1.2.7...v1.2.8) (2025-03-05) + ### Bug Fixes -- supports web3provider for endorsement chain ([#43](https://github.com/TrustVC/trustvc/issues/43)) ([2559f44](https://github.com/TrustVC/trustvc/commit/2559f44f1e432fd6c1784c87dfae00d5bc18e1e0)) +* supports web3provider for endorsement chain ([#43](https://github.com/TrustVC/trustvc/issues/43)) ([2559f44](https://github.com/TrustVC/trustvc/commit/2559f44f1e432fd6c1784c87dfae00d5bc18e1e0)) ## [1.2.7](https://github.com/TrustVC/trustvc/compare/v1.2.6...v1.2.7) (2025-02-12) + ### Bug Fixes -- package ([#42](https://github.com/TrustVC/trustvc/issues/42)) ([7b4d49e](https://github.com/TrustVC/trustvc/commit/7b4d49e71ed5fa8f5733e0d34611cb817ff2c101)) +* package ([#42](https://github.com/TrustVC/trustvc/issues/42)) ([7b4d49e](https://github.com/TrustVC/trustvc/commit/7b4d49e71ed5fa8f5733e0d34611cb817ff2c101)) ## [1.2.6](https://github.com/TrustVC/trustvc/compare/v1.2.5...v1.2.6) (2025-02-11) + ### Bug Fixes -- upgrade pkgs ([#41](https://github.com/TrustVC/trustvc/issues/41)) ([faa0ddc](https://github.com/TrustVC/trustvc/commit/faa0ddccbb6729e6ac43d75826434db597c7e63b)) +* upgrade pkgs ([#41](https://github.com/TrustVC/trustvc/issues/41)) ([faa0ddc](https://github.com/TrustVC/trustvc/commit/faa0ddccbb6729e6ac43d75826434db597c7e63b)) ## [1.2.5](https://github.com/TrustVC/trustvc/compare/v1.2.4...v1.2.5) (2025-02-11) + ### Bug Fixes -- ethers error ([#40](https://github.com/TrustVC/trustvc/issues/40)) ([f41bebc](https://github.com/TrustVC/trustvc/commit/f41bebc1604e693ec72a727fa587332ee7c0e522)) +* ethers error ([#40](https://github.com/TrustVC/trustvc/issues/40)) ([f41bebc](https://github.com/TrustVC/trustvc/commit/f41bebc1604e693ec72a727fa587332ee7c0e522)) ## [1.2.4](https://github.com/TrustVC/trustvc/compare/v1.2.3...v1.2.4) (2025-02-05) + ### Bug Fixes -- upgrade packages ([#37](https://github.com/TrustVC/trustvc/issues/37)) ([35f26c7](https://github.com/TrustVC/trustvc/commit/35f26c71f029a73cb9498c7fb2201d96c19f432e)) +* upgrade packages ([#37](https://github.com/TrustVC/trustvc/issues/37)) ([35f26c7](https://github.com/TrustVC/trustvc/commit/35f26c71f029a73cb9498c7fb2201d96c19f432e)) ## [1.2.3](https://github.com/TrustVC/trustvc/compare/v1.2.2...v1.2.3) (2025-02-04) + ### Bug Fixes -- upgrade pkgs and migration function from website ([#36](https://github.com/TrustVC/trustvc/issues/36)) ([6936e71](https://github.com/TrustVC/trustvc/commit/6936e719d9878957f52764af3e4528273afb7bd3)) +* upgrade pkgs and migration function from website ([#36](https://github.com/TrustVC/trustvc/issues/36)) ([6936e71](https://github.com/TrustVC/trustvc/commit/6936e719d9878957f52764af3e4528273afb7bd3)) ## [1.2.2](https://github.com/TrustVC/trustvc/compare/v1.2.1...v1.2.2) (2025-01-28) + ### Bug Fixes -- upgrade pkgs ([#35](https://github.com/TrustVC/trustvc/issues/35)) ([bf4d412](https://github.com/TrustVC/trustvc/commit/bf4d4129d756bfd3a406688117fa8a60d3bc7cd4)) +* upgrade pkgs ([#35](https://github.com/TrustVC/trustvc/issues/35)) ([bf4d412](https://github.com/TrustVC/trustvc/commit/bf4d4129d756bfd3a406688117fa8a60d3bc7cd4)) ## [1.2.1](https://github.com/TrustVC/trustvc/compare/v1.2.0...v1.2.1) (2025-01-09) + ### Bug Fixes -- import error ([#34](https://github.com/TrustVC/trustvc/issues/34)) ([1450333](https://github.com/TrustVC/trustvc/commit/145033337e40d1c9fa54933829a095eb83bbe465)) +* import error ([#34](https://github.com/TrustVC/trustvc/issues/34)) ([1450333](https://github.com/TrustVC/trustvc/commit/145033337e40d1c9fa54933829a095eb83bbe465)) ## [1.2.0](https://github.com/TrustVC/trustvc/compare/v1.1.3...v1.2.0) (2025-01-09) + ### Features -- add w3cEmptyCredentialStatus fragment ([#33](https://github.com/TrustVC/trustvc/issues/33)) ([76a3098](https://github.com/TrustVC/trustvc/commit/76a3098427cd00dc4102c3850475ce4828adaed2)) +* add w3cEmptyCredentialStatus fragment ([#33](https://github.com/TrustVC/trustvc/issues/33)) ([76a3098](https://github.com/TrustVC/trustvc/commit/76a3098427cd00dc4102c3850475ce4828adaed2)) ## [1.1.3](https://github.com/TrustVC/trustvc/compare/v1.1.2...v1.1.3) (2025-01-07) + ### Bug Fixes -- upgrade package ([#32](https://github.com/TrustVC/trustvc/issues/32)) ([4cb0ac0](https://github.com/TrustVC/trustvc/commit/4cb0ac0ac0cd4aaa2d16ba639f56001143ab45e9)) +* upgrade package ([#32](https://github.com/TrustVC/trustvc/issues/32)) ([4cb0ac0](https://github.com/TrustVC/trustvc/commit/4cb0ac0ac0cd4aaa2d16ba639f56001143ab45e9)) ## [1.1.2](https://github.com/TrustVC/trustvc/compare/v1.1.1...v1.1.2) (2025-01-03) + ### Bug Fixes -- exports and package upgrade ([#31](https://github.com/TrustVC/trustvc/issues/31)) ([b64b375](https://github.com/TrustVC/trustvc/commit/b64b375a45c662ee841dc9d20ee69d5ebc6cae21)) +* exports and package upgrade ([#31](https://github.com/TrustVC/trustvc/issues/31)) ([b64b375](https://github.com/TrustVC/trustvc/commit/b64b375a45c662ee841dc9d20ee69d5ebc6cae21)) ## [1.1.1](https://github.com/TrustVC/trustvc/compare/v1.1.0...v1.1.1) (2024-12-23) + ### Bug Fixes -- imports ([#29](https://github.com/TrustVC/trustvc/issues/29)) ([6cb78d5](https://github.com/TrustVC/trustvc/commit/6cb78d5fbb521ab2b514a0da0ad7ed1efd224d74)) +* imports ([#29](https://github.com/TrustVC/trustvc/issues/29)) ([6cb78d5](https://github.com/TrustVC/trustvc/commit/6cb78d5fbb521ab2b514a0da0ad7ed1efd224d74)) ## [1.1.0](https://github.com/TrustVC/trustvc/compare/v1.0.5...v1.1.0) (2024-12-19) + ### Features -- endorsement chain ([#27](https://github.com/TrustVC/trustvc/issues/27)) ([58c184a](https://github.com/TrustVC/trustvc/commit/58c184a2d1694978e6dd8a55c85c86aa7a9b1212)) +* endorsement chain ([#27](https://github.com/TrustVC/trustvc/issues/27)) ([58c184a](https://github.com/TrustVC/trustvc/commit/58c184a2d1694978e6dd8a55c85c86aa7a9b1212)) ## [1.0.5](https://github.com/TrustVC/trustvc/compare/v1.0.4...v1.0.5) (2024-12-17) + ### Bug Fixes -- credential status to verify set ([#26](https://github.com/TrustVC/trustvc/issues/26)) ([b869b9e](https://github.com/TrustVC/trustvc/commit/b869b9ed66e7f41e00c17efabf6990796ee9121f)) +* credential status to verify set ([#26](https://github.com/TrustVC/trustvc/issues/26)) ([b869b9e](https://github.com/TrustVC/trustvc/commit/b869b9ed66e7f41e00c17efabf6990796ee9121f)) ## [1.0.4](https://github.com/TrustVC/trustvc/compare/v1.0.3...v1.0.4) (2024-12-13) + ### Bug Fixes -- add imports ([#25](https://github.com/TrustVC/trustvc/issues/25)) ([3ea8b9f](https://github.com/TrustVC/trustvc/commit/3ea8b9f610ce93f9d86792a4951c09d3a2269450)) +* add imports ([#25](https://github.com/TrustVC/trustvc/issues/25)) ([3ea8b9f](https://github.com/TrustVC/trustvc/commit/3ea8b9f610ce93f9d86792a4951c09d3a2269450)) ## [1.0.3](https://github.com/TrustVC/trustvc/compare/v1.0.2...v1.0.3) (2024-12-06) + ### Bug Fixes -- update w3c package ([#24](https://github.com/TrustVC/trustvc/issues/24)) ([26d4371](https://github.com/TrustVC/trustvc/commit/26d4371fe0dbb477ed112240be2db7fa13c39a95)) +* update w3c package ([#24](https://github.com/TrustVC/trustvc/issues/24)) ([26d4371](https://github.com/TrustVC/trustvc/commit/26d4371fe0dbb477ed112240be2db7fa13c39a95)) ## [1.0.2](https://github.com/TrustVC/trustvc/compare/v1.0.1...v1.0.2) (2024-12-06) + ### Bug Fixes -- update imports ([#23](https://github.com/TrustVC/trustvc/issues/23)) ([cd5edd1](https://github.com/TrustVC/trustvc/commit/cd5edd1d1718c78273d8c314f542f1ada3e73cd2)) +* update imports ([#23](https://github.com/TrustVC/trustvc/issues/23)) ([cd5edd1](https://github.com/TrustVC/trustvc/commit/cd5edd1d1718c78273d8c314f542f1ada3e73cd2)) ## [1.0.1](https://github.com/TrustVC/trustvc/compare/v1.0.0...v1.0.1) (2024-12-06) + ### Bug Fixes -- update package dependencies ([#22](https://github.com/TrustVC/trustvc/issues/22)) ([92a9795](https://github.com/TrustVC/trustvc/commit/92a97958f06ce862f8e74138d22de60b2f85b2f6)) +* update package dependencies ([#22](https://github.com/TrustVC/trustvc/issues/22)) ([92a9795](https://github.com/TrustVC/trustvc/commit/92a97958f06ce862f8e74138d22de60b2f85b2f6)) + ### Miscellaneous Chores -- update readme ([#21](https://github.com/TrustVC/trustvc/issues/21)) ([b6f0eb3](https://github.com/TrustVC/trustvc/commit/b6f0eb305f26843307592d0efa26a22d05091022)) +* update readme ([#21](https://github.com/TrustVC/trustvc/issues/21)) ([b6f0eb3](https://github.com/TrustVC/trustvc/commit/b6f0eb305f26843307592d0efa26a22d05091022)) ## [1.0.0](https://github.com/TrustVC/trustvc/compare/v0.0.0...v1.0.0) (2024-12-02) + ### ⚠ BREAKING CHANGES -- v1 release +* v1 release ### Features -- breaking change: v1 release ([#19](https://github.com/TrustVC/trustvc/issues/19)) ([2b563e9](https://github.com/TrustVC/trustvc/commit/2b563e9766598aec12127b36260e2a8488932c3a)) +* breaking change: v1 release ([#19](https://github.com/TrustVC/trustvc/issues/19)) ([2b563e9](https://github.com/TrustVC/trustvc/commit/2b563e9766598aec12127b36260e2a8488932c3a)) + ### Miscellaneous Chores -- update ci ([6bcd602](https://github.com/TrustVC/trustvc/commit/6bcd60227e99ab170eed90ab6b3cccca34ed38aa)) +* update ci ([6bcd602](https://github.com/TrustVC/trustvc/commit/6bcd60227e99ab170eed90ab6b3cccca34ed38aa))