@@ -4,6 +4,13 @@ import {
44 beetsGetCirculatingSupplySonic ,
55 beetsGetTotalSupplySonic ,
66} from '../../modules/beets/lib/beets' ;
7+ import { latestTokenPrice } from '../../modules/token/latest-token-price' ;
8+ import config from '../../config' ;
9+ import { Chain } from '@prisma/client' ;
10+ import * as crypto from 'crypto' ;
11+
12+ const isHexAddress = ( addr : any ) =>
13+ typeof addr === 'string' && addr . length === 42 && addr . startsWith ( '0x' ) && / ^ [ 0 - 9 a - f ] { 40 } $ / i. test ( addr . slice ( 2 ) ) ;
714
815export function loadRestRoutes ( app : Express ) {
916 app . use ( '/health' , ( _ , res ) => res . sendStatus ( 200 ) ) ;
@@ -22,4 +29,48 @@ export function loadRestRoutes(app: Express) {
2229 res . send ( result ) ;
2330 } ) ;
2431 } ) ;
32+
33+ app . get ( '/price' , async ( req , res ) => {
34+ res . type ( 'application/json' ) ;
35+
36+ const chain = req . query . chain ;
37+ const tokens = req . query . tokens && ( req . query . tokens as string ) . split ( ',' ) ;
38+
39+ // Validate params
40+ if ( typeof chain !== 'string' || ! ( chain in config ) ) {
41+ res . status ( 400 ) . end ( ) ;
42+ return ;
43+ }
44+
45+ if ( ! Array . isArray ( tokens ) || tokens . length === 0 || ! tokens . every ( isHexAddress ) || tokens . length > 8 ) {
46+ res . status ( 400 ) . end ( ) ;
47+ return ;
48+ }
49+
50+ const prices = await latestTokenPrice ( chain as Chain , tokens as string [ ] ) ;
51+
52+ // Build response body
53+ const responseBody = { prices } ;
54+ const bodyString = JSON . stringify ( responseBody ) ;
55+
56+ // Generate strong ETag: MD5 hash of the body (fast for small JSON)
57+ const etag = crypto . createHash ( 'md5' ) . update ( bodyString ) . digest ( 'hex' ) ;
58+
59+ // Set ETag header
60+ res . set ( 'ETag' , `"${ etag } "` ) ;
61+
62+ // Check for conditional request
63+ const clientEtag = req . get ( 'If-None-Match' ) ;
64+ if ( clientEtag === `"${ etag } "` || clientEtag === etag ) {
65+ // Handle quoted/unquoted client headers
66+ res . status ( 304 ) . end ( ) ; // Not modified: No body needed
67+ return ;
68+ }
69+
70+ // Set caching headers (unchanged)
71+ res . set ( 'Cache-Control' , 'public, max-age=600, s-maxage=600, stale-while-revalidate=30, stale-if-error=86400' ) ;
72+
73+ // Send full response
74+ res . send ( responseBody ) ;
75+ } ) ;
2576}
0 commit comments