fix(token-aggregation): normalize inflated liquidityUsd; token-price report

- Apply decimal-aware liquidity normalization on /tokens and reports
- Add GET /report/token-price/:symbol compact evidence snapshot
- Extend route tests for normalization and token-price endpoint

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-11 12:55:07 -07:00
parent 76143a8fe3
commit 21578e1a13
4 changed files with 307 additions and 3 deletions

View File

@@ -15,7 +15,20 @@ jest.mock('../../database/repositories/token-repo', () => ({
}));
jest.mock('../../database/repositories/market-data-repo', () => ({
MarketDataRepository: jest.fn().mockImplementation(() => ({
getMarketData: jest.fn().mockResolvedValue(null),
getMarketData: jest.fn().mockImplementation(async (chainId: number, address: string) => {
if (chainId === 138 && String(address).toLowerCase() === '0xf22258f57794cc8e06237084b353ab30fffa640b') {
return {
priceUsd: 1,
volume24h: 0,
volume7d: 0,
volume30d: 0,
marketCapUsd: undefined,
liquidityUsd: 5180095723066127,
lastUpdated: new Date('2026-05-10T01:43:11.733Z'),
};
}
return null;
}),
})),
}));
jest.mock('../../database/repositories/pool-repo', () => ({
@@ -180,6 +193,71 @@ describe('Report API', () => {
});
});
describe('GET /api/v1/report/token-price/:symbol', () => {
it('returns a compact cWUSDC price evidence packet for reviewers', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/token-price/cWUSDC?chainId=1`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
expect(body).toMatchObject({
schema: 'dbis-token-price-evidence/v1',
chainId: 1,
token: {
address: '0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a',
symbol: 'cWUSDC',
decimals: 6,
},
price: {
usd: 1,
source: 'token-aggregation',
},
supply: {
totalSupply: 10451316981.309788,
totalSupplyRaw: '10451316981309788',
circulatingSupply: 10451316981.309788,
proof: expect.objectContaining({
schema: 'mainnet-cwusdc-supply-proof/v1',
}),
},
valuation: {
marketCapUsd: 10451316981.309788,
},
submissionLinks: {
coingeckoReport: expect.stringContaining('/api/v1/report/coingecko?chainId=1'),
cmcReport: expect.stringContaining('/api/v1/report/cmc?chainId=1'),
tokenList: expect.stringContaining('/api/v1/report/token-list?chainId=1'),
etherscan: 'https://etherscan.io/token/0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a',
},
});
expect(body.price.caveat).toContain('Etherscan USD Value');
expect(Array.isArray(body.pools)).toBe(true);
});
it('normalizes raw 6-decimal Chain 138 liquidity in compact price evidence', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/token-price/cUSDC?chainId=138`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
expect(body).toMatchObject({
chainId: 138,
token: {
symbol: 'cUSDC',
decimals: 6,
},
price: {
usd: 1,
},
});
expect(body.valuation.liquidityUsd).toBeLessThan(38601011267);
expect(body.valuation.liquidityUsd).toBeCloseTo(5180095723.066127, 6);
});
it('returns 404 for an unknown token symbol', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/token-price/NOPE?chainId=1`);
expect(res.status).toBe(404);
});
});
describe('GET /api/v1/report/all', () => {
it('includes GRU transport summary for operator visibility', async () => {
const res = await fetch(`${baseUrl}/api/v1/report/all?chainId=138`);

View File

@@ -363,6 +363,39 @@ function buildSupplyProofEnrichment(
};
}
function normalizePossiblyRawLiquidityUsd(
liquidityUsd: number | undefined,
decimals: number,
priceUsd: number | undefined,
totalSupplyUnits: string | undefined
): number | undefined {
if (liquidityUsd === undefined || !Number.isFinite(liquidityUsd) || liquidityUsd <= 0) return liquidityUsd;
if (priceUsd === undefined || !Number.isFinite(priceUsd) || priceUsd <= 0) return liquidityUsd;
if (!totalSupplyUnits) {
if (decimals === 6 && liquidityUsd >= 1_000_000_000_000 && priceUsd <= 10) {
return liquidityUsd / 10 ** decimals;
}
return liquidityUsd;
}
const supplyUnits = Number(totalSupplyUnits);
if (!Number.isFinite(supplyUnits) || supplyUnits <= 0) return liquidityUsd;
const normalizedSupplyValue = supplyUnits * priceUsd;
const divisor = 10 ** decimals;
const decimalAdjustedLiquidity = liquidityUsd / divisor;
if (
liquidityUsd > normalizedSupplyValue &&
decimalAdjustedLiquidity > 0 &&
decimalAdjustedLiquidity <= normalizedSupplyValue * 1.25
) {
return decimalAdjustedLiquidity;
}
return liquidityUsd;
}
function resolveGruV2ReserveRpcUrl(chainId: number): string {
const configured = resolvePmmQuoteRpcUrl();
if (chainId === 138 && process.env.NODE_ENV !== 'test') return configured || 'http://192.168.11.211:8545';
@@ -650,6 +683,11 @@ async function buildTokenReport(chainId: number) {
if (supplyProof?.marketCapUsd !== undefined && market) {
market.marketCapUsd = supplyProof.marketCapUsd;
}
if (market) {
market.liquidityUsd =
normalizePossiblyRawLiquidityUsd(market.liquidityUsd, spec.decimals, market.priceUsd, supplyProof?.totalSupply) ??
market.liquidityUsd;
}
const dbPoolEntries: ReportPoolEntry[] = resolvedPools.map((p) => ({
poolAddress: p.poolAddress,
@@ -1249,6 +1287,107 @@ router.get(
}
);
/** GET /report/token-price/:symbol — compact reviewer-facing price and evidence snapshot. */
router.get(
'/token-price/:symbol',
cacheMiddleware(60 * 1000),
async (req: Request, res: Response) => {
try {
const symbol = String(req.params.symbol || '').trim();
const chainId = parseInt(req.query.chainId as string, 10) || 1;
const tokens = await buildTokenReport(chainId);
const token = tokens.find((entry) => entry.symbol.toLowerCase() === symbol.toLowerCase());
if (!token) {
res.status(404).json({
error: 'Token not found',
symbol,
chainId,
});
return;
}
const poolLiquidityUsd = token.pools.reduce((sum, pool) => sum + (pool.tvl || 0), 0);
const marketLiquidityUsd = token.market?.liquidityUsd ?? 0;
const liquidityUsd = marketLiquidityUsd > 0 ? marketLiquidityUsd : poolLiquidityUsd;
const priceUsd = token.market?.priceUsd;
const circulatingSupply = token.circulatingSupply ? Number(token.circulatingSupply) : undefined;
const totalSupply = token.totalSupply ? Number(token.totalSupply) : undefined;
const marketCapUsd =
token.market?.marketCapUsd ??
(priceUsd !== undefined && circulatingSupply !== undefined ? priceUsd * circulatingSupply : undefined);
res.json({
generatedAt: new Date().toISOString(),
schema: 'dbis-token-price-evidence/v1',
chainId,
token: {
address: token.address,
symbol: token.symbol,
name: token.name,
decimals: token.decimals,
type: token.type,
registryFamily: token.registryFamily,
logoURI: token.logoURI,
},
price: {
usd: priceUsd,
source: token.market ? 'token-aggregation' : priceUsd !== undefined ? 'canonical-fallback' : 'unavailable',
lastUpdated: token.market?.lastUpdated,
caveat:
chainId === 1 && token.symbol === 'cWUSDC'
? 'This is DBIS tracker-submission evidence. Etherscan USD Value appears only after Etherscan/CoinGecko/Dex indexers accept a public price source.'
: undefined,
},
supply: {
totalSupply,
totalSupplyRaw: token.totalSupplyRaw,
circulatingSupply,
circulatingSupplyFormula: token.circulatingSupplyFormula,
proof: token.supplyProofProvenance,
caveats: token.trackerCaveats ?? [],
},
valuation: {
marketCapUsd,
liquidityUsd,
volume24hUsd: token.market?.volume24h ?? 0,
},
pools: token.pools.map((pool) => ({
poolAddress: pool.poolAddress,
dexId: pool.dex,
tvlUsd: pool.tvl,
volume24hUsd: pool.volume24h,
source: pool.source,
status: pool.status,
statusReason: pool.statusReason,
role: pool.role,
publicRoutingEnabled: pool.publicRoutingEnabled,
token0: {
address: pool.token0,
symbol: pool.token0Symbol,
},
token1: {
address: pool.token1,
symbol: pool.token1Symbol,
},
})),
submissionLinks: {
coingeckoReport: `${resolvePublicBaseUrl(req)}/api/v1/report/coingecko?chainId=${chainId}`,
cmcReport: `${resolvePublicBaseUrl(req)}/api/v1/report/cmc?chainId=${chainId}`,
tokenList: `${resolvePublicBaseUrl(req)}/api/v1/report/token-list?chainId=${chainId}`,
etherscan:
chainId === 1
? `https://etherscan.io/token/${token.address}`
: undefined,
},
});
} catch (error) {
logger.error('Error building report/token-price:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
);
/** GET /report/token-list — flat list of all canonical tokens (Uniswap token list format with logoURI).
* If TOKEN_LIST_JSON_URL is set (e.g. GitHub raw URL), fetches and returns that JSON; optional ?chainId= filters tokens.
*/

View File

@@ -222,6 +222,47 @@ describe('Tokens API', () => {
expect(body.token.canonicalLiquidity).toBeUndefined();
});
it('normalizes raw 6-decimal liquidity rows on token detail responses', async () => {
const cusdc = getCanonicalTokenBySymbol(138, 'cUSDC');
expect(cusdc?.addresses[138]).toBeTruthy();
const cusdcAddress = String(cusdc?.addresses[138]).toLowerCase();
mockGetToken.mockResolvedValue({
chainId: 138,
address: cusdcAddress,
name: 'USD Coin (Compliant)',
symbol: 'cUSDC',
decimals: 6,
totalSupply: '38601011267000000',
verified: true,
});
mockGetMarketData.mockResolvedValue({
chainId: 138,
tokenAddress: cusdcAddress,
priceUsd: 1,
volume24h: 0,
volume7d: 0,
volume30d: 26890.7,
liquidityUsd: 5180095723066127,
holdersCount: 0,
transfers24h: 0,
lastUpdated: new Date('2026-05-10T01:43:11.733Z'),
});
const res = await fetch(`${baseUrl}/api/v1/tokens/${cusdcAddress}?chainId=138`);
expect(res.status).toBe(200);
const body = (await res.json()) as Record<string, any>;
expect(body.token).toMatchObject({
symbol: 'cUSDC',
decimals: 6,
market: expect.objectContaining({
priceUsd: 1,
liquidityUsd: 5180095723.066127,
}),
});
});
it('returns historical price snapshots for a token at a requested timestamp', async () => {
const weth = getCanonicalTokenBySymbol(138, 'WETH');
expect(weth?.addresses[138]).toBeTruthy();

View File

@@ -106,6 +106,52 @@ function buildMarketPricingExplorer(
return { market, pricing, explorer };
}
function decimalStringToNumber(value?: string, decimals?: number): number | null {
if (!value || decimals === undefined || decimals < 0) return null;
try {
const raw = BigInt(value);
if (raw <= 0n) return 0;
const scale = 10n ** BigInt(decimals);
const whole = raw / scale;
const fraction = raw % scale;
const normalized = Number(whole) + Number(fraction) / Number(scale);
return Number.isFinite(normalized) ? normalized : null;
} catch {
return null;
}
}
function normalizePossiblyRawLiquidityUsd<T extends { liquidityUsd: number; priceUsd?: number }>(
market: T | null,
token: Token
): T | null {
if (!market || !(market.liquidityUsd > 0)) return market;
const decimals = Number(token.decimals);
if (!Number.isInteger(decimals) || decimals <= 0) return market;
const supplyUnits = decimalStringToNumber(token.totalSupply, decimals);
const priceUsd = market.priceUsd && market.priceUsd > 0 ? market.priceUsd : 1;
if (supplyUnits === null || supplyUnits <= 0) return market;
const plausibleSupplyValue = supplyUnits * priceUsd;
const scale = 10 ** decimals;
const normalizedLiquidity = market.liquidityUsd / scale;
if (
Number.isFinite(plausibleSupplyValue) &&
Number.isFinite(normalizedLiquidity) &&
market.liquidityUsd > plausibleSupplyValue &&
normalizedLiquidity <= plausibleSupplyValue
) {
return {
...market,
liquidityUsd: normalizedLiquidity,
};
}
return market;
}
function tokenFromCanonical(chainId: number, address: string): Token | null {
const spec = getCanonicalTokenByAddress(chainId, address.toLowerCase());
if (!spec) {
@@ -456,7 +502,7 @@ router.get('/tokens', cacheMiddleware(60 * 1000), async (req: Request, res: Resp
);
const out: Record<string, unknown> = {
...token,
market: market || undefined,
market: normalizePossiblyRawLiquidityUsd(market, token) || undefined,
pricing,
explorer,
};
@@ -534,7 +580,7 @@ router.get('/tokens/:address', cacheMiddleware(60 * 1000), async (req: Request,
onChain: {
totalSupply: token.totalSupply,
},
market: marketData || undefined,
market: normalizePossiblyRawLiquidityUsd(marketData, token) || undefined,
pricing,
explorer,
external: {