@@ -12,15 +12,25 @@ let cache: { prices: Record<string, number>; fetchedAt: number } | null = null;
1212const CACHE_TTL_MS = 60_000 ;
1313
1414const COINGECKO_URL =
15- "https://api.coingecko.com/api/v3/simple/price?ids=ethereum,bitcoin,solana,tron&vs_currencies=usd" ;
15+ "https://api.coingecko.com/api/v3/simple/price?ids=ethereum,bitcoin,solana,tron,avalanche-2,binancecoin,matic-network,mantle,celo,fantom &vs_currencies=usd" ;
1616
1717const ID_TO_SYMBOL : Record < string , string > = {
1818 ethereum : "ETH" ,
1919 bitcoin : "BTC" ,
2020 solana : "SOL" ,
2121 tron : "TRX" ,
22+ "avalanche-2" : "AVAX" ,
23+ binancecoin : "BNB" ,
24+ "matic-network" : "POL" ,
25+ mantle : "MNT" ,
26+ celo : "CELO" ,
27+ fantom : "FTM" ,
2228} ;
2329
30+ // --- Token contract price cache ---
31+
32+ let tokenPriceCache : Record < string , { price : number ; fetchedAt : number } > = { } ;
33+
2434// --- Price fetching ---
2535
2636export async function getUsdPrices ( ) : Promise < Record < string , number > > {
@@ -46,6 +56,7 @@ export async function getUsdPrices(): Promise<Record<string, number>> {
4656 const prices : Record < string , number > = {
4757 USDC : 1 ,
4858 USDT : 1 ,
59+ xDAI : 1 , // Gnosis chain native token is a stablecoin
4960 } ;
5061
5162 for ( const [ id , symbol ] of Object . entries ( ID_TO_SYMBOL ) ) {
@@ -59,11 +70,53 @@ export async function getUsdPrices(): Promise<Record<string, number>> {
5970 return prices ;
6071}
6172
73+ // --- Token price by contract address ---
74+
75+ export async function getTokenPriceByContract (
76+ coingeckoPlatformId : string ,
77+ contractAddress : string ,
78+ ) : Promise < number | null > {
79+ const cacheKey = `${ coingeckoPlatformId } :${ contractAddress . toLowerCase ( ) } ` ;
80+ const now = Date . now ( ) ;
81+ const cached = tokenPriceCache [ cacheKey ] ;
82+ if ( cached && now - cached . fetchedAt < CACHE_TTL_MS ) {
83+ return cached . price ;
84+ }
85+
86+ const addr = contractAddress . toLowerCase ( ) ;
87+ const url = `https://api.coingecko.com/api/v3/simple/token_price/${ coingeckoPlatformId } ?contract_addresses=${ addr } &vs_currencies=usd` ;
88+
89+ try {
90+ const res = await fetch ( url ) ;
91+ if ( ! res . ok ) {
92+ // Return cached value if available
93+ if ( cached ) return cached . price ;
94+ return null ;
95+ }
96+
97+ const data = ( await res . json ( ) ) as Record < string , { usd ?: number } > ;
98+ const price = data [ addr ] ?. usd ;
99+
100+ if ( price == null ) {
101+ return null ;
102+ }
103+
104+ tokenPriceCache [ cacheKey ] = { price, fetchedAt : now } ;
105+ return price ;
106+ } catch {
107+ // Return cached value if available
108+ if ( cached ) return cached . price ;
109+ return null ;
110+ }
111+ }
112+
62113// --- USD conversion ---
63114
64115export async function toUsdCents (
65116 token : string ,
66117 amount : string ,
118+ contractAddress ?: string ,
119+ coingeckoPlatformId ?: string ,
67120) : Promise < number > {
68121 const normalizedToken = token . toUpperCase ( ) . trim ( ) ;
69122 const parsedAmount = parseFloat ( amount ) ;
@@ -73,19 +126,31 @@ export async function toUsdCents(
73126 }
74127
75128 // Stablecoins are always $1
76- if ( normalizedToken === "USDC" || normalizedToken === "USDT" ) {
129+ if ( normalizedToken === "USDC" || normalizedToken === "USDT" || normalizedToken === "XDAI" ) {
77130 return Math . round ( parsedAmount * 100 ) ;
78131 }
79132
133+ // Try known symbols first (native tokens)
80134 const prices = await getUsdPrices ( ) ;
81- const price = prices [ normalizedToken ] ;
135+ const price = prices [ normalizedToken ] ?? prices [ token ] ; // try exact case too (e.g. "xDAI")
82136
83- if ( price == null ) {
84- throw new Error (
85- `No price available for token "${ token } ". Supported: ETH, BTC, SOL, TRX, USDC, USDT` ,
86- ) ;
137+ if ( price != null ) {
138+ const usd = parsedAmount * price ;
139+ return Math . round ( usd * 100 ) ;
140+ }
141+
142+ // Try contract address lookup via CoinGecko
143+ if ( contractAddress && coingeckoPlatformId ) {
144+ const contractPrice = await getTokenPriceByContract ( coingeckoPlatformId , contractAddress ) ;
145+ if ( contractPrice != null ) {
146+ const usd = parsedAmount * contractPrice ;
147+ return Math . round ( usd * 100 ) ;
148+ }
87149 }
88150
89- const usd = parsedAmount * price ;
90- return Math . round ( usd * 100 ) ;
151+ throw new Error (
152+ `No price available for token "${ token } "${ contractAddress ? ` (contract: ${ contractAddress } )` : "" } . ` +
153+ `Supported symbols: ${ Object . values ( ID_TO_SYMBOL ) . join ( ", " ) } , USDC, USDT. ` +
154+ `For other tokens, provide contractAddress and coingeckoPlatformId.` ,
155+ ) ;
91156}
0 commit comments