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
20 changes: 16 additions & 4 deletions dataService.js
Original file line number Diff line number Diff line change
Expand Up @@ -1039,10 +1039,22 @@ export function getAllChains() {
*/
export function countChainsByTag(chains) {
const totalChains = chains.length;
const totalTestnets = chains.filter(c => c.tags?.includes('Testnet')).length;
const totalL2s = chains.filter(c => c.tags?.includes('L2')).length;
const totalBeacons = chains.filter(c => c.tags?.includes('Beacon')).length;
const totalMainnets = chains.filter(c => !c.tags?.includes('Testnet') && !c.tags?.includes('L2') && !c.tags?.includes('Beacon')).length;
let totalTestnets = 0;
let totalL2s = 0;
let totalBeacons = 0;
let totalMainnets = 0;

for (const chain of chains) {
const tags = chain.tags || [];
const isTestnet = tags.includes('Testnet');
const isL2 = tags.includes('L2');
const isBeacon = tags.includes('Beacon');

if (isTestnet) totalTestnets += 1;
if (isL2) totalL2s += 1;
if (isBeacon) totalBeacons += 1;
if (!isTestnet && !isL2 && !isBeacon) totalMainnets += 1;
}

return { totalChains, totalMainnets, totalTestnets, totalL2s, totalBeacons };
}
Expand Down
8 changes: 4 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ export async function buildApp(options = {}) {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://unpkg.com"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
connectSrc: ["'self'", "https://raw.githubusercontent.com"],
scriptSrc: ["'self'"],
styleSrc: ["'self'"],
fontSrc: ["'self'"],
connectSrc: ["'self'"],
imgSrc: ["'self'", "data:"]
}
}
Expand Down
9 changes: 3 additions & 6 deletions package-lock.json

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

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
"url": "https://github.com/Johnaverse/chains-api/issues"
},
"homepage": "https://github.com/Johnaverse/chains-api#readme",
"engines": {
"node": ">=20"
},
"dependencies": {
"@fastify/cors": "^11.2.0",
"@fastify/helmet": "^13.0.2",
Expand Down
5 changes: 5 additions & 0 deletions public/3d-force-graph.min.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,13 @@ function initUI() {

async function fetchData() {
try {
// Try local API first (/export), fall back to GitHub raw
// Try local API first (/export), fall back to bundled export.json
let res;
try {
res = await fetch('/export');
if (!res.ok) throw new Error('Local export unavailable');
} catch {
res = await fetch('https://raw.githubusercontent.com/Johnaverse/chains-api/refs/heads/main/public/export.json');
res = await fetch('export.json');
}
const exportData = await res.json();

Expand Down
Binary file added public/fonts/inter-300.ttf
Binary file not shown.
Binary file added public/fonts/inter-400.ttf
Binary file not shown.
Binary file added public/fonts/inter-500.ttf
Binary file not shown.
Binary file added public/fonts/inter-600.ttf
Binary file not shown.
Binary file added public/fonts/inter-700.ttf
Binary file not shown.
35 changes: 35 additions & 0 deletions public/fonts/inter.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(inter-300.ttf) format('truetype');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(inter-400.ttf) format('truetype');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(inter-500.ttf) format('truetype');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(inter-600.ttf) format('truetype');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(inter-700.ttf) format('truetype');
}
10 changes: 4 additions & 6 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@
<title>Blockchain Networks Relationships</title>
<meta name="description" content="Interactive 3D visualization of blockchain network relationships, L2s, testnets, and beacon chains.">

<!-- 3D Force Graph Library -->
<script src="https://unpkg.com/3d-force-graph@1.79.1" integrity="sha384-rPBTChNmfoK7+PgrfhGgo2vR12+Pc+e4lg2YZYKUyXi2HLVWpzesq43bHEnnuFTl" crossorigin="anonymous"></script>
<!-- 3D Force Graph Library (served locally) -->
<script src="3d-force-graph.min.js"></script>

<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Inter font (served locally) -->
<link rel="stylesheet" href="fonts/inter.css">

<link rel="stylesheet" href="style.css">
</head>
Expand Down
81 changes: 80 additions & 1 deletion tests/unit/dataService.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,8 @@ import {
runRpcHealthCheck,
startRpcHealthCheck,
validateChainData,
traverseRelations
traverseRelations,
countChainsByTag
} from '../../dataService.js';

describe('fetchData', () => {
Expand Down Expand Up @@ -2743,3 +2744,81 @@ describe('traverseRelations', () => {
}
});
});

describe('countChainsByTag', () => {
it('should return all zeros for an empty array', () => {
const result = countChainsByTag([]);
expect(result).toEqual({ totalChains: 0, totalMainnets: 0, totalTestnets: 0, totalL2s: 0, totalBeacons: 0 });
});

it('should count chains with no tags as mainnets', () => {
const chains = [{ chainId: 1, name: 'Ethereum' }, { chainId: 56, name: 'BSC' }];
const result = countChainsByTag(chains);
expect(result.totalChains).toBe(2);
expect(result.totalMainnets).toBe(2);
expect(result.totalTestnets).toBe(0);
expect(result.totalL2s).toBe(0);
expect(result.totalBeacons).toBe(0);
});

it('should count Testnet-tagged chains correctly', () => {
const chains = [
{ chainId: 1, name: 'Ethereum', tags: [] },
{ chainId: 5, name: 'Goerli', tags: ['Testnet'] },
{ chainId: 11155111, name: 'Sepolia', tags: ['Testnet'] }
];
const result = countChainsByTag(chains);
expect(result.totalChains).toBe(3);
expect(result.totalTestnets).toBe(2);
expect(result.totalMainnets).toBe(1);
});

it('should count L2-tagged chains correctly and exclude them from mainnets', () => {
const chains = [
{ chainId: 1, name: 'Ethereum', tags: [] },
{ chainId: 10, name: 'Optimism', tags: ['L2'] },
{ chainId: 42161, name: 'Arbitrum One', tags: ['L2'] }
];
const result = countChainsByTag(chains);
expect(result.totalL2s).toBe(2);
expect(result.totalMainnets).toBe(1);
});

it('should count Beacon-tagged chains correctly and exclude them from mainnets', () => {
const chains = [
{ chainId: 1, name: 'Ethereum', tags: [] },
{ chainId: 9999, name: 'Beacon Chain', tags: ['Beacon'] }
];
const result = countChainsByTag(chains);
expect(result.totalBeacons).toBe(1);
expect(result.totalMainnets).toBe(1);
});

it('should handle chains with mixed tags (Testnet + L2)', () => {
const chains = [
{ chainId: 1, name: 'Ethereum', tags: [] },
{ chainId: 420, name: 'Optimism Goerli', tags: ['Testnet', 'L2'] }
];
const result = countChainsByTag(chains);
expect(result.totalChains).toBe(2);
expect(result.totalTestnets).toBe(1);
expect(result.totalL2s).toBe(1);
expect(result.totalMainnets).toBe(1);
});

it('should correctly total all categories in a mixed array', () => {
const chains = [
{ chainId: 1, name: 'Ethereum', tags: [] },
{ chainId: 5, name: 'Goerli', tags: ['Testnet'] },
{ chainId: 10, name: 'Optimism', tags: ['L2'] },
{ chainId: 9999, name: 'Beacon', tags: ['Beacon'] },
{ chainId: 420, name: 'OP Goerli', tags: ['Testnet', 'L2'] }
];
const result = countChainsByTag(chains);
expect(result.totalChains).toBe(5);
expect(result.totalMainnets).toBe(1);
expect(result.totalTestnets).toBe(2);
expect(result.totalL2s).toBe(2);
expect(result.totalBeacons).toBe(1);
});
});