3
3
crate :: {
4
4
env:: DuneConfig ,
5
5
error:: { RpcError , RpcResult } ,
6
- handlers:: balance:: { BalanceQueryParams , BalanceResponseBody } ,
6
+ handlers:: balance:: {
7
+ get_cached_metadata, set_cached_metadata, BalanceQueryParams , BalanceResponseBody ,
8
+ TokenMetadataCacheItem , H160_EMPTY_ADDRESS ,
9
+ } ,
7
10
providers:: {
8
11
balance:: { BalanceItem , BalanceQuantity } ,
9
12
ProviderKind ,
10
13
} ,
14
+ storage:: KeyValueStorage ,
11
15
utils:: crypto,
12
16
Metrics ,
13
17
} ,
@@ -153,6 +157,7 @@ impl BalanceProvider for DuneProvider {
153
157
& self ,
154
158
address : String ,
155
159
params : BalanceQueryParams ,
160
+ metadata_cache : & Option < Arc < dyn KeyValueStorage < TokenMetadataCacheItem > > > ,
156
161
metrics : Arc < Metrics > ,
157
162
) -> RpcResult < BalanceResponseBody > {
158
163
let namespace = params
@@ -175,76 +180,124 @@ impl BalanceProvider for DuneProvider {
175
180
}
176
181
} ;
177
182
178
- let balances_vec = balance_response
179
- . balances
180
- . into_iter ( )
181
- . filter_map ( |mut f| {
182
- // Skip the asset if there are no symbol, decimals, since this
183
- // is likely a spam token
184
- let symbol = f. symbol . take ( ) ?;
185
- let price_usd = f. price_usd . take ( ) ?;
186
- let decimals = f. decimals . take ( ) ?;
187
- let caip2_chain_id = match f. chain_id {
188
- Some ( cid) => format ! ( "{}:{}" , namespace, cid) ,
189
- None => match namespace {
190
- // Using defaul Mainnet chain ids if not provided since
191
- // Dune doesn't provide balances for testnets
192
- crypto:: CaipNamespaces :: Eip155 => format ! ( "{}:{}" , namespace, "1" ) ,
193
- crypto:: CaipNamespaces :: Solana => {
194
- format ! ( "{}:{}" , namespace, "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" )
183
+ let mut balances_vec = Vec :: new ( ) ;
184
+ for f in balance_response. balances {
185
+ // Skip if missing required fields as a possible spam token
186
+ let ( Some ( symbol) , Some ( price_usd) , Some ( decimals) ) =
187
+ ( f. symbol , f. price_usd , f. decimals )
188
+ else {
189
+ continue ;
190
+ } ;
191
+
192
+ // Build a CAIP-2 chain ID
193
+ let caip2_chain_id = match f. chain_id {
194
+ Some ( cid) => format ! ( "{}:{}" , namespace, cid) ,
195
+ None => match namespace {
196
+ // Use default Mainnet chain IDs if not provided
197
+ crypto:: CaipNamespaces :: Eip155 => format ! ( "{}:1" , namespace) ,
198
+ crypto:: CaipNamespaces :: Solana => {
199
+ format ! ( "{}:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" , namespace)
200
+ }
201
+ } ,
202
+ } ;
203
+
204
+ // Determine name
205
+ let name = if f. address == "native" {
206
+ f. chain . clone ( )
207
+ } else {
208
+ symbol. clone ( )
209
+ } ;
210
+
211
+ // Determine icon URL
212
+ let icon_url = if f. address == "native" {
213
+ NATIVE_TOKEN_ICONS . get ( & symbol) . unwrap_or ( & "" ) . to_string ( )
214
+ } else {
215
+ // If there's no token_metadata or no logo, skip
216
+ match & f. token_metadata {
217
+ Some ( m) => m. logo . clone ( ) ,
218
+ None => continue ,
219
+ }
220
+ } ;
221
+
222
+ // Build the CAIP-10 address
223
+ let caip10_token_address_strict = if f. address == "native" {
224
+ match namespace {
225
+ crypto:: CaipNamespaces :: Eip155 => {
226
+ format ! ( "{}:{}" , caip2_chain_id, H160_EMPTY_ADDRESS )
227
+ }
228
+ crypto:: CaipNamespaces :: Solana => {
229
+ format ! ( "{}:{}" , caip2_chain_id, crypto:: SOLANA_NATIVE_TOKEN_ADDRESS )
230
+ }
231
+ }
232
+ } else {
233
+ format ! ( "{}:{}" , caip2_chain_id, f. address)
234
+ } ;
235
+
236
+ // Get token metadata from the cache or update it
237
+ let token_metadata =
238
+ match get_cached_metadata ( metadata_cache, & caip10_token_address_strict) . await {
239
+ Some ( cached) => cached,
240
+ None => {
241
+ let new_item = TokenMetadataCacheItem {
242
+ name : name. clone ( ) ,
243
+ symbol : symbol. clone ( ) ,
244
+ icon_url : icon_url. clone ( ) ,
245
+ } ;
246
+ // Spawn a background task to set the cache without blocking
247
+ {
248
+ let metadata_cache = metadata_cache. clone ( ) ;
249
+ let address_key = caip10_token_address_strict. clone ( ) ;
250
+ let new_item_to_store = new_item. clone ( ) ;
251
+ tokio:: spawn ( async move {
252
+ set_cached_metadata (
253
+ & metadata_cache,
254
+ & address_key,
255
+ & new_item_to_store,
256
+ )
257
+ . await ;
258
+ } ) ;
195
259
}
196
- } ,
260
+ new_item
261
+ }
197
262
} ;
198
- Some ( BalanceItem {
199
- name : {
200
- if f. address == "native" {
201
- f. chain
202
- } else {
203
- symbol. clone ( )
204
- }
205
- } ,
206
- symbol : symbol. clone ( ) ,
207
- chain_id : Some ( caip2_chain_id. clone ( ) ) ,
208
- address : {
209
- // Return None if the address is native for the native token
210
- if f. address == "native" {
211
- // UI expecting `None`` for the Eip155 and Solana's native
212
- // token address for Solana
213
- match namespace {
214
- crypto:: CaipNamespaces :: Eip155 => None ,
215
- crypto:: CaipNamespaces :: Solana => {
216
- Some ( crypto:: SOLANA_NATIVE_TOKEN_ADDRESS . to_string ( ) )
217
- }
263
+
264
+ // Construct the final BalanceItem
265
+ let balance_item = BalanceItem {
266
+ name : token_metadata. name ,
267
+ symbol : token_metadata. symbol ,
268
+ chain_id : Some ( caip2_chain_id. clone ( ) ) ,
269
+ address : {
270
+ // Return None if the address is native (for EIP-155).
271
+ // For Solana’s “native” token, we return the Solana native token address.
272
+ if f. address == "native" {
273
+ match namespace {
274
+ crypto:: CaipNamespaces :: Eip155 => None ,
275
+ crypto:: CaipNamespaces :: Solana => {
276
+ Some ( crypto:: SOLANA_NATIVE_TOKEN_ADDRESS . to_string ( ) )
218
277
}
219
- } else {
220
- Some ( format ! ( "{}:{}" , caip2_chain_id, f. address. clone( ) ) )
221
- }
222
- } ,
223
- value : f. value_usd ,
224
- price : price_usd,
225
- quantity : BalanceQuantity {
226
- decimals : decimals. to_string ( ) ,
227
- numeric : crypto:: format_token_amount (
228
- U256 :: from_dec_str ( & f. amount ) . unwrap_or_default ( ) ,
229
- decimals,
230
- ) ,
231
- } ,
232
- icon_url : {
233
- if f. address == "native" {
234
- NATIVE_TOKEN_ICONS . get ( & symbol) . unwrap_or ( & "" ) . to_string ( )
235
- } else {
236
- f. token_metadata ?. logo
237
278
}
238
- } ,
239
- } )
240
- } )
241
- . collect :: < Vec < _ > > ( ) ;
279
+ } else {
280
+ Some ( format ! ( "{}:{}" , caip2_chain_id, f. address) )
281
+ }
282
+ } ,
283
+ value : f. value_usd ,
284
+ price : price_usd,
285
+ quantity : BalanceQuantity {
286
+ decimals : decimals. to_string ( ) ,
287
+ numeric : crypto:: format_token_amount (
288
+ U256 :: from_dec_str ( & f. amount ) . unwrap_or_default ( ) ,
289
+ decimals,
290
+ ) ,
291
+ } ,
292
+ icon_url : token_metadata. icon_url ,
293
+ } ;
242
294
243
- let response = BalanceResponseBody {
244
- balances : balances_vec,
245
- } ;
295
+ balances_vec. push ( balance_item) ;
296
+ }
246
297
247
- Ok ( response)
298
+ Ok ( BalanceResponseBody {
299
+ balances : balances_vec,
300
+ } )
248
301
}
249
302
}
250
303
0 commit comments