fix(token-aggregation): emit Uniswap-schema token lists for mainnet cWUSDC
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m35s
CI/CD Pipeline / Security Scanning (push) Successful in 2m58s
CI/CD Pipeline / Lint and Format (push) Failing after 40s
CI/CD Pipeline / Terraform Validation (push) Failing after 24s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 25s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 56s
Validation / validate-genesis (push) Successful in 32s
Validation / validate-terraform (push) Failing after 30s
Validation / validate-kubernetes (push) Failing after 10s
Validation / validate-smart-contracts (push) Failing after 10s
Validation / validate-security (push) Failing after 1m23s
Validation / validate-documentation (push) Failing after 17s
Verify Deployment / Verify Deployment (push) Failing after 1m0s

Normalize /report/token-list to checksummed addresses, version objects,
and extensions bag; add pinned static ethereum-mainnet list endpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-06-06 10:54:12 -07:00
parent 0d08d4e11f
commit b6ddd236e2
6 changed files with 486 additions and 69 deletions

View File

@@ -0,0 +1,104 @@
{
"name": "DBIS Ethereum Mainnet GRU",
"version": {
"major": 1,
"minor": 1,
"patch": 0
},
"timestamp": "2026-06-06T00:00:00.000Z",
"logoURI": "https://d-bis.org/tokens/cwusdc.svg",
"keywords": [
"ethereum",
"mainnet",
"stablecoin",
"gru",
"dbis",
"cwusdc"
],
"tokens": [
{
"chainId": 1,
"address": "0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a",
"name": "Wrapped cUSDC",
"symbol": "cWUSDC",
"decimals": 6,
"logoURI": "https://d-bis.org/tokens/cwusdc.svg",
"tags": [
"stablecoin",
"defi",
"compliant",
"fiat",
"cash",
"gru",
"wrapped"
],
"extensions": {
"category": "tokenized-fiat",
"instrument": "emoney-wrapped-transport",
"currency": "USD",
"settlement": "fiat",
"cashLike": true,
"backing": "cash,cash-equivalents,gru-reserve-policy",
"gruVersion": "v1",
"gruFamily": "cUSDC",
"canonicalSourceChainId": 138,
"canonicalSourceAddress": "0xf22258f57794CC8E06237084b353Ab30fFfa640b"
}
},
{
"chainId": 1,
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"name": "Tether USD",
"symbol": "USDT",
"decimals": 6,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
"tags": [
"stablecoin",
"defi",
"fiat",
"cash"
],
"extensions": {
"category": "tokenized-fiat",
"instrument": "fiat-backed-stablecoin",
"currency": "USD",
"settlement": "fiat",
"cashLike": true,
"backing": "cash,cash-equivalents",
"x402Ready": false,
"fwdCanon": false,
"walletClass": "cash-like-token"
}
}
],
"tags": {
"stablecoin": {
"name": "Stablecoin",
"description": "Stable value tokens pegged to fiat"
},
"defi": {
"name": "DeFi",
"description": "Decentralized Finance tokens"
},
"compliant": {
"name": "Compliant",
"description": "DBIS GRU compliant eMoney transport assets"
},
"fiat": {
"name": "Fiat",
"description": "Fiat referenced tokens"
},
"cash": {
"name": "Cashlike",
"description": "Cash reserve or cash rail assets"
},
"gru": {
"name": "GRU",
"description": "Global Reserve Unit family assets"
},
"wrapped": {
"name": "Wrapped",
"description": "Public network wrapped transport mirrors of hub assets"
}
}
}

View File

@@ -482,9 +482,11 @@ describe('Report API', () => {
expect.objectContaining({
symbol: 'cUSDT_V2',
chainId: 138,
familySymbol: 'cUSDT',
deploymentVersion: 'v2',
preferredForX402: true,
extensions: expect.objectContaining({
familySymbol: 'cUSDT',
deploymentVersion: 'v2',
preferredForX402: true,
}),
}),
expect.objectContaining({
symbol: 'cUSDC',
@@ -493,9 +495,11 @@ describe('Report API', () => {
expect.objectContaining({
symbol: 'cUSDC_V2',
chainId: 138,
familySymbol: 'cUSDC',
deploymentVersion: 'v2',
preferredForX402: true,
extensions: expect.objectContaining({
familySymbol: 'cUSDC',
deploymentVersion: 'v2',
preferredForX402: true,
}),
}),
])
);
@@ -598,7 +602,9 @@ describe('Report API', () => {
expect.objectContaining({
symbol: 'cBTC',
chainId: 138,
registryFamily: 'monetary_unit',
extensions: expect.objectContaining({
registryFamily: 'monetary_unit',
}),
}),
])
);
@@ -611,7 +617,9 @@ describe('Report API', () => {
expect.objectContaining({
symbol: 'cWBTC',
chainId: 1,
registryFamily: 'monetary_unit',
extensions: expect.objectContaining({
registryFamily: 'monetary_unit',
}),
}),
])
);
@@ -626,12 +634,16 @@ describe('Report API', () => {
expect.objectContaining({
symbol: 'cETH',
chainId: 138,
registryFamily: 'gas_native',
extensions: expect.objectContaining({
registryFamily: 'gas_native',
}),
}),
expect.objectContaining({
symbol: 'cETHL2',
chainId: 138,
registryFamily: 'gas_native',
extensions: expect.objectContaining({
registryFamily: 'gas_native',
}),
}),
])
);
@@ -644,7 +656,9 @@ describe('Report API', () => {
expect.objectContaining({
symbol: 'cWETHL2',
chainId: 10,
registryFamily: 'gas_native',
extensions: expect.objectContaining({
registryFamily: 'gas_native',
}),
}),
])
);
@@ -657,7 +671,9 @@ describe('Report API', () => {
expect.objectContaining({
symbol: 'cWETH',
chainId: 1,
registryFamily: 'gas_native',
extensions: expect.objectContaining({
registryFamily: 'gas_native',
}),
}),
])
);
@@ -669,10 +685,40 @@ describe('Report API', () => {
const body = (await res.json()) as Record<string, any>;
const cwusdc = body.tokens.find((token: Record<string, any>) => token.symbol === 'cWUSDC');
expect(cwusdc).toMatchObject({
name: 'Wrapped cUSDC',
address: '0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a',
logoURI: expect.stringMatching(/^https:\/\/127\.0\.0\.1:\d+\/api\/v1\/report\/logo\/cUSDC$/),
originalLogoURI: expect.stringContaining('/token-lists/logos/gru/cUSDC.svg'),
extensions: expect.objectContaining({
originalLogoURI: expect.stringContaining('/token-lists/logos/gru/cUSDC.svg'),
}),
});
});
it('returns Uniswap token list schema fields for mainnet lists', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=1`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
expect(body.name).toBe('DBIS Ethereum Mainnet GRU');
expect(body.version).toEqual({ major: 1, minor: 0, patch: 0 });
expect(body.tokens[0]).not.toHaveProperty('type');
});
it('serves the pinned static ethereum-mainnet token list', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/token-list/static/ethereum-mainnet`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
expect(body.name).toBe('DBIS Ethereum Mainnet GRU');
expect(body.version).toEqual({ major: 1, minor: 1, patch: 0 });
expect(body.tokens).toEqual(
expect.arrayContaining([
expect.objectContaining({
symbol: 'cWUSDC',
name: 'Wrapped cUSDC',
address: '0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a',
}),
])
);
});
});
describe('GET /api/v1/report/cw-registry', () => {

View File

@@ -38,6 +38,11 @@ import {
import { getGruV2DeploymentPoolRows } from '../../config/gru-v2-deployment-pools';
import { getCanonicalPriceSnapshotGeneratedAt, getCanonicalPriceUsd } from '../../services/canonical-price-oracle';
import { pmmVaultReserveFromChain, resolvePmmQuoteRpcUrl } from '../../services/pmm-onchain-quote';
import {
buildUniswapTokenList,
MAINNET_PUBLIC_DISPLAY_NAMES,
resolveTokenListName,
} from '../utils/uniswap-token-list';
const router: Router = Router();
const tokenRepo = new TokenRepository();
@@ -628,6 +633,70 @@ function absoluteReportLogoUri(remoteLogoUri: string, symbol: string): string {
return localLogoURI ?? remoteLogoUri;
}
function resolveEthereumMainnetTokenListPath(): string | null {
const candidates = [
process.env.ETHEREUM_MAINNET_TOKEN_LIST_JSON_PATH?.trim(),
path.join(__dirname, '../../../config/token-lists/ethereum-mainnet.tokenlist.json'),
path.join(process.cwd(), 'config/token-lists/ethereum-mainnet.tokenlist.json'),
].filter((value): value is string => Boolean(value));
for (const candidate of candidates) {
if (existsSync(candidate)) return candidate;
}
return null;
}
function loadStaticEthereumMainnetTokenList(): Record<string, unknown> | null {
const filePath = resolveEthereumMainnetTokenListPath();
if (!filePath) return null;
try {
return JSON.parse(readFileSync(filePath, 'utf8')) as Record<string, unknown>;
} catch (error) {
logger.error('Failed to read static ethereum-mainnet token list:', error);
return null;
}
}
function finalizeUniswapTokenListResponse(
req: Request,
res: Response,
payload: Record<string, unknown>,
chainIds: number[]
): void {
const logoURI = absolutePublicUrl(req, typeof payload.logoURI === 'string' ? payload.logoURI : undefined);
const tokens = Array.isArray(payload.tokens)
? payload.tokens.map((token) => {
const record = token as Record<string, unknown>;
return {
...record,
logoURI:
absolutePublicUrl(req, typeof record.logoURI === 'string' ? record.logoURI : undefined) ??
record.logoURI,
};
})
: [];
const displayNames = chainIds.length === 1 && chainIds[0] === 1 ? MAINNET_PUBLIC_DISPLAY_NAMES : undefined;
const body = buildUniswapTokenList({
name: resolveTokenListName(
chainIds,
typeof payload.name === 'string' ? payload.name : 'GRU Canonical Token List'
),
version: payload.version,
timestamp: typeof payload.timestamp === 'string' ? payload.timestamp : new Date().toISOString(),
logoURI,
tokens,
keywords: Array.isArray(payload.keywords) ? (payload.keywords as string[]) : undefined,
tags:
payload.tags && typeof payload.tags === 'object' && !Array.isArray(payload.tags)
? (payload.tags as Record<string, unknown>)
: undefined,
displayNames,
});
res.json(body);
}
/** Build token entries with DB market/pool data for a chain */
async function buildTokenReport(chainId: number) {
const canonical = getCanonicalTokensByChain(chainId);
@@ -1511,7 +1580,25 @@ router.get(
}
);
/** GET /report/token-list — flat list of all canonical tokens (Uniswap token list format with logoURI).
/** GET /report/token-list/static/ethereum-mainnet — pinned Uniswap-schema list for tokenlists.org submissions. */
router.get(
'/token-list/static/ethereum-mainnet',
cacheMiddleware(5 * 60 * 1000),
(req: Request, res: Response) => {
try {
const payload = loadStaticEthereumMainnetTokenList();
if (!payload) {
return res.status(404).json({ error: 'Static ethereum-mainnet token list not found' });
}
return finalizeUniswapTokenListResponse(req, res, payload, [1]);
} catch (error) {
logger.error('Error serving static ethereum-mainnet token list:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
);
/** GET /report/token-list — Uniswap token list schema (version object, checksummed addresses, extensions bag).
* If TOKEN_LIST_JSON_URL is set (e.g. GitHub raw URL), fetches and returns that JSON; optional ?chainId= filters tokens.
*/
router.get(
@@ -1519,58 +1606,37 @@ router.get(
cacheMiddleware(5 * 60 * 1000),
async (req: Request, res: Response) => {
try {
const tokenListUrl = process.env.TOKEN_LIST_JSON_URL?.trim();
if (tokenListUrl) {
try {
const data = (await fetchRemoteJson(tokenListUrl)) as {
name?: string;
version?: string;
timestamp?: string;
logoURI?: string;
tokens?: Array<{ chainId?: number; address?: string; symbol?: string; name?: string; decimals?: number; [key: string]: unknown }>;
};
const chainIdParam = req.query.chainId as string | undefined;
const chainIdFilter = chainIdParam ? parseInt(chainIdParam, 10) : null;
let tokens = Array.isArray(data.tokens) ? data.tokens : [];
if (!isNaN(chainIdFilter as number)) {
tokens = tokens.filter((t) => t.chainId === chainIdFilter);
}
const normalizedTokens = tokens.map((token) => ({
...token,
logoURI: absolutePublicUrl(req, typeof token.logoURI === 'string' ? token.logoURI : undefined) ?? token.logoURI,
}));
return res.json({
name: data.name ?? 'Token List',
version: data.version ?? '1.0.0',
timestamp: data.timestamp ?? new Date().toISOString(),
logoURI: absolutePublicUrl(req, data.logoURI) ?? data.logoURI,
tokens: normalizedTokens,
});
} catch (err) {
logger.error('TOKEN_LIST_JSON_URL fetch failed, using built-in token list:', err);
}
}
const chainIdParam = req.query.chainId as string | undefined;
const chainIds = chainIdParam
? [parseInt(chainIdParam, 10)].filter((n) => !isNaN(n))
: getSupportedChainIds();
const list: Array<{
chainId: number;
address: string;
symbol: string;
name: string;
decimals: number;
type: string;
logoURI: string;
originalLogoURI?: string;
registryFamily?: string;
familySymbol?: string;
deploymentVersion?: string;
deploymentStatus?: string;
preferredForX402?: boolean;
}> = [];
const tokenListUrl = process.env.TOKEN_LIST_JSON_URL?.trim();
if (tokenListUrl) {
try {
const data = (await fetchRemoteJson(tokenListUrl)) as Record<string, unknown>;
let tokens = Array.isArray(data.tokens) ? data.tokens : [];
if (chainIds.length === 1) {
tokens = tokens.filter((token) => {
const record = token as { chainId?: number };
return record.chainId === chainIds[0];
});
}
return finalizeUniswapTokenListResponse(
req,
res,
{
...data,
tokens,
},
chainIds
);
} catch (err) {
logger.error('TOKEN_LIST_JSON_URL fetch failed, using built-in token list:', err);
}
}
const list: Array<Record<string, unknown>> = [];
for (const chainId of chainIds) {
const specs = getCanonicalTokensByChain(chainId);
@@ -1580,7 +1646,7 @@ router.get(
const originalLogoURI = getLogoUriForSpec(spec);
list.push({
chainId,
address: address.toLowerCase(),
address,
symbol: spec.symbol,
name: spec.name,
decimals: spec.decimals,
@@ -1597,13 +1663,18 @@ router.get(
}
}
res.json({
name: 'GRU Canonical Token List',
version: '1.0.0',
timestamp: new Date().toISOString(),
logoURI: absolutePublicUrl(req, DBIS_CHAIN_138_LOGO_PATH),
tokens: list,
});
return finalizeUniswapTokenListResponse(
req,
res,
{
name: 'GRU Canonical Token List',
version: { major: 1, minor: 0, patch: 0 },
timestamp: new Date().toISOString(),
logoURI: absolutePublicUrl(req, DBIS_CHAIN_138_LOGO_PATH),
tokens: list,
},
chainIds
);
} catch (error) {
logger.error('Error building report/token-list:', error);
res.status(500).json({ error: 'Internal server error' });

View File

@@ -203,6 +203,7 @@ export class ApiServer {
tokenMappingPairs: '/api/v1/token-mapping/pairs',
tokenMappingResolve: '/api/v1/token-mapping/resolve',
reportTokenList: '/api/v1/report/token-list',
reportTokenListEthereumMainnet: '/api/v1/report/token-list/static/ethereum-mainnet',
routesTree: '/api/v1/routes/tree',
plannerProvidersCapabilities: '/api/v2/providers/capabilities',
plannerRoutesPlan: '/api/v2/routes/plan',

View File

@@ -0,0 +1,76 @@
import {
buildUniswapTokenList,
checksumTokenAddress,
normalizeTokenListVersion,
toUniswapTokenListToken,
} from './uniswap-token-list';
describe('uniswap-token-list utils', () => {
it('normalizes string versions into major/minor/patch objects', () => {
expect(normalizeTokenListVersion('1.2.3')).toEqual({ major: 1, minor: 2, patch: 3 });
expect(normalizeTokenListVersion({ major: 4, minor: 5, patch: 6 })).toEqual({
major: 4,
minor: 5,
patch: 6,
});
});
it('moves non-schema token fields into extensions and checksums addresses', () => {
const token = toUniswapTokenListToken(
{
chainId: 1,
address: '0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a',
symbol: 'cWUSDC',
name: 'USD Coin (Compliant Wrapped ISO-4217 M1)',
decimals: 6,
logoURI: 'https://d-bis.org/tokens/cwusdc.svg',
type: 'w',
registryFamily: 'iso4217',
originalLogoURI: 'https://example.com/logo.svg',
},
{ displayNames: { cWUSDC: 'Wrapped cUSDC' } }
);
expect(token).toMatchObject({
chainId: 1,
address: '0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a',
symbol: 'cWUSDC',
name: 'Wrapped cUSDC',
decimals: 6,
logoURI: 'https://d-bis.org/tokens/cwusdc.svg',
extensions: {
type: 'w',
registryFamily: 'iso4217',
originalLogoURI: 'https://example.com/logo.svg',
},
});
expect(token).not.toHaveProperty('type');
expect(token).not.toHaveProperty('registryFamily');
expect(checksumTokenAddress('0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a')).toBe(
'0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a'
);
});
it('builds a schema-compliant list envelope', () => {
const list = buildUniswapTokenList({
name: 'DBIS Ethereum Mainnet GRU',
version: '1.1.0',
timestamp: '2026-06-06T00:00:00.000Z',
logoURI: 'https://d-bis.org/tokens/cwusdc.svg',
tokens: [
{
chainId: 1,
address: '0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a',
symbol: 'cWUSDC',
name: 'Wrapped cUSDC',
decimals: 6,
logoURI: 'https://d-bis.org/tokens/cwusdc.svg',
},
],
});
expect(list.version).toEqual({ major: 1, minor: 1, patch: 0 });
expect(Array.isArray(list.tokens)).toBe(true);
expect((list.tokens as unknown[]).length).toBe(1);
});
});

View File

@@ -0,0 +1,119 @@
import { getAddress } from 'ethers';
export type UniswapTokenListVersion = {
major: number;
minor: number;
patch: number;
};
const TOKEN_EXTENSION_TOP_LEVEL_KEYS = [
'type',
'originalLogoURI',
'registryFamily',
'familySymbol',
'deploymentVersion',
'deploymentStatus',
'preferredForX402',
] as const;
/** Wallet/registry-facing names for mainnet transport tokens. */
export const MAINNET_PUBLIC_DISPLAY_NAMES: Record<string, string> = {
cWUSDC: 'Wrapped cUSDC',
cWUSDT: 'Wrapped cUSDT',
};
export function normalizeTokenListVersion(version: unknown): UniswapTokenListVersion {
if (version && typeof version === 'object' && 'major' in version) {
const record = version as { major?: unknown; minor?: unknown; patch?: unknown };
return {
major: Number(record.major ?? 1),
minor: Number(record.minor ?? 0),
patch: Number(record.patch ?? 0),
};
}
if (typeof version === 'string' && version.trim() !== '') {
const [major = '1', minor = '0', patch = '0'] = version.trim().split('.');
return {
major: Number.parseInt(major, 10) || 1,
minor: Number.parseInt(minor, 10) || 0,
patch: Number.parseInt(patch, 10) || 0,
};
}
return { major: 1, minor: 0, patch: 0 };
}
export function checksumTokenAddress(address: string): string {
try {
return getAddress(address);
} catch {
return address;
}
}
export function toUniswapTokenListToken(
token: Record<string, unknown>,
options?: { displayNames?: Record<string, string> }
): Record<string, unknown> {
const extensions: Record<string, unknown> = {};
for (const key of TOKEN_EXTENSION_TOP_LEVEL_KEYS) {
if (token[key] !== undefined) {
extensions[key] = token[key];
}
}
if (token.extensions && typeof token.extensions === 'object' && !Array.isArray(token.extensions)) {
Object.assign(extensions, token.extensions as Record<string, unknown>);
}
const symbol = String(token.symbol ?? '');
const displayName = options?.displayNames?.[symbol];
const out: Record<string, unknown> = {
chainId: token.chainId,
address: checksumTokenAddress(String(token.address ?? '')),
name: displayName ?? token.name,
symbol,
decimals: token.decimals,
};
if (token.logoURI) out.logoURI = token.logoURI;
if (Array.isArray(token.tags) && token.tags.length > 0) out.tags = token.tags;
if (Object.keys(extensions).length > 0) out.extensions = extensions;
return out;
}
export function buildUniswapTokenList(params: {
name: string;
version: unknown;
timestamp: string;
logoURI?: string;
tokens: Array<Record<string, unknown>>;
keywords?: string[];
tags?: Record<string, unknown>;
displayNames?: Record<string, string>;
}): Record<string, unknown> {
const body: Record<string, unknown> = {
name: params.name,
timestamp: params.timestamp,
version: normalizeTokenListVersion(params.version),
tokens: params.tokens.map((token) =>
toUniswapTokenListToken(token, { displayNames: params.displayNames })
),
};
if (params.logoURI) body.logoURI = params.logoURI;
if (params.keywords?.length) body.keywords = params.keywords;
if (params.tags && Object.keys(params.tags).length > 0) body.tags = params.tags;
return body;
}
export function resolveTokenListName(chainIds: number[], defaultName: string): string {
if (chainIds.length === 1 && chainIds[0] === 1) {
return 'DBIS Ethereum Mainnet GRU';
}
return defaultName;
}