@@ -13,6 +13,8 @@ import { getGasToken } from 'utils';
13
13
import type { Chain , ChainId } from '@wormhole-foundation/sdk' ;
14
14
import type { Transaction } from 'config/types' ;
15
15
import { toFixedDecimals } from 'utils/balance' ;
16
+ import { useTokens } from 'contexts/TokensContext' ;
17
+ import { Token } from 'config/tokens' ;
16
18
17
19
interface WormholeScanTransaction {
18
20
id : string ;
@@ -115,116 +117,135 @@ const useTransactionHistoryWHScan = (
115
117
const [ error , setError ] = useState ( '' ) ;
116
118
const [ isFetching , setIsFetching ] = useState ( false ) ;
117
119
const [ hasMore , setHasMore ] = useState ( true ) ;
120
+ const { getOrFetchToken } = useTokens ( ) ;
118
121
119
122
const { address, page = 0 , pageSize = 30 } = props ;
120
123
121
124
// Common parsing logic for a single transaction from WHScan API.
122
125
// IMPORTANT: Anything specific to a route, please use that route's parser:
123
126
// parseTokenBridgeTx | parseNTTTx | parseCCTPTx | parsePorticoTx
124
- const parseSingleTx = useCallback ( ( tx : WormholeScanTransaction ) => {
125
- const { content, data, sourceChain, targetChain } = tx ;
126
- const { tokenAmount, usdAmount } = data || { } ;
127
- const { standarizedProperties } = content || { } ;
128
-
129
- const fromChainId = standarizedProperties . fromChain || sourceChain ?. chainId ;
130
- const toChainId = standarizedProperties . toChain || targetChain ?. chainId ;
131
- const tokenChainId = standarizedProperties . tokenChain ;
132
-
133
- const fromChain = chainIdToChain ( fromChainId ) ;
134
-
135
- // Skip if we don't have the source chain
136
- if ( ! fromChain ) {
137
- return ;
138
- }
139
-
140
- const tokenChain = chainIdToChain ( tokenChainId ) ;
141
-
142
- // Skip if we don't have the token chain
143
- if ( ! tokenChain ) {
144
- return ;
145
- }
146
-
147
- let token = config . tokens . get (
148
- tokenChain ,
149
- standarizedProperties . tokenAddress ,
150
- ) ;
151
-
152
- if ( ! token ) {
153
- // IMPORTANT:
154
- // If we don't have the token config from the token address,
155
- // we can check if we can use the symbol to get it.
156
- // So far this case is only for SUI and APT
157
- const foundBySymbol =
158
- data ?. symbol && config . tokens . findBySymbol ( tokenChain , data . symbol ) ;
159
- if ( foundBySymbol ) {
160
- token = foundBySymbol ;
127
+ const parseSingleTx = useCallback (
128
+ async ( tx : WormholeScanTransaction ) => {
129
+ const { content, data, sourceChain, targetChain } = tx ;
130
+ const { standarizedProperties } = content || { } ;
131
+
132
+ const fromChainId =
133
+ standarizedProperties . fromChain || sourceChain ?. chainId ;
134
+ const toChainId = standarizedProperties . toChain || targetChain ?. chainId ;
135
+ const tokenChainId = standarizedProperties . tokenChain ;
136
+
137
+ const fromChain = chainIdToChain ( fromChainId ) ;
138
+
139
+ // Skip if we don't have the source chain
140
+ if ( ! fromChain ) {
141
+ return ;
142
+ }
143
+
144
+ const tokenChain = tokenChainId
145
+ ? chainIdToChain ( tokenChainId )
146
+ : chainIdToChain ( toChainId ) ;
147
+
148
+ // Skip if we don't have the token chain
149
+ if ( ! tokenChain ) {
150
+ return ;
151
+ }
152
+
153
+ let token : Token | undefined ;
154
+ try {
155
+ token = await getOrFetchToken (
156
+ Wormhole . tokenId ( tokenChain , standarizedProperties . tokenAddress ) ,
157
+ ) ;
158
+ } catch ( e ) {
159
+ // This is ok
161
160
}
162
- }
163
-
164
- // If we've still failed to get the token, return early
165
- if ( ! token ) {
166
- return ;
167
- }
168
-
169
- const toChain = chainIdToChain ( toChainId ) ;
170
-
171
- // data.tokenAmount holds the normalized token amount value.
172
- // Otherwise we need to format standarizedProperties.amount using decimals
173
- const sentAmountDisplay =
174
- tokenAmount ??
175
- sdkAmount . display (
176
- {
177
- amount : standarizedProperties . amount ,
178
- decimals : standarizedProperties . normalizedDecimals ?? DECIMALS ,
179
- } ,
180
- 0 ,
181
- ) ;
182
161
183
- const receiveAmountValue =
184
- BigInt ( standarizedProperties . amount ) - BigInt ( standarizedProperties . fee ) ;
185
- // It's unlikely, but in case the above subtraction returns a non-positive number,
186
- // we should not show that at all.
187
- const receiveAmountDisplay =
188
- receiveAmountValue > 0
189
- ? sdkAmount . display (
162
+ if ( ! token ) {
163
+ // IMPORTANT:
164
+ // If we don't have the token config from the token address,
165
+ // we can check if we can use the symbol to get it.
166
+ // So far this case is only for SUI and APT
167
+ const foundBySymbol =
168
+ data ?. symbol && config . tokens . findBySymbol ( tokenChain , data . symbol ) ;
169
+ if ( foundBySymbol ) {
170
+ token = foundBySymbol ;
171
+ }
172
+ }
173
+
174
+ if ( ! token ) {
175
+ console . warn ( "Can't find token" , tx ) ;
176
+ }
177
+
178
+ const toChain = chainIdToChain ( toChainId ) ;
179
+
180
+ let sentAmountDisplay : string | undefined = undefined ;
181
+ let receiveAmountDisplay : string | undefined = undefined ;
182
+ let usdAmount : number | undefined = undefined ;
183
+
184
+ if ( data && data . tokenAmount ) {
185
+ sentAmountDisplay = data . tokenAmount ;
186
+ } else if ( standarizedProperties . amount ) {
187
+ sentAmountDisplay = sdkAmount . display (
188
+ {
189
+ amount : standarizedProperties . amount ,
190
+ decimals : standarizedProperties . normalizedDecimals ?? DECIMALS ,
191
+ } ,
192
+ 0 ,
193
+ ) ;
194
+ }
195
+
196
+ if ( standarizedProperties . amount && standarizedProperties . fee ) {
197
+ const receiveAmountValue =
198
+ BigInt ( standarizedProperties . amount ) -
199
+ BigInt ( standarizedProperties . fee ) ;
200
+ // It's unlikely, but in case the above subtraction returns a non-positive number,
201
+ // we should not show that at all.
202
+ if ( receiveAmountValue > 0 ) {
203
+ receiveAmountDisplay = sdkAmount . display (
190
204
{
191
205
amount : receiveAmountValue . toString ( ) ,
192
206
decimals : DECIMALS ,
193
207
} ,
194
208
0 ,
195
- )
196
- : '' ;
197
-
198
- const txHash = sourceChain . transaction ?. txHash ;
199
-
200
- // Transaction is in-progress when the below are both true:
201
- // 1- Source chain has confirmed
202
- // 2- Target has either not received, or received but not completed
203
- const inProgress =
204
- sourceChain ?. status ?. toLowerCase ( ) === 'confirmed' &&
205
- targetChain ?. status ?. toLowerCase ( ) !== 'completed' ;
206
-
207
- const txData : Transaction = {
208
- txHash,
209
- sender : standarizedProperties . fromAddress || sourceChain . from ,
210
- recipient : standarizedProperties . toAddress ,
211
- amount : sentAmountDisplay ,
212
- amountUsd : usdAmount ? Number ( usdAmount ) : 0 ,
213
- receiveAmount : receiveAmountDisplay ,
214
- fromChain,
215
- fromToken : token ,
216
- toChain,
217
- toToken : token ,
218
- senderTimestamp : sourceChain ?. timestamp ,
219
- receiverTimestamp : targetChain ?. timestamp ,
220
- explorerLink : `${ WORMSCAN } tx/${ txHash } ${
221
- config . isMainnet ? '' : '?network=TESTNET'
222
- } `,
223
- inProgress,
224
- } ;
209
+ ) ;
210
+ }
211
+ }
212
+
213
+ if ( data && data . usdAmount ) {
214
+ usdAmount = Number ( data . usdAmount ) ;
215
+ }
225
216
226
- return txData ;
227
- } , [ ] ) ;
217
+ const txHash = sourceChain . transaction ?. txHash ;
218
+
219
+ // Transaction is in-progress when the below are both true:
220
+ // 1- Source chain has confirmed
221
+ // 2- Target has either not received, or received but not completed
222
+ const inProgress =
223
+ sourceChain ?. status ?. toLowerCase ( ) === 'confirmed' &&
224
+ targetChain ?. status ?. toLowerCase ( ) !== 'completed' ;
225
+
226
+ const txData : Transaction = {
227
+ txHash,
228
+ sender : standarizedProperties . fromAddress || sourceChain . from ,
229
+ recipient : standarizedProperties . toAddress ,
230
+ amount : sentAmountDisplay ,
231
+ amountUsd : usdAmount ,
232
+ receiveAmount : receiveAmountDisplay ,
233
+ fromChain,
234
+ fromToken : token ,
235
+ toChain,
236
+ toToken : token ,
237
+ senderTimestamp : sourceChain ?. timestamp ,
238
+ receiverTimestamp : targetChain ?. timestamp ,
239
+ explorerLink : `${ WORMSCAN } tx/${ txHash } ${
240
+ config . isMainnet ? '' : '?network=TESTNET'
241
+ } `,
242
+ inProgress,
243
+ } ;
244
+
245
+ return txData ;
246
+ } ,
247
+ [ getOrFetchToken ] ,
248
+ ) ;
228
249
229
250
// Parser for Portal Token Bridge transactions (appId === PORTAL_TOKEN_BRIDGE)
230
251
// IMPORTANT: This is where we can add any customizations specific to Token Bridge data
@@ -236,6 +257,16 @@ const useTransactionHistoryWHScan = (
236
257
[ parseSingleTx ] ,
237
258
) ;
238
259
260
+ // Parser for NTT transactions (appId === NATIVE_TOKEN_TRANSFER)
261
+ // IMPORTANT: This is where we can add any customizations specific to NTT data
262
+ // that we have retrieved from WHScan API
263
+ const parseGenericRelayer = useCallback (
264
+ ( tx : WormholeScanTransaction ) => {
265
+ return parseSingleTx ( tx ) ;
266
+ } ,
267
+ [ parseSingleTx ] ,
268
+ ) ;
269
+
239
270
// Parser for NTT transactions (appId === NATIVE_TOKEN_TRANSFER)
240
271
// IMPORTANT: This is where we can add any customizations specific to NTT data
241
272
// that we have retrieved from WHScan API
@@ -260,8 +291,8 @@ const useTransactionHistoryWHScan = (
260
291
// IMPORTANT: This is where we can add any customizations specific to Portico data
261
292
// that we have retrieved from WHScan API
262
293
const parsePorticoTx = useCallback (
263
- ( tx : WormholeScanTransaction ) => {
264
- const txData = parseSingleTx ( tx ) ;
294
+ async ( tx : WormholeScanTransaction ) => {
295
+ const txData = await parseSingleTx ( tx ) ;
265
296
if ( ! txData ) return ;
266
297
267
298
const payload = tx . content . payload
@@ -331,47 +362,56 @@ const useTransactionHistoryWHScan = (
331
362
const PARSERS = useMemo (
332
363
( ) => ( {
333
364
PORTAL_TOKEN_BRIDGE : parseTokenBridgeTx ,
365
+ GENERIC_RELAYER : parseGenericRelayer ,
334
366
NATIVE_TOKEN_TRANSFER : parseNTTTx ,
335
367
CCTP_WORMHOLE_INTEGRATION : parseCCTPTx ,
336
368
ETH_BRIDGE : parsePorticoTx ,
337
369
USDT_BRIDGE : parsePorticoTx ,
338
370
FAST_TRANSFERS : parseLLTx ,
339
371
WORMHOLE_LIQUIDITY_LAYER : parseLLTx ,
340
372
} ) ,
341
- [ parseCCTPTx , parseNTTTx , parsePorticoTx , parseTokenBridgeTx , parseLLTx ] ,
373
+ [
374
+ parseCCTPTx ,
375
+ parseNTTTx ,
376
+ parsePorticoTx ,
377
+ parseTokenBridgeTx ,
378
+ parseLLTx ,
379
+ parseGenericRelayer ,
380
+ ] ,
342
381
) ;
343
382
344
383
// eslint-disable-next-line @typescript-eslint/no-explicit-any
345
384
const parseTransactions = useCallback (
346
- ( allTxs : Array < WormholeScanTransaction > ) => {
347
- return allTxs
348
- . map ( ( tx ) => {
349
- // Locate the appIds
350
- const appIds : Array < string > =
351
- tx . content ?. standarizedProperties ?. appIds || [ ] ;
352
-
353
- // TODO: SDKV2
354
- // Some integrations may compose with multiple protocols and have multiple appIds
355
- // Choose a more specific parser if available
356
- if ( appIds . includes ( 'ETH_BRIDGE' ) || appIds . includes ( 'USDT_BRIDGE' ) ) {
357
- return parsePorticoTx ( tx ) ;
358
- }
385
+ async ( allTxs : Array < WormholeScanTransaction > ) => {
386
+ return (
387
+ await Promise . all (
388
+ allTxs . map ( async ( tx ) => {
389
+ // Locate the appIds
390
+ const appIds : Array < string > =
391
+ tx . content ?. standarizedProperties ?. appIds || [ ] ;
392
+
393
+ // TODO: SDKV2
394
+ // Some integrations may compose with multiple protocols and have multiple appIds
395
+ // Choose a more specific parser if available
396
+ if (
397
+ appIds . includes ( 'ETH_BRIDGE' ) ||
398
+ appIds . includes ( 'USDT_BRIDGE' )
399
+ ) {
400
+ return parsePorticoTx ( tx ) ;
401
+ }
359
402
360
- for ( const appId of appIds ) {
361
- // Retrieve the parser for an appId
362
- const parser = PARSERS [ appId ] ;
403
+ for ( const appId of appIds ) {
404
+ // Retrieve the parser for an appId
405
+ const parser = PARSERS [ appId ] ;
363
406
364
- // If no parsers specified for the given appIds, we'll skip this transaction
365
- if ( parser ) {
366
- try {
407
+ // If no parsers specified for the given appIds, we'll skip this transaction
408
+ if ( parser ) {
367
409
return parser ( tx ) ;
368
- } catch ( e ) {
369
- console . error ( `Error parsing transaction: ${ e } ` ) ;
370
410
}
371
411
}
372
- }
373
- } )
374
- . filter ( ( tx ) => ! ! tx ) ; // Filter out unsupported transactions
412
+ } ) ,
413
+ )
414
+ ) . filter ( ( tx ) => ! ! tx ) ; // Filter out unsupported transactions
375
415
} ,
376
416
[ PARSERS , parsePorticoTx ] ,
377
417
) ;
@@ -403,8 +443,9 @@ const useTransactionHistoryWHScan = (
403
443
if ( ! cancelled ) {
404
444
const resData = resPayload ?. operations ;
405
445
if ( resData ) {
446
+ const parsedTxs = await parseTransactions ( resData ) ;
447
+
406
448
setTransactions ( ( txs ) => {
407
- const parsedTxs = parseTransactions ( resData ) ;
408
449
if ( txs && txs . length > 0 ) {
409
450
// We need to keep track of existing tx hashes to prevent duplicates in the final list
410
451
const existingTxs = new Set < string > ( ) ;
0 commit comments