diff --git a/packages/sdk/src/index-dtf/protocol/index.test.ts b/packages/sdk/src/index-dtf/protocol/index.test.ts index f6e6ad2..71c5a20 100644 --- a/packages/sdk/src/index-dtf/protocol/index.test.ts +++ b/packages/sdk/src/index-dtf/protocol/index.test.ts @@ -1,107 +1,13 @@ import { indexDtfs as indexDtfCatalog } from "@reserve-protocol/dtf-catalog"; +import { isAddress } from "viem"; import { describe, expect, it } from "vitest"; import { createDtfClient } from "@/client"; import { getIndexDtfCatalogEntries, listIndexDtfs, resolveIndexDtfAlias } from "@/index-dtf/protocol/index"; -const NEW_INDEX_DTF_DEPLOYMENTS = [ - { - address: "0x5039ece83dc4e0621ebec391128339bd859a84d0", - chainId: 1, - name: "Reserve Photonics DTF (ETH)", - symbol: "PHOTON", - }, - { - address: "0xa0fe4e0aeca5479705ce996615b2eacb6b6a10fb", - chainId: 56, - name: "Reserve Photonics DTF (BSC)", - symbol: "PHOTON", - }, - { - address: "0x1ec1d815488936ec8add5cb76ac4563ceef09de3", - chainId: 1, - name: "Reserve AI Infrastructure DTF (ETH)", - symbol: "BUILDOUT", - }, - { - address: "0xd7ce7a841310982acd976d1a6fe7bb6063c5689d", - chainId: 56, - name: "Reserve AI Infrastructure DTF (BSC)", - symbol: "BUILDOUT", - }, - { - address: "0x09A823930FAB5b1FdA6e519b1EE33e7DA9bdA0E5", - chainId: 1, - name: "Reserve Robotics DTF (ETH)", - symbol: "ROBOTS", - }, - { - address: "0x75617e7653f86f074cc30b9fd4ebf52ba9b62247", - chainId: 56, - name: "Reserve Robotics DTF (BSC)", - symbol: "ROBOTS", - }, - { - address: "0x3CE752A0EB838084562c9d7A0e1dF24A8aE9542D", - chainId: 1, - name: "Reserve AI Power DTF (ETH)", - symbol: "POWER", - }, - { - address: "0x290bCc0Fd5096cC3261AE2021841c7BC67Cb0f51", - chainId: 56, - name: "Reserve AI Power DTF (BSC)", - symbol: "POWER", - }, - { - address: "0x9429a7332B5a3bCDE2781b65aC1A9EBd9f466e12", - chainId: 1, - name: "Reserve AI NeoCloud DTF (ETH)", - symbol: "NEOCLOUD", - }, - { - address: "0xf571Fe3F0d74521Bc7310B111Faea931C748f27B", - chainId: 56, - name: "Reserve AI NeoCloud DTF (BSC)", - symbol: "NEOCLOUD", - }, - { - address: "0xB394C76A9404b7Eb65841e1551952B97AAE7FD5C", - chainId: 1, - name: "Reserve AI Energy Generation DTF (ETH)", - symbol: "ENERGY", - }, - { - address: "0xdBd6bc5e04B5Fd627E0AA67740b06537800fE7DB", - chainId: 56, - name: "Reserve AI Energy Generation DTF (BSC)", - symbol: "ENERGY", - }, - { - address: "0x9beFcdDdC831198d5805c1690a2Fd3Bbd587845E", - chainId: 1, - name: "Reserve Quantum DTF (ETH)", - symbol: "QUANTUM", - }, - { - address: "0xf51E0e1Fdd6EA5967a4Ea72fBdAA37C1e3Ba1369", - chainId: 56, - name: "Reserve Quantum DTF (BSC)", - symbol: "QUANTUM", - }, - { - address: "0x8fc660a20b55BE94E7Ded6f47f8e17a2c7813383", - chainId: 1, - name: "Reserve AI Compute DTF (ETH)", - symbol: "COMPUTE", - }, - { - address: "0xc561439bd5b6a279f61EA2F8a3f0d25D70ff57ad", - chainId: 56, - name: "Reserve AI Compute DTF (BSC)", - symbol: "COMPUTE", - }, -] as const; +const INDEX_DTF_STATUSES = new Set(["active", "unsupported", "deprecated"]); + +type IndexDtfCatalogTestEntry = ReturnType[number]; describe("Index DTF catalog list", () => { const client = createDtfClient(); @@ -140,54 +46,80 @@ describe("Index DTF catalog list", () => { expect(resolveIndexDtfAlias({ input: "not a real dtf" })).toBeNull(); }); - it("includes the new Ethereum and BSC Index DTF deployments", () => { - const dtfs = getIndexDtfCatalogEntries(); - - for (const deployment of NEW_INDEX_DTF_DEPLOYMENTS) { - const dtf = dtfs.find( - (entry) => - entry.chainId === deployment.chainId && entry.address.toLowerCase() === deployment.address.toLowerCase(), - ); - - expect(dtf).toMatchObject({ - decimals: 18, - logo: "default.webp", - name: deployment.name, - status: "active", - symbol: deployment.symbol, - }); + it("keeps catalog JSON entries structurally valid", () => { + for (const [chainId, dtfs] of Object.entries(indexDtfCatalog)) { + for (const [address, dtf] of Object.entries(dtfs)) { + expect(isAddress(address)).toBe(true); + expect(isAddress(dtf.address)).toBe(true); + expect(dtf.address.toLowerCase()).toBe(address.toLowerCase()); + expect(dtf.chainId).toBe(Number(chainId)); + expect(Number.isInteger(dtf.decimals)).toBe(true); + expect(dtf.decimals).toBeGreaterThanOrEqual(0); + expect(Number.isInteger(dtf.createdAt)).toBe(true); + expect(dtf.createdAt).toBeGreaterThan(0); + expect(dtf.name.trim()).toBe(dtf.name); + expect(dtf.name.length).toBeGreaterThan(0); + expect(dtf.symbol.trim()).toBe(dtf.symbol); + expect(dtf.symbol.length).toBeGreaterThan(0); + expect(INDEX_DTF_STATUSES.has(dtf.status)).toBe(true); + + if (dtf.logo !== undefined) { + expect(dtf.logo.trim()).toBe(dtf.logo); + expect(dtf.logo.length).toBeGreaterThan(0); + } + if (dtf.tags !== undefined) { + expect(dtf.tags.every((tag) => tag.trim() === tag && tag.length > 0)).toBe(true); + } + } } }); - it("keeps new catalog addresses exactly as pasted", () => { - for (const deployment of NEW_INDEX_DTF_DEPLOYMENTS) { - const rawDtf = indexDtfCatalog[deployment.chainId][deployment.address]; + it("resolves duplicate symbols when scoped to a chain", () => { + const [first, second] = getDuplicateSymbolEntries(); - expect(rawDtf?.address).toBe(deployment.address); + for (const dtf of [first, second]) { + expect(resolveIndexDtfAlias({ input: dtf.symbol.toLowerCase(), chainId: dtf.chainId })).toMatchObject({ + address: dtf.address, + chainId: dtf.chainId, + name: dtf.name, + symbol: dtf.symbol, + }); } }); - it("resolves new duplicate symbols when scoped to a chain", () => { - expect(resolveIndexDtfAlias({ input: "compute", chainId: 1, status: "active" })).toMatchObject({ - chainId: 1, - name: "Reserve AI Compute DTF (ETH)", - symbol: "COMPUTE", - }); - expect(resolveIndexDtfAlias({ input: "PHOTON", chainId: 56, status: "active" })).toMatchObject({ - chainId: 56, - name: "Reserve Photonics DTF (BSC)", - symbol: "PHOTON", - }); - }); - it("returns ambiguous matches for duplicate symbols without a chain", () => { - const result = resolveIndexDtfAlias({ input: "compute", status: "active" }); - - expect(result).toMatchObject({ - ambiguous: [ - { chainId: 1, symbol: "COMPUTE" }, - { chainId: 56, symbol: "COMPUTE" }, - ], + const duplicateEntries = getDuplicateSymbolEntries(); + const result = resolveIndexDtfAlias({ input: duplicateEntries[0]!.symbol }); + + expect(result).toEqual({ + ambiguous: duplicateEntries.map((dtf) => ({ + address: dtf.address, + chainId: dtf.chainId, + name: dtf.name, + symbol: dtf.symbol, + status: dtf.status, + })), }); }); }); + +function getDuplicateSymbolEntries(): readonly [ + IndexDtfCatalogTestEntry, + IndexDtfCatalogTestEntry, + ...IndexDtfCatalogTestEntry[], +] { + const entriesBySymbol = new Map(); + + for (const dtf of getIndexDtfCatalogEntries()) { + const entries = entriesBySymbol.get(dtf.symbol.toLowerCase()) ?? []; + entries.push(dtf); + entriesBySymbol.set(dtf.symbol.toLowerCase(), entries); + } + + const duplicateEntries = [...entriesBySymbol.values()].find((entries) => entries.length > 1); + if (!duplicateEntries) { + throw new Error("Expected the catalog to include at least one duplicate symbol"); + } + + return duplicateEntries as [IndexDtfCatalogTestEntry, IndexDtfCatalogTestEntry, ...IndexDtfCatalogTestEntry[]]; +}