feat(token-aggregation): reports, PMM quotes, config; Engine X flash vaults

- Expand token-aggregation API (report routes), canonical tokens, pools
- Add flash vault contracts + tests (indexed, DODO cwUSDC, XAUT borrow)
- PMM pools JSON, deploy/export scripts, metamask verified list

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-10 12:56:30 -07:00
parent 27f8e3a500
commit 76143a8fe3
67 changed files with 6972 additions and 136 deletions

View File

@@ -78,6 +78,22 @@ function uniquePaths(paths: Array<string | undefined | null>): string[] {
return out;
}
/** Non-empty built-in CCIP / trustless lane counts for /bridge/metrics (telemetry-friendly). */
function summarizeBuiltInBridgeLanes() {
const payload = buildDefaultBridgeRoutes();
const trustlessKeys = payload.routes.trustless ? Object.keys(payload.routes.trustless) : [];
const chain138 = payload.chain138Bridges as Record<string, string | undefined>;
return {
weth9Destinations: Object.keys(payload.routes.weth9).length,
weth10Destinations: Object.keys(payload.routes.weth10).length,
trustlessDestinations: trustlessKeys.length,
chain138ConfiguredBridges: Object.keys(chain138).filter((k) => {
const v = chain138[k];
return typeof v === 'string' && v.startsWith('0x');
}),
};
}
function resolveBridgeRoutesPath(): string | null {
const candidates = uniquePaths([
process.env.BRIDGE_LIST_JSON_PATH,
@@ -176,16 +192,35 @@ router.get('/status', (_req: Request, res: Response) => {
router.get('/metrics', (_req: Request, res: Response) => {
const gruTransport = buildGruTransportStatus();
const builtIn = summarizeBuiltInBridgeLanes();
res.json({
ok: true,
lanes: [],
lanes: [
{
kind: 'ccip-weth9',
label: 'WETH9 bridge destinations (built-in catalog)',
count: builtIn.weth9Destinations,
},
{
kind: 'ccip-weth10',
label: 'WETH10 bridge destinations (built-in catalog)',
count: builtIn.weth10Destinations,
},
{
kind: 'trustless',
label: 'Trustless / Lockbox destinations (env-backed)',
count: builtIn.trustlessDestinations,
},
],
chain138Bridges: builtIn.chain138ConfiguredBridges,
gruTransport: gruTransport
? {
system: gruTransport.system,
summary: gruTransport.summary,
}
: null,
message: 'Bridge metrics include GRU Transport summary counts. Use /api/v1/report/cross-chain for aggregated data.',
message:
'Lane counts reflect the built-in CCIP route catalog plus GRU Transport summary. Use /api/v1/bridge/routes for full JSON and /api/v1/report/cross-chain for volumes.',
});
});

View File

@@ -139,4 +139,83 @@ describe('Config API runtime networks loader', () => {
await new Promise<void>((resolve, reject) => remoteServer.close((err) => (err ? reject(err) : resolve())));
}
});
it('serves wallet-facing MetaMask aliases with absolute token images', async () => {
const networksRes = await fetch(`${baseUrl}/api/v1/config/networks`);
expect(networksRes.status).toBe(200);
const networksBody = (await networksRes.json()) as Record<string, any>;
expect(networksBody.networks).toEqual(
expect.arrayContaining([
expect.objectContaining({
chainIdDecimal: 138,
iconUrls: expect.arrayContaining([
'https://explorer.d-bis.org/token-icons/chain-138.png',
'https://explorer.d-bis.org/api/v1/report/logo/chain-138',
]),
}),
])
);
const metamaskRes = await fetch(`${baseUrl}/api/v1/config/metamask?chainId=138`);
expect(metamaskRes.status).toBe(200);
const metamaskBody = (await metamaskRes.json()) as Record<string, any>;
expect(metamaskBody.addEthereumChain).toMatchObject({
chainId: '0x8a',
chainName: 'DeFi Oracle Meta Mainnet',
});
expect(metamaskBody.watchAssets).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'ERC20',
options: expect.objectContaining({
symbol: 'cUSDC',
image: expect.stringMatching(/^https:\/\/127\.0\.0\.1:\d+\/api\/v1\/report\/logo\/cUSDC\?v=20260510$/),
}),
}),
expect.objectContaining({
type: 'ERC20',
options: expect.objectContaining({
address: '0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03',
symbol: 'LINK',
image: expect.stringMatching(/^https:\/\/127\.0\.0\.1:\d+\/api\/v1\/report\/logo\/LINK\?v=20260510$/),
}),
}),
expect.objectContaining({
type: 'ERC20',
options: expect.objectContaining({
symbol: 'WETH',
image: expect.stringMatching(/^https:\/\/127\.0\.0\.1:\d+\/api\/v1\/report\/logo\/ETH\?v=20260510$/),
}),
}),
expect.objectContaining({
type: 'ERC20',
options: expect.objectContaining({
symbol: 'cWEMIX',
image: expect.stringMatching(/^https:\/\/127\.0\.0\.1:\d+\/api\/v1\/report\/logo\/WEMIX\?v=20260510$/),
}),
}),
])
);
expect(metamaskBody.watchAssets).toEqual(
expect.arrayContaining([
expect.objectContaining({
type: 'ERC20',
options: expect.objectContaining({
address: '0x219522c60e83dEe01FC5b0329d6fA8fD84b9D13d',
symbol: 'cUSDC',
}),
metadata: expect.objectContaining({
catalogSymbol: 'cUSDC_V2',
familySymbol: 'cUSDC',
deploymentVersion: 'v2',
}),
}),
])
);
expect(
metamaskBody.watchAssets.some(
(entry: Record<string, any>) => entry.options?.symbol === 'cUSDC_V2' || entry.options?.symbol === 'cUSDT_V2'
)
).toBe(false);
});
});

View File

@@ -1,12 +1,75 @@
import { Router, Request, Response } from 'express';
import fs from 'fs';
import path from 'path';
import { getNetworks, getConfigByChain, API_VERSION } from '../../config/networks';
import { getNetworks, getConfigByChain, API_VERSION, type NetworkEntry } from '../../config/networks';
import { getCanonicalTokensByChain, getLogoUriForSpec, getTokenRegistryFamily } from '../../config/canonical-tokens';
import { cacheMiddleware } from '../middleware/cache';
import { fetchRemoteJson } from '../utils/fetch-remote-json';
import { logger } from '../../utils/logger';
const router: Router = Router();
const DEFAULT_PUBLIC_BASE_URL = 'https://explorer.d-bis.org';
const DEFAULT_WALLET_METADATA_VERSION = '20260510';
function resolvePublicBaseUrl(req: Request): string {
const configured = (
process.env.TOKEN_AGGREGATION_PUBLIC_BASE_URL ??
process.env.PUBLIC_API_BASE_URL ??
process.env.PUBLIC_BASE_URL ??
''
).trim();
if (configured) return configured.replace(/\/+$/, '');
const host = String(req.get('x-forwarded-host') || req.get('host') || '').split(',')[0].trim();
if (host) {
let proto = String(req.get('x-forwarded-proto') || 'https').split(',')[0].trim() || 'https';
if (proto === 'http' && !/^(localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/i.test(host)) {
proto = 'https';
}
return `${proto}://${host}`.replace(/\/+$/, '');
}
return DEFAULT_PUBLIC_BASE_URL;
}
function absolutePublicUrl(req: Request, value: string | undefined): string | undefined {
if (!value) return undefined;
if (/^https?:\/\//i.test(value)) return value;
if (!value.startsWith('/')) return value;
return `${resolvePublicBaseUrl(req)}${value}`;
}
function appendWalletMetadataVersion(value: string | undefined): string | undefined {
if (!value) return undefined;
const version = (process.env.WALLET_METADATA_IMAGE_VERSION || DEFAULT_WALLET_METADATA_VERSION).trim();
if (!version) return value;
const separator = value.includes('?') ? '&' : '?';
return `${value}${separator}v=${encodeURIComponent(version)}`;
}
function localLogoPathForSymbol(symbol: string, originalLogoUri: string): string {
if (originalLogoUri.includes('/token-lists/logos/gru/')) {
const fileName = originalLogoUri.split('/').pop()?.replace(/\.svg$/i, '');
if (fileName) return `/api/v1/report/logo/${fileName}`;
}
if (originalLogoUri.includes('/blockchains/bitcoin/info/logo.png')) return '/api/v1/report/logo/cWBTC';
if (originalLogoUri.includes('/ipfs/')) {
const cid = originalLogoUri.split('/').pop();
if (cid) return `/api/v1/report/logo/ipfs-${cid}`;
}
if (symbol === 'cWUSDC') return '/api/v1/report/logo/cUSDC';
return originalLogoUri;
}
function resolveWalletWatchAssetSymbol(spec: { symbol: string; familySymbol?: string; deploymentVersion?: string }): string {
// MetaMask validates wallet_watchAsset.symbol against ERC-20 symbol().
// Staged V2 Chain 138 deployments currently keep the family symbol on-chain
// (for example cUSDC), while the catalog symbol distinguishes the row
// (for example cUSDC_V2). Keep the catalog identity in metadata and send the
// contract-facing symbol in options.symbol so EIP-747 succeeds.
if (spec.deploymentVersion && spec.familySymbol) return spec.familySymbol;
return spec.symbol;
}
type RuntimeNetworksPayload = {
version?: string | { major?: number; minor?: number; patch?: number };
@@ -124,16 +187,77 @@ async function resolveNetworksPayload(): Promise<NetworksPayload> {
};
}
async function sendNetworks(_req: Request, res: Response): Promise<void> {
res.set('Cache-Control', 'public, max-age=0, must-revalidate');
const payload = await resolveNetworksPayload();
res.json(payload);
}
/**
* GET /api/v1/networks
* Full EIP-3085 chain params for wallet_addEthereumChain (Chain 138, 1, 651940).
* If NETWORKS_JSON_URL is set (e.g. GitHub raw URL), fetches and returns that JSON; otherwise uses built-in networks.
*/
router.get('/networks', cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => {
router.get(['/networks', '/config/networks', '/metamask/networks'], cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => {
try {
await sendNetworks(req, res);
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
/**
* GET /api/v1/config/metamask
* Wallet-facing aliases for Chain 138 add-chain params and watchAsset token entries.
*/
router.get(['/config/metamask', '/metamask'], cacheMiddleware(5 * 60 * 1000), async (req: Request, res: Response) => {
try {
res.set('Cache-Control', 'public, max-age=0, must-revalidate');
const chainId = parseInt(String(req.query.chainId ?? '138'), 10) || 138;
const payload = await resolveNetworksPayload();
res.json(payload);
const networks = payload.networks as NetworkEntry[];
const network = networks.find((entry) => Number(entry.chainIdDecimal) === chainId);
if (!network) {
res.status(404).json({ error: 'Chain not found', chainId });
return;
}
const watchAssets = getCanonicalTokensByChain(chainId)
.map((spec) => {
const address = spec.addresses[chainId];
if (!address) return null;
const originalLogoURI = getLogoUriForSpec(spec);
return {
type: 'ERC20',
options: {
address,
symbol: resolveWalletWatchAssetSymbol(spec),
decimals: spec.decimals,
image: appendWalletMetadataVersion(absolutePublicUrl(req, localLogoPathForSymbol(spec.symbol, originalLogoURI))),
},
metadata: {
name: spec.name,
catalogSymbol: spec.symbol,
registryFamily: getTokenRegistryFamily(spec),
familySymbol: spec.familySymbol,
deploymentVersion: spec.deploymentVersion,
deploymentStatus: spec.deploymentStatus,
},
};
})
.filter(Boolean);
res.json({
source: payload.source,
version: payload.version,
chainId,
addEthereumChain: network,
watchAssets,
caveats: [
'MetaMask custom-token prices are controlled by MetaMask and its upstream asset/price providers; this endpoint supplies wallet metadata, logo URLs, and token-add payloads but cannot force MetaMask to render fiat prices.',
'After metadata changes, remove and re-add the custom network/token in MetaMask or use the companion UI to call wallet_addEthereumChain and wallet_watchAsset again.',
],
});
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}

View File

@@ -91,6 +91,24 @@ describe('Report API', () => {
const body = (await res.json()) as Record<string, unknown>;
expect(body.chainId).toBe(651940);
});
it('enriches Mainnet cWUSDC with supply proof fields for CMC reports', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/cmc?chainId=1`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
const cwusdc = body.tokens.find((token: Record<string, any>) => token.symbol === 'cWUSDC');
expect(cwusdc).toMatchObject({
contract_address: '0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a',
total_supply: 10451316981.309788,
total_supply_raw: '10451316981309788',
circulating_supply: 10451316981.309788,
market_cap: 10451316981.309788,
supply_proof_provenance: expect.objectContaining({
status: 'ready_for_tracker_review',
}),
});
expect(cwusdc.tracker_caveats).toEqual(expect.arrayContaining([expect.stringContaining('on-chain supply proof')]));
});
});
describe('GET /api/v1/report/coingecko', () => {
@@ -104,6 +122,62 @@ describe('Report API', () => {
expect(body).toHaveProperty('tokens');
expect(Array.isArray(body.tokens)).toBe(true);
});
it('enriches Mainnet cWUSDC with supply proof, circulating supply, and market cap', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/coingecko?chainId=1`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
const cwusdc = body.tokens.find((token: Record<string, any>) => token.symbol === 'cWUSDC');
expect(cwusdc).toMatchObject({
contract_address: '0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a',
total_supply: 10451316981.309788,
total_supply_raw: '10451316981309788',
circulating_supply: 10451316981.309788,
circulating_supply_formula: 'circulatingSupply = totalSupply - protocolControlledNonCirculatingBalances',
supply_proof_provenance: expect.objectContaining({
schema: 'mainnet-cwusdc-supply-proof/v1',
referenceBlock: 25047586,
}),
market_data: expect.objectContaining({
current_price: { usd: 1 },
market_cap: 10451316981.309788,
}),
});
expect(cwusdc.tracker_caveats).toEqual(expect.arrayContaining([expect.stringContaining('No public tracker')]));
});
it('surfaces GRU v2 deployment-status pools in tracker-facing token reports', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/coingecko?chainId=1`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
const cwusdc = body.tokens.find((token: Record<string, any>) => token.symbol === 'cWUSDC');
expect(cwusdc.liquidity_pools).toEqual(
expect.arrayContaining([
expect.objectContaining({
pool_address: '0x69776fc607e9eda8042e320e7e43f54d06c68f0e',
source: 'gru-v2-deployment-status',
status: 'live',
role: 'defense',
}),
])
);
});
it('surfaces explicit supply-proof gaps for Mainnet GRU assets without proof artifacts', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/coingecko?chainId=1`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
const cwusdt = body.tokens.find((token: Record<string, any>) => token.symbol === 'cWUSDT');
expect(cwusdt).toMatchObject({
contract_address: '0xaf5017d0163ecb99d9b5d94e3b4d7b09af44d8ae',
supply_proof_provenance: {
source: 'missing-supply-proof',
status: 'proof_required',
},
});
expect(cwusdt).not.toHaveProperty('total_supply');
expect(cwusdt.tracker_caveats).toEqual(expect.arrayContaining([expect.stringContaining('tracker-grade supply proof')]));
});
});
describe('GET /api/v1/report/all', () => {
@@ -149,6 +223,72 @@ describe('Report API', () => {
}),
});
});
it('includes Mainnet cWUSDC supply proof enrichment in unified reports', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=1`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
const cwusdc = body.tokens?.['1']?.find((token: Record<string, any>) => token.symbol === 'cWUSDC');
expect(cwusdc).toMatchObject({
totalSupply: '10451316981.309788',
totalSupplyRaw: '10451316981309788',
circulatingSupply: '10451316981.309788',
circulatingSupplyFormula: 'circulatingSupply = totalSupply - protocolControlledNonCirculatingBalances',
market: expect.objectContaining({
priceUsd: 1,
marketCapUsd: 10451316981.309788,
}),
supplyProofProvenance: expect.objectContaining({
schema: 'mainnet-cwusdc-supply-proof/v1',
referenceBlock: 25047586,
}),
});
});
it('distinguishes proof-gated Mainnet cW assets from deterministic placeholder bindings', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=1`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
const proofGated = body.tokens?.['1']?.find((entry: Record<string, any>) => entry.symbol === 'cWUSDT');
expect(proofGated).toMatchObject({
supplyProofProvenance: {
source: 'missing-supply-proof',
status: 'proof_required',
},
});
expect(proofGated.totalSupply).toBeUndefined();
expect(proofGated.trackerCaveats).toEqual(expect.arrayContaining([expect.stringContaining('proof artifact')]));
const placeholderSymbols = ['cWBTC', 'cWETH'];
for (const symbol of placeholderSymbols) {
const token = body.tokens?.['1']?.find((entry: Record<string, any>) => entry.symbol === symbol);
expect(token).toMatchObject({
supplyProofProvenance: {
source: 'deterministic-placeholder-address',
status: 'non_reportable_until_erc20_deployed',
},
});
expect(token.totalSupply).toBeUndefined();
expect(token.trackerCaveats).toEqual(expect.arrayContaining([expect.stringContaining('deterministic placeholder')]));
}
});
it('marks proofless base GRU c assets as proof gated instead of leaving silent supply fields', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=138`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
const cusdc = body.tokens?.['138']?.find((entry: Record<string, any>) => entry.symbol === 'cUSDC');
expect(cusdc).toMatchObject({
type: 'base',
registryFamily: 'iso4217',
supplyProofProvenance: {
source: 'missing-supply-proof',
status: 'proof_required',
},
});
expect(cusdc.totalSupply).toBeUndefined();
expect(cusdc.trackerCaveats).toEqual(expect.arrayContaining([expect.stringContaining('tracker-grade supply proof')]));
});
});
describe('GET /api/v1/report/gas-registry', () => {
@@ -185,6 +325,63 @@ describe('Report API', () => {
});
});
describe('GET /api/v1/report/adoption-readiness', () => {
it('summarizes proved, proof-gated, pool-indexed, and scoring gates', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/adoption-readiness`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
expect(body.scope).toBe('gru-c-and-cw-assets');
expect(body.counts).toMatchObject({
candidates: expect.any(Number),
reportableCandidates: expect.any(Number),
nonReportablePlaceholder: expect.any(Number),
proved: expect.any(Number),
proofRequired: expect.any(Number),
silent: 0,
liquidityMissing: expect.any(Number),
liquidityMissingWithPools: expect.any(Number),
liquidityMissingWithoutPools: expect.any(Number),
gruV2PoolsWithStatus: expect.any(Number),
});
expect(body.institutional.score).toEqual(expect.any(Number));
expect(body.cryptoListing.score).toEqual(expect.any(Number));
expect(Array.isArray(body.blockerInventory.proofRequiredByChain)).toBe(true);
expect(Array.isArray(body.blockerInventory.liquidityMissingByChain)).toBe(true);
expect(Array.isArray(body.blockerInventory.liquidityMissingWithPoolsByChain)).toBe(true);
expect(Array.isArray(body.blockerInventory.liquidityMissingWithoutPoolsByChain)).toBe(true);
expect(Array.isArray(body.blockerInventory.liquidityMissingDetails)).toBe(true);
expect(Array.isArray(body.blockerInventory.externalOfficialQuoteLiquidityByChain)).toBe(true);
expect(Array.isArray(body.blockerInventory.nonReportablePlaceholderByChain)).toBe(true);
expect(Array.isArray(body.blockerInventory.gruV2PoolsMissingStatus)).toBe(true);
expect(Array.isArray(body.blockerInventory.notes)).toBe(true);
});
it('does not treat external official USDC/USDT mirrors as GRU liquidity blockers', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/adoption-readiness`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
expect(body.counts.externalOfficialQuoteLiquidity).toBeGreaterThan(0);
expect(body.blockerInventory.externalOfficialQuoteLiquidityByChain).toEqual(
expect.arrayContaining([
expect.objectContaining({
chainId: 1,
symbols: expect.arrayContaining(['cUSDC', 'cUSDT']),
}),
expect.objectContaining({
chainId: 56,
symbols: expect.arrayContaining(['cUSDC', 'cUSDT']),
}),
])
);
expect(body.blockerInventory.liquidityMissingDetails).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ chainId: 1, symbol: 'cUSDT' }),
expect.objectContaining({ chainId: 56, symbol: 'cUSDC' }),
])
);
});
});
describe('GET /api/v1/report/token-list', () => {
it('surfaces both V1 and V2 Chain 138 canonical GRU deployments explicitly', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/token-list?chainId=138`);
@@ -379,6 +576,17 @@ describe('Report API', () => {
])
);
});
it('uses packaged DBIS-level local logo assets while preserving original logo references', 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>;
const cwusdc = body.tokens.find((token: Record<string, any>) => token.symbol === 'cWUSDC');
expect(cwusdc).toMatchObject({
logoURI: expect.stringMatching(/^https:\/\/127\.0\.0\.1:\d+\/api\/v1\/report\/logo\/cUSDC$/),
originalLogoURI: expect.stringContaining('/token-lists/logos/gru/cUSDC.svg'),
});
});
});
describe('GET /api/v1/report/cw-registry', () => {
@@ -488,6 +696,8 @@ describe('Report API', () => {
expect((body.pools as Array<{ poolAddress: string }>)[0]).toMatchObject({
poolAddress: '0x1111111111111111111111111111111111111111',
section: 'pmmPools',
status: 'routing_enabled',
statusReason: expect.stringContaining('public routing is enabled'),
});
} finally {
await import('fs/promises').then((fs) => fs.unlink(tempPath).catch(() => undefined));

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
import express, { Express, Request, Response, NextFunction } from 'express';
import { Server } from 'http';
import path from 'path';
import { readFileSync, existsSync } from 'fs';
import cors from 'cors';
@@ -48,6 +49,7 @@ export class ApiServer {
private indexerEnabled: boolean;
private indexer: MultiChainIndexer | null;
private omnlPoller: OmnlEventPoller | null;
private server: Server | null;
private resolveTrustProxySetting(): boolean | number | string {
const raw = (process.env.EXPRESS_TRUST_PROXY ?? process.env.TRUST_PROXY ?? '1').trim();
@@ -65,6 +67,7 @@ export class ApiServer {
this.indexerEnabled = this.resolveFeatureFlag('ENABLE_INDEXER', true);
this.indexer = this.indexerEnabled ? new MultiChainIndexer() : null;
this.omnlPoller = this.resolveFeatureFlag('ENABLE_OMNL_EVENT_POLLER', false) ? new OmnlEventPoller() : null;
this.server = null;
this.setupMiddleware();
this.setupRoutes();
@@ -106,6 +109,14 @@ export class ApiServer {
});
next();
});
const publicPath = path.join(__dirname, '../../public');
if (existsSync(publicPath)) {
this.app.use('/static', express.static(publicPath, {
immutable: true,
maxAge: '1d',
}));
}
}
private setupRoutes(): void {
@@ -151,6 +162,39 @@ export class ApiServer {
res.type('html').send(readFileSync(dashboardPath, 'utf8'));
});
// Public API catalog (register before routers so GET /api/v1 is not swallowed by middleware-only mounts)
const sendApiV1Catalog = (_req: Request, res: Response) => {
res.json({
service: 'token-aggregation',
version: '1.0.0',
note: 'Prefix paths with your public API origin (e.g. https://explorer.d-bis.org).',
paths: {
catalog: '/api/v1',
health: '/health',
chains: '/api/v1/chains',
networks: '/api/v1/networks',
config: '/api/v1/config',
tokens: '/api/v1/tokens',
tokenDetail: '/api/v1/tokens/{address}',
quote: '/api/v1/quote',
bridgeRoutes: '/api/v1/bridge/routes',
bridgeStatus: '/api/v1/bridge/status',
bridgeMetrics: '/api/v1/bridge/metrics',
bridgePreflight: '/api/v1/bridge/preflight',
tokenMappingPairs: '/api/v1/token-mapping/pairs',
tokenMappingResolve: '/api/v1/token-mapping/resolve',
reportTokenList: '/api/v1/report/token-list',
routesTree: '/api/v1/routes/tree',
plannerProvidersCapabilities: '/api/v2/providers/capabilities',
plannerRoutesPlan: '/api/v2/routes/plan',
plannerIntentsPlan: '/api/v2/intents/plan',
plannerInternalExecutionPlan: '/api/v2/routes/internal-execution-plan',
},
});
};
this.app.get('/api/v1', sendApiV1Catalog);
this.app.get('/api/v1/', sendApiV1Catalog);
// API routes
this.app.use('/api/v1', tokenRoutes);
this.app.use('/api/v1', configRoutes);
@@ -206,21 +250,25 @@ export class ApiServer {
async start(): Promise<void> {
try {
// Start server
this.server = this.app.listen(this.port, () => {
logger.info(`Token Aggregation Service listening on port ${this.port}`);
logger.info(`Health check: http://localhost:${this.port}/health`);
logger.info(`API: http://localhost:${this.port}/api/v1`);
});
if (this.indexer) {
await this.indexer.initialize();
await this.indexer.startAll();
this.indexer
.initialize()
.then(() => this.indexer?.startAll())
.catch((error) => {
logger.error('Token aggregation indexer failed after API startup:', error);
});
} else {
logger.info('Token aggregation indexer disabled by ENABLE_INDEXER flag');
}
this.omnlPoller?.start();
// Start server
this.app.listen(this.port, () => {
logger.info(`Token Aggregation Service listening on port ${this.port}`);
logger.info(`Health check: http://localhost:${this.port}/health`);
logger.info(`API: http://localhost:${this.port}/api/v1`);
});
} catch (error) {
logger.error('Failed to start server:', error);
process.exit(1);
@@ -230,6 +278,18 @@ export class ApiServer {
async stop(): Promise<void> {
this.omnlPoller?.stop();
this.indexer?.stopAll();
if (this.server) {
await new Promise<void>((resolve, reject) => {
this.server?.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
this.server = null;
}
logger.info('Server stopped');
}
}

View File

@@ -44,6 +44,41 @@ describe('canonical cW token catalog', () => {
expect(getCanonicalTokenByAddress(56, '0xC2FA05F12a75Ac84ea778AF9D6935cA807275E55')?.symbol).toBe('cWUSDW');
});
it('keeps Cronos and the broader wrapped fiat/commodity family in the canonical cW mesh', () => {
const cronosCwUsdc = getCanonicalTokenBySymbol(25, 'cWUSDC');
expect(cronosCwUsdc).toMatchObject({
symbol: 'cWUSDC',
type: 'w',
currencyCode: 'USD',
});
expect(cronosCwUsdc?.addresses[25]).toBe('0x932566E5bB6BEBF6B035B94f3DE1f75f126304Ec');
expect(getCanonicalTokenByAddress(25, '0x932566E5bB6BEBF6B035B94f3DE1f75f126304Ec')?.symbol).toBe('cWUSDC');
const expected = [
['cWEURC', 'EUR', '0x7574d37F42528B47c88962931e48FC61608a4050'],
['cWEURT', 'EUR', '0x9f833b4f1012F52eb3317b09922a79c6EdFca77D'],
['cWGBPC', 'GBP', '0xe5c65A76A541368d3061fe9E7A2140cABB903dbF'],
['cWGBPT', 'GBP', '0xBb58fa16bAc8E789f09C14243adEE6480D8213A2'],
['cWAUDC', 'AUD', '0xff3084410A732231472Ee9f93F5855dA89CC5254'],
['cWJPYC', 'JPY', '0x52aD62B8bD01154e2A4E067F8Dc4144C9988d203'],
['cWCHFC', 'CHF', '0xB55F49D6316322d5caA96D34C6e4b1003BD3E670'],
['cWCADC', 'CAD', '0x32aD687F24F77bF8C86605c202c829163Ac5Ab36'],
['cWXAUC', 'XAU', '0xf1B771c95573113E993374c0c7cB2dc1a7908B12'],
['cWXAUT', 'XAU', '0xD517C0cF7013f988946A468c880Cc9F8e2A4BCbE'],
] as const;
for (const [symbol, currencyCode, cronosAddress] of expected) {
const token = getCanonicalTokenBySymbol(25, symbol);
expect(token).toMatchObject({
symbol,
type: 'w',
currencyCode,
});
expect(token?.addresses[25]).toBe(cronosAddress);
expect(getCanonicalTokenByAddress(25, cronosAddress)?.symbol).toBe(symbol);
}
});
it('surfaces cUSDW on Chain 138 as the repo-native USDW hub asset', () => {
const cusdw = getCanonicalTokenBySymbol(138, 'cUSDW');
expect(cusdw).toMatchObject({

View File

@@ -70,7 +70,7 @@ const LEGACY_CHAIN_ENV_SUFFIX: Partial<Record<number, string>> = {
};
/** L2/mainnet chain IDs for cUSDT/cUSDC multichain (env: CUSDT_ADDRESS_56, CUSDC_ADDRESS_137, etc.) */
const L2_CHAIN_IDS = [1, 56, 137, 10, 42161, 8453, 43114, 25, 100, 42220, 1111] as const;
const GRU_CW_CHAIN_IDS = [1, 56, 137, 10, 42161, 8453, 43114, 100, 42220] as const;
const GRU_CW_CHAIN_IDS = [1, 10, 25, 56, 100, 137, 8453, 42161, 42220, 43114] as const;
const BTC_CW_CHAIN_IDS = [1, 10, 25, 56, 100, 137, 42161, 42220, 43114, 8453, 1111] as const;
const ETH_MAINNET_CW_CHAIN_IDS = [1] as const;
const ETH_L2_CW_CHAIN_IDS = [10, 42161, 8453] as const;
@@ -165,33 +165,156 @@ const FALLBACK_ADDRESSES: Record<string, Partial<Record<number, string>>> = {
cWAUSDT: {
[56]: '0xe1a51Bc037a79AB36767561B147eb41780124934',
[137]: '0xf12e262F85107df26741726b074606CaFa24AAe7',
[43114]: '0xff3084410A732231472Ee9f93F5855dA89CC5254',
[42220]: '0xC158b6cD3A3088C52F797D41f5Aa02825361629e',
[43114]: '0xff3084410A732231472Ee9f93F5855dA89CC5254',
},
cWUSDC: {
[1]: '0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a',
[56]: '0x5355148C4740fcc3D7a96F05EdD89AB14851206b',
[137]: '0xd6969bC19b53f866C64f2148aE271B2Dae0C58E4',
[100]: '0xd6969bC19b53f866C64f2148aE271B2Dae0C58E4',
[10]: '0x377a5FaA3162b3Fc6f4e267301A3c817bAd18105',
[42161]: '0x0cb0192C056aa425C557BdeAD8E56C7eEabf7acF',
[25]: '0x932566E5bB6BEBF6B035B94f3DE1f75f126304Ec',
[56]: '0x5355148C4740fcc3D7a96F05EdD89AB14851206b',
[100]: '0xd6969bC19b53f866C64f2148aE271B2Dae0C58E4',
[137]: '0xd6969bC19b53f866C64f2148aE271B2Dae0C58E4',
[8453]: '0x377a5FaA3162b3Fc6f4e267301A3c817bAd18105',
[43114]: '0x0C242b513008Cd49C89078F5aFb237A3112251EB',
[42161]: '0x0cb0192C056aa425C557BdeAD8E56C7eEabf7acF',
[42220]: '0x4C38F9A5ed68A04cd28a72E8c68C459Ec34576f3',
[43114]: '0x0C242b513008Cd49C89078F5aFb237A3112251EB',
},
cWUSDT: {
[1]: '0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE',
[56]: '0x9a1D0dBEE997929ED02fD19E0E199704d20914dB',
[137]: '0x0cb0192C056aa425C557BdeAD8E56C7eEabf7acF',
[100]: '0x0cb0192C056aa425C557BdeAD8E56C7eEabf7acF',
[10]: '0x04B2AE3c3bb3d70Df506FAd8717b0FBFC78ED7E6',
[42161]: '0x73ADaF7dBa95221c080db5631466d2bC54f6a76B',
[25]: '0x72948a7a813B60b37Cd0c920C4657DbFF54312b8',
[56]: '0x9a1D0dBEE997929ED02fD19E0E199704d20914dB',
[100]: '0x0cb0192C056aa425C557BdeAD8E56C7eEabf7acF',
[137]: '0x0cb0192C056aa425C557BdeAD8E56C7eEabf7acF',
[8453]: '0x04B2AE3c3bb3d70Df506FAd8717b0FBFC78ED7E6',
[43114]: '0x8142BA530B08f3950128601F00DaaA678213DFdf',
[42161]: '0x73ADaF7dBa95221c080db5631466d2bC54f6a76B',
[42220]: '0x73376eB92c16977B126dB9112936A20Fa0De3442',
[43114]: '0x8142BA530B08f3950128601F00DaaA678213DFdf',
},
cWEURC: {
[1]: '0xD4aEAa8cD3fB41Dc8437FaC7639B6d91B60A5e8d',
[10]: '0x4ab39b5bab7b463435209a9039bd40cf241f5a82',
[25]: '0x7574d37F42528B47c88962931e48FC61608a4050',
[56]: '0x50b073d0D1D2f002745cb9FC28a057d5be84911c',
[100]: '0x25603ae4bff0b71d637b3573d1b6657f5f6d17ef',
[137]: '0x3CD9ee18db7ad13616FCC1c83bC6098e03968E66',
[8453]: '0xcb145ba9a370681e3545f60e55621ebf218b1031',
[42161]: '0x2a0023ad5ce1ac6072b454575996dffb1bb11b16',
[42220]: '0xb6D2f38b9015F32ccE8818509c712264E7fceeD3',
[43114]: '0x84353ed1f0c7a703a17abad19b0db15bc9a5e3e5',
},
cWEURT: {
[1]: '0x855d74FFB6CF75721a9bAbc8B2ed35c8119241dC',
[10]: '0x6f521cd9fcf7884cd4e9486c7790e818638e09dd',
[25]: '0x9f833b4f1012F52eb3317b09922a79c6EdFca77D',
[56]: '0x1ED9E491A5eCd53BeF21962A5FCE24880264F63f',
[100]: '0x8e54c52d34a684e22865ac9f2d7c27c30561a7b9',
[137]: '0xBeF5A0Bcc0E77740c910f197138cdD90F98d2427',
[8453]: '0x73e0cf8bf861d376b3a4c87c136f975027f045ff',
[42161]: '0x22b98130ab4d9c355512b25ade4c35e75a4e7e89',
[42220]: '0x7e6fB8D80f81430e560F8232b2A4fd06249d74ce',
[43114]: '0xfc7d256e48253f7a7e08f0e55b9ff7039eb2524c',
},
cWGBPC: {
[1]: '0xc074007dc0bfb384b1cf6426a56287ed23fe4d52',
[10]: '0x3f8c409c6072a2b6a4ff17071927ba70f80c725f',
[25]: '0xe5c65A76A541368d3061fe9E7A2140cABB903dbF',
[56]: '0x8b6EE72001cAFcb21D56a6c4686D6Db951d499A6',
[100]: '0x4d9bc6c74ba65e37c4139f0aec9fc5ddff28dcc4',
[137]: '0x948690147D2e50ffe50C5d38C14125aD6a9FA036',
[8453]: '0x2a0023ad5ce1ac6072b454575996dffb1bb11b16',
[42161]: '0xa846aead3071df1b6439d5d813156ace7c2c1da1',
[42220]: '0xE37c332a88f112F9e039C5d92D821402A89c7052',
[43114]: '0xbdf0c4ea1d81e8e769b0f41389a2c733e3ff723e',
},
cWGBPT: {
[1]: '0x1dDF9970F01c76A692Fdba2706203E6f16e0C46F',
[10]: '0x456373d095d6b9260f01709f93fccf1d8aa14d11',
[25]: '0xBb58fa16bAc8E789f09C14243adEE6480D8213A2',
[56]: '0xA6eFb8783C8ad2740ec880e46D4f7E608E893B1B',
[100]: '0x9f6d2578003fe04e58a9819a4943732f2a203a61',
[137]: '0x58a8D8F78F1B65c06dAd7542eC46b299629A60dd',
[8453]: '0x22b98130ab4d9c355512b25ade4c35e75a4e7e89',
[42161]: '0x29828e9ab2057cd3df3c9211455ae1f76e53d2af',
[42220]: '0x1dBa81f91f1BeC47FFf60eC3e7DeD780ad9968E3',
[43114]: '0x4611d3424e059392a52b957e508273bc761c80f2',
},
cWAUDC: {
[1]: '0x5020Db641B3Fc0dAbBc0c688C845bc4E3699f35F',
[10]: '0x25603ae4bff0b71d637b3573d1b6657f5f6d17ef',
[25]: '0xff3084410A732231472Ee9f93F5855dA89CC5254',
[56]: '0x7062f35567BBAb4d98dc33af03B0d14Df42294D5',
[100]: '0xddc4063f770f7c49d00b5a10fb552e922aa39b2c',
[137]: '0xFb4B6Cc81211F7d886950158294A44C312abCA29',
[8453]: '0xa846aead3071df1b6439d5d813156ace7c2c1da1',
[42161]: '0xc1535e88578d984f12eab55863376b8d8b9fb05a',
[42220]: '0x2d3a2ED4Ca4d69912d217c305EE921609F7906A8',
[43114]: '0x04e1e22b0d41e99f4275bd40a50480219bc9a223',
},
cWJPYC: {
[1]: '0x07EEd0D7dD40984e47B9D3a3bdded1c536435582',
[10]: '0x8e54c52d34a684e22865ac9f2d7c27c30561a7b9',
[25]: '0x52aD62B8bD01154e2A4E067F8Dc4144C9988d203',
[56]: '0x5fbCE65524211BC1bFb0309fd9EE09E786c6D097',
[100]: '0x145e8e8c49b6a021969dd9d2c01c8fea44374f61',
[137]: '0xf9f5D0ACD71C76F9476F10B3F3d3E201F0883C68',
[8453]: '0x29828e9ab2057cd3df3c9211455ae1f76e53d2af',
[42161]: '0xdc383c489533a4dd9a6bd3007386e25d5078b878',
[42220]: '0x0b39F47D2E68aB0eB18d4b637Bbd1dD8E97cFbB5',
[43114]: '0x3714b1a312e0916c7dcdc4edf480fc0339e59a59',
},
cWCHFC: {
[1]: '0x0F91C5E6Ddd46403746aAC970D05d70FFe404780',
[10]: '0x4d9bc6c74ba65e37c4139f0aec9fc5ddff28dcc4',
[25]: '0xB55F49D6316322d5caA96D34C6e4b1003BD3E670',
[56]: '0xD9f8710caeeBA3b3D423D7D14a918701426B5ef3',
[100]: '0x46d90d7947f1139477c206c39268923b99cf09e4',
[137]: '0xeE17bB0322383fecCA2784fbE2d4CD7d02b1905B',
[8453]: '0xc1535e88578d984f12eab55863376b8d8b9fb05a',
[42161]: '0x7e4b4682453bcce19ec903fb69153d3031986bc4',
[42220]: '0x8142BA530B08f3950128601F00DaaA678213DFdf',
[43114]: '0xc2fa05f12a75ac84ea778af9d6935ca807275e55',
},
cWCADC: {
[1]: '0x209FE32fe7B541751D190ae4e50cd005DcF8EDb4',
[10]: '0x9f6d2578003fe04e58a9819a4943732f2a203a61',
[25]: '0x32aD687F24F77bF8C86605c202c829163Ac5Ab36',
[56]: '0x9AE7a6B311584D60Fa93f973950d609061875775',
[100]: '0xa7133c78e0ec74503a5941bcbd44257615b6b4f6',
[137]: '0xc9750828124D4c10e7a6f4B655cA8487bD3842EB',
[8453]: '0xdc383c489533a4dd9a6bd3007386e25d5078b878',
[42161]: '0xcc6ae6016d564e9ab82aaff44d65e05a9b18951c',
[42220]: '0x0C242b513008Cd49C89078F5aFb237A3112251EB',
[43114]: '0x1872e033b30f3ce0498847926857433e0146394e',
},
cWXAUC: {
[1]: '0x572Be0fa8CA0534d642A567CEDb398B771D8a715',
[10]: '0xddc4063f770f7c49d00b5a10fb552e922aa39b2c',
[25]: '0xf1B771c95573113E993374c0c7cB2dc1a7908B12',
[56]: '0xCB145bA9A370681e3545F60e55621eBf218B1031',
[100]: '0x23873b85cfeb343eb952618e8c9e9bfb7f6a0d45',
[137]: '0x328Cd365Bb35524297E68ED28c6fF2C9557d1363',
[8453]: '0x7e4b4682453bcce19ec903fb69153d3031986bc4',
[42161]: '0xa7762b63c4871581885ad17c5714ebb286a7480b',
[42220]: '0x61D642979eD75c1325f35b9275C5A7FE97F22451',
[43114]: '0x4f95297c23d9f4a1032b1c6a2e553225cb175bee',
},
cWXAUT: {
[1]: '0xACE1DBF857549a11aF1322e1f91F2F64b029c906',
[10]: '0x145e8e8c49b6a021969dd9d2c01c8fea44374f61',
[25]: '0xD517C0cF7013f988946A468c880Cc9F8e2A4BCbE',
[56]: '0x73E0CF8BF861D376B3a4C87c136F975027f045ff',
[100]: '0xc6189d404dc60cae7b48e2190e44770a03193e5f',
[137]: '0x9e6044d730d4183bF7a666293d257d035Fba6d44',
[8453]: '0xcc6ae6016d564e9ab82aaff44d65e05a9b18951c',
[42161]: '0x66568899ffe8f00b25dc470e878b65a478994e76',
[42220]: '0x30751782486eed825187C1EAe5DE4b4baD428AaE',
[43114]: '0xd2b4dbf2f6bd6704e066d752eec61fb0be953fd3',
},
cWUSDW: {
[56]: '0xC2FA05F12a75Ac84ea778AF9D6935cA807275E55',
[42220]: '0x176a1b6Aa59F24B3aa65F2b697AB262Bca9093B5',
[43114]: '0xcfdCe5E660FC2C8052BDfa7aEa1865DD753411Ae',
},
cWBTC: {
@@ -236,7 +359,7 @@ const FALLBACK_ADDRESSES: Record<string, Partial<Record<number, string>>> = {
cWWEMIX: {
[1111]: '0xc111000000000000000000000000000000000457',
},
// Compliant Fiat on Chain 138 from DeployCompliantFiatTokens (2026-02-27)
// Compliant Fiat on Chain 138 - from DeployCompliantFiatTokens (2026-02-27)
cEURC: { [CHAIN_138]: '0x8085961F9cF02b4d800A3c6d386D31da4B34266a' },
cEURT: { [CHAIN_138]: '0xdf4b71c61E5912712C1Bdd451416B9aC26949d72' },
cGBPC: { [CHAIN_138]: '0x003960f16D9d34F2e98d62723B6721Fb92074aD2' },
@@ -247,9 +370,10 @@ const FALLBACK_ADDRESSES: Record<string, Partial<Record<number, string>>> = {
cCADC: { [CHAIN_138]: '0x54dBd40cF05e15906A2C21f600937e96787f5679' },
cXAUC: { [CHAIN_138]: '0x290E52a8819A4fbD0714E517225429aA2B70EC6b' },
cXAUT: { [CHAIN_138]: '0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E' },
LINK: { [CHAIN_138]: '0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03' },
WETH: { [CHAIN_138]: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' },
WETH10: { [CHAIN_138]: '0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f' },
// ISO-4217W on Cronos (25) from DeployISO4217WSystem
// ISO-4217W on Cronos (25) - from DeployISO4217WSystem
USDW: { [CHAIN_25]: '0x948690147D2e50ffe50C5d38C14125aD6a9FA036' },
EURW: { [CHAIN_25]: '0x58a8D8F78F1B65c06dAd7542eC46b299629A60dd' },
GBPW: { [CHAIN_25]: '0xFb4B6Cc81211F7d886950158294A44C312abCA29' },
@@ -451,6 +575,16 @@ export const CANONICAL_TOKENS: CanonicalTokenSpec[] = [
{ symbol: 'cWAUSDT', name: 'Alltra USD Token (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form for the live Chain 138 cAUSDT surface.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWAUSDT', id)])) } },
{ symbol: 'cWUSDC', name: 'USD Coin (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDC', id)])) } },
{ symbol: 'cWUSDT', name: 'Tether USD (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDT.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDT', id)])) } },
{ symbol: 'cWEURC', name: 'Euro Coin (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'EUR', description: 'Public-network mirrored transport form of canonical Chain 138 cEURC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWEURC', id)])) } },
{ symbol: 'cWEURT', name: 'Tether EUR (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'EUR', description: 'Public-network mirrored transport form of canonical Chain 138 cEURT.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWEURT', id)])) } },
{ symbol: 'cWGBPC', name: 'Pound Sterling (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'GBP', description: 'Public-network mirrored transport form of canonical Chain 138 cGBPC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWGBPC', id)])) } },
{ symbol: 'cWGBPT', name: 'Tether GBP (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'GBP', description: 'Public-network mirrored transport form of canonical Chain 138 cGBPT.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWGBPT', id)])) } },
{ symbol: 'cWAUDC', name: 'Australian Dollar (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'AUD', description: 'Public-network mirrored transport form of canonical Chain 138 cAUDC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWAUDC', id)])) } },
{ symbol: 'cWJPYC', name: 'Japanese Yen (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'JPY', description: 'Public-network mirrored transport form of canonical Chain 138 cJPYC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWJPYC', id)])) } },
{ symbol: 'cWCHFC', name: 'Swiss Franc (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'CHF', description: 'Public-network mirrored transport form of canonical Chain 138 cCHFC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWCHFC', id)])) } },
{ symbol: 'cWCADC', name: 'Canadian Dollar (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'CAD', description: 'Public-network mirrored transport form of canonical Chain 138 cCADC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWCADC', id)])) } },
{ symbol: 'cWXAUC', name: 'Gold (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'XAU', registryFamily: 'commodity', description: 'Public-network mirrored transport form of canonical Chain 138 cXAUC.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWXAUC', id)])) } },
{ symbol: 'cWXAUT', name: 'Tether XAU (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'XAU', registryFamily: 'commodity', description: 'Public-network mirrored transport form of canonical Chain 138 cXAUT.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWXAUT', id)])) } },
{ symbol: 'cWUSDW', name: 'USD W (Compliant Wrapped ISO-4217 M1)', type: 'w', decimals: 6, currencyCode: 'USD', description: 'Public-network mirrored transport form of canonical Chain 138 cUSDW.', addresses: { ...Object.fromEntries(GRU_CW_CHAIN_IDS.map((id) => [id, addr('cWUSDW', id)])) } },
{
symbol: 'WETH',
@@ -472,6 +606,16 @@ export const CANONICAL_TOKENS: CanonicalTokenSpec[] = [
description: 'Chain 138 WETH10 pilot wrapped ETH surface used by DODO v3 routing and flash-capable paths.',
addresses: { [CHAIN_138]: addr('WETH10', CHAIN_138) || '' },
},
{
symbol: 'LINK',
name: 'Chainlink Token',
type: 'base',
decimals: 18,
currencyCode: 'LINK',
registryFamily: 'unclassified',
description: 'Chain 138 LINK token used for CCIP and oracle fee accounting.',
addresses: { [CHAIN_138]: addr('LINK', CHAIN_138) || '' },
},
{
symbol: 'cWBTC',
name: 'Bitcoin (Compliant Wrapped Monetary Unit)',
@@ -730,7 +874,18 @@ export function resolveCanonicalQuoteAddress(chainId: number, address: string):
const IPFS_GATEWAY = 'https://ipfs.io/ipfs';
const GRU_LOGO_BASE =
'https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru';
const ETH_LOGO = `${IPFS_GATEWAY}/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong`;
const ETH_LOGO = '/api/v1/report/logo/ETH';
const LINK_LOGO = '/api/v1/report/logo/LINK';
const GAS_NATIVE_LOGO_BY_CODE: Record<string, string> = {
ETH: ETH_LOGO,
BNB: '/api/v1/report/logo/BNB',
POL: '/api/v1/report/logo/POL',
AVAX: '/api/v1/report/logo/AVAX',
CRO: '/api/v1/report/logo/CRO',
XDAI: '/api/v1/report/logo/XDAI',
CELO: '/api/v1/report/logo/CELO',
WEMIX: '/api/v1/report/logo/WEMIX',
};
const USDC_LOGO = `${GRU_LOGO_BASE}/cUSDC.svg`;
const USDT_LOGO = `${GRU_LOGO_BASE}/cUSDT.svg`;
const BTC_LOGO = 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/bitcoin/info/logo.png';
@@ -750,8 +905,10 @@ const LOGO_BY_SYMBOL: Record<string, string> = {
cWUSDC: USDC_LOGO,
cWUSDT: USDT_LOGO,
cWUSDW: USDC_LOGO,
cWEMIX: GAS_NATIVE_LOGO_BY_CODE.WEMIX,
WETH: ETH_LOGO,
WETH10: ETH_LOGO,
LINK: LINK_LOGO,
cEURC: `${GRU_LOGO_BASE}/cEURC.svg`,
cEURT: `${GRU_LOGO_BASE}/cEURT.svg`,
cGBPC: `${GRU_LOGO_BASE}/cGBPC.svg`,
@@ -781,6 +938,15 @@ export function getLogoUriForSpec(spec: CanonicalTokenSpec): string {
if (spec.logoUrl) return spec.logoUrl;
const bySymbol = LOGO_BY_SYMBOL[spec.symbol];
if (bySymbol) return bySymbol;
const gasLogo = spec.registryFamily === 'gas_native' && spec.currencyCode
? GAS_NATIVE_LOGO_BY_CODE[spec.currencyCode.toUpperCase()]
: undefined;
if (gasLogo) return gasLogo;
if (spec.symbol.startsWith('cW')) {
const hubSymbol = `c${spec.symbol.slice(2)}`;
const hubSpec = CANONICAL_TOKENS.find((t) => t.symbol === hubSymbol);
if (hubSpec && hubSpec.symbol !== spec.symbol) return getLogoUriForSpec(hubSpec);
}
if (spec.symbol.startsWith('ac')) return getLogoUriForSpec(CANONICAL_TOKENS.find((t) => t.symbol === spec.symbol.replace('ac', 'c')) || spec);
if (spec.symbol.startsWith('vdc') || spec.symbol.startsWith('sdc')) {
const base = spec.symbol.replace(/^(vd|sd)c/, 'c');

View File

@@ -81,7 +81,7 @@ export const CHAIN_CONFIGS: Record<number, ChainConfig> = {
137: {
chainId: 137,
name: 'Polygon',
rpcUrl: process.env.CHAIN_137_RPC_URL || 'https://polygon-rpc.com',
rpcUrl: process.env.CHAIN_137_RPC_URL || 'https://polygon-bor-rpc.publicnode.com',
explorerUrl: 'https://polygonscan.com',
nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18 },
blockTime: 2,

View File

@@ -69,6 +69,9 @@ function buildDeploymentStatusCandidates(): string[] {
process.env.DEPLOYMENT_STATUS_JSON_PATH,
process.env.CW_REGISTRY_JSON_PATH,
process.env.CROSS_CHAIN_PMM_DEPLOYMENT_STATUS_PATH,
process.env.PROXMOX_REPO_ROOT
? path.resolve(process.env.PROXMOX_REPO_ROOT, 'cross-chain-pmm-lps/config/deployment-status.json')
: undefined,
path.resolve(process.cwd(), 'cross-chain-pmm-lps/config/deployment-status.json'),
path.resolve(process.cwd(), '..', 'cross-chain-pmm-lps/config/deployment-status.json'),
path.resolve(process.cwd(), '..', '..', 'cross-chain-pmm-lps/config/deployment-status.json'),

View File

@@ -7,6 +7,8 @@ export interface GruV2DeploymentPoolRow {
chainId: number;
chainName: string;
section: GruV2PmmSection;
status: 'live' | 'routing_enabled' | 'configured' | 'proof_required';
statusReason: string;
baseSymbol: string;
quoteSymbol: string;
baseAddress: string;
@@ -101,6 +103,16 @@ export function buildGruV2PoolRegistryFromDeploymentData(data: DeploymentStatusF
chainId,
chainName,
section,
status:
pool.publicRoutingEnabled === true
? 'routing_enabled'
: poolAddress.startsWith('0x')
? 'live'
: 'configured',
statusReason:
pool.publicRoutingEnabled === true
? 'Pool address is configured in deployment-status and public routing is enabled.'
: 'Pool address is configured in deployment-status; routing enablement is not asserted.',
baseSymbol,
quoteSymbol,
baseAddress,

View File

@@ -34,8 +34,9 @@ export const NETWORKS: NetworkEntry[] = [
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
blockExplorerUrls: ['https://explorer.d-bis.org'],
iconUrls: [
'https://explorer.d-bis.org/api/v1/report/logo/chain-138',
'https://explorer.d-bis.org/token-icons/chain-138.png',
'https://explorer.d-bis.org/favicon.ico',
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png',
],
oracles: [
{ name: 'ETH/USD', address: '0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6', decimals: 8 },
@@ -87,7 +88,13 @@ export const NETWORKS: NetworkEntry[] = [
chainId: '0x89',
chainIdDecimal: 137,
chainName: 'Polygon',
rpcUrls: ['https://polygon-rpc.com', 'https://rpc.ankr.com/polygon'],
rpcUrls: [
'https://polygon-bor-rpc.publicnode.com',
'https://1rpc.io/matic',
'https://polygon.drpc.org',
'https://polygon-rpc.com',
'https://rpc.ankr.com/polygon',
],
nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18 },
blockExplorerUrls: ['https://polygonscan.com'],
iconUrls: ['https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/info/logo.png'],

View File

@@ -26,6 +26,7 @@ export function getDatabasePool(): Pool {
connectionString: process.env.DATABASE_URL,
min: parseInt(process.env.DATABASE_POOL_MIN || '2', 10),
max: parseInt(process.env.DATABASE_POOL_MAX || '10', 10),
connectionTimeoutMillis: parseInt(process.env.DATABASE_CONNECTION_TIMEOUT_MS || '3000', 10),
};
// If connectionString is not provided, use individual config

View File

@@ -19,7 +19,16 @@ export class MarketDataRepository {
return code === '42P01' || (message.includes('relation "') && message.includes('" does not exist'));
}
private shouldUseReadFallback(error: unknown): boolean {
if (this.isMissingRelationError(error)) return true;
if (String(process.env.TOKEN_AGGREGATION_DB_READ_FALLBACK ?? '1').toLowerCase() === '0') return false;
const message = (error as { message?: string })?.message || '';
const code = (error as { code?: string })?.code || '';
return ['ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND'].includes(code) || /timeout|connect/i.test(message);
}
async getMarketData(chainId: number, tokenAddress: string): Promise<TokenMarketData | null> {
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return null;
try {
const result = await this.pool.query(
`SELECT chain_id, token_address, price_usd, price_change_24h, volume_24h, volume_7d, volume_30d,
@@ -49,7 +58,7 @@ export class MarketDataRepository {
lastUpdated: row.last_updated,
};
} catch (error) {
if (this.isMissingRelationError(error)) {
if (this.shouldUseReadFallback(error)) {
return null;
}
throw error;
@@ -99,6 +108,7 @@ export class MarketDataRepository {
}
async getTopTokensByVolume(chainId: number, limit: number = 50): Promise<TokenMarketData[]> {
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
try {
const result = await this.pool.query(
`SELECT chain_id, token_address, price_usd, price_change_24h, volume_24h, volume_7d, volume_30d,
@@ -125,7 +135,7 @@ export class MarketDataRepository {
lastUpdated: row.last_updated,
}));
} catch (error) {
if (this.isMissingRelationError(error)) {
if (this.shouldUseReadFallback(error)) {
return [];
}
throw error;
@@ -133,6 +143,7 @@ export class MarketDataRepository {
}
async getTopTokensByLiquidity(chainId: number, limit: number = 50): Promise<TokenMarketData[]> {
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
try {
const result = await this.pool.query(
`SELECT chain_id, token_address, price_usd, price_change_24h, volume_24h, volume_7d, volume_30d,
@@ -159,7 +170,7 @@ export class MarketDataRepository {
lastUpdated: row.last_updated,
}));
} catch (error) {
if (this.isMissingRelationError(error)) {
if (this.shouldUseReadFallback(error)) {
return [];
}
throw error;

View File

@@ -53,7 +53,16 @@ export class PoolRepository {
return code === '42P01' || message.includes('relation "') && message.includes('" does not exist');
}
private shouldUseReadFallback(error: unknown): boolean {
if (this.isMissingRelationError(error)) return true;
if (String(process.env.TOKEN_AGGREGATION_DB_READ_FALLBACK ?? '1').toLowerCase() === '0') return false;
const message = (error as { message?: string })?.message || '';
const code = (error as { code?: string })?.code || '';
return ['ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND'].includes(code) || /timeout|connect/i.test(message);
}
async getPool(chainId: number, poolAddress: string): Promise<LiquidityPool | null> {
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return null;
try {
const result = await this.pool.query(
`SELECT id, chain_id, pool_address, token0_address, token1_address, dex_type,
@@ -70,7 +79,7 @@ export class PoolRepository {
return this.mapRowToPool(result.rows[0]);
} catch (error) {
if (this.isMissingRelationError(error)) {
if (this.shouldUseReadFallback(error)) {
return null;
}
throw error;
@@ -78,6 +87,7 @@ export class PoolRepository {
}
async getPoolsByChain(chainId: number, limit: number = 500): Promise<LiquidityPool[]> {
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
try {
const result = await this.pool.query(
`SELECT id, chain_id, pool_address, token0_address, token1_address, dex_type,
@@ -91,7 +101,7 @@ export class PoolRepository {
);
return result.rows.map((row) => this.mapRowToPool(row));
} catch (error) {
if (this.isMissingRelationError(error)) {
if (this.shouldUseReadFallback(error)) {
return [];
}
throw error;
@@ -99,6 +109,7 @@ export class PoolRepository {
}
async getPoolsByToken(chainId: number, tokenAddress: string): Promise<LiquidityPool[]> {
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
try {
const result = await this.pool.query(
`SELECT id, chain_id, pool_address, token0_address, token1_address, dex_type,
@@ -112,7 +123,7 @@ export class PoolRepository {
return result.rows.map((row) => this.mapRowToPool(row));
} catch (error) {
if (this.isMissingRelationError(error)) {
if (this.shouldUseReadFallback(error)) {
return [];
}
throw error;

View File

@@ -46,7 +46,16 @@ export class TokenRepository {
return code === '42P01' || (message.includes('relation "') && message.includes('" does not exist'));
}
private shouldUseReadFallback(error: unknown): boolean {
if (this.isMissingRelationError(error)) return true;
if (String(process.env.TOKEN_AGGREGATION_DB_READ_FALLBACK ?? '1').toLowerCase() === '0') return false;
const message = (error as { message?: string })?.message || '';
const code = (error as { code?: string })?.code || '';
return ['ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND'].includes(code) || /timeout|connect/i.test(message);
}
async getToken(chainId: number, address: string): Promise<Token | null> {
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return null;
try {
const result = await this.pool.query(
`SELECT chain_id, address, name, symbol, decimals, total_supply, logo_url, website_url, description, verified
@@ -73,7 +82,7 @@ export class TokenRepository {
verified: row.verified,
};
} catch (error) {
if (this.isMissingRelationError(error)) {
if (this.shouldUseReadFallback(error)) {
return null;
}
throw error;
@@ -81,6 +90,7 @@ export class TokenRepository {
}
async getTokens(chainId: number, limit: number = 50, offset: number = 0): Promise<Token[]> {
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
try {
const result = await this.pool.query(
`SELECT chain_id, address, name, symbol, decimals, total_supply, logo_url, website_url, description, verified
@@ -104,7 +114,7 @@ export class TokenRepository {
verified: row.verified,
}));
} catch (error) {
if (this.isMissingRelationError(error)) {
if (this.shouldUseReadFallback(error)) {
return [];
}
throw error;

View File

@@ -3,6 +3,7 @@ import { Contract, JsonRpcProvider } from 'ethers';
const POOL_ABI = [
'function _BASE_TOKEN_() view returns (address)',
'function _QUOTE_TOKEN_() view returns (address)',
'function getVaultReserve() view returns (uint256,uint256)',
'function querySellBase(address,uint256) view returns (uint256,uint256)',
'function querySellQuote(address,uint256) view returns (uint256,uint256)',
];
@@ -40,6 +41,25 @@ export async function pmmQuoteAmountOutFromChain(params: {
}
}
/** Best-effort reserve read for DODO-style PMM/DVM pools. */
export async function pmmVaultReserveFromChain(params: {
rpcUrl: string;
poolAddress: string;
}): Promise<{ baseReserveRaw: bigint; quoteReserveRaw: bigint } | null> {
const { rpcUrl, poolAddress } = params;
try {
const provider = new JsonRpcProvider(rpcUrl);
const pool = new Contract(poolAddress, POOL_ABI, provider);
const [baseReserve, quoteReserve] = await pool.getVaultReserve();
return {
baseReserveRaw: BigInt(baseReserve.toString()),
quoteReserveRaw: BigInt(quoteReserve.toString()),
};
} catch {
return null;
}
}
/** RPC for PMM eth_call quotes on Chain 138 (optional; unset = skip on-chain override). */
export function resolvePmmQuoteRpcUrl(): string {
return (