0xtrails 0.0.1 → 0.0.2

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 (359) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +42 -0
  3. package/dist/abi.d.ts +37 -0
  4. package/dist/abi.d.ts.map +1 -0
  5. package/dist/abi.js +36 -0
  6. package/dist/apiClient.d.ts +9 -0
  7. package/dist/apiClient.d.ts.map +1 -0
  8. package/dist/apiClient.js +18 -0
  9. package/dist/buffer.d.ts +3 -0
  10. package/dist/buffer.d.ts.map +1 -0
  11. package/dist/buffer.js +8 -0
  12. package/dist/cctp.d.ts +84 -0
  13. package/dist/cctp.d.ts.map +1 -0
  14. package/dist/cctp.js +401 -0
  15. package/dist/chainSwitch.d.ts +7 -0
  16. package/dist/chainSwitch.d.ts.map +1 -0
  17. package/dist/chainSwitch.js +33 -0
  18. package/dist/chains.d.ts +13 -0
  19. package/dist/chains.d.ts.map +1 -0
  20. package/dist/chains.js +95 -0
  21. package/dist/constants.d.ts +11 -0
  22. package/dist/constants.d.ts.map +1 -0
  23. package/dist/constants.js +16 -0
  24. package/dist/encoders.d.ts +7 -0
  25. package/dist/encoders.d.ts.map +1 -0
  26. package/dist/encoders.js +8 -0
  27. package/dist/error.d.ts +2 -0
  28. package/dist/error.d.ts.map +1 -0
  29. package/dist/error.js +12 -0
  30. package/dist/explorer.d.ts +12 -0
  31. package/dist/explorer.d.ts.map +1 -0
  32. package/dist/explorer.js +18 -0
  33. package/dist/gasless.d.ts +116 -0
  34. package/dist/gasless.d.ts.map +1 -0
  35. package/dist/gasless.js +297 -0
  36. package/dist/index.d.ts +13 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +9 -0
  39. package/dist/indexerClient.d.ts +9 -0
  40. package/dist/indexerClient.d.ts.map +1 -0
  41. package/dist/indexerClient.js +18 -0
  42. package/dist/intents.d.ts +83 -0
  43. package/dist/intents.d.ts.map +1 -0
  44. package/dist/intents.js +288 -0
  45. package/dist/metaTxnMonitor.d.ts +14 -0
  46. package/dist/metaTxnMonitor.d.ts.map +1 -0
  47. package/dist/metaTxnMonitor.js +121 -0
  48. package/dist/metaTxns.d.ts +6 -0
  49. package/dist/metaTxns.d.ts.map +1 -0
  50. package/dist/metaTxns.js +4 -0
  51. package/dist/paymasterSend.d.ts +90 -0
  52. package/dist/paymasterSend.d.ts.map +1 -0
  53. package/dist/paymasterSend.js +329 -0
  54. package/dist/preconditions.d.ts +11 -0
  55. package/dist/preconditions.d.ts.map +1 -0
  56. package/dist/preconditions.js +29 -0
  57. package/dist/prepareSend.d.ts +102 -0
  58. package/dist/prepareSend.d.ts.map +1 -0
  59. package/dist/prepareSend.js +1080 -0
  60. package/dist/prices.d.ts +18 -0
  61. package/dist/prices.d.ts.map +1 -0
  62. package/dist/prices.js +142 -0
  63. package/dist/queryParams.d.ts +9 -0
  64. package/dist/queryParams.d.ts.map +1 -0
  65. package/dist/queryParams.js +66 -0
  66. package/dist/relaySdk.d.ts +65 -0
  67. package/dist/relaySdk.d.ts.map +1 -0
  68. package/dist/relaySdk.js +314 -0
  69. package/dist/relayer.d.ts +23 -0
  70. package/dist/relayer.d.ts.map +1 -0
  71. package/dist/relayer.js +230 -0
  72. package/dist/sendUserOp.d.ts +140 -0
  73. package/dist/sendUserOp.d.ts.map +1 -0
  74. package/dist/sendUserOp.js +388 -0
  75. package/dist/sequenceWallet.d.ts +79 -0
  76. package/dist/sequenceWallet.d.ts.map +1 -0
  77. package/dist/sequenceWallet.js +374 -0
  78. package/dist/theme.d.ts +3 -0
  79. package/dist/theme.d.ts.map +1 -0
  80. package/dist/theme.js +1 -0
  81. package/dist/toSimpleSmartAccount.d.ts +95 -0
  82. package/dist/toSimpleSmartAccount.d.ts.map +1 -0
  83. package/dist/toSimpleSmartAccount.js +373 -0
  84. package/dist/tokenBalances.d.ts +118 -0
  85. package/dist/tokenBalances.d.ts.map +1 -0
  86. package/dist/tokenBalances.js +492 -0
  87. package/dist/tokens.d.ts +50 -0
  88. package/dist/tokens.d.ts.map +1 -0
  89. package/dist/tokens.js +356 -0
  90. package/dist/trails.d.ts +128 -0
  91. package/dist/trails.d.ts.map +1 -0
  92. package/dist/trails.js +1031 -0
  93. package/dist/umd/trails.min.js +12610 -0
  94. package/dist/umd/trails.min.js.map +1 -0
  95. package/dist/umd.d.ts +24 -0
  96. package/dist/umd.d.ts.map +1 -0
  97. package/dist/utils.d.ts +5 -0
  98. package/dist/utils.d.ts.map +1 -0
  99. package/dist/utils.js +9 -0
  100. package/dist/widget/ConstantsUtil-B-_-u8aQ.js +6 -0
  101. package/dist/widget/add-hVLs3ldJ.js +20 -0
  102. package/dist/widget/all-wallets-Cwxnx4BT.js +11 -0
  103. package/dist/widget/app-store-CAAVQjW0.js +22 -0
  104. package/dist/widget/apple-C3BSbglw.js +23 -0
  105. package/dist/widget/arrow-bottom-circle-BGU9MmsZ.js +16 -0
  106. package/dist/widget/arrow-bottom-hS_SA8Gp.js +13 -0
  107. package/dist/widget/arrow-left-CJZanWz7.js +13 -0
  108. package/dist/widget/arrow-right-C1qL8EMd.js +13 -0
  109. package/dist/widget/arrow-top-CbuCmbQs.js +13 -0
  110. package/dist/widget/bank-CXBEEGbb.js +19 -0
  111. package/dist/widget/bin-Dqzv3zCZ.js +9 -0
  112. package/dist/widget/bitcoin-4y3sovZp.js +18 -0
  113. package/dist/widget/browser-DyOl4_8m.js +1413 -0
  114. package/dist/widget/browser-t7Fh0sEU.js +19 -0
  115. package/dist/widget/card-Bo4CZkTs.js +19 -0
  116. package/dist/widget/ccip-BynehMIN.js +232 -0
  117. package/dist/widget/checkmark-DV6OKvnY.js +16 -0
  118. package/dist/widget/checkmark-bold-CAp1-IQ2.js +13 -0
  119. package/dist/widget/chevron-bottom-BjzsVzk9.js +13 -0
  120. package/dist/widget/chevron-left-CQZBDCiR.js +13 -0
  121. package/dist/widget/chevron-right-Dhg4zeZM.js +13 -0
  122. package/dist/widget/chevron-top-CDQmfJef.js +13 -0
  123. package/dist/widget/chrome-store-BNaC_b6w.js +66 -0
  124. package/dist/widget/circle-BC_GBj91.js +9 -0
  125. package/dist/widget/clock-BmF8-4a0.js +13 -0
  126. package/dist/widget/close-Bf61nZ8o.js +13 -0
  127. package/dist/widget/coinPlaceholder-7cZW2058.js +13 -0
  128. package/dist/widget/compass-CFC3yhnW.js +13 -0
  129. package/dist/widget/components/ChainImage.d.ts +8 -0
  130. package/dist/widget/components/ChainImage.d.ts.map +1 -0
  131. package/dist/widget/components/ChainImage.js +6 -0
  132. package/dist/widget/components/ConnectWallet.d.ts +18 -0
  133. package/dist/widget/components/ConnectWallet.d.ts.map +1 -0
  134. package/dist/widget/components/ConnectWallet.js +66 -0
  135. package/dist/widget/components/DebugScreensDropdown.d.ts +9 -0
  136. package/dist/widget/components/DebugScreensDropdown.d.ts.map +1 -0
  137. package/dist/widget/components/DebugScreensDropdown.js +40 -0
  138. package/dist/widget/components/FeeOptions.d.ts +17 -0
  139. package/dist/widget/components/FeeOptions.d.ts.map +1 -0
  140. package/dist/widget/components/FeeOptions.js +65 -0
  141. package/dist/widget/components/Footer.d.ts +9 -0
  142. package/dist/widget/components/Footer.d.ts.map +1 -0
  143. package/dist/widget/components/Footer.js +13 -0
  144. package/dist/widget/components/GreenCheckAnimation.d.ts +2 -0
  145. package/dist/widget/components/GreenCheckAnimation.d.ts.map +1 -0
  146. package/dist/widget/components/GreenCheckAnimation.js +74 -0
  147. package/dist/widget/components/Modal.d.ts +11 -0
  148. package/dist/widget/components/Modal.d.ts.map +1 -0
  149. package/dist/widget/components/Modal.js +36 -0
  150. package/dist/widget/components/Receipt.d.ts +13 -0
  151. package/dist/widget/components/Receipt.d.ts.map +1 -0
  152. package/dist/widget/components/Receipt.js +36 -0
  153. package/dist/widget/components/SendForm.d.ts +44 -0
  154. package/dist/widget/components/SendForm.d.ts.map +1 -0
  155. package/dist/widget/components/SendForm.js +177 -0
  156. package/dist/widget/components/TokenImage.d.ts +10 -0
  157. package/dist/widget/components/TokenImage.d.ts.map +1 -0
  158. package/dist/widget/components/TokenImage.js +8 -0
  159. package/dist/widget/components/TokenList.d.ts +16 -0
  160. package/dist/widget/components/TokenList.d.ts.map +1 -0
  161. package/dist/widget/components/TokenList.js +39 -0
  162. package/dist/widget/components/TransferPending.d.ts +11 -0
  163. package/dist/widget/components/TransferPending.d.ts.map +1 -0
  164. package/dist/widget/components/TransferPending.js +77 -0
  165. package/dist/widget/components/TransferPendingVertical.d.ts +18 -0
  166. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -0
  167. package/dist/widget/components/TransferPendingVertical.js +183 -0
  168. package/dist/widget/components/WalletConfirmation.d.ts +18 -0
  169. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -0
  170. package/dist/widget/components/WalletConfirmation.js +22 -0
  171. package/dist/widget/config.d.ts +4 -0
  172. package/dist/widget/config.d.ts.map +1 -0
  173. package/dist/widget/config.js +3 -0
  174. package/dist/widget/copy-e0xXvKN0.js +20 -0
  175. package/dist/widget/cursor-CqM3v0xJ.js +8 -0
  176. package/dist/widget/cursor-transparent-CUQpdsCG.js +17 -0
  177. package/dist/widget/desktop-DUDGIRpM.js +14 -0
  178. package/dist/widget/disconnect-DUFST9QQ.js +13 -0
  179. package/dist/widget/discord-C1cj365Z.js +22 -0
  180. package/dist/widget/email-BHhmb_lX.js +703 -0
  181. package/dist/widget/embedded-wallet-CuuC4eah.js +467 -0
  182. package/dist/widget/ethereum-CfmBVfeB.js +15 -0
  183. package/dist/widget/etherscan-BSiynDhW.js +11 -0
  184. package/dist/widget/exclamation-triangle-DEiFNpHw.js +9 -0
  185. package/dist/widget/extension-mRmfCDxo.js +13 -0
  186. package/dist/widget/external-link-B4xMIVnW.js +13 -0
  187. package/dist/widget/facebook-CBAZStBR.js +31 -0
  188. package/dist/widget/farcaster-LHDEDf5S.js +17 -0
  189. package/dist/widget/filters-CBijuvFv.js +13 -0
  190. package/dist/widget/github-C3ILD420.js +23 -0
  191. package/dist/widget/google-CSj73POX.js +23 -0
  192. package/dist/widget/help-circle-2hdG5IdB.js +17 -0
  193. package/dist/widget/hooks/useAmountUsd.d.ts +13 -0
  194. package/dist/widget/hooks/useAmountUsd.d.ts.map +1 -0
  195. package/dist/widget/hooks/useAmountUsd.js +35 -0
  196. package/dist/widget/hooks/useSendForm.d.ts +125 -0
  197. package/dist/widget/hooks/useSendForm.d.ts.map +1 -0
  198. package/dist/widget/hooks/useSendForm.js +450 -0
  199. package/dist/widget/hooks/useTokenList.d.ts +52 -0
  200. package/dist/widget/hooks/useTokenList.d.ts.map +1 -0
  201. package/dist/widget/hooks/useTokenList.js +252 -0
  202. package/dist/widget/id-ByYSrwsd.js +17 -0
  203. package/dist/widget/if-defined-DRXJEhv7.js +752 -0
  204. package/dist/widget/image-C90L4Rf6.js +9 -0
  205. package/dist/widget/index-B3SlQ9v3.js +46 -0
  206. package/dist/widget/index-B8LPuLXQ.js +78 -0
  207. package/dist/widget/index-BDbworWA.js +171 -0
  208. package/dist/widget/index-BTlDgFSK.js +98 -0
  209. package/dist/widget/index-BUCcjXbd.js +306 -0
  210. package/dist/widget/index-BZ34edi2.js +1055 -0
  211. package/dist/widget/index-BiCU29wK.js +147 -0
  212. package/dist/widget/index-BlmqIKsY.js +266 -0
  213. package/dist/widget/index-BlviH5nG.js +55 -0
  214. package/dist/widget/index-Bwd5X3fS.js +8306 -0
  215. package/dist/widget/index-C5gmknHK.js +78 -0
  216. package/dist/widget/index-CD6dBcRj.js +76 -0
  217. package/dist/widget/index-CDlhy529.js +63 -0
  218. package/dist/widget/index-CHXa5ke-.js +59 -0
  219. package/dist/widget/index-CK94R-H7.js +22498 -0
  220. package/dist/widget/index-CQzo3m3x.js +182 -0
  221. package/dist/widget/index-CRT8cAwG.js +325 -0
  222. package/dist/widget/index-CY7Lt2Yu.js +310 -0
  223. package/dist/widget/index-CiKfAu1E.js +79 -0
  224. package/dist/widget/index-CkCu6rMi.js +258 -0
  225. package/dist/widget/index-CngLTu_R.js +517 -0
  226. package/dist/widget/index-CsMV8-em.js +2577 -0
  227. package/dist/widget/index-Cu2Wva8v.js +200 -0
  228. package/dist/widget/index-DKBxLTEF.js +240 -0
  229. package/dist/widget/index-DQEVT3dx.js +511 -0
  230. package/dist/widget/index-DU2HcCis.js +200 -0
  231. package/dist/widget/index-DutZGWNW.js +321 -0
  232. package/dist/widget/index-O0glArmc.js +182 -0
  233. package/dist/widget/index-O8FmRjKe.js +63719 -0
  234. package/dist/widget/index-RtKXrB6I.js +576 -0
  235. package/dist/widget/index-TIYtS0gE.js +88 -0
  236. package/dist/widget/index-dQNJvWHs.js +66 -0
  237. package/dist/widget/index-wmEwdsq7.js +909 -0
  238. package/dist/widget/index.d.ts +3 -0
  239. package/dist/widget/index.d.ts.map +1 -0
  240. package/dist/widget/index.js +2 -0
  241. package/dist/widget/info-DMPChDjV.js +8 -0
  242. package/dist/widget/info-circle-DAvS_7nY.js +17 -0
  243. package/dist/widget/lightbulb-DnZ9mNEs.js +8 -0
  244. package/dist/widget/lit-html-BRjl1r6K.js +243 -0
  245. package/dist/widget/mail-DpaVSOP8.js +13 -0
  246. package/dist/widget/mobile-CRvdyu7I.js +14 -0
  247. package/dist/widget/more-C5VqW9PR.js +16 -0
  248. package/dist/widget/network-placeholder-CZ0vApma.js +19 -0
  249. package/dist/widget/nftPlaceholder-7jjIK2bT.js +13 -0
  250. package/dist/widget/off-4mHjJLLX.js +9 -0
  251. package/dist/widget/onramp-Bc0ozVsw.js +929 -0
  252. package/dist/widget/play-store-Uocul8nC.js +37 -0
  253. package/dist/widget/plus-DrYF7siO.js +18 -0
  254. package/dist/widget/prepareSend-BQJmzM5B.js +54987 -0
  255. package/dist/widget/qr-code-DcnGMUB3.js +11 -0
  256. package/dist/widget/receive-fvIVd7R_.js +184 -0
  257. package/dist/widget/recycle-horizontal-DrDwXC4D.js +14 -0
  258. package/dist/widget/ref-CXNmEjML.js +41 -0
  259. package/dist/widget/refresh-OK9lIPLS.js +13 -0
  260. package/dist/widget/reown-logo-C-Qn7mS3.js +17 -0
  261. package/dist/widget/search-DZqv1oKg.js +13 -0
  262. package/dist/widget/send-CJlmI-xe.js +1039 -0
  263. package/dist/widget/send-otoEC8uU.js +20 -0
  264. package/dist/widget/socials-BJciurWF.js +599 -0
  265. package/dist/widget/solana-Bv5Hs_0T.js +18 -0
  266. package/dist/widget/swapHorizontal-BzOPGV37.js +13 -0
  267. package/dist/widget/swapHorizontalBold-axyHnSmj.js +13 -0
  268. package/dist/widget/swapHorizontalMedium-C6YOPfPz.js +21 -0
  269. package/dist/widget/swapHorizontalRoundedBold-yVcLbWNT.js +13 -0
  270. package/dist/widget/swapVertical-BDjxt9pE.js +13 -0
  271. package/dist/widget/swaps-DEWNj4kd.js +1637 -0
  272. package/dist/widget/telegram-BQJD7dlP.js +21 -0
  273. package/dist/widget/three-dots-DW9jmSMG.js +10 -0
  274. package/dist/widget/transactions-uCseGQQt.js +38 -0
  275. package/dist/widget/twitch-XugxDfOE.js +23 -0
  276. package/dist/widget/twitterIcon-DQVObQUL.js +11 -0
  277. package/dist/widget/types.d.ts +51 -0
  278. package/dist/widget/types.d.ts.map +1 -0
  279. package/dist/widget/types.js +1 -0
  280. package/dist/widget/verify-DpMYHxLf.js +13 -0
  281. package/dist/widget/verify-filled-KpEL6ZJ_.js +13 -0
  282. package/dist/widget/w3m-modal-C8e-6Kba.js +1047 -0
  283. package/dist/widget/wallet-D8ssEB0o.js +13 -0
  284. package/dist/widget/wallet-placeholder-HtAy21Wc.js +19 -0
  285. package/dist/widget/walletconnect-Bp_4XfrY.js +37 -0
  286. package/dist/widget/warning-circle-FgYS7P7n.js +17 -0
  287. package/dist/widget/widget/index.js +7 -0
  288. package/dist/widget/widget.d.ts +47 -0
  289. package/dist/widget/widget.d.ts.map +1 -0
  290. package/dist/widget/widget.js +932 -0
  291. package/dist/widget/x-DlZBoP9k.js +17 -0
  292. package/dist/widget/x-mark-Ba9pt-_h.js +8 -0
  293. package/package.json +102 -8
  294. package/src/abi.ts +38 -0
  295. package/src/apiClient.ts +32 -0
  296. package/src/buffer.ts +10 -0
  297. package/src/cctp.ts +579 -0
  298. package/src/chainSwitch.ts +55 -0
  299. package/src/chains.ts +124 -0
  300. package/src/constants.ts +26 -0
  301. package/src/encoders.ts +20 -0
  302. package/src/error.ts +15 -0
  303. package/src/explorer.ts +37 -0
  304. package/src/gasless.ts +545 -0
  305. package/src/index.ts +48 -0
  306. package/src/indexerClient.ts +36 -0
  307. package/src/intents.ts +537 -0
  308. package/src/metaTxnMonitor.ts +163 -0
  309. package/src/metaTxns.ts +21 -0
  310. package/src/paymasterSend.ts +503 -0
  311. package/src/preconditions.ts +52 -0
  312. package/src/prepareSend.ts +1849 -0
  313. package/src/prices.ts +186 -0
  314. package/src/queryParams.ts +80 -0
  315. package/src/relaySdk.ts +481 -0
  316. package/src/relayer.ts +255 -0
  317. package/src/sendUserOp.ts +570 -0
  318. package/src/sequenceWallet.ts +579 -0
  319. package/src/theme.ts +2 -0
  320. package/src/toSimpleSmartAccount.ts +567 -0
  321. package/src/tokenBalances.ts +760 -0
  322. package/src/tokens.ts +471 -0
  323. package/src/trails.ts +1591 -0
  324. package/src/types.d.ts +11 -0
  325. package/src/umd.tsx +49 -0
  326. package/src/utils.ts +16 -0
  327. package/src/vite-env.d.ts +4 -0
  328. package/src/widget/assets/MetaMask-icon-fox-with-margins.svg +31 -0
  329. package/src/widget/assets/MetaMask-icon-fox.svg +26 -0
  330. package/src/widget/assets/MetaMask-logo-black.svg +3 -0
  331. package/src/widget/assets/MetaMask-logo-white.svg +16 -0
  332. package/src/widget/assets/Privy_Brandmark_Black.svg +9 -0
  333. package/src/widget/assets/Privy_Brandmark_White.svg +9 -0
  334. package/src/widget/assets/Trails-logo-black.svg +11 -0
  335. package/src/widget/assets/Trails-logo-white.svg +11 -0
  336. package/src/widget/components/ChainImage.tsx +28 -0
  337. package/src/widget/components/ConnectWallet.tsx +206 -0
  338. package/src/widget/components/DebugScreensDropdown.tsx +88 -0
  339. package/src/widget/components/FeeOptions.tsx +199 -0
  340. package/src/widget/components/Footer.tsx +51 -0
  341. package/src/widget/components/GreenCheckAnimation.tsx +119 -0
  342. package/src/widget/components/Modal.tsx +97 -0
  343. package/src/widget/components/Receipt.tsx +237 -0
  344. package/src/widget/components/SendForm.tsx +695 -0
  345. package/src/widget/components/TokenImage.tsx +37 -0
  346. package/src/widget/components/TokenList.tsx +287 -0
  347. package/src/widget/components/TransferPending.tsx +204 -0
  348. package/src/widget/components/TransferPendingVertical.tsx +412 -0
  349. package/src/widget/components/WalletConfirmation.tsx +172 -0
  350. package/src/widget/config.ts +5 -0
  351. package/src/widget/hooks/useAmountUsd.ts +59 -0
  352. package/src/widget/hooks/useSendForm.ts +715 -0
  353. package/src/widget/hooks/useTokenList.ts +397 -0
  354. package/src/widget/index.css +2 -0
  355. package/src/widget/index.tsx +8 -0
  356. package/src/widget/types/svg.d.ts +8 -0
  357. package/src/widget/types.ts +59 -0
  358. package/src/widget/widget.tsx +1438 -0
  359. package/index.js +0 -1
@@ -0,0 +1,760 @@
1
+ import type {
2
+ GatewayNativeTokenBalances,
3
+ GatewayTokenBalance,
4
+ GetTokenBalancesSummaryReturn,
5
+ NativeTokenBalance,
6
+ SequenceIndexerGateway,
7
+ TokenBalance,
8
+ } from "@0xsequence/indexer"
9
+ import { ContractVerificationStatus } from "@0xsequence/indexer"
10
+ import type { Page, Price, SequenceAPIClient } from "@0xsequence/trails-api"
11
+ import { QueryClient, useQuery } from "@tanstack/react-query"
12
+ import type { Address } from "ox"
13
+ import { useEffect, useState } from "react"
14
+ import { formatUnits, parseUnits, zeroAddress } from "viem"
15
+ import { useAPIClient } from "./apiClient.js"
16
+ import { useIndexerGatewayClient } from "./indexerClient.js"
17
+ import { getTokenPrices, useTokenPrices } from "./prices.js"
18
+
19
+ export type { NativeTokenBalance, TokenBalance }
20
+
21
+ // Initialize query client for token balances
22
+ const tokenBalancesQueryClient = new QueryClient({
23
+ defaultOptions: {
24
+ queries: {
25
+ staleTime: 60000, // 1 minute
26
+ gcTime: 300000, // 5 minutes
27
+ retry: 2,
28
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
29
+ refetchOnWindowFocus: false,
30
+ refetchOnReconnect: true,
31
+ },
32
+ },
33
+ })
34
+
35
+ // Default empty page info for query fallback
36
+ const defaultPage = { page: 1, pageSize: 10, more: false }
37
+
38
+ // Type guard for native token balance
39
+ export function isNativeToken(
40
+ token: TokenBalance | NativeTokenBalance,
41
+ ): token is NativeTokenBalance {
42
+ if ("contractAddress" in token) {
43
+ return false
44
+ }
45
+ return true
46
+ }
47
+
48
+ export interface TokenBalanceWithPrice extends TokenBalance {
49
+ price?: Price
50
+ balanceUsd?: number
51
+ balanceUsdFormatted?: string
52
+ }
53
+
54
+ export interface NativeTokenBalanceWithPrice extends NativeTokenBalance {
55
+ price?: Price
56
+ balanceUsd?: number
57
+ balanceUsdFormatted?: string
58
+ symbol?: string
59
+ }
60
+
61
+ export type TokenBalanceExtended =
62
+ | TokenBalanceWithPrice
63
+ | NativeTokenBalanceWithPrice
64
+
65
+ export function sortTokensByPriority(
66
+ a: TokenBalanceExtended,
67
+ b: TokenBalanceExtended,
68
+ ): number {
69
+ // First sort by USD balance if available
70
+ const aUsdBalance = a.balanceUsd ?? 0
71
+ const bUsdBalance = b.balanceUsd ?? 0
72
+ if (aUsdBalance !== bUsdBalance) {
73
+ return bUsdBalance - aUsdBalance // Higher USD balance first
74
+ }
75
+
76
+ // Then sort by native token status
77
+ if (isNativeToken(a) && !isNativeToken(b)) return -1
78
+ if (!isNativeToken(a) && isNativeToken(b)) return 1
79
+
80
+ // Finally sort by token balance
81
+ try {
82
+ const balanceA = BigInt(a.balance)
83
+ const balanceB = BigInt(b.balance)
84
+ if (balanceA > balanceB) return -1
85
+ if (balanceA < balanceB) return 1
86
+ } catch {
87
+ // If balance comparison fails, maintain current order
88
+ return 0
89
+ }
90
+
91
+ return 0
92
+ }
93
+
94
+ export interface GetTokenBalancesWithPrice {
95
+ page: Page
96
+ nativeBalances: Array<
97
+ NativeTokenBalance & {
98
+ price?: Price
99
+ symbol?: string
100
+ balanceUsd?: number
101
+ balanceUsdFormatted?: string
102
+ }
103
+ >
104
+ balances: Array<
105
+ TokenBalance & {
106
+ price?: Price
107
+ balanceUsd?: number
108
+ balanceUsdFormatted?: string
109
+ }
110
+ >
111
+ }
112
+
113
+ export function useTokenBalances(
114
+ address: Address.Address,
115
+ indexerGatewayClient?: SequenceIndexerGateway,
116
+ sequenceApiClient?: SequenceAPIClient,
117
+ ): {
118
+ tokenBalancesData: GetTokenBalancesSummaryReturn | undefined
119
+ isLoadingBalances: boolean
120
+ isLoadingPrices: boolean
121
+ isLoadingSortedTokens: boolean
122
+ balanceError: Error | null
123
+ sortedTokens: TokenBalanceExtended[]
124
+ } {
125
+ // Always call hooks unconditionally to fix React rules violation
126
+ const hookIndexerClient = useIndexerGatewayClient()
127
+ const hookApiClient = useAPIClient()
128
+
129
+ // Use passed parameters if available, otherwise use hook results
130
+ const indexerClient = indexerGatewayClient ?? hookIndexerClient
131
+ const apiClient = sequenceApiClient ?? hookApiClient
132
+
133
+ // Fetch token balances with improved query key structure
134
+ const {
135
+ data: tokenBalancesData,
136
+ isLoading: isLoadingBalances,
137
+ error: balanceError,
138
+ } = useQuery<GetTokenBalancesWithPrice>({
139
+ queryKey: ["tokenBalances", "summary", address],
140
+ queryFn: async (): Promise<GetTokenBalancesWithPrice> => {
141
+ if (!address) {
142
+ console.warn("[trails-sdk] No account address or indexer client")
143
+ return {
144
+ balances: [],
145
+ nativeBalances: [],
146
+ page: defaultPage,
147
+ } as GetTokenBalancesWithPrice
148
+ }
149
+ try {
150
+ const summaryFromGateway = await getTokenBalances({
151
+ account: address,
152
+ indexerGatewayClient: indexerClient,
153
+ })
154
+
155
+ return {
156
+ page: summaryFromGateway.page,
157
+ balances: (
158
+ summaryFromGateway.balances as unknown as GatewayTokenBalance[]
159
+ ).flatMap((b) => b.results),
160
+ nativeBalances: (
161
+ summaryFromGateway.nativeBalances as unknown as GatewayNativeTokenBalances[]
162
+ ).flatMap((b) => b.results),
163
+ }
164
+ } catch (error) {
165
+ console.error("[trails-sdk] Failed to fetch token balances:", error)
166
+ throw error
167
+ }
168
+ },
169
+ enabled: !!address && !!indexerClient,
170
+ staleTime: 60000, // 1 minute
171
+ gcTime: 300000, // 5 minutes cache time
172
+ retry: (failureCount, error) => {
173
+ // Don't retry 404s or network errors after 3 attempts
174
+ if (error && "status" in error && error.status === 404) return false
175
+ if (failureCount < 3) return true
176
+ return false
177
+ },
178
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), // Exponential backoff
179
+ refetchOnWindowFocus: false, // Prevent refetch on window focus
180
+ refetchOnReconnect: true, // Refetch on reconnect
181
+ refetchInterval: 300000, // Background refetch every 5 minutes
182
+ refetchIntervalInBackground: true,
183
+ })
184
+
185
+ const { tokenPrices, isLoadingTokenPrices } = useTokenPrices(
186
+ (tokenBalancesData?.balances ?? [])
187
+ .map((b: any) => {
188
+ return {
189
+ tokenId: b.contractInfo?.symbol,
190
+ contractAddress: b.contractAddress,
191
+ chainId: b.contractInfo?.chainId!,
192
+ }
193
+ })
194
+ .concat(
195
+ (tokenBalancesData?.nativeBalances ?? []).map((b) => {
196
+ return {
197
+ tokenId: b.symbol,
198
+ contractAddress: zeroAddress,
199
+ chainId: b.chainId,
200
+ }
201
+ }),
202
+ ) ?? [],
203
+ apiClient,
204
+ )
205
+
206
+ const { data: sortedTokens = [], isLoading: isLoadingSortedTokens } =
207
+ useQuery<TokenBalanceExtended[]>({
208
+ queryKey: [
209
+ "tokenBalances",
210
+ "sorted",
211
+ address,
212
+ tokenBalancesData?.page?.page,
213
+ tokenPrices?.length,
214
+ ],
215
+ queryFn: () => {
216
+ if (!tokenBalancesData || !tokenPrices) {
217
+ return []
218
+ }
219
+
220
+ const balances = [
221
+ ...tokenBalancesData.nativeBalances,
222
+ ...tokenBalancesData.balances,
223
+ ].filter((token) => {
224
+ try {
225
+ return BigInt(token.balance) > 0n
226
+ } catch {
227
+ return false
228
+ }
229
+ })
230
+
231
+ // First pass: add prices to all tokens
232
+ const tokensWithPrices = balances.map((token) => {
233
+ const isNative = isNativeToken(token)
234
+ const priceData = tokenPrices.find(
235
+ (p: { token: { contractAddress: string; chainId: number } }) =>
236
+ p.token.contractAddress ===
237
+ (isNative ? zeroAddress : token.contractAddress) &&
238
+ p.token.chainId ===
239
+ (isNative ? token.chainId : token.contractInfo?.chainId),
240
+ )
241
+
242
+ if (priceData?.price) {
243
+ const tokenWithPrice = { ...token, price: priceData.price }
244
+ tokenWithPrice.balanceUsd = getTokenBalanceUsd(
245
+ token,
246
+ priceData.price,
247
+ )
248
+ tokenWithPrice.balanceUsdFormatted = getTokenBalanceUsdFormatted(
249
+ token,
250
+ priceData.price,
251
+ )
252
+ return tokenWithPrice
253
+ }
254
+
255
+ return token
256
+ })
257
+
258
+ return tokensWithPrices.sort(sortTokensByPriority)
259
+ },
260
+ enabled:
261
+ !isLoadingBalances &&
262
+ !isLoadingTokenPrices &&
263
+ !!tokenBalancesData &&
264
+ !!tokenPrices,
265
+ staleTime: 30000, // 30 seconds for sorted tokens
266
+ gcTime: 120000, // 2 minutes cache time
267
+ refetchOnWindowFocus: false,
268
+ })
269
+
270
+ return {
271
+ tokenBalancesData,
272
+ isLoadingBalances,
273
+ isLoadingPrices: isLoadingTokenPrices,
274
+ isLoadingSortedTokens:
275
+ isLoadingSortedTokens || isLoadingBalances || isLoadingTokenPrices,
276
+ balanceError,
277
+ sortedTokens,
278
+ }
279
+ }
280
+
281
+ // Helper to format balance
282
+ export function formatBalance(balance: string, decimals: number = 18) {
283
+ try {
284
+ const formatted = formatUnits(BigInt(balance), decimals)
285
+ return formatValue(formatted)
286
+ } catch (e) {
287
+ console.error("[trails-sdk] Error formatting balance:", e)
288
+ return balance
289
+ }
290
+ }
291
+
292
+ export function getTokenBalanceUsd(
293
+ token: TokenBalance | NativeTokenBalance,
294
+ tokenPrice: Price,
295
+ ): number {
296
+ const isNative = isNativeToken(token)
297
+ const formattedBalance = formatBalance(
298
+ token.balance,
299
+ isNative ? 18 : token.contractInfo?.decimals,
300
+ )
301
+ const priceUsd = Number(tokenPrice.value) ?? 0
302
+ return Number(formattedBalance) * priceUsd
303
+ }
304
+
305
+ export function formatValue(value: string | number): string {
306
+ try {
307
+ return Number(value).toLocaleString(undefined, {
308
+ maximumFractionDigits: 5,
309
+ minimumFractionDigits: 2,
310
+ })
311
+ } catch (err) {
312
+ console.error("[trails-sdk] Error formatting value:", err)
313
+ }
314
+
315
+ return value.toString()
316
+ }
317
+
318
+ export function formatUsdValue(value: number | string = 0): string {
319
+ return Intl.NumberFormat("en-US", {
320
+ style: "currency",
321
+ currency: "USD",
322
+ maximumFractionDigits: 2,
323
+ minimumFractionDigits: 2,
324
+ }).format(Number(value))
325
+ }
326
+
327
+ export function getTokenBalanceUsdFormatted(
328
+ token: TokenBalance | NativeTokenBalance,
329
+ tokenPrice: Price,
330
+ ): string {
331
+ const balanceUsd = getTokenBalanceUsd(token, tokenPrice)
332
+ return formatUsdValue(balanceUsd)
333
+ }
334
+
335
+ export function useTokenBalanceUsdFormat(
336
+ token: TokenBalance | NativeTokenBalance,
337
+ tokenPrice: Price,
338
+ ): string {
339
+ const [format, setFormat] = useState<string>("")
340
+ useEffect(() => {
341
+ const formattedBalance = getTokenBalanceUsdFormatted(token, tokenPrice)
342
+ setFormat(formattedBalance)
343
+ }, [token, tokenPrice])
344
+ return format
345
+ }
346
+
347
+ export type GetTokenBalancesParams = {
348
+ account: string
349
+ indexerGatewayClient: SequenceIndexerGateway
350
+ }
351
+
352
+ // Separate fetch function for token balances summary
353
+ export async function fetchGetTokenBalancesSummary({
354
+ account,
355
+ indexerGatewayClient,
356
+ }: GetTokenBalancesParams): Promise<GetTokenBalancesSummaryReturn> {
357
+ if (!account || !indexerGatewayClient) {
358
+ throw new Error("Account address and indexer client are required")
359
+ }
360
+
361
+ try {
362
+ const summaryFromGateway =
363
+ await indexerGatewayClient.getTokenBalancesSummary({
364
+ filter: {
365
+ accountAddresses: [account],
366
+ contractStatus: ContractVerificationStatus.VERIFIED,
367
+ contractTypes: ["ERC20"],
368
+ omitNativeBalances: false,
369
+ },
370
+ })
371
+
372
+ return summaryFromGateway as unknown as GetTokenBalancesSummaryReturn
373
+ } catch (error) {
374
+ console.error("[trails-sdk] Failed to fetch token balances summary:", error)
375
+ throw error
376
+ }
377
+ }
378
+
379
+ export async function getTokenBalances({
380
+ account,
381
+ indexerGatewayClient,
382
+ }: GetTokenBalancesParams): Promise<GetTokenBalancesSummaryReturn> {
383
+ return tokenBalancesQueryClient.fetchQuery({
384
+ queryKey: ["tokenBalances", "summary", account],
385
+ queryFn: () =>
386
+ fetchGetTokenBalancesSummary({ account, indexerGatewayClient }),
387
+ staleTime: 60000, // 1 minute
388
+ gcTime: 300000, // 5 minutes
389
+ })
390
+ }
391
+
392
+ // Cache invalidation utility function
393
+ export function invalidateTokenBalancesCache(account?: string) {
394
+ if (account) {
395
+ // Invalidate specific account's token balances
396
+ tokenBalancesQueryClient.invalidateQueries({
397
+ queryKey: ["tokenBalances", account],
398
+ })
399
+ } else {
400
+ // Invalidate all token balance queries
401
+ tokenBalancesQueryClient.invalidateQueries({
402
+ queryKey: ["tokenBalances"],
403
+ })
404
+ }
405
+ }
406
+
407
+ export type GetTokenBalancesFlatArrayParams = {
408
+ account: string
409
+ indexerGatewayClient: SequenceIndexerGateway
410
+ }
411
+
412
+ export type GetTokenBalancesFlatArrayReturn = {
413
+ balances: TokenBalance[]
414
+ }
415
+
416
+ export async function getTokenBalancesFlatArray({
417
+ account,
418
+ indexerGatewayClient,
419
+ }: GetTokenBalancesFlatArrayParams): Promise<TokenBalance[]> {
420
+ const summaryFromGateway = await getTokenBalances({
421
+ account,
422
+ indexerGatewayClient,
423
+ })
424
+ const tokenMap = new Map<string, TokenBalance>()
425
+
426
+ for (const balance of summaryFromGateway.balances) {
427
+ ;(balance as any).results.forEach((b: any) => {
428
+ tokenMap.set(
429
+ `${b.contractAddress}-${b.contractInfo?.chainId}-${b.contractInfo?.symbol}`,
430
+ {
431
+ ...b,
432
+ contractAddress: b.contractAddress ?? zeroAddress,
433
+ tokenId: b.contractInfo?.symbol,
434
+ },
435
+ )
436
+ })
437
+ }
438
+
439
+ for (const balance of summaryFromGateway.nativeBalances) {
440
+ ;(balance as any).results.forEach((b: any) => {
441
+ tokenMap.set(`${b.contractAddress}-${b.chainId}-${b.symbol}`, {
442
+ ...b,
443
+ contractAddress: b.contractAddress ?? zeroAddress,
444
+ tokenId: b.symbol,
445
+ })
446
+ })
447
+ }
448
+
449
+ const tokens = Array.from(tokenMap.values())
450
+
451
+ return tokens
452
+ }
453
+
454
+ export type GetTokenBalancesWithPricesParams = {
455
+ account: string
456
+ indexerGatewayClient: SequenceIndexerGateway
457
+ apiClient: SequenceAPIClient
458
+ }
459
+
460
+ export type GetTokenBalancesWithPriceReturn = {
461
+ balances: TokenBalanceWithPrice[]
462
+ }
463
+
464
+ export async function getTokenBalancesWithPrices({
465
+ account,
466
+ indexerGatewayClient,
467
+ apiClient,
468
+ }: GetTokenBalancesWithPricesParams): Promise<GetTokenBalancesWithPriceReturn> {
469
+ const tokens = await getTokenBalancesFlatArray({
470
+ account,
471
+ indexerGatewayClient,
472
+ })
473
+ const tokenPrices = await getTokenPrices(apiClient, tokens)
474
+ const balancesWithPrices = tokens.map((b) => {
475
+ const price = tokenPrices.find((p) => {
476
+ const isSameChain = p.token.chainId === b.chainId
477
+ let isSameToken = p.token.contractAddress === b.contractAddress
478
+ if (!b.contractAddress) {
479
+ isSameToken =
480
+ p.token.contractAddress === zeroAddress || !p.token.contractAddress
481
+ }
482
+ return isSameChain && isSameToken
483
+ })
484
+
485
+ return {
486
+ ...b,
487
+ price: price?.price,
488
+ balanceUsd: price?.price
489
+ ? getTokenBalanceUsd(b, price?.price)
490
+ : undefined,
491
+ balanceUsdFormatted: price?.price
492
+ ? getTokenBalanceUsdFormatted(b, price?.price)
493
+ : undefined,
494
+ }
495
+ })
496
+
497
+ return {
498
+ balances: balancesWithPrices,
499
+ }
500
+ }
501
+
502
+ export type UseAccountTokenBalanceParams = {
503
+ account?: string
504
+ token?: string
505
+ chainId?: number
506
+ indexerGatewayClient?: SequenceIndexerGateway
507
+ apiClient?: SequenceAPIClient
508
+ }
509
+
510
+ export function useAccountTokenBalance({
511
+ account,
512
+ token,
513
+ chainId,
514
+ indexerGatewayClient,
515
+ apiClient,
516
+ }: UseAccountTokenBalanceParams) {
517
+ const { data: tokenBalance, isLoading: isLoadingTokenBalance } = useQuery({
518
+ queryKey: ["tokenBalances", "balances", account],
519
+ queryFn: async () => {
520
+ if (
521
+ !account ||
522
+ !indexerGatewayClient ||
523
+ !apiClient ||
524
+ !token ||
525
+ !chainId
526
+ ) {
527
+ return null
528
+ }
529
+ const { balances } = await getTokenBalancesWithPrices({
530
+ account,
531
+ indexerGatewayClient,
532
+ apiClient,
533
+ })
534
+ const tokenBalance = balances.find(
535
+ (b) =>
536
+ b.chainId === chainId &&
537
+ (b.contractAddress?.toLowerCase() === token.toLowerCase() ||
538
+ (!b.contractAddress && token === zeroAddress)),
539
+ )
540
+
541
+ return tokenBalance
542
+ },
543
+ })
544
+
545
+ return {
546
+ tokenBalance,
547
+ isLoadingTokenBalance,
548
+ }
549
+ }
550
+
551
+ export type HasSufficientBalanceParams = {
552
+ account: string
553
+ token: string
554
+ amount: string
555
+ chainId: number
556
+ indexerGatewayClient: SequenceIndexerGateway
557
+ apiClient: SequenceAPIClient
558
+ }
559
+
560
+ export async function getHasSufficientBalanceToken({
561
+ account,
562
+ token,
563
+ amount,
564
+ chainId,
565
+ indexerGatewayClient,
566
+ apiClient,
567
+ }: HasSufficientBalanceParams): Promise<boolean> {
568
+ const { balances } = await getTokenBalancesWithPrices({
569
+ account,
570
+ indexerGatewayClient,
571
+ apiClient,
572
+ })
573
+ const tokenBalance = balances.find(
574
+ (b) =>
575
+ b.chainId === chainId &&
576
+ (b.contractAddress?.toLowerCase() === token.toLowerCase() ||
577
+ (!b.contractAddress && token === zeroAddress)),
578
+ )
579
+ if (!tokenBalance) {
580
+ return false
581
+ }
582
+ const decimals = tokenBalance?.contractInfo?.decimals ?? 18
583
+ return tokenBalance?.balance
584
+ ? BigInt(tokenBalance.balance) >= parseUnits(amount, decimals)
585
+ : false
586
+ }
587
+
588
+ export function useHasSufficientBalanceToken(
589
+ account: string,
590
+ token: string,
591
+ amount: string,
592
+ chainId: number,
593
+ ): {
594
+ hasSufficientBalanceToken: boolean
595
+ isLoadingHasSufficientBalanceToken: boolean
596
+ } {
597
+ const indexerGatewayClient = useIndexerGatewayClient()
598
+ const apiClient = useAPIClient()
599
+
600
+ const {
601
+ data: hasSufficientBalanceToken,
602
+ isLoading: isLoadingHasSufficientBalanceToken,
603
+ } = useQuery({
604
+ queryKey: ["tokenBalances", "sufficient", account, token, amount, chainId],
605
+ queryFn: () =>
606
+ account
607
+ ? getHasSufficientBalanceToken({
608
+ account: account,
609
+ token: token,
610
+ amount: amount,
611
+ chainId: chainId,
612
+ indexerGatewayClient: indexerGatewayClient,
613
+ apiClient: apiClient,
614
+ })
615
+ : null,
616
+ enabled: !!account && !!token && !!amount && !!chainId,
617
+ staleTime: 45000, // 45 seconds
618
+ gcTime: 180000, // 3 minutes cache time
619
+ retry: (failureCount, error) => {
620
+ if (error && "status" in error && error.status === 404) return false
621
+ if (failureCount < 2) return true
622
+ return false
623
+ },
624
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
625
+ refetchOnWindowFocus: false,
626
+ })
627
+
628
+ return {
629
+ hasSufficientBalanceToken: hasSufficientBalanceToken || false,
630
+ isLoadingHasSufficientBalanceToken,
631
+ }
632
+ }
633
+
634
+ export type GetHasSufficientBalanceUsdParams = {
635
+ account: string
636
+ targetAmountUsd: number | string
637
+ indexerGatewayClient: SequenceIndexerGateway
638
+ apiClient: SequenceAPIClient
639
+ }
640
+
641
+ export async function getHasSufficientBalanceUsd({
642
+ account,
643
+ targetAmountUsd,
644
+ indexerGatewayClient,
645
+ apiClient,
646
+ }: GetHasSufficientBalanceUsdParams): Promise<boolean> {
647
+ const totalBalanceUsd = await getAccountTotalBalanceUsd({
648
+ account,
649
+ indexerGatewayClient,
650
+ apiClient,
651
+ })
652
+ return totalBalanceUsd >= Number(targetAmountUsd)
653
+ }
654
+
655
+ export function useHasSufficientBalanceUsd(
656
+ account: string,
657
+ targetAmountUsd?: number | string | null,
658
+ ): {
659
+ hasSufficientBalanceUsd: boolean
660
+ isLoadingHasSufficientBalanceUsd: boolean
661
+ hasSufficientBalanceUsdError: Error | null
662
+ } {
663
+ const indexerGatewayClient = useIndexerGatewayClient()
664
+ const apiClient = useAPIClient()
665
+
666
+ const {
667
+ data: hasSufficientBalanceUsd,
668
+ isLoading: isLoadingHasSufficientBalanceUsd,
669
+ error: hasSufficientBalanceUsdError,
670
+ } = useQuery({
671
+ queryKey: ["tokenBalances", "sufficientUsd", account, targetAmountUsd],
672
+ queryFn: () =>
673
+ account && targetAmountUsd
674
+ ? getHasSufficientBalanceUsd({
675
+ account: account,
676
+ targetAmountUsd: targetAmountUsd,
677
+ indexerGatewayClient: indexerGatewayClient,
678
+ apiClient: apiClient,
679
+ })
680
+ : false,
681
+ enabled: !!account && !!targetAmountUsd,
682
+ staleTime: 45000, // 45 seconds
683
+ gcTime: 180000, // 3 minutes cache time
684
+ retry: (failureCount, error) => {
685
+ if (error && "status" in error && error.status === 404) return false
686
+ if (failureCount < 2) return true
687
+ return false
688
+ },
689
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
690
+ refetchOnWindowFocus: false,
691
+ })
692
+
693
+ return {
694
+ hasSufficientBalanceUsd: hasSufficientBalanceUsd || false,
695
+ isLoadingHasSufficientBalanceUsd:
696
+ isLoadingHasSufficientBalanceUsd || !targetAmountUsd || !account,
697
+ hasSufficientBalanceUsdError,
698
+ }
699
+ }
700
+
701
+ export type GetAccountTotalBalanceUsdParams = {
702
+ account: string
703
+ indexerGatewayClient: SequenceIndexerGateway
704
+ apiClient: SequenceAPIClient
705
+ }
706
+
707
+ export async function getAccountTotalBalanceUsd({
708
+ account,
709
+ indexerGatewayClient,
710
+ apiClient,
711
+ }: GetAccountTotalBalanceUsdParams): Promise<number> {
712
+ const { balances } = await getTokenBalancesWithPrices({
713
+ account,
714
+ indexerGatewayClient,
715
+ apiClient,
716
+ })
717
+
718
+ return balances.reduce((acc, b) => acc + (b.balanceUsd ?? 0), 0)
719
+ }
720
+
721
+ export function useAccountTotalBalanceUsd(account: string): {
722
+ totalBalanceUsd: number
723
+ isLoadingTotalBalanceUsd: boolean
724
+ totalBalanceUsdFormatted: string
725
+ } {
726
+ const indexerGatewayClient = useIndexerGatewayClient()
727
+ const apiClient = useAPIClient()
728
+
729
+ const { data: totalBalanceUsd, isLoading: isLoadingTotalBalanceUsd } =
730
+ useQuery({
731
+ queryKey: ["tokenBalances", "totalUsd", account],
732
+ queryFn: () =>
733
+ account
734
+ ? getAccountTotalBalanceUsd({
735
+ account: account,
736
+ indexerGatewayClient: indexerGatewayClient,
737
+ apiClient: apiClient,
738
+ })
739
+ : null,
740
+ enabled: !!account,
741
+ staleTime: 60000, // 1 minute
742
+ gcTime: 300000, // 5 minutes cache time
743
+ retry: (failureCount, error) => {
744
+ if (error && "status" in error && error.status === 404) return false
745
+ if (failureCount < 2) return true
746
+ return false
747
+ },
748
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
749
+ refetchOnWindowFocus: false,
750
+ refetchOnReconnect: true,
751
+ refetchInterval: 300000, // Background refetch every 5 minutes
752
+ refetchIntervalInBackground: true,
753
+ })
754
+
755
+ return {
756
+ totalBalanceUsd: totalBalanceUsd || 0,
757
+ isLoadingTotalBalanceUsd,
758
+ totalBalanceUsdFormatted: formatUsdValue(totalBalanceUsd || 0),
759
+ }
760
+ }