Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
uses: ./.github/workflows/tests.yml
secrets:
ANKR_API_KEY: ${{ secrets.ANKR_API_KEY }}
POLYGONSCAN_API_KEY: ${{ secrets.POLYGONSCAN_API_KEY }}

linters:
name: Linters
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ on:
secrets:
ANKR_API_KEY:
required: true
POLYGONSCAN_API_KEY:
required: true

env:
NODE_ENV: ci
Expand Down Expand Up @@ -34,6 +36,7 @@ jobs:
- run: npm run test
env:
ANKR_API_KEY: ${{ secrets.ANKR_API_KEY }}
POLYGONSCAN_API_KEY: ${{ secrets.POLYGONSCAN_API_KEY }}

test-build:
name: Test Build
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ testem.log
.DS_Store
Thumbs.db
.env
#local yalc configuration for help build and test local environment
.yalc/
yalc.lock
9 changes: 4 additions & 5 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@trustvc/trustvc",
"version": "2.13.0",
"version": "2.14.0",
"description": "TrustVC library",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down Expand Up @@ -120,7 +120,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.5.0",
"@tradetrust-tt/tradetrust": "^6.10.3",
"@tradetrust-tt/tt-verify": "^9.7.4",
"@tradetrust-tt/tt-verify": "^9.7.5",
"@trustvc/document-store": "^1.0.3",
"@trustvc/w3c": "^2.2.0",
"@trustvc/w3c-context": "^2.2.0",
Expand Down
51 changes: 51 additions & 0 deletions src/__tests__/core/verify.amoy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, it, expect } from 'vitest';
import { CHAIN_ID, SUPPORTED_CHAINS } from '../../utils/supportedChains';

import amoyOaTokenRegistryMinted from '../fixtures/amoy-oa-token-registry-minted.json';
import amoyW3cTransferableRecordMinted from '../fixtures/amoy-w3c-transferable-record-minted.json';
import {
isMintedFixtureReady,
oaTokenRegistryMintedTests,
w3cTransferableRecordMintedTests,
} from './verify.polygon-network.helpers';

const AMOY_RPC_URL = process.env.AMOY_RPC || 'https://rpc-amoy.polygon.technology/';

describe('Polygon Amoy (testnet) network support', () => {
describe('CHAIN_ID and SUPPORTED_CHAINS', () => {
it('CHAIN_ID.amoy should equal chain ID 80002', () => {
expect(CHAIN_ID.amoy).toBe('80002');
});

it('SUPPORTED_CHAINS[CHAIN_ID.amoy] should have currency POL', () => {
expect(SUPPORTED_CHAINS[CHAIN_ID.amoy].currency).toBe('POL');
});

it('SUPPORTED_CHAINS[CHAIN_ID.amoy] should have name amoy', () => {
expect(SUPPORTED_CHAINS[CHAIN_ID.amoy].name).toBe('amoy');
});
});

describe.skipIf(!isMintedFixtureReady(amoyOaTokenRegistryMinted))(
'amoy-oa-token-registry-minted',
() => {
oaTokenRegistryMintedTests({
fixture: amoyOaTokenRegistryMinted,
rpcUrl: AMOY_RPC_URL,
expectedNetworkChainId: '80002',
includeSignatureTamperTest: true,
});
},
);

describe.skipIf(!isMintedFixtureReady(amoyW3cTransferableRecordMinted))(
'amoy-w3c-transferable-record-minted',
() => {
w3cTransferableRecordMintedTests({
fixture: amoyW3cTransferableRecordMinted,
rpcUrl: AMOY_RPC_URL,
chainId: 80002,
});
},
);
});
171 changes: 171 additions & 0 deletions src/__tests__/core/verify.pol.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { describe, it, expect } from 'vitest';
import { verifyDocument } from '../../core/verify';
import { W3C_TRANSFERABLE_RECORD_POL } from '../fixtures/fixtures';
import { CHAIN_ID, SUPPORTED_CHAINS } from '../../utils/supportedChains';

import polW3cTransferableRecordMinted from '../fixtures/pol-w3c-transferable-record-minted.json';
import polOaTokenRegistryMinted from '../fixtures/pol-oa-token-registry-minted.json';
import polW3cVerifiableDocument from '../fixtures/pol-w3c-verifiable-document.json';
import {
expectTransferableRecordError,
isMintedFixtureReady,
oaTokenRegistryMintedTests,
w3cTransferableRecordMintedTests,
} from './verify.polygon-network.helpers';

const POL_RPC_URL = process.env.POL_RPC || 'https://polygon-bor-rpc.publicnode.com';

describe('Polygon (POL) network support', () => {
describe('CHAIN_ID and SUPPORTED_CHAINS', () => {
it('CHAIN_ID.pol should equal chain ID 137', () => {
expect(CHAIN_ID.pol).toBe('137');
});

it('CHAIN_ID.matic (backward-compat alias) should also equal 137', () => {
expect(CHAIN_ID.matic).toBe('137');
});

it('SUPPORTED_CHAINS[CHAIN_ID.pol] should have currency POL', () => {
expect(SUPPORTED_CHAINS[CHAIN_ID.pol].currency).toBe('POL');
});

it('SUPPORTED_CHAINS[CHAIN_ID.pol] and SUPPORTED_CHAINS[CHAIN_ID.matic] should be the same object', () => {
expect(SUPPORTED_CHAINS[CHAIN_ID.pol]).toBe(SUPPORTED_CHAINS[CHAIN_ID.matic]);
});
});

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);
});

it('should have a DataIntegrityProof with ecdsa-sd-2023 cryptosuite', () => {
expect(W3C_TRANSFERABLE_RECORD_POL.proof.type).toBe('DataIntegrityProof');
expect(W3C_TRANSFERABLE_RECORD_POL.proof.cryptosuite).toBe('ecdsa-sd-2023');
});

it('issuer should be did:web:didhost.vercel.app', () => {
expect(W3C_TRANSFERABLE_RECORD_POL.issuer).toBe('did:web:didhost.vercel.app');
});
});

describe('W3C_TRANSFERABLE_RECORD_POL — POL network routing', () => {
it(
'verifyDocument should return fragments for a POL credential',
{ timeout: 30000 },
async () => {
const fragments = await verifyDocument(W3C_TRANSFERABLE_RECORD_POL as any);
const names = fragments.map((f) => f.name);
expect(names).toContain('EcdsaW3CSignatureIntegrity');
expect(names).toContain('W3CCredentialStatus');
expect(names).toContain('W3CIssuerIdentity');
},
);

it(
'should reach Polygon mainnet (chain 137) for DOCUMENT_STATUS check',
{ timeout: 300000 },
async () => {
const fragments = await verifyDocument(W3C_TRANSFERABLE_RECORD_POL as any, {
rpcProviderUrl: POL_RPC_URL,
});
const statusFragment = fragments.find((f) => f.name === 'W3CCredentialStatus');
expect(statusFragment).toBeDefined();
expect(statusFragment?.status).not.toBe('ERROR');
},
);

it.each([
{
label: 'tokenRegistry is missing',
credentialStatus: {
...W3C_TRANSFERABLE_RECORD_POL.credentialStatus,
tokenRegistry: '',
},
},
{
label: 'tokenNetwork.chainId is missing',
credentialStatus: {
...W3C_TRANSFERABLE_RECORD_POL.credentialStatus,
tokenNetwork: { chain: 'POL', chainId: '' },
},
},
])('should return ERROR when $label', async ({ credentialStatus }) => {
const tampered: any = {
...W3C_TRANSFERABLE_RECORD_POL,
credentialStatus,
};
expectTransferableRecordError(await verifyDocument(tampered));
});

it('should return INVALID for DOCUMENT_INTEGRITY when proof is tampered', async () => {
const tampered: any = {
...W3C_TRANSFERABLE_RECORD_POL,
proof: { ...W3C_TRANSFERABLE_RECORD_POL.proof, proofValue: 'u2V0AhVhAINVALIDPROOF' },
};
const fragments = await verifyDocument(tampered);
expect(fragments).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'EcdsaW3CSignatureIntegrity', status: 'INVALID' }),
]),
);
});
});

describe.skipIf(!isMintedFixtureReady(polW3cTransferableRecordMinted))(
'pol-w3c-transferable-record-minted',
() => {
w3cTransferableRecordMintedTests({
fixture: polW3cTransferableRecordMinted,
rpcUrl: POL_RPC_URL,
chainId: 137,
});
},
);

describe.skipIf(!isMintedFixtureReady(polOaTokenRegistryMinted))(
'pol-oa-token-registry-minted',
() => {
oaTokenRegistryMintedTests({
fixture: polOaTokenRegistryMinted,
rpcUrl: POL_RPC_URL,
});
},
);

describe.skipIf(!isMintedFixtureReady(polW3cVerifiableDocument))(
'pol-w3c-verifiable-document — structural (offline)',
() => {
it('all verifier types should produce fragments', async () => {
const fragments = await verifyDocument(polW3cVerifiableDocument as any);
expect(fragments.map((f) => f.name)).toContain('W3CIssuerIdentity');
});

it('W3CCredentialStatus should be SKIPPED and W3CEmptyCredentialStatus VALID when no credentialStatus', async () => {
const doc: any = { ...polW3cVerifiableDocument };
delete doc.credentialStatus;
const fragments = await verifyDocument(doc);
const statusFragments = fragments.filter((f) => f.type === 'DOCUMENT_STATUS');
expect(statusFragments.find((f) => f.name === 'W3CCredentialStatus')?.status).toBe(
'SKIPPED',
);
expect(statusFragments.find((f) => f.name === 'W3CEmptyCredentialStatus')?.status).toBe(
'VALID',
);
expect(statusFragments.every((f) => f.status !== 'INVALID')).toBe(true);
});

it('should return INVALID for DOCUMENT_INTEGRITY when proof is tampered', async () => {
const doc = polW3cVerifiableDocument as any;
if (!doc.proof) return;
const tampered: any = { ...doc, proof: { ...doc.proof, proofValue: 'uINVALID' } };
const fragments = await verifyDocument(tampered);
const integrityFragment = fragments.find(
(f) => f.type === 'DOCUMENT_INTEGRITY' && f.status !== 'SKIPPED',
);
expect(integrityFragment?.status).toBe('INVALID');
});
},
);
});
Loading
Loading