0xtrails 0.1.13 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +11 -2
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/apiClient.d.ts +1 -1
  5. package/dist/apiClient.d.ts.map +1 -1
  6. package/dist/{proxyCaller.d.ts → balanceInjector.d.ts} +5 -4
  7. package/dist/balanceInjector.d.ts.map +1 -0
  8. package/dist/{ccip-D3gTQONK.js → ccip-D6ToCrWc.js} +12 -12
  9. package/dist/cctp.d.ts.map +1 -1
  10. package/dist/cctpqueue.d.ts +3 -3
  11. package/dist/cctpqueue.d.ts.map +1 -1
  12. package/dist/chains.d.ts.map +1 -1
  13. package/dist/config.d.ts +17 -3
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/constants.d.ts +5 -4
  16. package/dist/constants.d.ts.map +1 -1
  17. package/dist/contractUtils.d.ts +2 -0
  18. package/dist/contractUtils.d.ts.map +1 -1
  19. package/dist/customChains.d.ts +24 -0
  20. package/dist/customChains.d.ts.map +1 -0
  21. package/dist/{index-CnUM7lKf.js → index-BqgeTLL8.js} +34072 -30146
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +411 -400
  25. package/dist/intentEntrypoint.d.ts +96 -0
  26. package/dist/intentEntrypoint.d.ts.map +1 -0
  27. package/dist/intents.d.ts +5 -3
  28. package/dist/intents.d.ts.map +1 -1
  29. package/dist/metaTxnMonitor.d.ts.map +1 -1
  30. package/dist/morpho.d.ts.map +1 -1
  31. package/dist/pools.d.ts +3 -1
  32. package/dist/pools.d.ts.map +1 -1
  33. package/dist/prepareSend.d.ts +8 -2
  34. package/dist/prepareSend.d.ts.map +1 -1
  35. package/dist/prices.d.ts +1 -1
  36. package/dist/prices.d.ts.map +1 -1
  37. package/dist/relaySdk.d.ts.map +1 -1
  38. package/dist/relayer.d.ts.map +1 -1
  39. package/dist/toast.d.ts +9 -0
  40. package/dist/toast.d.ts.map +1 -0
  41. package/dist/tokenBalances.d.ts +6 -2
  42. package/dist/tokenBalances.d.ts.map +1 -1
  43. package/dist/tokens.d.ts.map +1 -1
  44. package/dist/trails.d.ts +6 -5
  45. package/dist/trails.d.ts.map +1 -1
  46. package/dist/trailsClient.d.ts +12 -0
  47. package/dist/trailsClient.d.ts.map +1 -0
  48. package/dist/transactions.d.ts +8 -0
  49. package/dist/transactions.d.ts.map +1 -1
  50. package/dist/wallets.d.ts.map +1 -1
  51. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  52. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  53. package/dist/widget/components/AccountSettings.d.ts +7 -0
  54. package/dist/widget/components/AccountSettings.d.ts.map +1 -0
  55. package/dist/widget/components/ChainList.d.ts +0 -1
  56. package/dist/widget/components/ChainList.d.ts.map +1 -1
  57. package/dist/widget/components/ClassicSwap.d.ts +46 -0
  58. package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
  59. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  60. package/dist/widget/components/ConnectedWallets.d.ts +9 -0
  61. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
  62. package/dist/widget/components/DebugMenu.d.ts.map +1 -1
  63. package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
  64. package/dist/widget/components/DebugToast.d.ts +3 -0
  65. package/dist/widget/components/DebugToast.d.ts.map +1 -0
  66. package/dist/widget/components/Earn.d.ts.map +1 -1
  67. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  68. package/dist/widget/components/Fund.d.ts +44 -0
  69. package/dist/widget/components/Fund.d.ts.map +1 -0
  70. package/dist/widget/components/Identicon.d.ts +9 -0
  71. package/dist/widget/components/Identicon.d.ts.map +1 -0
  72. package/dist/widget/components/Pay.d.ts +46 -0
  73. package/dist/widget/components/Pay.d.ts.map +1 -0
  74. package/dist/widget/components/Receive.d.ts.map +1 -1
  75. package/dist/widget/components/RecentTokens.d.ts.map +1 -1
  76. package/dist/widget/components/Recipients.d.ts +9 -0
  77. package/dist/widget/components/Recipients.d.ts.map +1 -0
  78. package/dist/widget/components/RefundWarning.d.ts +9 -0
  79. package/dist/widget/components/RefundWarning.d.ts.map +1 -0
  80. package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
  81. package/dist/widget/components/Swap.d.ts.map +1 -1
  82. package/dist/widget/components/SwapSettings.d.ts +1 -5
  83. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  84. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  85. package/dist/widget/components/ThemeSyncer.d.ts +6 -0
  86. package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
  87. package/dist/widget/components/Toast.d.ts +24 -0
  88. package/dist/widget/components/Toast.d.ts.map +1 -0
  89. package/dist/widget/components/TokenList.d.ts.map +1 -1
  90. package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
  91. package/dist/widget/components/TruncatedAddress.d.ts +2 -0
  92. package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
  93. package/dist/widget/components/UserPreferences.d.ts +7 -0
  94. package/dist/widget/components/UserPreferences.d.ts.map +1 -0
  95. package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
  96. package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
  97. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  98. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  99. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  100. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  101. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  102. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
  103. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
  104. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  105. package/dist/widget/hooks/usePayMessage.d.ts +34 -0
  106. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
  107. package/dist/widget/hooks/useRecipients.d.ts +17 -0
  108. package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
  109. package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
  110. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
  111. package/dist/widget/hooks/useSendForm.d.ts +2 -0
  112. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  113. package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
  114. package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
  115. package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
  116. package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
  117. package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
  118. package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
  119. package/dist/widget/hooks/useTheme.d.ts +14 -0
  120. package/dist/widget/hooks/useTheme.d.ts.map +1 -0
  121. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  122. package/dist/widget/index.js +2 -2
  123. package/dist/widget/widget.d.ts +9 -0
  124. package/dist/widget/widget.d.ts.map +1 -1
  125. package/package.json +29 -28
  126. package/src/aave.ts +6 -1
  127. package/src/analytics.ts +103 -53
  128. package/src/apiClient.ts +1 -1
  129. package/src/{proxyCaller.ts → balanceInjector.ts} +22 -17
  130. package/src/cctp.ts +6 -2
  131. package/src/cctpqueue.ts +7 -7
  132. package/src/chains.ts +8 -0
  133. package/src/config.ts +40 -9
  134. package/src/constants.ts +11 -8
  135. package/src/contractUtils.ts +33 -2
  136. package/src/customChains.ts +24 -0
  137. package/src/index.ts +11 -1
  138. package/src/intentEntrypoint.ts +253 -0
  139. package/src/intents.ts +87 -54
  140. package/src/metaTxnMonitor.ts +1 -0
  141. package/src/morpho.ts +13 -2
  142. package/src/pools.ts +68 -86
  143. package/src/prepareSend.ts +437 -207
  144. package/src/prices.ts +51 -7
  145. package/src/relaySdk.ts +6 -4
  146. package/src/relayer.ts +2 -0
  147. package/src/toast.ts +110 -0
  148. package/src/tokenBalances.ts +112 -20
  149. package/src/tokens.ts +70 -7
  150. package/src/trails.ts +80 -77
  151. package/src/trailsClient.ts +45 -0
  152. package/src/transactions.ts +27 -35
  153. package/src/umd.tsx +1 -1
  154. package/src/wallets.ts +2 -1
  155. package/src/widget/assets/sequence-logo.svg +15 -0
  156. package/src/widget/compiled.css +2 -2
  157. package/src/widget/components/AccountActionsDropdown.tsx +18 -159
  158. package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
  159. package/src/widget/components/AccountSettings.tsx +96 -0
  160. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  161. package/src/widget/components/ChainList.tsx +10 -20
  162. package/src/widget/components/ClassicSwap.tsx +923 -0
  163. package/src/widget/components/ConfigDisplay.tsx +8 -5
  164. package/src/widget/components/ConnectedWallets.tsx +260 -0
  165. package/src/widget/components/DebugMenu.tsx +2 -0
  166. package/src/widget/components/DebugScreensList.tsx +3 -0
  167. package/src/widget/components/DebugToast.tsx +63 -0
  168. package/src/widget/components/Earn.tsx +108 -116
  169. package/src/widget/components/EarnPools.tsx +2 -4
  170. package/src/widget/components/EarnPoolsFilters.tsx +6 -6
  171. package/src/widget/components/Fund.tsx +1245 -0
  172. package/src/widget/components/FundMethods.tsx +1 -1
  173. package/src/widget/components/FundSendForm.tsx +1 -1
  174. package/src/widget/components/Identicon.tsx +158 -0
  175. package/src/widget/components/Pay.tsx +1088 -0
  176. package/src/widget/components/PaySendForm.tsx +1 -1
  177. package/src/widget/components/QuoteDetails.tsx +1 -1
  178. package/src/widget/components/Receipt.tsx +1 -1
  179. package/src/widget/components/Receive.tsx +4 -2
  180. package/src/widget/components/RecentTokens.tsx +2 -1
  181. package/src/widget/components/Recipients.tsx +448 -0
  182. package/src/widget/components/RefundWarning.tsx +61 -0
  183. package/src/widget/components/ScreenHeader.tsx +1 -1
  184. package/src/widget/components/SimpleSwap.tsx +74 -58
  185. package/src/widget/components/Swap.tsx +35 -853
  186. package/src/widget/components/SwapSettings.tsx +5 -11
  187. package/src/widget/components/ThemeProvider.tsx +32 -0
  188. package/src/widget/components/ThemeSyncer.tsx +47 -0
  189. package/src/widget/components/Toast.tsx +315 -0
  190. package/src/widget/components/TokenList.tsx +2 -34
  191. package/src/widget/components/TokenSelector.tsx +3 -3
  192. package/src/widget/components/TransactionDetails.tsx +153 -13
  193. package/src/widget/components/TruncatedAddress.tsx +5 -1
  194. package/src/widget/components/UserPreferences.tsx +156 -0
  195. package/src/widget/components/WalletList.tsx +1 -1
  196. package/src/widget/hooks/useBalanceVisible.tsx +40 -2
  197. package/src/widget/hooks/useCheckout.ts +13 -0
  198. package/src/widget/hooks/useCurrentScreen.tsx +3 -0
  199. package/src/widget/hooks/useDebugScreens.ts +12 -2
  200. package/src/widget/hooks/useDefaultTokenSelection.tsx +475 -0
  201. package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
  202. package/src/widget/hooks/usePayMessage.tsx +370 -0
  203. package/src/widget/hooks/useRecipients.ts +168 -0
  204. package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
  205. package/src/widget/hooks/useSendForm.ts +179 -26
  206. package/src/widget/hooks/useSwapAmount.tsx +50 -0
  207. package/src/widget/hooks/useSwapSettings.tsx +100 -0
  208. package/src/widget/hooks/useTargetAmount.ts +23 -0
  209. package/src/widget/hooks/useTheme.tsx +80 -0
  210. package/src/widget/hooks/useTokenList.ts +20 -11
  211. package/src/widget/index.css +45 -21
  212. package/src/widget/widget.tsx +164 -68
  213. package/dist/address.d.ts +0 -2
  214. package/dist/address.d.ts.map +0 -1
  215. package/dist/proxyCaller.d.ts.map +0 -1
  216. package/src/address.ts +0 -6
package/src/prices.ts CHANGED
@@ -1,8 +1,4 @@
1
- import type {
2
- SequenceAPIClient,
3
- Token,
4
- TokenPrice,
5
- } from "@0xsequence/trails-api"
1
+ import type { SequenceAPIClient, Token, TokenPrice } from "@0xsequence/api"
6
2
  import { QueryClient, useQuery } from "@tanstack/react-query"
7
3
  import { zeroAddress } from "viem"
8
4
  import { logger } from "./logger.js"
@@ -193,13 +189,61 @@ export function calcAmountUsdPrice({
193
189
  amount: number | string
194
190
  usdPrice: string | number | null | undefined
195
191
  }) {
196
- return normalizeNumber(amount) * normalizeNumber(usdPrice)
192
+ const sanitizedAmount = normalizeNumber(amount)
193
+ const sanitizedPrice = normalizeNumber(usdPrice)
194
+
195
+ // Validate inputs
196
+ if (
197
+ !Number.isFinite(sanitizedAmount) ||
198
+ Number.isNaN(sanitizedAmount) ||
199
+ sanitizedAmount < 0
200
+ ) {
201
+ return 0
202
+ }
203
+ if (
204
+ !Number.isFinite(sanitizedPrice) ||
205
+ Number.isNaN(sanitizedPrice) ||
206
+ sanitizedPrice < 0
207
+ ) {
208
+ return 0
209
+ }
210
+
211
+ const result = sanitizedAmount * sanitizedPrice
212
+
213
+ // Validate result
214
+ if (!Number.isFinite(result) || Number.isNaN(result)) {
215
+ logger.console.error("[trails-sdk] Error calculating amount USD:", {
216
+ sanitizedAmount,
217
+ sanitizedPrice,
218
+ result,
219
+ amount,
220
+ usdPrice,
221
+ })
222
+ return 0
223
+ }
224
+
225
+ return result
197
226
  }
198
227
 
199
228
  export function normalizeNumber(
200
229
  number: number | string | null | undefined,
201
230
  ): number {
202
- return Number(number?.toString().replace(/[^0-9.-]/g, "") || 0)
231
+ if (number === null || number === undefined) {
232
+ return 0
233
+ }
234
+
235
+ const normalized = Number(number?.toString().replace(/[^0-9.-]/g, "") || 0)
236
+
237
+ // Return 0 for invalid numbers
238
+ if (!Number.isFinite(normalized) || Number.isNaN(normalized)) {
239
+ logger.console.error("[trails-sdk] Error normalizing number:", {
240
+ number,
241
+ normalized,
242
+ })
243
+ return 0
244
+ }
245
+
246
+ return normalized
203
247
  }
204
248
 
205
249
  // Format TVL (Total Value Locked) for display
package/src/relaySdk.ts CHANGED
@@ -69,6 +69,7 @@ import {
69
69
  arenaz,
70
70
  b3,
71
71
  } from "viem/chains"
72
+ import { somnia } from "./customChains.js"
72
73
  import { logger } from "./logger.js"
73
74
 
74
75
  export type Chain = any
@@ -140,6 +141,7 @@ export const relaySupportedChains: Record<number, Chain> = {
140
141
  [zksync.id]: zksync,
141
142
  [zora.id]: zora,
142
143
  [katana.id]: katana,
144
+ [somnia.id]: somnia,
143
145
  }
144
146
 
145
147
  getRelaySupportedChains().then((relayChains) => {
@@ -347,7 +349,7 @@ export async function getRelaySupportedTokens(): Promise<RelayToken[]> {
347
349
  if (!chain.disabled) {
348
350
  // Add native currency
349
351
  tokens.push({
350
- id: chain.currency.id,
352
+ id: chain.currency.id || `${chain.currency.symbol}-${chain.name}`,
351
353
  symbol: chain.currency.symbol,
352
354
  name: chain.currency.name,
353
355
  contractAddress: chain.currency.address,
@@ -360,7 +362,7 @@ export async function getRelaySupportedTokens(): Promise<RelayToken[]> {
360
362
  // Add featured tokens
361
363
  chain.featuredTokens.forEach((token) => {
362
364
  tokens.push({
363
- id: token.id,
365
+ id: token.id || `${token.symbol}-${chain.name}`,
364
366
  symbol: token.symbol,
365
367
  name: token.name,
366
368
  contractAddress: token.address,
@@ -374,7 +376,7 @@ export async function getRelaySupportedTokens(): Promise<RelayToken[]> {
374
376
  // Add ERC20 currencies
375
377
  chain.erc20Currencies.forEach((token) => {
376
378
  tokens.push({
377
- id: token.id,
379
+ id: token.id || `${token.symbol}-${chain.name}`,
378
380
  symbol: token.symbol,
379
381
  name: token.name,
380
382
  contractAddress: token.address,
@@ -388,7 +390,7 @@ export async function getRelaySupportedTokens(): Promise<RelayToken[]> {
388
390
  // Add solver currencies (fallback for chains that might not have featuredTokens/erc20Currencies)
389
391
  chain.solverCurrencies.forEach((token) => {
390
392
  tokens.push({
391
- id: token.id,
393
+ id: token.id || `${token.symbol}-${chain.name}`,
392
394
  symbol: token.symbol,
393
395
  name: token.name,
394
396
  contractAddress: token.address,
package/src/relayer.ts CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  blast,
25
25
  optimism,
26
26
  } from "viem/chains"
27
+ import { somnia } from "./customChains.js"
27
28
 
28
29
  export interface MetaTxnReceiptLog {
29
30
  address: string
@@ -74,6 +75,7 @@ const relayerChainIdToSlug: Record<number, string> = {
74
75
  [blast.id]: "blast",
75
76
  [gnosis.id]: "gnosis",
76
77
  [soneium.id]: "soneium",
78
+ [somnia.id]: "somnia",
77
79
  [xai.id]: "xai",
78
80
  [bsc.id]: "bsc",
79
81
  [katana.id]: "katana",
package/src/toast.ts ADDED
@@ -0,0 +1,110 @@
1
+ import type { ToastType } from "./widget/components/Toast.js"
2
+
3
+ let globalShowToast:
4
+ | ((
5
+ title: string,
6
+ message: string,
7
+ type?: ToastType,
8
+ duration?: number,
9
+ iconUrl?: string,
10
+ ) => void)
11
+ | null = null
12
+
13
+ let globalUpdatePersistentToast:
14
+ | ((
15
+ title: string,
16
+ message: string,
17
+ type?: ToastType,
18
+ iconUrl?: string,
19
+ ) => void)
20
+ | null = null
21
+
22
+ let globalRemovePersistentToast: (() => void) | null = null
23
+
24
+ let toastEnabled = false
25
+
26
+ export function setGlobalShowToast(
27
+ showToast: (
28
+ title: string,
29
+ message: string,
30
+ type?: ToastType,
31
+ duration?: number,
32
+ iconUrl?: string,
33
+ ) => void,
34
+ ) {
35
+ globalShowToast = showToast
36
+ }
37
+
38
+ export function setGlobalUpdatePersistentToast(
39
+ updatePersistentToast: (
40
+ title: string,
41
+ message: string,
42
+ type?: ToastType,
43
+ iconUrl?: string,
44
+ ) => void,
45
+ ) {
46
+ globalUpdatePersistentToast = updatePersistentToast
47
+ }
48
+
49
+ export function setGlobalRemovePersistentToast(
50
+ removePersistentToast: () => void,
51
+ ) {
52
+ globalRemovePersistentToast = removePersistentToast
53
+ }
54
+
55
+ export function setToastEnabled(enabled: boolean) {
56
+ toastEnabled = enabled
57
+ }
58
+
59
+ export function showToast(
60
+ title: string,
61
+ message: string,
62
+ type?: ToastType,
63
+ duration?: number,
64
+ iconUrl?: string,
65
+ ) {
66
+ if (!toastEnabled) {
67
+ return
68
+ }
69
+
70
+ if (globalShowToast) {
71
+ globalShowToast(title, message, type, duration, iconUrl)
72
+ } else {
73
+ console.warn(
74
+ "[trails-sdk] Toast not available yet. Make sure ToastProvider is mounted.",
75
+ )
76
+ }
77
+ }
78
+
79
+ export function updatePersistentToast(
80
+ title: string,
81
+ message: string,
82
+ type?: ToastType,
83
+ iconUrl?: string,
84
+ ) {
85
+ if (!toastEnabled) {
86
+ return
87
+ }
88
+
89
+ if (globalUpdatePersistentToast) {
90
+ globalUpdatePersistentToast(title, message, type, iconUrl)
91
+ } else {
92
+ console.warn(
93
+ "[trails-sdk] Toast not available yet. Make sure ToastProvider is mounted.",
94
+ )
95
+ }
96
+ }
97
+
98
+ export function removePersistentToast() {
99
+ if (!toastEnabled) {
100
+ return
101
+ }
102
+
103
+ if (globalRemovePersistentToast) {
104
+ globalRemovePersistentToast()
105
+ } else {
106
+ console.warn(
107
+ "[trails-sdk] Toast not available yet. Make sure ToastProvider is mounted.",
108
+ )
109
+ }
110
+ }
@@ -7,7 +7,7 @@ import type {
7
7
  TokenBalance,
8
8
  } from "@0xsequence/indexer"
9
9
  import { ContractVerificationStatus } from "@0xsequence/indexer"
10
- import type { Page, Price, SequenceAPIClient } from "@0xsequence/trails-api"
10
+ import type { Page, Price, SequenceAPIClient } from "@0xsequence/api"
11
11
  import { QueryClient, useQuery } from "@tanstack/react-query"
12
12
  import type { Address } from "ox"
13
13
  import { useEffect, useState } from "react"
@@ -16,6 +16,7 @@ import { useAPIClient } from "./apiClient.js"
16
16
  import { useIndexerGatewayClient } from "./indexerClient.js"
17
17
  import { getTokenPrices, useTokenPrices } from "./prices.js"
18
18
  import { logger } from "./logger.js"
19
+ import { getChainInfo } from "./chains.js"
19
20
 
20
21
  export type { NativeTokenBalance, TokenBalance }
21
22
 
@@ -30,6 +31,7 @@ const tokenBalancesQueryClient = new QueryClient({
30
31
  retry: 2,
31
32
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
32
33
  refetchOnWindowFocus: false,
34
+ refetchIntervalInBackground: true, // Refetch in background
33
35
  refetchOnReconnect: true,
34
36
  },
35
37
  },
@@ -42,16 +44,20 @@ const defaultPage = { page: 1, pageSize: 10, more: false }
42
44
  export function isNativeToken(
43
45
  token: TokenBalance | NativeTokenBalance,
44
46
  ): token is NativeTokenBalance {
45
- if ("contractAddress" in token) {
46
- return false
47
+ if (!("contractAddress" in token)) {
48
+ return true
49
+ }
50
+ if (token.contractAddress === zeroAddress) {
51
+ return true
47
52
  }
48
- return true
53
+ return false
49
54
  }
50
55
 
51
56
  export interface TokenBalanceWithPrice extends TokenBalance {
52
57
  price?: Price
53
58
  balanceUsd?: number
54
59
  balanceUsdFormatted?: string
60
+ chainName?: string
55
61
  }
56
62
 
57
63
  export interface NativeTokenBalanceWithPrice extends NativeTokenBalance {
@@ -59,6 +65,7 @@ export interface NativeTokenBalanceWithPrice extends NativeTokenBalance {
59
65
  balanceUsd?: number
60
66
  balanceUsdFormatted?: string
61
67
  symbol?: string
68
+ chainName?: string
62
69
  }
63
70
 
64
71
  export type TokenBalanceExtended =
@@ -132,6 +139,7 @@ export interface GetTokenBalancesWithPrice {
132
139
  symbol?: string
133
140
  balanceUsd?: number
134
141
  balanceUsdFormatted?: string
142
+ chainName?: string
135
143
  }
136
144
  >
137
145
  balances: Array<
@@ -139,6 +147,7 @@ export interface GetTokenBalancesWithPrice {
139
147
  price?: Price
140
148
  balanceUsd?: number
141
149
  balanceUsdFormatted?: string
150
+ chainName?: string
142
151
  }
143
152
  >
144
153
  }
@@ -255,8 +264,16 @@ export function useTokenBalances(
255
264
  }
256
265
 
257
266
  const balances = [
258
- ...tokenBalancesData.nativeBalances,
259
- ...tokenBalancesData.balances,
267
+ ...tokenBalancesData.nativeBalances.map((token) => ({
268
+ ...token,
269
+ contractAddress: zeroAddress, // Native tokens always use zero address
270
+ chainName: getChainInfo(token.chainId)?.name || "",
271
+ })),
272
+ ...tokenBalancesData.balances.map((token) => ({
273
+ ...token,
274
+ contractAddress: token.contractAddress,
275
+ chainName: getChainInfo(token.contractInfo?.chainId)?.name || "",
276
+ })),
260
277
  ].filter((token) => {
261
278
  try {
262
279
  return BigInt(token.balance) > 0n
@@ -301,6 +318,7 @@ export function useTokenBalances(
301
318
  !!tokenPrices,
302
319
  staleTime: REFRESH_INTERVAL, // 10 seconds for sorted tokens
303
320
  gcTime: REFRESH_INTERVAL, // 10 seconds cache time
321
+ refetchIntervalInBackground: true, // Refetch in background
304
322
  refetchOnWindowFocus: true,
305
323
  })
306
324
 
@@ -318,17 +336,38 @@ export function useTokenBalances(
318
336
  // Helper to format balance
319
337
  export function formatRawAmount(
320
338
  balance: string | bigint,
321
- decimals: number = 18,
339
+ decimals: number,
322
340
  ): string {
323
341
  if (!balance) {
324
342
  return "0"
325
343
  }
344
+
326
345
  try {
327
- const formatted = formatUnits(BigInt(balance), decimals)
346
+ // Validate decimals
347
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 18) {
348
+ throw new Error("Invalid decimals")
349
+ }
350
+
351
+ if (decimals === 0) {
352
+ throw new Error("Invalid decimals, decimals cannot be 0")
353
+ }
354
+
355
+ const balanceBigInt = BigInt(balance)
356
+
357
+ // Check for negative balance
358
+ if (balanceBigInt < 0n) {
359
+ throw new Error("Negative balance")
360
+ }
361
+
362
+ const formatted = formatUnits(balanceBigInt, decimals)
328
363
  return formatAmount(formatted)
329
- } catch (e) {
330
- logger.console.error("[trails-sdk] Error formatting balance:", e)
331
- return balance.toString()
364
+ } catch (err) {
365
+ logger.console.error("[trails-sdk] Error formatting balance:", {
366
+ balance,
367
+ decimals,
368
+ err,
369
+ })
370
+ throw err
332
371
  }
333
372
  }
334
373
 
@@ -337,12 +376,45 @@ export function getTokenBalanceUsd(
337
376
  tokenPrice: Price,
338
377
  ): number {
339
378
  const isNative = isNativeToken(token)
340
- const formattedBalance = formatRawAmount(
341
- token.balance,
342
- isNative ? 18 : token.contractInfo?.decimals,
343
- )
379
+ const decimals = isNative ? 18 : token.contractInfo?.decimals
380
+ if (!decimals) {
381
+ throw new Error("Decimals not found")
382
+ }
383
+ const formattedBalance = formatRawAmount(token.balance, decimals)
384
+
344
385
  const priceUsd = Number(tokenPrice.value) ?? 0
345
- return Number(formattedBalance) * priceUsd
386
+
387
+ // Validate price
388
+ if (!Number.isFinite(priceUsd) || Number.isNaN(priceUsd) || priceUsd < 0) {
389
+ return 0
390
+ }
391
+
392
+ const balanceNum = Number(formattedBalance)
393
+
394
+ // Validate balance
395
+ if (
396
+ !Number.isFinite(balanceNum) ||
397
+ Number.isNaN(balanceNum) ||
398
+ balanceNum < 0
399
+ ) {
400
+ return 0
401
+ }
402
+
403
+ const result = balanceNum * priceUsd
404
+
405
+ // Validate result
406
+ if (!Number.isFinite(result) || Number.isNaN(result)) {
407
+ logger.console.error("[trails-sdk] Error calculating balance USD:", {
408
+ balanceNum,
409
+ priceUsd,
410
+ result,
411
+ token,
412
+ tokenPrice,
413
+ })
414
+ return 0
415
+ }
416
+
417
+ return result
346
418
  }
347
419
 
348
420
  export function formatAmount(
@@ -395,14 +467,30 @@ export function formatUsdAmountDisplay(value: number | string = 0): string {
395
467
  if (!value) {
396
468
  value = 0
397
469
  }
470
+
471
+ const numValue = Number(value)
472
+
473
+ // Validate input
474
+ if (!Number.isFinite(numValue) || Number.isNaN(numValue)) {
475
+ return "$0.00"
476
+ }
477
+
478
+ // Handle negative values explicitly
479
+ const isNegative = numValue < 0
480
+ const absValue = Math.abs(numValue)
481
+
482
+ // Cap at reasonable max to prevent display issues (100 trillion)
483
+ const MAX_DISPLAY_VALUE = 100_000_000_000_000
484
+ const cappedValue = Math.min(absValue, MAX_DISPLAY_VALUE)
485
+
398
486
  const displayValue = Intl.NumberFormat("en-US", {
399
487
  style: "currency",
400
488
  currency: "USD",
401
489
  maximumFractionDigits: 2,
402
490
  minimumFractionDigits: 2,
403
- }).format(Number(value))
491
+ }).format(isNegative ? -cappedValue : cappedValue)
404
492
 
405
- if (displayValue === "$0.00" && Number(value) > 0 && Number(value) < 0.01) {
493
+ if (displayValue === "$0.00" && absValue > 0 && absValue < 0.01) {
406
494
  return `<$0.01`
407
495
  }
408
496
 
@@ -658,7 +746,7 @@ export async function getHasSufficientBalanceToken({
658
746
  indexerGatewayClient,
659
747
  apiClient,
660
748
  })
661
- const tokenBalance = balances.find(
749
+ const tokenBalance: TokenBalanceExtended | null | undefined = balances.find(
662
750
  (b) =>
663
751
  b.chainId === chainId &&
664
752
  (b.contractAddress?.toLowerCase() === token.toLowerCase() ||
@@ -667,7 +755,11 @@ export async function getHasSufficientBalanceToken({
667
755
  if (!tokenBalance) {
668
756
  return false
669
757
  }
670
- const decimals = tokenBalance?.contractInfo?.decimals ?? 18
758
+ const isNative = isNativeToken(tokenBalance)
759
+ const decimals = isNative ? 18 : tokenBalance?.contractInfo?.decimals
760
+ if (!decimals) {
761
+ throw new Error("Decimals not found")
762
+ }
671
763
  return tokenBalance?.balance
672
764
  ? BigInt(tokenBalance.balance) >= parseUnits(amount, decimals)
673
765
  : false
package/src/tokens.ts CHANGED
@@ -13,10 +13,11 @@ import {
13
13
  unichain,
14
14
  worldchain,
15
15
  } from "viem/chains"
16
- import { getRelaySupportedTokens } from "./relaySdk.js"
16
+ import { getRelaySupportedTokens, type Chain } from "./relaySdk.js"
17
17
  import { useReadContracts } from "wagmi"
18
18
  import { useMemo } from "react"
19
19
  import { logger } from "./logger.js"
20
+ import { somnia } from "./customChains.js"
20
21
 
21
22
  export type SupportedToken = {
22
23
  id: string
@@ -42,6 +43,7 @@ export const commonTokenImages: Record<string, string> = {
42
43
  LINK: "https://assets.sequence.info/images/tokens/large/1/0x514910771af9ca656af840dff83e8264ecf986ca.webp",
43
44
  XTZ: "https://assets.sequence.info/images/tokens/large/42793/0x0000000000000000000000000000000000000000.webp",
44
45
  WXTZ: "https://assets.coingecko.com/coins/images/976/standard/Tezos-logo.png?1696502091",
46
+ SOMI: "https://assets.sequence.info/images/tokens/large/5031/0x0000000000000000000000000000000000000000.webp",
45
47
  }
46
48
 
47
49
  const cacheVersion = "01"
@@ -375,14 +377,32 @@ export async function getSupportedTokens(): Promise<SupportedToken[]> {
375
377
  }
376
378
  }
377
379
 
378
- const supportedChains = await getSupportedChains()
380
+ let supportedChains: Chain[] = []
381
+ try {
382
+ supportedChains = await getSupportedChains({ quoteProvider: "relay" })
383
+ } catch (error) {
384
+ logger.console.error("[trails-sdk] Error getting supported chains:", error)
385
+ // Fallback: include all chains that have tokens
386
+ supportedChains = []
387
+ }
388
+
379
389
  const allTokens = [...tokens, ...additionalTokens]
380
390
  const supportedChainTokens = allTokens.filter((token) => {
381
391
  // Always include Etherlink tokens
382
392
  if (token.chainId === etherlink.id) {
383
393
  return true
384
394
  }
385
- return supportedChains.some((chain) => chain.id === token.chainId)
395
+ // If we have supported chains, filter by them
396
+ if (supportedChains.length > 0) {
397
+ return supportedChains.some((chain) => chain.id === token.chainId)
398
+ }
399
+ // Fallback: include tokens from chains that are in both relay tokens and common tokens
400
+ const relayChainIds = tokens.map((t) => t.chainId)
401
+ const commonChainIds = commonTokens.map((t) => t.chainId)
402
+ const supportedChainIds = [
403
+ ...new Set([...relayChainIds, ...commonChainIds]),
404
+ ]
405
+ return supportedChainIds.includes(token.chainId)
386
406
  })
387
407
  const uniqueTokens = supportedChainTokens.filter(
388
408
  (token, index, self) =>
@@ -521,12 +541,13 @@ export async function getTokenInfo(
521
541
  // Check if it's a native token
522
542
  const chainInfo = getChainInfo(chainId)
523
543
  if (normalizedAddress === zeroAddress.toLowerCase()) {
544
+ const decimals = chainInfo?.nativeCurrency.decimals ?? 18
524
545
  const nativeInfo: SupportedToken = {
525
546
  id: `${chainInfo?.nativeCurrency.symbol || "ETH"}-${chainInfo?.name || "ethereum"}`,
526
547
  symbol: chainInfo?.nativeCurrency.symbol || "ETH",
527
548
  name: chainInfo?.nativeCurrency.name || "Ethereum",
528
549
  contractAddress: zeroAddress,
529
- decimals: chainInfo?.nativeCurrency.decimals || 18,
550
+ decimals,
530
551
  chainId,
531
552
  chainName: chainInfo?.name || "Ethereum",
532
553
  imageUrl: getTokenImageUrl({
@@ -803,6 +824,10 @@ export function useTokenInfo({
803
824
  // Always call hooks unconditionally
804
825
  const isAddress = address?.startsWith("0x") ?? false
805
826
 
827
+ // Handle any native token (zero address) - before any blockchain calls
828
+ const isNativeToken =
829
+ address?.toLowerCase() === zeroAddress.toLowerCase() && !!chainId
830
+
806
831
  // Check cache first
807
832
  const cachedInfo = useMemo(() => {
808
833
  if (!isAddress || !chainId) return null
@@ -816,7 +841,7 @@ export function useTokenInfo({
816
841
  } as const
817
842
  const result = useReadContracts({
818
843
  contracts:
819
- !!isAddress && !!chainId && !cachedInfo
844
+ !!isAddress && !!chainId && !cachedInfo && !isNativeToken
820
845
  ? [
821
846
  { ...contract, functionName: "name" },
822
847
  { ...contract, functionName: "symbol" },
@@ -825,11 +850,33 @@ export function useTokenInfo({
825
850
  : [],
826
851
  })
827
852
  const error =
828
- result?.error ?? result?.data?.find((r) => r.error)?.error ?? null
853
+ result?.error ?? result?.data?.find((r: any) => r.error)?.error ?? null
829
854
  const [name, symbol, decimals] = result.data ?? []
830
855
  const chainInfo = getChainInfo(chainId!)
831
856
 
832
857
  const tokenInfo = useMemo(() => {
858
+ // Handle native token (zero address) for any chain
859
+ if (isNativeToken) {
860
+ const nativeSymbol = chainInfo?.nativeCurrency.symbol || "ETH"
861
+ const nativeName = chainInfo?.nativeCurrency.name || "Ethereum"
862
+ const decimals = chainInfo?.nativeCurrency.decimals ?? 18
863
+
864
+ return {
865
+ id: `${nativeSymbol}-${chainInfo?.name || ""}`,
866
+ name: nativeName,
867
+ symbol: nativeSymbol,
868
+ decimals,
869
+ chainId: chainId!,
870
+ contractAddress: zeroAddress,
871
+ chainName: chainInfo?.name || "",
872
+ imageUrl: getTokenImageUrl({
873
+ chainId,
874
+ contractAddress: zeroAddress,
875
+ symbol: nativeSymbol,
876
+ }),
877
+ }
878
+ }
879
+
833
880
  // If we have cached info, use it
834
881
  if (cachedInfo) {
835
882
  return {
@@ -869,10 +916,14 @@ export function useTokenInfo({
869
916
 
870
917
  return null
871
918
  }, [
872
- cachedInfo,
873
919
  address,
874
920
  chainId,
921
+ isNativeToken,
922
+ cachedInfo,
875
923
  chainInfo?.name,
924
+ chainInfo?.nativeCurrency.symbol,
925
+ chainInfo?.nativeCurrency.name,
926
+ chainInfo?.nativeCurrency.decimals,
876
927
  name?.result,
877
928
  symbol?.result,
878
929
  decimals?.result,
@@ -1163,6 +1214,18 @@ export const commonTokens: SupportedToken[] = [
1163
1214
  chainName: etherlink.name,
1164
1215
  imageUrl: commonTokenImages.USDT as string,
1165
1216
  },
1217
+
1218
+ // Somnia
1219
+ {
1220
+ id: "SOMI-somnia",
1221
+ symbol: "SOMI",
1222
+ name: "Somnia",
1223
+ contractAddress: "0x0000000000000000000000000000000000000000",
1224
+ decimals: 18,
1225
+ chainId: somnia.id,
1226
+ chainName: somnia.name,
1227
+ imageUrl: commonTokenImages.SOMI as string,
1228
+ },
1166
1229
  ]
1167
1230
 
1168
1231
  export const wethAddresses: Record<string, string> = {