@lifi/widget 4.6.0 → 4.7.1

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 (424) hide show
  1. package/README.md +26 -3
  2. package/dist/esm/App.d.ts +1 -2
  3. package/dist/esm/App.d.ts.map +1 -1
  4. package/dist/esm/AppDrawer.d.ts +1 -2
  5. package/dist/esm/AppDrawer.d.ts.map +1 -1
  6. package/dist/esm/components/ActionRow/ActionRow.d.ts +1 -2
  7. package/dist/esm/components/ActionRow/ActionRow.d.ts.map +1 -1
  8. package/dist/esm/components/AmountInput/AmountInput.style.d.ts +2 -2
  9. package/dist/esm/components/AmountInput/AmountInput.style.d.ts.map +1 -1
  10. package/dist/esm/components/AmountInput/AmountInputEndAdornment.d.ts +1 -2
  11. package/dist/esm/components/AmountInput/AmountInputEndAdornment.d.ts.map +1 -1
  12. package/dist/esm/components/AmountInput/PriceFormHelperText.style.d.ts +1 -2
  13. package/dist/esm/components/AmountInput/PriceFormHelperText.style.d.ts.map +1 -1
  14. package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js +6 -2
  15. package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js.map +1 -1
  16. package/dist/esm/components/Avatar/Avatar.d.ts +1 -2
  17. package/dist/esm/components/Avatar/Avatar.d.ts.map +1 -1
  18. package/dist/esm/components/Avatar/TokenAvatar.d.ts +1 -2
  19. package/dist/esm/components/Avatar/TokenAvatar.d.ts.map +1 -1
  20. package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.d.ts +1 -2
  21. package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.d.ts.map +1 -1
  22. package/dist/esm/components/BaseTransactionButton/types.d.ts +1 -2
  23. package/dist/esm/components/BaseTransactionButton/types.d.ts.map +1 -1
  24. package/dist/esm/components/BottomSheet/BottomSheet.js +1 -0
  25. package/dist/esm/components/BottomSheet/BottomSheet.js.map +1 -1
  26. package/dist/esm/components/BottomSheet/types.d.ts +1 -2
  27. package/dist/esm/components/BottomSheet/types.d.ts.map +1 -1
  28. package/dist/esm/components/ButtonTertiary.d.ts +1 -2
  29. package/dist/esm/components/ButtonTertiary.d.ts.map +1 -1
  30. package/dist/esm/components/Card/CardIconButton.d.ts +1 -2
  31. package/dist/esm/components/Card/CardIconButton.d.ts.map +1 -1
  32. package/dist/esm/components/Card/CardTitle.d.ts +1 -2
  33. package/dist/esm/components/Card/CardTitle.d.ts.map +1 -1
  34. package/dist/esm/components/Card/InputCard.d.ts +1 -2
  35. package/dist/esm/components/Card/InputCard.d.ts.map +1 -1
  36. package/dist/esm/components/ChainSelect/ChainSelect.d.ts +1 -2
  37. package/dist/esm/components/ChainSelect/ChainSelect.d.ts.map +1 -1
  38. package/dist/esm/components/ChainSelect/ChainSelect.style.d.ts +1 -2
  39. package/dist/esm/components/ChainSelect/ChainSelect.style.d.ts.map +1 -1
  40. package/dist/esm/components/ContractComponent/ContractComponent.d.ts +1 -2
  41. package/dist/esm/components/ContractComponent/ContractComponent.d.ts.map +1 -1
  42. package/dist/esm/components/ContractComponent/ItemPrice.d.ts +2 -3
  43. package/dist/esm/components/ContractComponent/ItemPrice.d.ts.map +1 -1
  44. package/dist/esm/components/ContractComponent/NFT/NFT.d.ts +1 -2
  45. package/dist/esm/components/ContractComponent/NFT/NFT.d.ts.map +1 -1
  46. package/dist/esm/components/ContractComponent/NFT/NFTBase.d.ts +1 -2
  47. package/dist/esm/components/ContractComponent/NFT/NFTBase.d.ts.map +1 -1
  48. package/dist/esm/components/ContractComponent/NFT/types.d.ts +3 -4
  49. package/dist/esm/components/ContractComponent/NFT/types.d.ts.map +1 -1
  50. package/dist/esm/components/ExecutionStatusCard/ExecutionStatusCard.d.ts +1 -2
  51. package/dist/esm/components/ExecutionStatusCard/ExecutionStatusCard.d.ts.map +1 -1
  52. package/dist/esm/components/ExecutionStatusCard/StatusIcon.d.ts +1 -2
  53. package/dist/esm/components/ExecutionStatusCard/StatusIcon.d.ts.map +1 -1
  54. package/dist/esm/components/FeeBreakdownTooltip.d.ts +1 -2
  55. package/dist/esm/components/FeeBreakdownTooltip.d.ts.map +1 -1
  56. package/dist/esm/components/IconCircle/IconCircle.d.ts +1 -2
  57. package/dist/esm/components/IconCircle/IconCircle.d.ts.map +1 -1
  58. package/dist/esm/components/IconCircle/statusIcons.d.ts +1 -2
  59. package/dist/esm/components/IconCircle/statusIcons.d.ts.map +1 -1
  60. package/dist/esm/components/IconTypography.d.ts +1 -2
  61. package/dist/esm/components/IconTypography.d.ts.map +1 -1
  62. package/dist/esm/components/ListItem/ListItemButton.d.ts +1 -2
  63. package/dist/esm/components/ListItem/ListItemButton.d.ts.map +1 -1
  64. package/dist/esm/components/Messages/WarningMessages.d.ts +1 -2
  65. package/dist/esm/components/Messages/WarningMessages.d.ts.map +1 -1
  66. package/dist/esm/components/PageContainer.d.ts +2 -3
  67. package/dist/esm/components/PageContainer.d.ts.map +1 -1
  68. package/dist/esm/components/PoweredBy/PoweredBy.d.ts +1 -2
  69. package/dist/esm/components/PoweredBy/PoweredBy.d.ts.map +1 -1
  70. package/dist/esm/components/ProgressToNextUpdate.d.ts +1 -2
  71. package/dist/esm/components/ProgressToNextUpdate.d.ts.map +1 -1
  72. package/dist/esm/components/RouteCard/RouteCard.d.ts +1 -2
  73. package/dist/esm/components/RouteCard/RouteCard.d.ts.map +1 -1
  74. package/dist/esm/components/RouteCard/RouteCardSkeleton.d.ts +1 -2
  75. package/dist/esm/components/RouteCard/RouteCardSkeleton.d.ts.map +1 -1
  76. package/dist/esm/components/RouteCard/RouteDetails.d.ts +1 -2
  77. package/dist/esm/components/RouteCard/RouteDetails.d.ts.map +1 -1
  78. package/dist/esm/components/RouteCard/RouteDetails.style.d.ts +5 -6
  79. package/dist/esm/components/RouteCard/RouteDetails.style.d.ts.map +1 -1
  80. package/dist/esm/components/RouteCard/RouteNotFoundCard.d.ts +1 -2
  81. package/dist/esm/components/RouteCard/RouteNotFoundCard.d.ts.map +1 -1
  82. package/dist/esm/components/RouteCard/RouteTokens.d.ts +1 -2
  83. package/dist/esm/components/RouteCard/RouteTokens.d.ts.map +1 -1
  84. package/dist/esm/components/RouteCard/types.d.ts +2 -3
  85. package/dist/esm/components/RouteCard/types.d.ts.map +1 -1
  86. package/dist/esm/components/Search/SearchInput.d.ts +1 -2
  87. package/dist/esm/components/Search/SearchInput.d.ts.map +1 -1
  88. package/dist/esm/components/SendToWallet/SendToWalletButton.d.ts +1 -2
  89. package/dist/esm/components/SendToWallet/SendToWalletButton.d.ts.map +1 -1
  90. package/dist/esm/components/Skeleton/WidgetSkeleton.d.ts +1 -2
  91. package/dist/esm/components/Skeleton/WidgetSkeleton.d.ts.map +1 -1
  92. package/dist/esm/components/StepActions/SentToWalletRow.d.ts +1 -2
  93. package/dist/esm/components/StepActions/SentToWalletRow.d.ts.map +1 -1
  94. package/dist/esm/components/StepActions/StepActionRow.style.d.ts +1 -2
  95. package/dist/esm/components/StepActions/StepActionRow.style.d.ts.map +1 -1
  96. package/dist/esm/components/StepActions/StepActionsList.d.ts +1 -2
  97. package/dist/esm/components/StepActions/StepActionsList.d.ts.map +1 -1
  98. package/dist/esm/components/StepActions/executionRows.d.ts +2 -3
  99. package/dist/esm/components/StepActions/executionRows.d.ts.map +1 -1
  100. package/dist/esm/components/Token/Token.d.ts.map +1 -1
  101. package/dist/esm/components/Token/Token.js +2 -4
  102. package/dist/esm/components/Token/Token.js.map +1 -1
  103. package/dist/esm/components/TokenList/RecentTokensHeader.js +44 -0
  104. package/dist/esm/components/TokenList/RecentTokensHeader.js.map +1 -0
  105. package/dist/esm/components/TokenList/RecentTokensToggle.js +36 -0
  106. package/dist/esm/components/TokenList/RecentTokensToggle.js.map +1 -0
  107. package/dist/esm/components/TokenList/RemoveRecentTokenButton.js +32 -0
  108. package/dist/esm/components/TokenList/RemoveRecentTokenButton.js.map +1 -0
  109. package/dist/esm/components/TokenList/TokenList.js +55 -5
  110. package/dist/esm/components/TokenList/TokenList.js.map +1 -1
  111. package/dist/esm/components/TokenList/TokenListItem.js +35 -19
  112. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  113. package/dist/esm/components/TokenList/TokenNotFound.d.ts +1 -2
  114. package/dist/esm/components/TokenList/TokenNotFound.d.ts.map +1 -1
  115. package/dist/esm/components/TokenList/VirtualizedTokenList.d.ts +1 -2
  116. package/dist/esm/components/TokenList/VirtualizedTokenList.d.ts.map +1 -1
  117. package/dist/esm/components/TokenList/VirtualizedTokenList.js +55 -42
  118. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  119. package/dist/esm/components/TokenList/types.d.ts +9 -2
  120. package/dist/esm/components/TokenList/types.d.ts.map +1 -1
  121. package/dist/esm/components/TokenList/useTokenSelect.d.ts +1 -2
  122. package/dist/esm/components/TokenList/useTokenSelect.d.ts.map +1 -1
  123. package/dist/esm/components/TokenRate/TokenRate.d.ts +1 -2
  124. package/dist/esm/components/TokenRate/TokenRate.d.ts.map +1 -1
  125. package/dist/esm/components/TransactionCard/TransactionCard.style.d.ts +2 -3
  126. package/dist/esm/components/TransactionCard/TransactionCard.style.d.ts.map +1 -1
  127. package/dist/esm/config/constants.d.ts +1 -2
  128. package/dist/esm/config/constants.d.ts.map +1 -1
  129. package/dist/esm/config/version.d.ts +2 -3
  130. package/dist/esm/config/version.d.ts.map +1 -1
  131. package/dist/esm/config/version.js +1 -1
  132. package/dist/esm/config/version.js.map +1 -1
  133. package/dist/esm/hooks/useAddressActivity.d.ts +1 -2
  134. package/dist/esm/hooks/useAddressActivity.d.ts.map +1 -1
  135. package/dist/esm/hooks/useAddressValidation.d.ts +2 -3
  136. package/dist/esm/hooks/useAddressValidation.d.ts.map +1 -1
  137. package/dist/esm/hooks/useAvailableChains.d.ts +1 -2
  138. package/dist/esm/hooks/useAvailableChains.d.ts.map +1 -1
  139. package/dist/esm/hooks/useChain.d.ts +1 -2
  140. package/dist/esm/hooks/useChain.d.ts.map +1 -1
  141. package/dist/esm/hooks/useContactSupport.d.ts +1 -2
  142. package/dist/esm/hooks/useContactSupport.d.ts.map +1 -1
  143. package/dist/esm/hooks/useDebouncedWatch.d.ts +1 -2
  144. package/dist/esm/hooks/useDebouncedWatch.d.ts.map +1 -1
  145. package/dist/esm/hooks/useDisplayedTokens.js +6 -4
  146. package/dist/esm/hooks/useDisplayedTokens.js.map +1 -1
  147. package/dist/esm/hooks/useExplorer.d.ts +2 -3
  148. package/dist/esm/hooks/useExplorer.d.ts.map +1 -1
  149. package/dist/esm/hooks/useExplorer.js +7 -3
  150. package/dist/esm/hooks/useExplorer.js.map +1 -1
  151. package/dist/esm/hooks/useHeader.d.ts +1 -2
  152. package/dist/esm/hooks/useHeader.d.ts.map +1 -1
  153. package/dist/esm/hooks/useListHeight.d.ts +1 -2
  154. package/dist/esm/hooks/useListHeight.d.ts.map +1 -1
  155. package/dist/esm/hooks/useNavigateBack.d.ts +1 -2
  156. package/dist/esm/hooks/useNavigateBack.d.ts.map +1 -1
  157. package/dist/esm/hooks/useRecentTokens.js +46 -0
  158. package/dist/esm/hooks/useRecentTokens.js.map +1 -0
  159. package/dist/esm/hooks/useRouteExecution.d.ts +1 -2
  160. package/dist/esm/hooks/useRouteExecution.d.ts.map +1 -1
  161. package/dist/esm/hooks/useRouteExecutionMessage.d.ts +1 -2
  162. package/dist/esm/hooks/useRouteExecutionMessage.d.ts.map +1 -1
  163. package/dist/esm/hooks/useRoutes.d.ts +1 -2
  164. package/dist/esm/hooks/useRoutes.d.ts.map +1 -1
  165. package/dist/esm/hooks/useRoutes.js +7 -2
  166. package/dist/esm/hooks/useRoutes.js.map +1 -1
  167. package/dist/esm/hooks/useToAddressRequirements.d.ts +1 -2
  168. package/dist/esm/hooks/useToAddressRequirements.d.ts.map +1 -1
  169. package/dist/esm/hooks/useToAddressRequirements.js +8 -2
  170. package/dist/esm/hooks/useToAddressRequirements.js.map +1 -1
  171. package/dist/esm/hooks/useToken.d.ts +1 -2
  172. package/dist/esm/hooks/useToken.d.ts.map +1 -1
  173. package/dist/esm/hooks/useTokenAddressBalance.d.ts +1 -2
  174. package/dist/esm/hooks/useTokenAddressBalance.d.ts.map +1 -1
  175. package/dist/esm/hooks/useTokenBalances.d.ts +3 -2
  176. package/dist/esm/hooks/useTokenBalances.d.ts.map +1 -1
  177. package/dist/esm/hooks/useTokenBalances.js +15 -9
  178. package/dist/esm/hooks/useTokenBalances.js.map +1 -1
  179. package/dist/esm/hooks/useTokenList.d.ts +1 -2
  180. package/dist/esm/hooks/useTokenList.d.ts.map +1 -1
  181. package/dist/esm/hooks/useTokenSearch.js +3 -2
  182. package/dist/esm/hooks/useTokenSearch.js.map +1 -1
  183. package/dist/esm/hooks/useTokens.js +26 -15
  184. package/dist/esm/hooks/useTokens.js.map +1 -1
  185. package/dist/esm/hooks/useTools.d.ts +1 -2
  186. package/dist/esm/hooks/useTools.d.ts.map +1 -1
  187. package/dist/esm/hooks/useWidgetChains.d.ts +1 -2
  188. package/dist/esm/hooks/useWidgetChains.d.ts.map +1 -1
  189. package/dist/esm/hooks/useWidgetEvents.d.ts +3 -4
  190. package/dist/esm/hooks/useWidgetEvents.d.ts.map +1 -1
  191. package/dist/esm/i18n/bn.json +4 -0
  192. package/dist/esm/i18n/de.json +4 -0
  193. package/dist/esm/i18n/en.json +4 -0
  194. package/dist/esm/i18n/es.json +4 -0
  195. package/dist/esm/i18n/fr.json +4 -0
  196. package/dist/esm/i18n/hi.json +4 -0
  197. package/dist/esm/i18n/id.json +4 -0
  198. package/dist/esm/i18n/it.json +4 -0
  199. package/dist/esm/i18n/ja.json +4 -0
  200. package/dist/esm/i18n/ko.json +4 -0
  201. package/dist/esm/i18n/pl.json +4 -0
  202. package/dist/esm/i18n/pt.json +4 -0
  203. package/dist/esm/i18n/th.json +4 -0
  204. package/dist/esm/i18n/tr.json +4 -0
  205. package/dist/esm/i18n/uk.json +4 -0
  206. package/dist/esm/i18n/vi.json +4 -0
  207. package/dist/esm/i18n/zh.json +4 -0
  208. package/dist/esm/pages/MainPage/MainWarningMessages.d.ts +1 -2
  209. package/dist/esm/pages/MainPage/MainWarningMessages.d.ts.map +1 -1
  210. package/dist/esm/pages/SelectChainPage/SelectChainPage.d.ts +1 -2
  211. package/dist/esm/pages/SelectChainPage/SelectChainPage.d.ts.map +1 -1
  212. package/dist/esm/pages/SelectChainPage/types.d.ts +1 -2
  213. package/dist/esm/pages/SelectChainPage/types.d.ts.map +1 -1
  214. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.d.ts +1 -2
  215. package/dist/esm/pages/SelectTokenPage/SearchTokenInput.d.ts.map +1 -1
  216. package/dist/esm/pages/SendToWallet/BookmarksPage.js +2 -1
  217. package/dist/esm/pages/SendToWallet/BookmarksPage.js.map +1 -1
  218. package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js +2 -1
  219. package/dist/esm/pages/SendToWallet/ConnectedWalletsPage.js.map +1 -1
  220. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js +2 -1
  221. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js.map +1 -1
  222. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js +2 -1
  223. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js.map +1 -1
  224. package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.d.ts +1 -2
  225. package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.d.ts.map +1 -1
  226. package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js +2 -1
  227. package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js.map +1 -1
  228. package/dist/esm/pages/TransactionDetailsPage/TransferIdCard.js +2 -1
  229. package/dist/esm/pages/TransactionDetailsPage/TransferIdCard.js.map +1 -1
  230. package/dist/esm/pages/TransactionPage/ConfirmToAddressSheet.d.ts +1 -2
  231. package/dist/esm/pages/TransactionPage/ConfirmToAddressSheet.d.ts.map +1 -1
  232. package/dist/esm/pages/TransactionPage/ExchangeRateBottomSheet.d.ts +2 -3
  233. package/dist/esm/pages/TransactionPage/ExchangeRateBottomSheet.d.ts.map +1 -1
  234. package/dist/esm/pages/TransactionPage/ExecutionDoneCard.d.ts +1 -2
  235. package/dist/esm/pages/TransactionPage/ExecutionDoneCard.d.ts.map +1 -1
  236. package/dist/esm/pages/TransactionPage/RouteTracker.d.ts +1 -2
  237. package/dist/esm/pages/TransactionPage/RouteTracker.d.ts.map +1 -1
  238. package/dist/esm/pages/TransactionPage/StartTransactionButton.d.ts +1 -2
  239. package/dist/esm/pages/TransactionPage/StartTransactionButton.d.ts.map +1 -1
  240. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.d.ts +1 -2
  241. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.d.ts.map +1 -1
  242. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js +0 -1
  243. package/dist/esm/pages/TransactionPage/TokenValueBottomSheet.js.map +1 -1
  244. package/dist/esm/pages/TransactionPage/TokenVerificationBottomSheet.js +0 -1
  245. package/dist/esm/pages/TransactionPage/TokenVerificationBottomSheet.js.map +1 -1
  246. package/dist/esm/pages/TransactionPage/TransactionDoneButtons.d.ts +1 -2
  247. package/dist/esm/pages/TransactionPage/TransactionDoneButtons.d.ts.map +1 -1
  248. package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js +38 -12
  249. package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js.map +1 -1
  250. package/dist/esm/pages/TransactionPage/TransactionReview.js +22 -21
  251. package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
  252. package/dist/esm/pages/TransactionPage/types.d.ts +1 -2
  253. package/dist/esm/pages/TransactionPage/types.d.ts.map +1 -1
  254. package/dist/esm/pages/TransactionPage/utils.d.ts +2 -3
  255. package/dist/esm/pages/TransactionPage/utils.d.ts.map +1 -1
  256. package/dist/esm/pages/TransactionPage/utils.js +23 -1
  257. package/dist/esm/pages/TransactionPage/utils.js.map +1 -1
  258. package/dist/esm/providers/I18nProvider/I18nProvider.d.ts +1 -2
  259. package/dist/esm/providers/I18nProvider/I18nProvider.d.ts.map +1 -1
  260. package/dist/esm/providers/I18nProvider/types.d.ts +4 -5
  261. package/dist/esm/providers/I18nProvider/types.d.ts.map +1 -1
  262. package/dist/esm/providers/QueryClientProvider.d.ts +1 -2
  263. package/dist/esm/providers/QueryClientProvider.d.ts.map +1 -1
  264. package/dist/esm/providers/SDKClientProvider.d.ts +2 -3
  265. package/dist/esm/providers/SDKClientProvider.d.ts.map +1 -1
  266. package/dist/esm/providers/WalletProvider/WalletProvider.d.ts +1 -2
  267. package/dist/esm/providers/WalletProvider/WalletProvider.d.ts.map +1 -1
  268. package/dist/esm/providers/WidgetProvider/WidgetProvider.d.ts +2 -3
  269. package/dist/esm/providers/WidgetProvider/WidgetProvider.d.ts.map +1 -1
  270. package/dist/esm/providers/WidgetProvider/types.d.ts +2 -3
  271. package/dist/esm/providers/WidgetProvider/types.d.ts.map +1 -1
  272. package/dist/esm/shared.d.ts +2 -1
  273. package/dist/esm/shared.js +2 -1
  274. package/dist/esm/stores/StoreProvider.d.ts +1 -2
  275. package/dist/esm/stores/StoreProvider.d.ts.map +1 -1
  276. package/dist/esm/stores/StoreProvider.js +10 -6
  277. package/dist/esm/stores/StoreProvider.js.map +1 -1
  278. package/dist/esm/stores/bookmarks/BookmarkStore.d.ts +1 -2
  279. package/dist/esm/stores/bookmarks/BookmarkStore.d.ts.map +1 -1
  280. package/dist/esm/stores/bookmarks/types.d.ts +5 -6
  281. package/dist/esm/stores/bookmarks/types.d.ts.map +1 -1
  282. package/dist/esm/stores/chains/ChainOrderStore.d.ts +1 -2
  283. package/dist/esm/stores/chains/ChainOrderStore.d.ts.map +1 -1
  284. package/dist/esm/stores/chains/ChainOrderStore.js +11 -5
  285. package/dist/esm/stores/chains/ChainOrderStore.js.map +1 -1
  286. package/dist/esm/stores/chains/types.d.ts +1 -2
  287. package/dist/esm/stores/chains/types.d.ts.map +1 -1
  288. package/dist/esm/stores/form/types.d.ts +20 -21
  289. package/dist/esm/stores/form/types.d.ts.map +1 -1
  290. package/dist/esm/stores/form/useFieldActions.d.ts +1 -2
  291. package/dist/esm/stores/form/useFieldActions.d.ts.map +1 -1
  292. package/dist/esm/stores/form/useFieldValues.d.ts +1 -2
  293. package/dist/esm/stores/form/useFieldValues.d.ts.map +1 -1
  294. package/dist/esm/stores/header/types.d.ts +1 -2
  295. package/dist/esm/stores/header/types.d.ts.map +1 -1
  296. package/dist/esm/stores/header/useHeaderStore.d.ts +2 -3
  297. package/dist/esm/stores/header/useHeaderStore.d.ts.map +1 -1
  298. package/dist/esm/stores/inputMode/useInputModeStore.d.ts +1 -2
  299. package/dist/esm/stores/inputMode/useInputModeStore.d.ts.map +1 -1
  300. package/dist/esm/stores/recentTokens/RecentTokensStore.js +23 -0
  301. package/dist/esm/stores/recentTokens/RecentTokensStore.js.map +1 -0
  302. package/dist/esm/stores/recentTokens/createRecentTokensStore.js +54 -0
  303. package/dist/esm/stores/recentTokens/createRecentTokensStore.js.map +1 -0
  304. package/dist/esm/stores/recentTokens/utils.js +15 -0
  305. package/dist/esm/stores/recentTokens/utils.js.map +1 -0
  306. package/dist/esm/stores/routes/RouteExecutionStore.d.ts +2 -3
  307. package/dist/esm/stores/routes/RouteExecutionStore.d.ts.map +1 -1
  308. package/dist/esm/stores/routes/types.d.ts +3 -4
  309. package/dist/esm/stores/routes/types.d.ts.map +1 -1
  310. package/dist/esm/stores/routes/utils.d.ts +4 -5
  311. package/dist/esm/stores/routes/utils.d.ts.map +1 -1
  312. package/dist/esm/stores/settings/SettingsStore.d.ts +2 -3
  313. package/dist/esm/stores/settings/SettingsStore.d.ts.map +1 -1
  314. package/dist/esm/stores/settings/types.d.ts +2 -3
  315. package/dist/esm/stores/settings/types.d.ts.map +1 -1
  316. package/dist/esm/stores/types.d.ts +2 -3
  317. package/dist/esm/stores/types.d.ts.map +1 -1
  318. package/dist/esm/themes/azureLight.d.ts +1 -2
  319. package/dist/esm/themes/azureLight.d.ts.map +1 -1
  320. package/dist/esm/themes/jumper.d.ts +1 -2
  321. package/dist/esm/themes/jumper.d.ts.map +1 -1
  322. package/dist/esm/themes/palettes.d.ts +3 -4
  323. package/dist/esm/themes/palettes.d.ts.map +1 -1
  324. package/dist/esm/themes/types.d.ts +1 -0
  325. package/dist/esm/themes/watermelonLight.d.ts +1 -2
  326. package/dist/esm/themes/watermelonLight.d.ts.map +1 -1
  327. package/dist/esm/themes/windows95.d.ts +1 -2
  328. package/dist/esm/themes/windows95.d.ts.map +1 -1
  329. package/dist/esm/types/events.d.ts +13 -14
  330. package/dist/esm/types/events.d.ts.map +1 -1
  331. package/dist/esm/types/token.d.ts +4 -2
  332. package/dist/esm/types/token.d.ts.map +1 -1
  333. package/dist/esm/types/widget.d.ts +55 -51
  334. package/dist/esm/types/widget.d.ts.map +1 -1
  335. package/dist/esm/types/widget.js.map +1 -1
  336. package/dist/esm/utils/address.js +19 -0
  337. package/dist/esm/utils/address.js.map +1 -0
  338. package/dist/esm/utils/compactNumberFormatter.d.ts +1 -2
  339. package/dist/esm/utils/compactNumberFormatter.d.ts.map +1 -1
  340. package/dist/esm/utils/converters.d.ts +1 -2
  341. package/dist/esm/utils/converters.d.ts.map +1 -1
  342. package/dist/esm/utils/currencyExtendedFormatter.d.ts +1 -2
  343. package/dist/esm/utils/currencyExtendedFormatter.d.ts.map +1 -1
  344. package/dist/esm/utils/customReceiver.js +6 -1
  345. package/dist/esm/utils/customReceiver.js.map +1 -1
  346. package/dist/esm/utils/elements.d.ts +2 -3
  347. package/dist/esm/utils/elements.d.ts.map +1 -1
  348. package/dist/esm/utils/enum.d.ts +1 -2
  349. package/dist/esm/utils/enum.d.ts.map +1 -1
  350. package/dist/esm/utils/fees.d.ts +2 -3
  351. package/dist/esm/utils/fees.d.ts.map +1 -1
  352. package/dist/esm/utils/format.d.ts +8 -9
  353. package/dist/esm/utils/format.d.ts.map +1 -1
  354. package/dist/esm/utils/getPriceImpact.d.ts +1 -2
  355. package/dist/esm/utils/getPriceImpact.d.ts.map +1 -1
  356. package/dist/esm/utils/input.d.ts +1 -2
  357. package/dist/esm/utils/input.d.ts.map +1 -1
  358. package/dist/esm/utils/navigationRoutes.d.ts +2 -3
  359. package/dist/esm/utils/navigationRoutes.d.ts.map +1 -1
  360. package/dist/esm/utils/percentFormatter.d.ts +1 -2
  361. package/dist/esm/utils/percentFormatter.d.ts.map +1 -1
  362. package/dist/esm/utils/recentTokens.js +97 -0
  363. package/dist/esm/utils/recentTokens.js.map +1 -0
  364. package/dist/esm/utils/token.js +30 -4
  365. package/dist/esm/utils/token.js.map +1 -1
  366. package/dist/esm/utils/tokenList.js +18 -6
  367. package/dist/esm/utils/tokenList.js.map +1 -1
  368. package/dist/esm/utils/tokenListBands.js +85 -0
  369. package/dist/esm/utils/tokenListBands.js.map +1 -0
  370. package/dist/esm/utils/url.d.ts +5 -0
  371. package/dist/esm/utils/url.d.ts.map +1 -0
  372. package/dist/esm/utils/url.js +17 -0
  373. package/dist/esm/utils/url.js.map +1 -0
  374. package/dist/esm/utils/wallet.d.ts +1 -2
  375. package/dist/esm/utils/wallet.d.ts.map +1 -1
  376. package/package.json +9 -9
  377. package/src/components/AmountInputCard/ReceiveAmountCard.tsx +16 -3
  378. package/src/components/BottomSheet/BottomSheet.tsx +3 -0
  379. package/src/components/Token/Token.tsx +2 -1
  380. package/src/components/TokenList/RecentTokensHeader.tsx +52 -0
  381. package/src/components/TokenList/RecentTokensToggle.tsx +45 -0
  382. package/src/components/TokenList/RemoveRecentTokenButton.tsx +38 -0
  383. package/src/components/TokenList/TokenList.tsx +92 -3
  384. package/src/components/TokenList/TokenListItem.tsx +15 -0
  385. package/src/components/TokenList/VirtualizedTokenList.tsx +84 -125
  386. package/src/components/TokenList/types.ts +9 -0
  387. package/src/config/version.ts +1 -1
  388. package/src/hooks/useDisplayedTokens.ts +17 -3
  389. package/src/hooks/useExplorer.test.tsx +106 -0
  390. package/src/hooks/useExplorer.ts +11 -5
  391. package/src/hooks/useRecentTokens.ts +88 -0
  392. package/src/hooks/useRoutes.ts +8 -5
  393. package/src/hooks/useToAddressRequirements.ts +11 -5
  394. package/src/hooks/useTokenBalances.ts +34 -31
  395. package/src/hooks/useTokenSearch.ts +5 -14
  396. package/src/hooks/useTokens.ts +69 -27
  397. package/src/pages/SendToWallet/BookmarksPage.tsx +3 -3
  398. package/src/pages/SendToWallet/ConnectedWalletsPage.tsx +3 -3
  399. package/src/pages/SendToWallet/RecentWalletsPage.tsx +3 -3
  400. package/src/pages/SendToWallet/SendToConfiguredWalletPage.tsx +3 -3
  401. package/src/pages/TransactionDetailsPage/TransactionDetailsPage.tsx +5 -5
  402. package/src/pages/TransactionDetailsPage/TransferIdCard.tsx +2 -1
  403. package/src/pages/TransactionPage/TokenValueBottomSheet.tsx +1 -1
  404. package/src/pages/TransactionPage/TokenVerificationBottomSheet.tsx +1 -1
  405. package/src/pages/TransactionPage/TransactionFailedButtons.tsx +62 -26
  406. package/src/pages/TransactionPage/TransactionReview.tsx +36 -37
  407. package/src/pages/TransactionPage/utils.ts +78 -0
  408. package/src/shared.ts +1 -0
  409. package/src/stores/StoreProvider.tsx +10 -7
  410. package/src/stores/chains/ChainOrderStore.test.tsx +298 -0
  411. package/src/stores/chains/ChainOrderStore.tsx +25 -6
  412. package/src/stores/recentTokens/RecentTokensStore.tsx +39 -0
  413. package/src/stores/recentTokens/createRecentTokensStore.ts +107 -0
  414. package/src/stores/recentTokens/types.ts +37 -0
  415. package/src/stores/recentTokens/utils.ts +18 -0
  416. package/src/types/token.ts +2 -0
  417. package/src/types/widget.ts +5 -0
  418. package/src/utils/address.ts +21 -0
  419. package/src/utils/customReceiver.ts +24 -0
  420. package/src/utils/recentTokens.ts +187 -0
  421. package/src/utils/token.ts +69 -10
  422. package/src/utils/tokenList.ts +41 -23
  423. package/src/utils/tokenListBands.ts +181 -0
  424. package/src/utils/url.ts +17 -0
@@ -20,9 +20,12 @@ import { ConfirmToAddressSheet } from './ConfirmToAddressSheet.js'
20
20
  import { StartTransactionButton } from './StartTransactionButton.js'
21
21
  import { TokenValueBottomSheet } from './TokenValueBottomSheet.js'
22
22
  import { TokenVerificationBottomSheet } from './TokenVerificationBottomSheet.js'
23
+ import type { StartGate } from './utils.js'
23
24
  import {
24
25
  calculateValueLossPercentage,
26
+ getStartGates,
25
27
  getTokenValueLossThreshold,
28
+ openNextGate,
26
29
  } from './utils.js'
27
30
 
28
31
  interface TransactionReviewProps {
@@ -74,7 +77,6 @@ export const TransactionReview: React.FC<TransactionReviewProps> = ({
74
77
  })
75
78
  }
76
79
  tokenValueBottomSheetRef.current?.close()
77
- flaggedTokenSheetRef.current?.close()
78
80
  executeRoute()
79
81
  setFieldValue('fromAmount', '')
80
82
  if (mode === 'custom') {
@@ -86,49 +88,46 @@ export const TransactionReview: React.FC<TransactionReviewProps> = ({
86
88
  })
87
89
  }
88
90
 
89
- // Runs the remaining gates. The flagged-token sheet continues into this
90
- // rather than executing, so accepting it does not skip the others.
91
- const handleStartAfterTokenCheck = () => {
92
- if (
93
- toAddress &&
94
- !hasActivity &&
95
- !isLoadingAddressActivity &&
96
- isActivityAddressFetched &&
97
- !hiddenUI?.lowAddressActivityConfirmation
98
- ) {
99
- confirmToAddressSheetRef.current?.open()
100
- return
101
- }
102
-
91
+ const startGates = (() => {
103
92
  const { gasCostUSD, feeCostUSD } = getAccumulatedFeeCostsBreakdown(route)
104
- const fromAmountUSD = Number.parseFloat(route.fromAmountUSD)
105
- const toAmountUSD = Number.parseFloat(route.toAmountUSD)
106
- const tokenValueLossThresholdExceeded = getTokenValueLossThreshold(
107
- fromAmountUSD,
108
- toAmountUSD,
109
- gasCostUSD,
110
- feeCostUSD
93
+ return getStartGates({
94
+ flaggedTokenCount: flaggedTokens.length,
95
+ toAddress,
96
+ hasActivity,
97
+ isLoadingAddressActivity,
98
+ isActivityAddressFetched,
99
+ confirmationHidden: Boolean(hiddenUI?.lowAddressActivityConfirmation),
100
+ valueLossExceeded: getTokenValueLossThreshold(
101
+ Number.parseFloat(route.fromAmountUSD),
102
+ Number.parseFloat(route.toAmountUSD),
103
+ gasCostUSD,
104
+ feeCostUSD
105
+ ),
106
+ isCustomMode: mode === 'custom',
107
+ })
108
+ })()
109
+
110
+ const openGate = (after?: StartGate) =>
111
+ openNextGate(
112
+ startGates,
113
+ {
114
+ flagged: () => flaggedTokenSheetRef.current?.open(),
115
+ address: () => confirmToAddressSheetRef.current?.open(),
116
+ value: () => tokenValueBottomSheetRef.current?.open(),
117
+ },
118
+ handleExecuteRoute,
119
+ after
111
120
  )
112
- if (tokenValueLossThresholdExceeded && mode !== 'custom') {
113
- tokenValueBottomSheetRef.current?.open()
114
- } else {
115
- handleExecuteRoute()
116
- }
117
- }
118
121
 
119
- const handleStartClick = () => {
120
- if (flaggedTokens.length) {
121
- flaggedTokenSheetRef.current?.open()
122
- return
123
- }
124
- handleStartAfterTokenCheck()
125
- }
122
+ const handleStartClick = () => openGate()
126
123
 
127
124
  const handleFlaggedTokensContinue = () => {
128
125
  flaggedTokenSheetRef.current?.close()
129
- handleStartAfterTokenCheck()
126
+ openGate('flagged')
130
127
  }
131
128
 
129
+ const handleConfirmToAddressContinue = () => openGate('address')
130
+
132
131
  const getButtonText = (): string => {
133
132
  switch (mode) {
134
133
  case 'custom':
@@ -180,7 +179,7 @@ export const TransactionReview: React.FC<TransactionReviewProps> = ({
180
179
  {!hiddenUI?.lowAddressActivityConfirmation ? (
181
180
  <ConfirmToAddressSheet
182
181
  ref={confirmToAddressSheetRef}
183
- onContinue={handleExecuteRoute}
182
+ onContinue={handleConfirmToAddressContinue}
184
183
  toAddress={toAddress!}
185
184
  toChainId={route.toChainId!}
186
185
  />
@@ -23,3 +23,81 @@ export const getTokenValueLossThreshold = (
23
23
  }
24
24
  return toAmountUSD / (fromAmountUSD + gasCostUSD + feeCostUSD) < 0.9
25
25
  }
26
+
27
+ interface AddressGateInput {
28
+ toAddress: string | undefined
29
+ hasActivity: boolean | undefined
30
+ isLoadingAddressActivity: boolean
31
+ isActivityAddressFetched: boolean
32
+ confirmationHidden: boolean
33
+ }
34
+
35
+ const needsAddressConfirmation = ({
36
+ toAddress,
37
+ hasActivity,
38
+ isLoadingAddressActivity,
39
+ isActivityAddressFetched,
40
+ confirmationHidden,
41
+ }: AddressGateInput): boolean =>
42
+ Boolean(
43
+ toAddress &&
44
+ !hasActivity &&
45
+ !isLoadingAddressActivity &&
46
+ isActivityAddressFetched &&
47
+ !confirmationHidden
48
+ )
49
+
50
+ export type RetryGate = 'address' | 'value'
51
+
52
+ export const getRetryGates = (
53
+ input: AddressGateInput & {
54
+ valueLossExceeded: boolean
55
+ isCustomMode: boolean
56
+ }
57
+ ): readonly (readonly [gate: RetryGate, needed: boolean])[] => [
58
+ ['address', needsAddressConfirmation(input)],
59
+ ['value', input.valueLossExceeded && !input.isCustomMode],
60
+ ]
61
+
62
+ /** Ordering lives here so no sheet decides what follows it — that is how gates came to be skipped. */
63
+ export const nextGate = <T extends string>(
64
+ gates: readonly (readonly [gate: T, needed: boolean])[],
65
+ after?: T
66
+ ): T | undefined => {
67
+ if (!after) {
68
+ return gates.find(([, needed]) => needed)?.[0]
69
+ }
70
+ const index = gates.findIndex(([gate]) => gate === after)
71
+ if (index < 0) {
72
+ return undefined
73
+ }
74
+ return gates.slice(index + 1).find(([, needed]) => needed)?.[0]
75
+ }
76
+
77
+ export const openNextGate = <T extends string>(
78
+ gates: readonly (readonly [gate: T, needed: boolean])[],
79
+ open: Record<T, () => void>,
80
+ done: () => void,
81
+ after?: T
82
+ ): void => {
83
+ const gate = nextGate(gates, after)
84
+ if (gate) {
85
+ open[gate]()
86
+ return
87
+ }
88
+ done()
89
+ }
90
+
91
+ export type StartGate = 'flagged' | 'address' | 'value'
92
+
93
+ export const getStartGates = (
94
+ input: AddressGateInput & {
95
+ flaggedTokenCount: number
96
+ valueLossExceeded: boolean
97
+ isCustomMode: boolean
98
+ }
99
+ ): readonly (readonly [gate: StartGate, needed: boolean])[] => [
100
+ ['flagged', input.flaggedTokenCount > 0],
101
+ ['address', needsAddressConfirmation(input)],
102
+ ['value', input.valueLossExceeded && !input.isCustomMode],
103
+ ]
package/src/shared.ts CHANGED
@@ -188,4 +188,5 @@ export {
188
188
  export { getPriceImpact } from './utils/getPriceImpact.js'
189
189
  export { fitInputText } from './utils/input.js'
190
190
  export { navigationRoutes } from './utils/navigationRoutes.js'
191
+ export { isHttpUrl, openInNewTab } from './utils/url.js'
191
192
  export { shortenAddress } from './utils/wallet.js'
@@ -6,6 +6,7 @@ import { FormStoreProvider } from './form/FormStore.js'
6
6
  import { HeaderStoreProvider } from './header/useHeaderStore.js'
7
7
  import { NavigationTabsStoreProvider } from './navigationTabs/useNavigationTabsStore.js'
8
8
  import { PinnedTokensStoreProvider } from './pinnedTokens/PinnedTokensStore.js'
9
+ import { RecentTokensStoreProvider } from './recentTokens/RecentTokensStore.js'
9
10
  import { RouteExecutionStoreProvider } from './routes/RouteExecutionStore.js'
10
11
 
11
12
  export const StoreProvider: React.FC<PropsWithChildren<WidgetConfigProps>> = ({
@@ -18,13 +19,15 @@ export const StoreProvider: React.FC<PropsWithChildren<WidgetConfigProps>> = ({
18
19
  <HeaderStoreProvider namePrefix={config?.keyPrefix}>
19
20
  <BookmarkStoreProvider namePrefix={config?.keyPrefix}>
20
21
  <PinnedTokensStoreProvider namePrefix={config?.keyPrefix}>
21
- <FormStoreProvider formRef={formRef}>
22
- <ChainOrderStoreProvider namePrefix={config?.keyPrefix}>
23
- <RouteExecutionStoreProvider namePrefix={config?.keyPrefix}>
24
- {children}
25
- </RouteExecutionStoreProvider>
26
- </ChainOrderStoreProvider>
27
- </FormStoreProvider>
22
+ <RecentTokensStoreProvider namePrefix={config?.keyPrefix}>
23
+ <FormStoreProvider formRef={formRef}>
24
+ <ChainOrderStoreProvider namePrefix={config?.keyPrefix}>
25
+ <RouteExecutionStoreProvider namePrefix={config?.keyPrefix}>
26
+ {children}
27
+ </RouteExecutionStoreProvider>
28
+ </ChainOrderStoreProvider>
29
+ </FormStoreProvider>
30
+ </RecentTokensStoreProvider>
28
31
  </PinnedTokensStoreProvider>
29
32
  </BookmarkStoreProvider>
30
33
  </HeaderStoreProvider>
@@ -0,0 +1,298 @@
1
+ /** @vitest-environment happy-dom */
2
+
3
+ import type { ExtendedChain } from '@lifi/sdk'
4
+ import { act } from 'react'
5
+ import { createRoot, type Root } from 'react-dom/client'
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7
+
8
+ /**
9
+ * Regression cover for JUM-1541.
10
+ *
11
+ * Mock identities must stay stable. In production this effect's dependencies
12
+ * are config values, so it does not re-run when a form field changes; mocks
13
+ * that rebuild objects each render would re-run it and prove behaviour the
14
+ * widget does not have.
15
+ */
16
+
17
+ const ETHEREUM = 1
18
+ const ARBITRUM = 42161
19
+ const POLYGON = 137
20
+ const SOLANA = 1151111081099710
21
+
22
+ const chain = (id: number): ExtendedChain =>
23
+ ({ id, name: `Chain ${id}`, chainType: 'EVM' }) as unknown as ExtendedChain
24
+
25
+ let chainsConfig: { from?: { allow: number[] } } | undefined
26
+ let availableChains: ExtendedChain[] = []
27
+ let swapOnly = false
28
+ let fromChainConfig: number | undefined
29
+
30
+ const formValues = new Map<string, unknown>()
31
+ const touchedFields = new Set<string>()
32
+ const setFieldValue = vi.fn(
33
+ (key: string, value: unknown, options?: { isTouched?: boolean }) => {
34
+ formValues.set(key, value)
35
+ if (options?.isTouched === true) {
36
+ touchedFields.add(key)
37
+ } else if (options?.isTouched === false) {
38
+ touchedFields.delete(key)
39
+ }
40
+ }
41
+ )
42
+
43
+ vi.mock('../../hooks/useChains.js', () => ({
44
+ useChains: () => ({ chains: availableChains }),
45
+ }))
46
+
47
+ vi.mock('../../hooks/useSwapOnly.js', () => ({
48
+ useSwapOnly: () => swapOnly,
49
+ }))
50
+
51
+ // Stable identities, as production has them: `useExternalWalletProvider`
52
+ // memoizes and `useFieldActions` selects store methods through `useShallow`.
53
+ // Returning fresh objects here would put a new value in the effect's
54
+ // dependency array on every render and re-run it — which production does not
55
+ // do, and which would make these tests prove something the widget does not.
56
+ const externalWalletProvider = {
57
+ externalChainTypes: [] as string[],
58
+ useExternalWalletProvidersOnly: false,
59
+ }
60
+ vi.mock('../../providers/WalletProvider/useExternalWalletProvider.js', () => ({
61
+ useExternalWalletProvider: () => externalWalletProvider,
62
+ }))
63
+
64
+ vi.mock('../../providers/WidgetProvider/WidgetProvider.js', () => ({
65
+ useWidgetConfig: () => ({
66
+ chains: chainsConfig,
67
+ hiddenUI: undefined,
68
+ fromChain: fromChainConfig,
69
+ toChain: undefined,
70
+ buildUrl: true,
71
+ variant: 'compact',
72
+ }),
73
+ }))
74
+
75
+ const getFieldValues = (key: string) => [formValues.get(key)]
76
+ const isTouched = (key: string) => touchedFields.has(key)
77
+ const fieldActions = { setFieldValue, getFieldValues, isTouched }
78
+ vi.mock('../form/useFieldActions.js', () => ({
79
+ useFieldActions: () => fieldActions,
80
+ }))
81
+
82
+ const { ChainOrderStoreProvider, useChainOrderStore } = await import(
83
+ './ChainOrderStore.js'
84
+ )
85
+
86
+ /** What `selectAllNetworks` does to the form: clear the field, as touched. */
87
+ const clickAllNetworks = (key: 'from' | 'to') => {
88
+ formValues.set(`${key}Chain`, '')
89
+ touchedFields.add(`${key}Chain`)
90
+ }
91
+
92
+ let fromIsAllNetworks: boolean | undefined
93
+
94
+ const StoreHandle = (): null => {
95
+ fromIsAllNetworks = useChainOrderStore((state) => state.fromIsAllNetworks)
96
+ return null
97
+ }
98
+
99
+ let root: Root
100
+
101
+ const render = () => {
102
+ act(() => {
103
+ root.render(
104
+ <ChainOrderStoreProvider namePrefix="test">
105
+ <StoreHandle />
106
+ </ChainOrderStoreProvider>
107
+ )
108
+ })
109
+ }
110
+
111
+ const fromChainWrites = () =>
112
+ setFieldValue.mock.calls.filter(([key]) => key === 'fromChain')
113
+
114
+ const toChainWrites = () =>
115
+ setFieldValue.mock.calls.filter(([key]) => key === 'toChain')
116
+
117
+ /** An integrator that derives `chains` from form state rebuilds it on every write. */
118
+ const rebuildHostConfig = () => {
119
+ chainsConfig = { ...chainsConfig }
120
+ render()
121
+ }
122
+
123
+ beforeEach(() => {
124
+ ;(
125
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }
126
+ ).IS_REACT_ACT_ENVIRONMENT = true
127
+ window.history.replaceState({}, '', '/')
128
+ window.localStorage.clear()
129
+ formValues.clear()
130
+ touchedFields.clear()
131
+ setFieldValue.mockClear()
132
+ chainsConfig = undefined
133
+ swapOnly = false
134
+ fromChainConfig = undefined
135
+ availableChains = [chain(ETHEREUM), chain(ARBITRUM)]
136
+ fromIsAllNetworks = undefined
137
+ root = createRoot(document.createElement('div'))
138
+ })
139
+
140
+ afterEach(() => {
141
+ act(() => root.unmount())
142
+ })
143
+
144
+ describe('ChainOrderStoreProvider', () => {
145
+ it('starts on "All networks" when nothing names a chain', () => {
146
+ render()
147
+
148
+ expect(fromIsAllNetworks).toBe(true)
149
+ expect(fromChainWrites()).toHaveLength(0)
150
+ })
151
+
152
+ it('starts off "All networks" when a deep link named a chain', () => {
153
+ // How the widget learns this when the config carries nothing.
154
+ window.history.replaceState({}, '', `/?fromChain=${ARBITRUM}`)
155
+ formValues.set('fromChain', ARBITRUM)
156
+ render()
157
+
158
+ expect(fromIsAllNetworks).toBe(false)
159
+ expect(fromChainWrites()).toHaveLength(0)
160
+ })
161
+
162
+ it('starts off "All networks" when the config names a chain', () => {
163
+ fromChainConfig = POLYGON
164
+ render()
165
+
166
+ expect(fromIsAllNetworks).toBe(false)
167
+ })
168
+
169
+ it('does not put a chain back after the user chooses "All networks"', () => {
170
+ // jumper-frontend seeds its config from the URL, so a deep link arrives
171
+ // as a config chain as well as a form value and a query param.
172
+ fromChainConfig = ARBITRUM
173
+ formValues.set('fromChain', ARBITRUM)
174
+ window.history.replaceState({}, '', `/?fromChain=${ARBITRUM}`)
175
+ render()
176
+ expect(fromIsAllNetworks).toBe(false)
177
+
178
+ clickAllNetworks('from')
179
+ setFieldValue.mockClear()
180
+
181
+ // jumper-frontend rebuilds `chains` from form state, so clearing the
182
+ // field re-runs the effect on that same commit — while the config and the
183
+ // query string still name Arbitrum.
184
+ chainsConfig = { ...chainsConfig }
185
+ render()
186
+
187
+ expect(fromIsAllNetworks).toBe(true)
188
+ expect(fromChainWrites()).toHaveLength(0)
189
+ })
190
+
191
+ it('fills an empty chain when the "All networks" tab is withdrawn', () => {
192
+ render()
193
+ expect(fromChainWrites()).toHaveLength(0)
194
+
195
+ // An allow-list of one chain withdraws the tab, so this side needs a
196
+ // fallback.
197
+ chainsConfig = { from: { allow: [ARBITRUM] } }
198
+ setFieldValue.mockClear()
199
+ render()
200
+
201
+ expect(fromIsAllNetworks).toBe(false)
202
+ expect(fromChainWrites()).toHaveLength(1)
203
+ expect(fromChainWrites()[0][1]).toBe(ARBITRUM)
204
+ })
205
+ })
206
+
207
+ describe('ChainOrderStoreProvider in swap-only modes', () => {
208
+ beforeEach(() => {
209
+ swapOnly = true
210
+ })
211
+
212
+ const pickSource = (chainId: number) => {
213
+ window.history.replaceState({}, '', `/?fromChain=${chainId}`)
214
+ formValues.set('fromChain', chainId)
215
+ }
216
+
217
+ // The destination used to flip on every host rebuild until React threw #185.
218
+ it('does not alternate the destination while the source is on "All networks"', () => {
219
+ const destinations: unknown[] = []
220
+ render()
221
+ destinations.push(formValues.get('toChain'))
222
+ for (let rebuild = 0; rebuild < 4; rebuild++) {
223
+ rebuildHostConfig()
224
+ destinations.push(formValues.get('toChain'))
225
+ }
226
+
227
+ expect(fromIsAllNetworks).toBe(true)
228
+ expect(destinations).toEqual(Array(5).fill(ETHEREUM))
229
+ expect(toChainWrites()).toHaveLength(1)
230
+ })
231
+
232
+ it('fills an empty destination from the source, not from the chain order', () => {
233
+ pickSource(ARBITRUM)
234
+ render()
235
+
236
+ expect(formValues.get('toChain')).toBe(ARBITRUM)
237
+ })
238
+
239
+ it('moves a stale destination onto the source', () => {
240
+ pickSource(ARBITRUM)
241
+ formValues.set('toChain', ETHEREUM)
242
+ render()
243
+
244
+ expect(formValues.get('toChain')).toBe(ARBITRUM)
245
+ })
246
+
247
+ it('stops writing the destination once it matches the source', () => {
248
+ pickSource(ARBITRUM)
249
+ render()
250
+ expect(formValues.get('toChain')).toBe(ARBITRUM)
251
+ setFieldValue.mockClear()
252
+
253
+ rebuildHostConfig()
254
+ rebuildHostConfig()
255
+
256
+ expect(toChainWrites()).toHaveLength(0)
257
+ })
258
+
259
+ it('keeps the destination when the source goes back to "All networks"', () => {
260
+ pickSource(ARBITRUM)
261
+ render()
262
+ expect(formValues.get('toChain')).toBe(ARBITRUM)
263
+ clickAllNetworks('from')
264
+ setFieldValue.mockClear()
265
+
266
+ rebuildHostConfig()
267
+ rebuildHostConfig()
268
+
269
+ expect(formValues.get('toChain')).toBe(ARBITRUM)
270
+ expect(toChainWrites()).toHaveLength(0)
271
+ })
272
+
273
+ it('treats a zero source as no source', () => {
274
+ formValues.set('fromChain', 0)
275
+ render()
276
+ rebuildHostConfig()
277
+
278
+ expect(formValues.get('toChain')).toBe(ETHEREUM)
279
+ expect(toChainWrites()).toHaveLength(1)
280
+ })
281
+
282
+ // The list can shrink under a destination, e.g. when the integrator narrows `chains`.
283
+ it('replaces a destination the chain list does not offer while there is no source', () => {
284
+ swapOnly = false
285
+ availableChains = [chain(ETHEREUM), chain(ARBITRUM), chain(SOLANA)]
286
+ formValues.set('toChain', SOLANA)
287
+ render()
288
+ expect(formValues.get('toChain')).toBe(SOLANA)
289
+
290
+ swapOnly = true
291
+ availableChains = [chain(ETHEREUM), chain(ARBITRUM)]
292
+ render()
293
+ rebuildHostConfig()
294
+
295
+ expect(formValues.get('toChain')).toBe(ETHEREUM)
296
+ expect(toChainWrites()).toHaveLength(1)
297
+ })
298
+ })
@@ -30,7 +30,7 @@ export function ChainOrderStoreProvider({
30
30
  } = useWidgetConfig()
31
31
  const storeRef = useRef<ChainOrderStore>(null)
32
32
  const { chains } = useChains()
33
- const { setFieldValue, getFieldValues } = useFieldActions()
33
+ const { setFieldValue, getFieldValues, isTouched } = useFieldActions()
34
34
  const swapOnly = useSwapOnly()
35
35
  const { variant } = useWidgetConfig()
36
36
  const { externalChainTypes, useExternalWalletProvidersOnly } =
@@ -85,15 +85,30 @@ export function ChainOrderStoreProvider({
85
85
  key === 'from' ? urlValues.fromChain : urlValues.toChain
86
86
  const configChainValue =
87
87
  key === 'from' ? fromChainConfig : toChainConfig
88
+
89
+ // A touched, empty chain field was cleared deliberately — "All
90
+ // networks". It has to outrank the two values above, because both
91
+ // still name the chain the page was opened on at that moment: the
92
+ // query string is rewritten a commit later, and an integrator that
93
+ // seeds its config from the URL never rewrites it.
94
+ const [chainValue] = getFieldValues(`${key}Chain`)
95
+ const clearedForAllNetworks = !chainValue && isTouched(`${key}Chain`)
96
+
88
97
  const initialIsAllNetworks =
89
- showAllNetworks && !configChainValue && !urlChainValue
98
+ showAllNetworks &&
99
+ (clearedForAllNetworks || (!configChainValue && !urlChainValue))
90
100
  storeRef.current?.getState().setIsAllNetworks(initialIsAllNetworks, key)
91
101
  storeRef.current?.getState().setShowAllNetworks(showAllNetworks, key)
92
102
 
93
- // If swap only, set the to chain to the from chain
103
+ // Copying an empty source would alternate with the fallback below.
94
104
  if (isSwapTo) {
95
105
  const [fromChainValue] = getFieldValues('fromChain')
96
- setFieldValue('toChain', fromChainValue)
106
+ if (fromChainValue) {
107
+ if (fromChainValue !== chainValue) {
108
+ setFieldValue('toChain', fromChainValue)
109
+ }
110
+ return
111
+ }
97
112
  }
98
113
 
99
114
  // When "All Networks" is active, don't auto-select a chain from the
@@ -104,8 +119,11 @@ export function ChainOrderStoreProvider({
104
119
  return
105
120
  }
106
121
 
107
- const [chainValue] = getFieldValues(`${key}Chain`)
108
- if (chainValue) {
122
+ // With no source to follow, drop a destination the list no longer offers.
123
+ const keepsChain =
124
+ chainValue &&
125
+ (!isSwapTo || filteredChains.some((chain) => chain.id === chainValue))
126
+ if (keepsChain) {
109
127
  return
110
128
  }
111
129
 
@@ -130,6 +148,7 @@ export function ChainOrderStoreProvider({
130
148
  chainsConfig,
131
149
  externalChainTypes,
132
150
  getFieldValues,
151
+ isTouched,
133
152
  setFieldValue,
134
153
  useExternalWalletProvidersOnly,
135
154
  variant,
@@ -0,0 +1,39 @@
1
+ import { type Context, createContext, use, useRef } from 'react'
2
+ import { useShallow } from 'zustand/shallow'
3
+ import type { PersistStoreProviderProps } from '../types.js'
4
+ import { createRecentTokensStore } from './createRecentTokensStore.js'
5
+ import type { RecentTokensState, RecentTokensStore } from './types.js'
6
+
7
+ const RecentTokensStoreContext: Context<RecentTokensStore | null> =
8
+ createContext<RecentTokensStore | null>(null)
9
+
10
+ export const RecentTokensStoreProvider: React.FC<PersistStoreProviderProps> = ({
11
+ children,
12
+ ...props
13
+ }) => {
14
+ const storeRef = useRef<RecentTokensStore>(null)
15
+
16
+ if (!storeRef.current) {
17
+ storeRef.current = createRecentTokensStore(props)
18
+ }
19
+
20
+ return (
21
+ <RecentTokensStoreContext value={storeRef.current}>
22
+ {children}
23
+ </RecentTokensStoreContext>
24
+ )
25
+ }
26
+
27
+ export function useRecentTokensStore<T>(
28
+ selector: (store: RecentTokensState) => T
29
+ ): T {
30
+ const useStore = use(RecentTokensStoreContext)
31
+
32
+ if (!useStore) {
33
+ throw new Error(
34
+ `You forgot to wrap your component in <${RecentTokensStoreProvider.name}>.`
35
+ )
36
+ }
37
+
38
+ return useStore(useShallow(selector))
39
+ }