Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b63ca01
feat: implement obligation registry functions and enhance document bu…
manishdex25 Jul 30, 2026
da8f982
fix: update provider URL to use environment variable for Amoy RPC
manishdex25 Jul 30, 2026
3dde46f
chore: add AMOY_RPC environment variable for Polygon Amoy RPC integra…
manishdex25 Jul 30, 2026
348df07
Update src/__tests__/e2e/obligation-registry-functions/transfer.e2e.t…
manishdex25 Jul 30, 2026
b855d8f
Update src/obligation-registry-functions/ownerOf.ts
manishdex25 Jul 30, 2026
a4ebe10
Update src/obligation-registry-functions/utils.ts
manishdex25 Jul 30, 2026
994b088
Update src/verify/fragments/document-status/obligationRecords/utils.ts
manishdex25 Jul 30, 2026
8a5af28
refactor: streamline mint and returnToken functions by replacing
manishdex25 Jul 30, 2026
95a931e
fix: improve token ID validation in resolveObligationEscrowAddress fu…
manishdex25 Jul 30, 2026
334f840
refactor: remove redundant variable declaration in ownerOf function
manishdex25 Jul 30, 2026
2650f6e
Update README.md
manishdex25 Jul 30, 2026
70af724
chore: update AMOY_RPC URL in tests and workflows for consistency
manishdex25 Jul 30, 2026
874a461
Merge branch 'feature/boe-v2' of github.com:TrustVC/trustvc into feat…
manishdex25 Jul 30, 2026
4b2e604
docs: update README to reflect changes in obligation registry imports
manishdex25 Jul 30, 2026
abfff65
refactor: simplify obligation registry functions by utilizing createR…
manishdex25 Jul 30, 2026
2f87b30
refactor: enhance null safety and streamline code in documentBuilder …
manishdex25 Jul 30, 2026
cca932c
refactor: make properties readonly in W3CObligationRecordsConfig
manishdex25 Jul 30, 2026
caa4d67
refactor: update chain ID comparison logic and improve error handling…
manishdex25 Jul 30, 2026
02de09f
refactor: add ObligationEscrow support to fetchEscrowTransfers and up…
manishdex25 Jul 30, 2026
b32f679
Update README.md
manishdex25 Jul 30, 2026
c22441a
refactor: update chain ID handling and improve test structure in obli…
manishdex25 Jul 30, 2026
491e732
refactor: enhance obligation registry utility with notMintedReason fu…
manishdex25 Jul 30, 2026
bab0a76
chore: trigger ci
manishdex25 Jul 30, 2026
a4c3c26
feat: add beta-boe prerelease configuration to release settings
manishdex25 Jul 30, 2026
c613ba7
refactor: replace chai assertions with node:assert in obligation regi…
manishdex25 Jul 30, 2026
47e38b9
chore: trigger review
manishdex25 Jul 30, 2026
c63751d
refactor: enhance error handling in obligation records utilities by i…
manishdex25 Jul 30, 2026
696fe71
docs: fix markdown links in README for obligation records section
manishdex25 Jul 30, 2026
0f37c7c
chore: remove beta-boe prerelease configuration from release settings
manishdex25 Jul 31, 2026
cafed21
Merge branch 'beta' of github.com:TrustVC/trustvc into feature/boe-v2
manishdex25 Jul 31, 2026
c8ea6b3
docs: update README to reflect change from ObligationRecords
manishdex25 Jul 31, 2026
d0a17a9
ci: add beta and alpha branches to CI workflow triggers
manishdex25 Jul 31, 2026
e721272
ci: update workflows to use Node.js 20 and enhance npm installation p…
manishdex25 Jul 31, 2026
982abe4
Update src/core/documentBuilder.ts
manishdex25 Jul 31, 2026
687eaf9
Update src/verify/fragments/document-status/obligationRecords/obligat…
manishdex25 Jul 31, 2026
fdb6d21
refactor: adjust type definitions in obligationRecordVerifier to impr…
manishdex25 Jul 31, 2026
812d4f2
ci: streamline npm installation in workflows by removing legacy-peer-…
manishdex25 Jul 31, 2026
63c64c2
ci: remove unnecessary eslint_flags from CI workflow configuration
manishdex25 Jul 31, 2026
645a151
chore: update package dependencies and streamline npm installation in…
manishdex25 Jul 31, 2026
811b19f
chore: update commitlint dependency to version 21.2.0 in workflows
manishdex25 Jul 31, 2026
7a84736
ci: increase Node.js memory limit in publish workflow to improve buil…
manishdex25 Jul 31, 2026
924c02e
Merge branch 'beta' of github.com:TrustVC/trustvc into feature/boe-he…
manishdex25 Jul 31, 2026
53d2969
refactor: simplify obligation record verifier tests by removing unuse…
manishdex25 Jul 31, 2026
d4fad0b
Merge branch 'beta' of github.com:TrustVC/trustvc into feature/boe-fi…
manishdex25 Jul 31, 2026
2e24dd1
fix: build package during prepare for GitHub installs
manishdex25 Jul 31, 2026
c9730e3
docs: update README to clarify token lifecycle and role rules in obli…
manishdex25 Jul 31, 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
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ const fragments = await verifyDocument(signedBoeVc, {
isObligationRecord(signedBoeVc); // true when obligationRegistry is present
```

After on-chain reject, discharge, or `acceptReturnedObligationRegistry`, verify returns **INVALID** (token burned). See [§7c](#c-obligation-registry-boe) for the on-chain SDK.
After on-chain **reject**, **discharge**, or **`acceptReturnedObligationRegistry`**, the token is burned (`0xdEaD`). Verify still treats the title as minted (same as classic ETR shredded titles); the website surfaces “Taken Out of Circulation”. See [§7c](#c-obligation-registry-boe) for the on-chain SDK.

---

Expand Down Expand Up @@ -780,11 +780,23 @@ For more information on Token Registry and Title Escrow contracts **version v5**

**Lifecycle**

Deploy factory + registry → Mint (Issued) → Accept (Accepted) / Reject (Rejected, auto-burn)
→ Discharge (Discharged, auto-burn) | Transfers | Return to issuer → restore / burn
```
1. Deploy factory + obligation registry
2. Mint → status **Issued**
3. Holder **accept** → **Accepted**, or holder **reject** → **Rejected** (burns / takes out of circulation)
4. Optional transfers / nominate / endorse (same pattern as Title Escrow)
5. Beneficiary **discharge** (from **Accepted**) → **Discharged** (burns)

> [!NOTE]
> **Return to issuer** uses the same rules as classic ETR: the caller must be both beneficiary and holder; then the issuer runs **accept-return** (burn) or **reject-return** (restore). Status does **not** need to be Rejected or Discharged first.

**Role rules**

**Role rules:** accept / reject / discharge require `beneficiary != holder`; `returnToIssuerObligationRegistry` requires dual role (`beneficiary == holder`).
| Action | Who |
|--------|-----|
| `accept` / `reject` | Holder, with `beneficiary != holder` |
| `discharge` | Beneficiary, with `beneficiary != holder` |
| `returnToIssuerObligationRegistry` | Dual role (`beneficiary == holder`) — same as classic ETR `returnToIssuer` |
| `acceptReturnedObligationRegistry` / `rejectReturnedObligationRegistry` | Issuer (registry accepter / restorer roles) |

**Status enums** (from `@trustvc/trustvc`):

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"build": "npm run clean && tsup",
"clean": "rm -rf dist/",
"precommit": "lint-staged",
"prepare": "husky",
"prepare": "npm run build && husky",
"release": "semantic-release --parallel=1 --repositoryUrl=https://github.com/TrustVC/trustvc.git --verbose"
},
"keywords": [
Expand Down
44 changes: 4 additions & 40 deletions src/__tests__/verify/obligationRecordVerifier.utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,13 @@ import { OpenAttestationEthereumTokenRegistryStatusCode } from '@tradetrust-tt/t
import { isTokenMintedOnObligationRegistry } from '../../verify/fragments/document-status/obligationRecords/utils';

const mockOwnerOf = vi.fn();
const mockActive = vi.fn();
const mockIsHoldingToken = vi.fn();
const mockGetObligationEscrowAddress = vi.fn();

vi.mock('@tradetrust-tt/token-registry-v5/contracts', () => ({
TrustVCToken__factory: {
connect: vi.fn(() => ({
ownerOf: mockOwnerOf,
})),
},
ObligationEscrow__factory: {
connect: vi.fn(() => ({
active: mockActive,
isHoldingToken: mockIsHoldingToken,
})),
},
}));

vi.mock('../../core/endorsement-chain/obligation', () => ({
getObligationEscrowAddress: (...args: unknown[]) => mockGetObligationEscrowAddress(...args),
}));

describe('isTokenMintedOnObligationRegistry', () => {
Expand All @@ -35,13 +22,10 @@ describe('isTokenMintedOnObligationRegistry', () => {

beforeEach(() => {
vi.clearAllMocks();
mockGetObligationEscrowAddress.mockResolvedValue('0xEscrow');
mockOwnerOf.mockResolvedValue('0xEscrow');
mockActive.mockResolvedValue(true);
mockIsHoldingToken.mockResolvedValue(true);
});

it('returns minted when owner is set and title is live', async () => {
it('returns minted when owner is set (active title)', async () => {
const result = await isTokenMintedOnObligationRegistry({
obligationRegistryAddress,
tokenId,
Expand All @@ -52,8 +36,8 @@ describe('isTokenMintedOnObligationRegistry', () => {
expect(result).toEqual({ minted: true, address: obligationRegistryAddress });
});

it('returns not minted when title is inactive or not holding', async () => {
mockIsHoldingToken.mockResolvedValue(false);
it('returns minted when owner is burn address (shredded, same as classic ETR)', async () => {
mockOwnerOf.mockResolvedValue('0x000000000000000000000000000000000000dEaD');

const result = await isTokenMintedOnObligationRegistry({
obligationRegistryAddress,
Expand All @@ -62,13 +46,7 @@ describe('isTokenMintedOnObligationRegistry', () => {
chainId: 80002,
});

expect(result.minted).toBe(false);
expect(result).toMatchObject({
reason: {
code: OpenAttestationEthereumTokenRegistryStatusCode.DOCUMENT_NOT_MINTED,
message: expect.stringContaining('title is not active'),
},
});
expect(result).toEqual({ minted: true, address: obligationRegistryAddress });
});

it('returns not minted when owner is zero address', async () => {
Expand Down Expand Up @@ -104,19 +82,5 @@ describe('isTokenMintedOnObligationRegistry', () => {
message: 'Document has not been issued under token registry',
},
});
expect(mockGetObligationEscrowAddress).not.toHaveBeenCalled();
});

it('rethrows failures from escrow address / active / isHoldingToken calls', async () => {
mockGetObligationEscrowAddress.mockRejectedValue(new Error('RPC failed resolving escrow'));

await expect(
isTokenMintedOnObligationRegistry({
obligationRegistryAddress,
tokenId,
provider,
chainId: 80002,
}),
).rejects.toThrow('RPC failed resolving escrow');
});
});
31 changes: 4 additions & 27 deletions src/verify/fragments/document-status/obligationRecords/utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import {
ObligationEscrow__factory,
TrustVCToken__factory,
} from '@tradetrust-tt/token-registry-v5/contracts';
import { TrustVCToken__factory } from '@tradetrust-tt/token-registry-v5/contracts';
import {
InvalidTokenRegistryStatus,
OpenAttestationEthereumTokenRegistryStatusCode,
ValidTokenRegistryStatus,
} from '@tradetrust-tt/tt-verify';
import { constants, providers } from 'ethers';
import { getObligationEscrowAddress } from '../../../../core/endorsement-chain/obligation';
import { decodeError, type EthersError } from '../transferableRecords/utils';

const notMintedReason = (
Expand All @@ -30,6 +26,7 @@ const notMintedReason = (
},
});

// Same minted semantics as classic ETR: ownerOf !== AddressZero (includes burn 0xdEaD).
export const isTokenMintedOnObligationRegistry = async ({
obligationRegistryAddress,
tokenId,
Expand Down Expand Up @@ -75,32 +72,12 @@ export const isTokenMintedOnObligationRegistry = async ({
if (!minted) {
return notMintedReason(obligationRegistryAddress, tokenId);
}

return { minted: true, address: obligationRegistryAddress };
} catch (error: unknown) {
// Only ownerOf absence / registry miss maps to DOCUMENT_NOT_MINTED.
// CodedError (e.g. SERVER_ERROR) and unexpected reverts from decodeError propagate.
const ethersError = error as EthersError;
return notMintedReason(obligationRegistryAddress, tokenId, decodeError(ethersError));
}

const obligationEscrowAddress = await getObligationEscrowAddress(
obligationRegistryAddress,
tokenId,
provider,
{ titleEscrowVersion: 'v5' },
);
const obligationEscrow = ObligationEscrow__factory.connect(obligationEscrowAddress, provider);
const [active, isHoldingToken] = await Promise.all([
obligationEscrow.active(),
obligationEscrow.isHoldingToken(),
]);

if (!active || !isHoldingToken) {
return notMintedReason(
obligationRegistryAddress,
tokenId,
`Document ${tokenId} title is not active under contract ${obligationRegistryAddress}`,
);
}

return { minted: true, address: obligationRegistryAddress };
};
Loading