Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9a54c5a
feat: add obligation registry and related functions to the project
manishdex25 Jul 21, 2026
7fc5571
fix: enhance error handling and validation in obligation record verif…
manishdex25 Jul 21, 2026
d885122
Update src/core/obligation-endorsement-chain/fetchObligationEscrowTra…
manishdex25 Jul 21, 2026
5180e38
refactor: consolidate obligation registry functions and enhance error…
manishdex25 Jul 21, 2026
9b35719
Merge branch 'feature/boe-v-trustvc' of github.com:TrustVC/trustvc in…
manishdex25 Jul 21, 2026
b27053c
test: update returnToken.e2e.test.ts to reflect soft-burn behavior of…
manishdex25 Jul 21, 2026
e177a6f
Update src/obligation-registry-functions/lifecycle.ts
manishdex25 Jul 21, 2026
a93edcc
Update src/obligation-registry-functions/utils.ts
manishdex25 Jul 21, 2026
13094da
Merge branch 'feature/boe-v-trustvc' of github.com:TrustVC/trustvc in…
manishdex25 Jul 21, 2026
63c4b6d
docs: update readme to include Obligation Registry features
manishdex25 Jul 21, 2026
223a5a0
fix: update RPC URLs in tests and fixtures for Amoy network
manishdex25 Jul 21, 2026
b77a07c
feat: enhance credential status verification to support obligation re…
manishdex25 Jul 23, 2026
ffe7dae
feat: add support for verify-obligation module and update test fixtur…
manishdex25 Jul 23, 2026
dbb7a6c
feat: add unit tests for DocumentBuilder obligation records and refac…
manishdex25 Jul 23, 2026
98bc2d4
feat: introduce ObligationDocumentBuilder for managing Obligation Rec…
manishdex25 Jul 23, 2026
a5b7a6c
feat: update README with Obligation Registry guide
manishdex25 Jul 27, 2026
66e6d5e
docs: add detailed Obligation Registry user guide to README
manishdex25 Jul 27, 2026
691f828
docs: update README to reflect changes in BoE credential fields and i…
manishdex25 Jul 28, 2026
d285708
refactor: unify verification process for Obligation Records and updat…
manishdex25 Jul 28, 2026
cf12916
chore: update token-registry-v5 dependency to version 5.6.0-beta.1
manishdex25 Jul 30, 2026
22e8723
Update README.md
manishdex25 Jul 30, 2026
9f27476
chore: update package dependencies and improve type definitions for t…
manishdex25 Jul 30, 2026
447646f
chore: increase Node.js memory limit in CI workflow
manishdex25 Jul 30, 2026
b25c7ba
test: enhance tests for DocumentBuilder and Obligation Records verifi…
manishdex25 Jul 30, 2026
40778f5
test: add afterEach hook to restore mocks in DocumentBuilder tests
manishdex25 Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci

Check warning on line 35 in .github/workflows/tests.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--ignore-scripts" allows lifecycle scripts to run during package installation.

See more on https://sonarcloud.io/project/issues?id=TrustVC_trustvc&issues=AZ-x5XUUAEHaxv8kvC_-&open=AZ-x5XUUAEHaxv8kvC_-&pullRequest=153
- run: npm run test
env:
ANKR_API_KEY: ${{ secrets.ANKR_API_KEY }}
Expand All @@ -58,5 +58,7 @@
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci

Check warning on line 61 in .github/workflows/tests.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--ignore-scripts" allows lifecycle scripts to run during package installation.

See more on https://sonarcloud.io/project/issues?id=TrustVC_trustvc&issues=AZ-x5XUUAEHaxv8kvC__&open=AZ-x5XUUAEHaxv8kvC__&pullRequest=153
- run: npm run build
env:
NODE_OPTIONS: --max-old-space-size=8192
472 changes: 466 additions & 6 deletions README.md

Large diffs are not rendered by default.

346 changes: 165 additions & 181 deletions package-lock.json

Large diffs are not rendered by default.

37 changes: 31 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@
"import": "./dist/esm/token-registry-v5/contracts.js",
"require": "./dist/cjs/token-registry-v5/contracts.js"
},
"./obligation-registry": {
"types": "./dist/types/obligation-registry/index.d.ts",
"import": "./dist/esm/obligation-registry/index.js",
"require": "./dist/cjs/obligation-registry/index.js"
},
"./obligation-registry/contracts": {
"types": "./dist/types/obligation-registry/contracts.d.ts",
"import": "./dist/esm/obligation-registry/contracts.js",
"require": "./dist/cjs/obligation-registry/contracts.js"
},
"./obligation-registry-functions": {
"types": "./dist/types/obligation-registry-functions/index.d.ts",
"import": "./dist/esm/obligation-registry-functions/index.js",
"require": "./dist/cjs/obligation-registry-functions/index.js"
},
"./utils": {
"types": "./dist/types/utils/index.d.ts",
"import": "./dist/esm/utils/index.js",
Expand All @@ -88,6 +103,16 @@
"import": "./dist/esm/verify/fragments/index.js",
"require": "./dist/cjs/verify/fragments/index.js"
},
"./verify-obligation": {
"types": "./dist/types/verify-obligation/index.d.ts",
"import": "./dist/esm/verify-obligation/index.js",
"require": "./dist/cjs/verify-obligation/index.js"
},
"./verify-obligation/fragments": {
"types": "./dist/types/verify-obligation/fragments/index.d.ts",
"import": "./dist/esm/verify-obligation/fragments/index.js",
"require": "./dist/cjs/verify-obligation/fragments/index.js"
},
"./w3c": {
"types": "./dist/types/w3c/index.d.ts",
"import": "./dist/esm/w3c/index.js",
Expand Down Expand Up @@ -118,15 +143,15 @@
"@tradetrust-tt/dnsprove": "^2.18.0",
"@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.5.0",
"@tradetrust-tt/token-registry-v5": "npm:@tradetrust-tt/token-registry@^5.6.0-beta.1",
"@tradetrust-tt/tradetrust": "^6.10.3",
"@tradetrust-tt/tt-verify": "^9.7.5",
"@trustvc/document-store": "^1.0.3",
"@trustvc/w3c": "^2.2.0",
"@trustvc/w3c-context": "^2.2.0",
"@trustvc/w3c-credential-status": "^2.2.0",
"@trustvc/w3c-issuer": "^2.2.0",
"@trustvc/w3c-vc": "^2.2.0",
"@trustvc/w3c": "^2.4.1",
"@trustvc/w3c-context": "^2.4.0",
"@trustvc/w3c-credential-status": "^2.4.0",
"@trustvc/w3c-issuer": "^2.3.0",
"@trustvc/w3c-vc": "^2.4.1",
"ethers": "^5.8.0",
"ethersV6": "npm:ethers@^6.14.4",
"js-sha3": "^0.9.3",
Expand Down
24 changes: 24 additions & 0 deletions src/__tests__/core/documentBuilder.obligation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { TR_CONTEXT_URL } from '@trustvc/w3c-context';
import { DocumentBuilder } from '../../core/documentBuilder';

describe('DocumentBuilder (obligation records)', () => {
let documentBuilder: DocumentBuilder;

beforeEach(() => {
documentBuilder = new DocumentBuilder({
'@context': 'https://trustvc.io/context/bill-of-lading.json',
}).credentialSubject({ type: ['BillOfLading'] });
});

it('should configure obligationRecords correctly', () => {
documentBuilder.credentialStatus({
chain: 'amoy',
chainId: 80002,
obligationRegistry: '0x71D28767662cB233F887aD2Bb65d048d760bA694',
rpcProviderUrl: 'https://rpc-amoy.polygon.technology',
});
expect(documentBuilder).toBeDefined();
expect(documentBuilder.toString()).toContain(TR_CONTEXT_URL);
});
});
25 changes: 23 additions & 2 deletions src/__tests__/core/documentBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { DocumentBuilder } from '../../core/documentBuilder';
import {
Bbs2023PrivateKeyPair,
Expand Down Expand Up @@ -148,7 +148,7 @@ describe('DocumentBuilder data model 2.0 using ECDSA', () => {
index: 10,
}),
).toThrow(
'Configuration Error: Do not mix transferable records and verifiable document properties.',
'Configuration Error: Do not mix transferable records, obligation records, and verifiable document properties.',
);
});

Expand All @@ -162,6 +162,16 @@ describe('DocumentBuilder data model 2.0 using ECDSA', () => {
});

describe('Sign, Derive and Verify', () => {
beforeEach(() => {
vi.spyOn(DocumentBuilder.prototype as never, 'verifyTokenRegistry').mockResolvedValue(
undefined,
);
});

afterEach(() => {
vi.restoreAllMocks();
});

it('should sign, derive and verify the document successfully for transferableRecords using ECDSA', async () => {
documentBuilder.credentialStatus({
chain: 'amoy',
Expand Down Expand Up @@ -235,13 +245,20 @@ describe('DocumentBuilder data model 2.0 using ECDSA', () => {
});

describe('Error Handling', () => {
afterEach(() => {
vi.restoreAllMocks();
});

it('Should throw error if document builder initialized with data model v1.1 context', () => {
expect(
() => new DocumentBuilder({ '@context': ['https://www.w3.org/2018/credentials/v1'] }),
).toThrow('Document builder does not support data model v1.1.');
});

it('Should throw an error when trying to verify without deriving using ECDSA', async () => {
vi.spyOn(DocumentBuilder.prototype as never, 'verifyTokenRegistry').mockResolvedValue(
undefined,
);
documentBuilder.credentialStatus({
chain: 'amoy',
chainId: 80002,
Expand All @@ -256,6 +273,9 @@ describe('DocumentBuilder data model 2.0 using ECDSA', () => {
});

it('Should throw an error when trying to verify without deriving using BBS2023', async () => {
vi.spyOn(DocumentBuilder.prototype as never, 'verifyTokenRegistry').mockResolvedValue(
undefined,
);
documentBuilder.credentialStatus({
chain: 'amoy',
chainId: 80002,
Expand All @@ -282,6 +302,7 @@ describe('DocumentBuilder data model 2.0 using ECDSA', () => {
});

it('should throw an error when unable to verify token registry', async () => {
vi.restoreAllMocks();
documentBuilder.credentialStatus({
chain: 'amoy',
chainId: 80002,
Expand Down
7 changes: 5 additions & 2 deletions src/__tests__/core/verify.pol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ describe('Polygon (POL) network support', () => {

describe('W3C_TRANSFERABLE_RECORD_POL fixture structure', () => {
it('should have chain POL and chainId 137 in credentialStatus', () => {
expect(W3C_TRANSFERABLE_RECORD_POL.credentialStatus.tokenNetwork.chain).toBe('POL');
expect(W3C_TRANSFERABLE_RECORD_POL.credentialStatus.tokenNetwork.chainId).toBe(137);
const credentialStatus = W3C_TRANSFERABLE_RECORD_POL.credentialStatus as unknown as {
tokenNetwork: { chain: string; chainId: number };
};
expect(credentialStatus.tokenNetwork.chain).toBe('POL');
expect(credentialStatus.tokenNetwork.chainId).toBe(137);
});

it('should have a DataIntegrityProof with ecdsa-sd-2023 cryptosuite', () => {
Expand Down
200 changes: 200 additions & 0 deletions src/__tests__/core/verify.rpc-resilience.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { beforeEach, describe, it, vi } from 'vitest';
import { verifyDocument } from '../..';
import * as transferableRecordsUtils from '../../verify/fragments/document-status/transferableRecords/utils';
import {
W3C_TRANSFERABLE_RECORD,
WRAPPED_DOCUMENT_DID_TOKEN_REGISTRY_V3,
} from '../fixtures/fixtures';
import { W3CCredentialStatusCode } from '../../verify/fragments/document-status/w3cCredentialStatus';

/**
* Extra verify coverage that mocks / soft-asserts around flaky public Amoy RPC.
* Keeps src/__tests__/core/verify.test.ts aligned with main (live RPC snapshots).
*/
const providerUrl = 'https://rpc-amoy.polygon.technology';

describe.concurrent('W3C verify (RPC resilience)', () => {
describe.concurrent('W3C_TRANSFERABLE_RECORD mocked mint checks', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetAllMocks();
});

it(
'should return VALID status for TransferableRecords when mint check is mocked',
{ timeout: 300000 },
async ({ expect }) => {
vi.spyOn(transferableRecordsUtils, 'isTokenMintedOnRegistry').mockResolvedValue({
minted: true,
address: '0x6c2a002A5833a100f38458c50F11E71Aa1A342c6',
});
expect(
await verifyDocument(W3C_TRANSFERABLE_RECORD as any, { rpcProviderUrl: providerUrl }),
).toMatchInlineSnapshot(`
[
{
"data": true,
"name": "W3CSignatureIntegrity",
"status": "VALID",
"type": "DOCUMENT_INTEGRITY",
},
{
"name": "EcdsaW3CSignatureIntegrity",
"reason": {
"code": 0,
"codeString": "SKIPPED",
"message": "Document either has no proof or proof type is not 'DataIntegrityProof' or proof cryptosuite is not 'ecdsa-sd-2023'.",
},
"status": "SKIPPED",
"type": "DOCUMENT_INTEGRITY",
},
{
"name": "Bbs2023W3CSignatureIntegrity",
"reason": {
"code": 0,
"codeString": "SKIPPED",
"message": "Document either has no proof or proof type is not 'DataIntegrityProof' or proof cryptosuite is not 'bbs-2023'.",
},
"status": "SKIPPED",
"type": "DOCUMENT_INTEGRITY",
},
{
"name": "W3CCredentialStatus",
"reason": {
"code": 0,
"codeString": "SKIPPED",
"message": "Document does not have a valid credentialStatus or type.",
},
"status": "SKIPPED",
"type": "DOCUMENT_STATUS",
},
{
"data": {
"tokenRegistry": "0x6c2a002A5833a100f38458c50F11E71Aa1A342c6",
},
"name": "TransferableRecords",
"status": "VALID",
"type": "DOCUMENT_STATUS",
},
{
"name": "ObligationRecords",
"reason": {
"code": 0,
"codeString": "SKIPPED",
"message": "Document does not have ObligationRecords status",
},
"status": "SKIPPED",
"type": "DOCUMENT_STATUS",
},
{
"name": "W3CEmptyCredentialStatus",
"reason": {
"code": 0,
"codeString": "SKIPPED",
"message": "Document contains a credentialStatus.",
},
"status": "SKIPPED",
"type": "DOCUMENT_STATUS",
},
{
"data": true,
"name": "W3CIssuerIdentity",
"status": "VALID",
"type": "ISSUER_IDENTITY",
},
]
`);
},
);

it('should return INVALID status for TransferableRecords when mint check is mocked as not minted', async ({
expect,
}) => {
vi.spyOn(transferableRecordsUtils, 'isTokenMintedOnRegistry').mockResolvedValue({
minted: false,
address: '0x6c2a002A5833a100f38458c50F11E71Aa1A342c6',
reason: {
code: W3CCredentialStatusCode.DOCUMENT_NOT_ISSUED,
codeString: 'DOCUMENT_NOT_MINTED',
message: 'Document has not been issued under token registry',
},
});
const tampered: any = {
...W3C_TRANSFERABLE_RECORD,
credentialStatus: {
...W3C_TRANSFERABLE_RECORD.credentialStatus,
tokenId: '123',
},
};
expect(await verifyDocument(tampered, { rpcProviderUrl: providerUrl })).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'TransferableRecords',
reason: {
code: W3CCredentialStatusCode.DOCUMENT_NOT_ISSUED,
codeString: 'DOCUMENT_NOT_MINTED',
message: 'Document has not been issued under token registry',
},
status: 'INVALID',
type: 'DOCUMENT_STATUS',
}),
]),
);
});
});
});

describe.concurrent('V3 verify (RPC resilience)', () => {
it(
'should verify a DID_TOKEN_REGISTRY document with soft asserts for flaky Amoy RPC',
{ timeout: 300000 },
async ({ expect }) => {
const fragments = await verifyDocument(WRAPPED_DOCUMENT_DID_TOKEN_REGISTRY_V3, {
rpcProviderUrl: providerUrl,
});

expect(fragments).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'OpenAttestationHash',
status: 'VALID',
type: 'DOCUMENT_INTEGRITY',
}),
expect.objectContaining({
name: 'OpenAttestationEthereumDocumentStoreStatus',
status: 'SKIPPED',
type: 'DOCUMENT_STATUS',
}),
expect.objectContaining({
name: 'OpenAttestationDidSignedDocumentStatus',
status: 'SKIPPED',
type: 'DOCUMENT_STATUS',
}),
expect.objectContaining({
name: 'OpenAttestationDnsDidIdentityProof',
status: 'SKIPPED',
type: 'ISSUER_IDENTITY',
}),
expect.objectContaining({
name: 'OpencertsRegistryVerifier',
status: 'SKIPPED',
type: 'ISSUER_IDENTITY',
}),
]),
);

// Token registry / DNS-TXT can ERROR when public Amoy RPC is flaky.
const tokenRegistryFragment = fragments.find(
(fragment) => fragment.name === 'OpenAttestationEthereumTokenRegistryStatus',
);
expect(tokenRegistryFragment).toBeDefined();
expect(['VALID', 'ERROR']).toContain(tokenRegistryFragment?.status);

const dnsTxtFragment = fragments.find(
(fragment) => fragment.name === 'OpenAttestationDnsTxtIdentityProof',
);
expect(dnsTxtFragment).toBeDefined();
expect(['VALID', 'ERROR']).toContain(dnsTxtFragment?.status);
},
);
});
Loading
Loading