Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c2a0881
feat: enhance log fetching logic by introducing new error handling
manishdex25 Aug 12, 2026
6176182
feat: add constants for log fetching and enhance error handling in es…
manishdex25 Aug 12, 2026
24dbecb
feat: enhance error handling in log fetching and escrow transfer
manishdex25 Aug 12, 2026
c7cf475
feat: add includeObligationStatus parameter to fetchEscrowTransfersV5…
manishdex25 Aug 12, 2026
faeee44
feat: add fetchEndorsementChain function for unified escrow path hand…
manishdex25 Aug 12, 2026
acbd3fe
Update src/core/endorsement-chain/fetchEscrowTransfer.ts
manishdex25 Aug 12, 2026
53e346c
refactor: streamline endorsement chain handling by consolidating publ…
manishdex25 Aug 12, 2026
0411334
feat: enhance obligation handling in endorsement chain by integrating…
manishdex25 Aug 13, 2026
ffb9d3d
chore: update @tradetrust-tt/token-registry-v5 to version 5.6.0-beta.3
manishdex25 Aug 13, 2026
c28a659
fix: remove optional terminationReason from TransferEvent interface
manishdex25 Aug 13, 2026
7bd32c0
refactor: simplify terminationReason handling in endorsement chain ev…
manishdex25 Aug 13, 2026
086e5cc
fix: update owner addresses in endorsement chain test cases
manishdex25 Aug 13, 2026
134d312
fix: correct alias name in CLAUDE.md and update dead address format i…
manishdex25 Aug 13, 2026
b2025b2
Update src/core/endorsement-chain/fetchEscrowTransfer.ts
manishdex25 Aug 13, 2026
ecb5fd7
refactor: remove unused constants and simplify budget handling in fet…
manishdex25 Aug 13, 2026
9a47ed9
Update src/core/endorsement-chain/fetchLogsChunked.ts
manishdex25 Aug 13, 2026
0dbe40d
style: format code for better readability in fetchLogsChunked.ts
manishdex25 Aug 13, 2026
d14425e
refactor: streamline event mapping and log handling
manishdex25 Aug 13, 2026
63c156a
refactor: enhance regex patterns for error handling
manishdex25 Aug 13, 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
33 changes: 33 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Source map (`src/`):
| Path | What |
| --- | --- |
| `src/core/verify.ts` | **`verifyDocument()`** — the unified verify entry point (OA + W3C). |
| `src/core/endorsement-chain/useEndorsementChain.ts` | **`fetchEndorsementChain()`** — unified V4/V5/Obligation escrow path (auto-detects contract type). |
| `src/verify/verify.ts` | `verificationBuilder`, `openAttestationVerifiers`, `w3cVerifiers`. |
| `src/verify/fragments/` | Verifier fragments by dimension: `document-integrity`, `document-status`, `issuer-identity`, `presentation`. |
| `src/w3c/` | The W3C surface: `sign`, `derive`, `verify`, **`presentation`** (VP wrappers), `types`. |
Expand Down Expand Up @@ -103,8 +104,40 @@ so an unsigned VP is still routed in and then judged INVALID by the integrity fr
aligned to both enforce proof-presence + holder binding. If you touch one, keep the other
in step.

## Endorsement chain (`src/core/endorsement-chain/useEndorsementChain.ts`)

**`fetchEndorsementChain()`** is the single public path for Token Registry V4/V5 and
Obligation/BoE titles. It auto-detects the escrow contract via `supportsInterface`
(including `supportInterfaceIdsV5.ObligationEscrow` around the obligation check).

These public aliases were removed:

| Removed | Use instead |
| --- | --- |
| `fetchObligationEndorsementChain` | `fetchEndorsementChain` |
| `fetchEscrowTransfersObligation` | `fetchEscrowTransfersV5` (auto-detects obligation status events) |
| `ObligationEscrowInterface` | `supportInterfaceIdsV5.ObligationEscrow` |

Do **not** re-add the removed aliases. User-facing docs also live in `README.md`
(Obligation Registry section).

## Gotchas (hard-won — add to this list)

- **Endorsement chain has one public path.** See
[Endorsement chain](#endorsement-chain-srccoreendorsement-chainuseendorsementchaints)
— do not re-add `fetchObligationEndorsementChain`, `fetchEscrowTransfersObligation`,
or `ObligationEscrowInterface`.
- **Obligation mint merges to INITIAL.** ObligationEscrow emits `StatusInitialized`
in the same tx as `TokenReceived(isMinting)` (often *before* it in log order).
`mergeTransfersV5` must prefer `INITIAL` so owner/holder/remarks match classic ETR
mint rows. Do not let `STATUS_INITIALIZED` win that merge.
- **eBoE shred last parties + reason come from the contract.** ObligationEscrow
persists `lastBeneficiary` / `lastHolder` in `_deactivate` and emits them on
`Shred` with `TerminationReason`. SDK maps those onto `RETURN_TO_ISSUER_ACCEPTED`
(`owner`/`holder`/`terminationReason`). Do not reconstruct parties from transfer
history as the primary source of truth. Classic ETR shred UI still blanks parties
(no reason field). **ABI break:** redeploy or upgrade obligation registries /
escrow impl before validating against live docs.
- **Selective disclosure keeps the subject `id`.** If a credential was issued *with* a
`credentialSubject.id`, deriving it (even revealing only other fields) **retains that
id**. To test/produce a credential with *no* subject id, it must be issued without one.
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -905,12 +905,19 @@ Escrow calls accept `{ obligationRegistryAddress, tokenId }` or `{ obligationEsc
**Endorsement chain** — pass the `TrustVCToken` address to existing helpers:

```ts
import { fetchEndorsementChain, fetchObligationEndorsementChain } from '@trustvc/trustvc';
import { fetchEndorsementChain } from '@trustvc/trustvc';

const chain = await fetchEndorsementChain(obligationRegistry, tokenId, provider);
// or: fetchObligationEndorsementChain(obligationRegistry, tokenId, provider, { encryptionId })
const chain = await fetchEndorsementChain(obligationRegistry, tokenId, provider, encryptionKeyId);
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Obligation / BoE titles use the same functions as Token Registry V5 (`fetchEndorsementChain` auto-detects `ObligationEscrow`). These public aliases were removed:

| Removed | Use instead |
| --- | --- |
| `fetchObligationEndorsementChain` | `fetchEndorsementChain` |
| `fetchEscrowTransfersObligation` | `fetchEscrowTransfersV5` (auto-detects obligation status events) |
| `ObligationEscrowInterface` | `v5SupportInterfaceIds.ObligationEscrow` |

**Low-level contracts** (`@trustvc/trustvc/token-registry-v5/contracts`):

```ts
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
"@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.6.0-beta.2",
"@tradetrust-tt/token-registry-v5": "npm:@tradetrust-tt/token-registry@^5.6.0-beta.3",
"@tradetrust-tt/tradetrust": "^6.10.3",
"@tradetrust-tt/tt-verify": "^9.7.5",
"@trustvc/document-store": "^1.0.3",
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/e2e/obligation-registry-functions/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
mintObligationRegistry,
} from '../../../obligation-registry-functions';
import type { TransactionOptions } from '../../../obligation-registry-functions/types';
import { getObligationEscrowAddress } from '../../../core';
import { getTitleEscrowAddress } from '../../../core';
import { getSignersV5, getSignersV6, providerV5, providerV6 } from '../fixtures';
import { createSampleBoeTxOptions } from '../fixtures/sample-boe-credential';

Expand Down Expand Up @@ -95,7 +95,7 @@ export const getObligationE2EEscrowAddress = async (
setup: ObligationE2ESetup,
tokenId: string | number,
): Promise<string> => {
return getObligationEscrowAddress(setup.obligationRegistry, String(tokenId), setup.provider, {
return getTitleEscrowAddress(setup.obligationRegistry, String(tokenId), setup.provider, {
titleEscrowVersion: 'v5',
});
};
10 changes: 5 additions & 5 deletions src/__tests__/fixtures/endorsement-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1334,7 +1334,7 @@ export const testCases = [
{
blockNumber: 15068712,
holder: '0xCA93690Bb57EEaB273c796a9309246BC0FB93649',
owner: '0xe0A71284EF59483795053266CB796B65E48B5124',
owner: '0x433097a1C1b8a3e9188d8C54eCC057B1D69f1638',
remark: 'Transfer Holdership',
timestamp: 1732987703000,
transactionHash: '0xadb9231bece27ae3aac4e2483752046014e983b80d54dfc490e3459da451dbfa',
Expand Down Expand Up @@ -1364,7 +1364,7 @@ export const testCases = [
{
blockNumber: 15069476,
holder: '0x433097a1C1b8a3e9188d8C54eCC057B1D69f1638',
owner: '0xe0A71284EF59483795053266CB796B65E48B5124',
owner: '0x433097a1C1b8a3e9188d8C54eCC057B1D69f1638',
remark: 'Transfer Holder',
timestamp: 1732989327000,
transactionHash: '0x2d53578ffe1889dd82eecd5e923dabb06b57eb67a2d16e2c8b210e02b398c5c5',
Expand All @@ -1374,7 +1374,7 @@ export const testCases = [
{
blockNumber: 15069490,
holder: '0x433097a1C1b8a3e9188d8C54eCC057B1D69f1638',
owner: '0xe0A71284EF59483795053266CB796B65E48B5124',
owner: '0x433097a1C1b8a3e9188d8C54eCC057B1D69f1638',
remark: 'Return To Issuer',
timestamp: 1732989357000,
transactionHash: '0x8e575e2a281d3bce5d6e4b6298e1e54b9c49bad0ef135ae9e68fc9d02ccc1ba1',
Expand All @@ -1384,7 +1384,7 @@ export const testCases = [
{
blockNumber: 15069501,
holder: '0x433097a1C1b8a3e9188d8C54eCC057B1D69f1638',
owner: '0xe0A71284EF59483795053266CB796B65E48B5124',
owner: '0x433097a1C1b8a3e9188d8C54eCC057B1D69f1638',
remark: 'Reject Return To Issuer',
timestamp: 1732989379000,
transactionHash: '0x3bf456d1fa29e4b7cfc3cbfc3a568f5c2c4e1dd8454d25f8822eea3b65c66956',
Expand All @@ -1394,7 +1394,7 @@ export const testCases = [
{
blockNumber: 15069511,
holder: '0x433097a1C1b8a3e9188d8C54eCC057B1D69f1638',
owner: '0xe0A71284EF59483795053266CB796B65E48B5124',
owner: '0x433097a1C1b8a3e9188d8C54eCC057B1D69f1638',
remark: 'Return To Issuer',
timestamp: 1732989401000,
transactionHash: '0x99e56c4a1ddcf1a8031402a46eb41b4c41f1379f420287aaa57cad0e18ed85ce',
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/obligation-registry-functions/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ vi.mock('../../utils/ethers', async (importOriginal) => {

vi.mock('../../core', () => ({
encrypt: vi.fn(() => 'encrypted_remarks'),
getObligationEscrowAddress: vi.fn(),
getTitleEscrowAddress: vi.fn(),
checkSupportsInterface: vi.fn(),
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe.each(providers)(
} as unknown as Network);
}

vi.spyOn(coreModule, 'getObligationEscrowAddress').mockResolvedValue(
vi.spyOn(coreModule, 'getTitleEscrowAddress').mockResolvedValue(
MOCK_OBLIGATION_ESCROW_ADDRESS,
);
mockObligationEscrowContract.callStatic.accept.mockResolvedValue(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe.each(providers)(
} as unknown as Network);
}

vi.spyOn(coreModule, 'getObligationEscrowAddress').mockResolvedValue(
vi.spyOn(coreModule, 'getTitleEscrowAddress').mockResolvedValue(
MOCK_OBLIGATION_ESCROW_ADDRESS,
);
mockObligationEscrowContract.callStatic.rejectTransferHolder.mockResolvedValue(true);
Expand Down Expand Up @@ -72,7 +72,7 @@ describe.each(providers)(
);

expect(result).toEqual('reject_transfer_beneficiary_tx_hash');
expect(coreModule.getObligationEscrowAddress).toHaveBeenCalled();
expect(coreModule.getTitleEscrowAddress).toHaveBeenCalled();
});

it('rejectTransferOwnersObligationRegistry without remarks', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe.each(providers)(
} as unknown as Network);
}

vi.spyOn(coreModule, 'getObligationEscrowAddress').mockResolvedValue(
vi.spyOn(coreModule, 'getTitleEscrowAddress').mockResolvedValue(
MOCK_OBLIGATION_ESCROW_ADDRESS,
);
vi.spyOn(coreModule, 'checkSupportsInterface').mockResolvedValue(true);
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/obligation-registry-functions/status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe.each(providers)(
vi.spyOn(Provider, 'getNetwork').mockResolvedValue({ chainId: 1 } as unknown as Network);
}

vi.spyOn(coreModule, 'getObligationEscrowAddress').mockResolvedValue(
vi.spyOn(coreModule, 'getTitleEscrowAddress').mockResolvedValue(
MOCK_OBLIGATION_ESCROW_ADDRESS,
);
mockObligationEscrowContract.status.mockResolvedValue(ObligationDocumentStatus.Accepted);
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/obligation-registry-functions/transfers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe.each(providers)(
} as unknown as Network);
}

vi.spyOn(coreModule, 'getObligationEscrowAddress').mockResolvedValue(
vi.spyOn(coreModule, 'getTitleEscrowAddress').mockResolvedValue(
MOCK_OBLIGATION_ESCROW_ADDRESS,
);
Object.values(mockObligationEscrowContract.callStatic).forEach((fn) =>
Expand Down Expand Up @@ -74,7 +74,7 @@ describe.each(providers)(
);

expect(result).toEqual('transfer_holder_tx_hash');
expect(coreModule.getObligationEscrowAddress).toHaveBeenCalled();
expect(coreModule.getTitleEscrowAddress).toHaveBeenCalled();
});

it('transferBeneficiaryObligationRegistry without remarks', async () => {
Expand Down
47 changes: 47 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,48 @@
export const DEFAULT_KEY = '4d5a4e3f2f6d2b0a1f2e9b8f8a3c7a0b8d4f5c2e7b1a1c3f2e7b8c2d5a4f7e3e';

// Match JSON-RPC / HTTP codes as standalone tokens (not inside block numbers, gas, chain IDs).
const rpcCode = (code: string): string => `(?:^|[^0-9])${code}(?![0-9])`;

export const INFURA_FREE_TIER_RANGE_RE = new RegExp(
`free tier plan|10\\s*block difference|block range should work:\\s*\\[0x0,\\s*0x9\\]|Upgrade to PAYG|${rpcCode('-32600')}`,

Check warning on line 7 in src/constants.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

`String.raw` should be used to avoid escaping `\`.

See more on https://sonarcloud.io/project/issues?id=TrustVC_trustvc&issues=AZ_6nHRxfOdhf-t9QnJq&open=AZ_6nHRxfOdhf-t9QnJq&pullRequest=165
'i',
);
// reponse extracted from infura logs and also alchemy logs
export const RANGE_TOO_LARGE_ERROR_RE = new RegExp(
[
'query returned more than',
'too large',
'block range',
'10,?000 results',
'response size',
'exceeds limit',
'10\\s*block',

Check warning on line 19 in src/constants.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

`String.raw` should be used to avoid escaping `\`.

See more on https://sonarcloud.io/project/issues?id=TrustVC_trustvc&issues=AZ_6nHRxfOdhf-t9QnJr&open=AZ_6nHRxfOdhf-t9QnJr&pullRequest=165
'free tier',
'block difference',
'Upgrade to PAYG',
rpcCode('-32012'),
rpcCode('-32600'),
].join('|'),
'i',
);
// response extracted from infura logs and also alchemy logs
export const RATE_LIMIT_ERROR_RE = new RegExp(
[
`rate-?limit`,
`too many requests`,
`could not coalesce`,
rpcCode('429'),
rpcCode('-32005'),
].join('|'),
'i',
);

export const INITIAL_CHUNK_SIZE = 10_000;
export const FREE_TIER_MAX_CHUNK_SIZE = 10;
export const MIN_CHUNK_SIZE = 1;
export const MAX_CHUNK_SIZE = 50_000;
export const DEFAULT_MAX_BLOCKS_TO_SCAN = 200_000;
export const FREE_TIER_MAX_REQUESTS = 5_000;
export const FREE_TIER_MAX_DURATION_MS = 60_000;
export const RATE_LIMIT_MAX_RETRIES = 3;
export const RATE_LIMIT_BASE_DELAY_MS = 500;
Loading
Loading