@lifi/widget 4.5.0 → 4.6.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 (199) hide show
  1. package/dist/esm/AppDrawer.js +2 -1
  2. package/dist/esm/AppDrawer.js.map +1 -1
  3. package/dist/esm/components/AmountInput/AmountInput.js +13 -7
  4. package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
  5. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +2 -13
  6. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
  7. package/dist/esm/components/AmountInputCard/PercentageChips.js +2 -13
  8. package/dist/esm/components/AmountInputCard/PercentageChips.js.map +1 -1
  9. package/dist/esm/components/AmountInputCard/PercentageChips.style.js +1 -1
  10. package/dist/esm/components/AmountInputCard/PercentageChips.style.js.map +1 -1
  11. package/dist/esm/components/AmountInputCard/SendAmountCard.js +13 -6
  12. package/dist/esm/components/AmountInputCard/SendAmountCard.js.map +1 -1
  13. package/dist/esm/components/Chains/AllChainsAvatar.js +7 -1
  14. package/dist/esm/components/Chains/AllChainsAvatar.js.map +1 -1
  15. package/dist/esm/components/Dialog/Dialog.js +2 -1
  16. package/dist/esm/components/Dialog/Dialog.js.map +1 -1
  17. package/dist/esm/components/Expansion/Expansion.js +4 -3
  18. package/dist/esm/components/Expansion/Expansion.js.map +1 -1
  19. package/dist/esm/components/Header/SettingsButton.js +2 -1
  20. package/dist/esm/components/Header/SettingsButton.js.map +1 -1
  21. package/dist/esm/components/IconCircle/IconCircle.js +2 -1
  22. package/dist/esm/components/IconCircle/IconCircle.js.map +1 -1
  23. package/dist/esm/components/Messages/UnsupportedToAddressMessage.js +27 -0
  24. package/dist/esm/components/Messages/UnsupportedToAddressMessage.js.map +1 -0
  25. package/dist/esm/components/Messages/WarningMessages.d.ts.map +1 -1
  26. package/dist/esm/components/Messages/WarningMessages.js +2 -0
  27. package/dist/esm/components/Messages/WarningMessages.js.map +1 -1
  28. package/dist/esm/components/Messages/useMessageQueue.js +6 -1
  29. package/dist/esm/components/Messages/useMessageQueue.js.map +1 -1
  30. package/dist/esm/components/RouteCard/RouteDetails.js +2 -1
  31. package/dist/esm/components/RouteCard/RouteDetails.js.map +1 -1
  32. package/dist/esm/components/Routes/LimitOrderRoutes.js +2 -2
  33. package/dist/esm/components/Routes/LimitOrderRoutes.js.map +1 -1
  34. package/dist/esm/components/Routes/RoutesContent.js +2 -2
  35. package/dist/esm/components/Routes/RoutesContent.js.map +1 -1
  36. package/dist/esm/components/SendToWallet/SendToWalletExpandButton.js +2 -2
  37. package/dist/esm/components/SendToWallet/SendToWalletExpandButton.js.map +1 -1
  38. package/dist/esm/components/Skeleton/WidgetSkeleton.js +2 -1
  39. package/dist/esm/components/Skeleton/WidgetSkeleton.js.map +1 -1
  40. package/dist/esm/components/Step/StepActions.js +18 -16
  41. package/dist/esm/components/Step/StepActions.js.map +1 -1
  42. package/dist/esm/components/StepActions/StepActionRow.js +2 -1
  43. package/dist/esm/components/StepActions/StepActionRow.js.map +1 -1
  44. package/dist/esm/components/Switch.js +1 -1
  45. package/dist/esm/components/Switch.js.map +1 -1
  46. package/dist/esm/components/Tabs/Tabs.style.js +4 -2
  47. package/dist/esm/components/Tabs/Tabs.style.js.map +1 -1
  48. package/dist/esm/components/TokenList/TokenListItem.js +46 -8
  49. package/dist/esm/components/TokenList/TokenListItem.js.map +1 -1
  50. package/dist/esm/components/TokenList/VirtualizedTokenList.js +22 -9
  51. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  52. package/dist/esm/components/TokenRate/TokenRate.js +2 -1
  53. package/dist/esm/components/TokenRate/TokenRate.js.map +1 -1
  54. package/dist/esm/components/TransactionCard/ActiveTransactionCard.js +22 -21
  55. package/dist/esm/components/TransactionCard/ActiveTransactionCard.js.map +1 -1
  56. package/dist/esm/config/version.d.ts +1 -1
  57. package/dist/esm/config/version.js +1 -1
  58. package/dist/esm/config/version.js.map +1 -1
  59. package/dist/esm/hooks/timer/time.js +2 -2
  60. package/dist/esm/hooks/timer/time.js.map +1 -1
  61. package/dist/esm/hooks/useAvailableChains.d.ts.map +1 -1
  62. package/dist/esm/hooks/useAvailableChains.js +2 -1
  63. package/dist/esm/hooks/useAvailableChains.js.map +1 -1
  64. package/dist/esm/hooks/useExplorer.d.ts.map +1 -1
  65. package/dist/esm/hooks/useExplorer.js +4 -0
  66. package/dist/esm/hooks/useExplorer.js.map +1 -1
  67. package/dist/esm/hooks/useFlaggedTokenGuard.js +28 -0
  68. package/dist/esm/hooks/useFlaggedTokenGuard.js.map +1 -0
  69. package/dist/esm/hooks/useGasSufficiency.js +3 -1
  70. package/dist/esm/hooks/useGasSufficiency.js.map +1 -1
  71. package/dist/esm/hooks/useMaxSendAmount.js +15 -0
  72. package/dist/esm/hooks/useMaxSendAmount.js.map +1 -0
  73. package/dist/esm/hooks/usePacedRouteExecution.js +3 -2
  74. package/dist/esm/hooks/usePacedRouteExecution.js.map +1 -1
  75. package/dist/esm/hooks/useRouteExecutionMessage.js +3 -2
  76. package/dist/esm/hooks/useRouteExecutionMessage.js.map +1 -1
  77. package/dist/esm/hooks/useRoutes.d.ts.map +1 -1
  78. package/dist/esm/hooks/useRoutes.js +3 -1
  79. package/dist/esm/hooks/useRoutes.js.map +1 -1
  80. package/dist/esm/hooks/useToAddressRequirements.d.ts +2 -0
  81. package/dist/esm/hooks/useToAddressRequirements.d.ts.map +1 -1
  82. package/dist/esm/hooks/useToAddressRequirements.js +7 -1
  83. package/dist/esm/hooks/useToAddressRequirements.js.map +1 -1
  84. package/dist/esm/hooks/useTokenBalances.d.ts.map +1 -1
  85. package/dist/esm/hooks/useTokenBalances.js +7 -2
  86. package/dist/esm/hooks/useTokenBalances.js.map +1 -1
  87. package/dist/esm/hooks/useTokenBalancesQueries.js +29 -28
  88. package/dist/esm/hooks/useTokenBalancesQueries.js.map +1 -1
  89. package/dist/esm/hooks/useTokenSearch.js +3 -5
  90. package/dist/esm/hooks/useTokenSearch.js.map +1 -1
  91. package/dist/esm/hooks/useTokens.js +14 -10
  92. package/dist/esm/hooks/useTokens.js.map +1 -1
  93. package/dist/esm/hooks/useWideVariant.js +2 -1
  94. package/dist/esm/hooks/useWideVariant.js.map +1 -1
  95. package/dist/esm/i18n/bn.json +3 -1
  96. package/dist/esm/i18n/de.json +3 -1
  97. package/dist/esm/i18n/en.json +12 -3
  98. package/dist/esm/i18n/es.json +3 -1
  99. package/dist/esm/i18n/fr.json +3 -1
  100. package/dist/esm/i18n/hi.json +3 -1
  101. package/dist/esm/i18n/id.json +3 -1
  102. package/dist/esm/i18n/it.json +3 -1
  103. package/dist/esm/i18n/ja.json +3 -1
  104. package/dist/esm/i18n/ko.json +3 -1
  105. package/dist/esm/i18n/pl.json +3 -1
  106. package/dist/esm/i18n/pt.json +3 -1
  107. package/dist/esm/i18n/th.json +3 -1
  108. package/dist/esm/i18n/tr.json +3 -1
  109. package/dist/esm/i18n/uk.json +3 -1
  110. package/dist/esm/i18n/vi.json +3 -1
  111. package/dist/esm/i18n/zh.json +3 -1
  112. package/dist/esm/pages/MainPage/MainPage.js +2 -1
  113. package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
  114. package/dist/esm/pages/MainPage/ReviewButton.js +2 -2
  115. package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
  116. package/dist/esm/pages/RoutesPage/RoutesPage.js +2 -2
  117. package/dist/esm/pages/RoutesPage/RoutesPage.js.map +1 -1
  118. package/dist/esm/pages/SelectChainPage/SelectChainPage.js +13 -12
  119. package/dist/esm/pages/SelectChainPage/SelectChainPage.js.map +1 -1
  120. package/dist/esm/pages/SelectEnabledToolsPage.js +8 -5
  121. package/dist/esm/pages/SelectEnabledToolsPage.js.map +1 -1
  122. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js +2 -1
  123. package/dist/esm/pages/SelectTokenPage/SelectTokenPage.js.map +1 -1
  124. package/dist/esm/pages/SettingsPage/SmallBalanceFilterSettings.js +2 -1
  125. package/dist/esm/pages/SettingsPage/SmallBalanceFilterSettings.js.map +1 -1
  126. package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.js +2 -1
  127. package/dist/esm/pages/TransactionDetailsPage/ContactSupportButton.js.map +1 -1
  128. package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js +2 -1
  129. package/dist/esm/pages/TransactionDetailsPage/TransactionDetailsPage.js.map +1 -1
  130. package/dist/esm/pages/TransactionPage/StartTransactionButton.js +4 -3
  131. package/dist/esm/pages/TransactionPage/StartTransactionButton.js.map +1 -1
  132. package/dist/esm/pages/TransactionPage/TokenVerificationBottomSheet.js +64 -0
  133. package/dist/esm/pages/TransactionPage/TokenVerificationBottomSheet.js.map +1 -0
  134. package/dist/esm/pages/TransactionPage/TransactionExecutionContent.js +19 -16
  135. package/dist/esm/pages/TransactionPage/TransactionExecutionContent.js.map +1 -1
  136. package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js +3 -1
  137. package/dist/esm/pages/TransactionPage/TransactionFailedButtons.js.map +1 -1
  138. package/dist/esm/pages/TransactionPage/TransactionReview.js +24 -2
  139. package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
  140. package/dist/esm/providers/I18nProvider/I18nProvider.js +37 -36
  141. package/dist/esm/providers/I18nProvider/I18nProvider.js.map +1 -1
  142. package/dist/esm/providers/ThemeProvider/ThemeProvider.js +2 -1
  143. package/dist/esm/providers/ThemeProvider/ThemeProvider.js.map +1 -1
  144. package/dist/esm/providers/WalletProvider/WalletProvider.js +10 -9
  145. package/dist/esm/providers/WalletProvider/WalletProvider.js.map +1 -1
  146. package/dist/esm/providers/WalletProvider/useExternalWalletProvider.js +7 -3
  147. package/dist/esm/providers/WalletProvider/useExternalWalletProvider.js.map +1 -1
  148. package/dist/esm/stores/routes/useRouteExecutionIndicator.js +4 -3
  149. package/dist/esm/stores/routes/useRouteExecutionIndicator.js.map +1 -1
  150. package/dist/esm/types/token.d.ts +4 -1
  151. package/dist/esm/types/token.d.ts.map +1 -1
  152. package/dist/esm/utils/compactNumberFormatter.js +1 -3
  153. package/dist/esm/utils/compactNumberFormatter.js.map +1 -1
  154. package/dist/esm/utils/customReceiver.js +7 -0
  155. package/dist/esm/utils/customReceiver.js.map +1 -0
  156. package/dist/esm/utils/gas.js +24 -0
  157. package/dist/esm/utils/gas.js.map +1 -0
  158. package/dist/esm/utils/getErrorMessage.js +0 -1
  159. package/dist/esm/utils/getErrorMessage.js.map +1 -1
  160. package/dist/esm/utils/limitOrder.js +1 -1
  161. package/dist/esm/utils/limitOrder.js.map +1 -1
  162. package/dist/esm/utils/token.js +21 -4
  163. package/dist/esm/utils/token.js.map +1 -1
  164. package/dist/esm/utils/tokenList.js +38 -21
  165. package/dist/esm/utils/tokenList.js.map +1 -1
  166. package/package.json +15 -15
  167. package/src/components/AmountInput/AmountInputEndAdornment.tsx +2 -21
  168. package/src/components/AmountInputCard/PercentageChips.tsx +2 -18
  169. package/src/components/Chains/AllChainsAvatar.tsx +7 -1
  170. package/src/components/Messages/UnsupportedToAddressMessage.tsx +24 -0
  171. package/src/components/Messages/WarningMessages.tsx +3 -0
  172. package/src/components/Messages/useMessageQueue.ts +9 -0
  173. package/src/components/Routes/LimitOrderRoutes.tsx +5 -2
  174. package/src/components/Routes/RoutesContent.tsx +5 -2
  175. package/src/components/SendToWallet/SendToWalletExpandButton.tsx +2 -1
  176. package/src/components/TokenList/TokenListItem.tsx +68 -9
  177. package/src/components/TokenList/VirtualizedTokenList.tsx +33 -9
  178. package/src/components/TokenList/types.ts +4 -0
  179. package/src/config/version.ts +1 -1
  180. package/src/hooks/useAvailableChains.ts +1 -0
  181. package/src/hooks/useExplorer.ts +2 -0
  182. package/src/hooks/useFlaggedTokenGuard.ts +33 -0
  183. package/src/hooks/useGasSufficiency.ts +9 -1
  184. package/src/hooks/useMaxSendAmount.ts +21 -0
  185. package/src/hooks/useRoutes.ts +7 -0
  186. package/src/hooks/useToAddressRequirements.ts +17 -1
  187. package/src/hooks/useTokenBalances.ts +18 -2
  188. package/src/hooks/useTokenSearch.ts +1 -5
  189. package/src/hooks/useTokens.ts +28 -13
  190. package/src/pages/MainPage/ReviewButton.tsx +6 -2
  191. package/src/pages/RoutesPage/RoutesPage.tsx +5 -2
  192. package/src/pages/TransactionPage/TokenVerificationBottomSheet.tsx +93 -0
  193. package/src/pages/TransactionPage/TransactionReview.tsx +28 -1
  194. package/src/providers/WalletProvider/useExternalWalletProvider.ts +9 -1
  195. package/src/types/token.ts +6 -3
  196. package/src/utils/customReceiver.ts +7 -0
  197. package/src/utils/gas.ts +41 -0
  198. package/src/utils/token.ts +53 -9
  199. package/src/utils/tokenList.ts +28 -0
@@ -1 +1 @@
1
- {"version":3,"file":"limitOrder.js","names":[],"sources":["../../../src/utils/limitOrder.ts"],"sourcesContent":["import { formatInputAmount } from './format.js'\n\n/** Default order validity: 1 day, expressed as a duration in seconds. */\nexport const DEFAULT_VALID_UNTIL_SECONDS: number = 24 * 60 * 60\n\n/**\n * High-precision formatter for derived limit-order values. Mirrors the\n * approach used by `priceToTokenAmount` — these are display-level derivations;\n * the route request re-parses amounts via `parseUnits`.\n */\nconst preciseFormatter = new Intl.NumberFormat('en', {\n notation: 'standard',\n roundingPriority: 'morePrecision',\n maximumSignificantDigits: 20,\n maximumFractionDigits: 20,\n useGrouping: false,\n})\n\nfunction parsePositive(value?: string): number | undefined {\n if (!value) {\n return undefined\n }\n const parsed = Number.parseFloat(value)\n if (Number.isNaN(parsed) || parsed <= 0) {\n return undefined\n }\n return parsed\n}\n\n/**\n * Derive the receive amount: `receiveAmount = sendAmount × limitPrice`.\n * Returns an empty string when either input is missing or invalid.\n */\nexport function deriveReceiveAmount(\n sendAmount?: string,\n limitPrice?: string,\n toDecimals?: number\n): string {\n const send = parsePositive(sendAmount)\n const price = parsePositive(limitPrice)\n if (send === undefined || price === undefined) {\n return ''\n }\n return formatInputAmount(\n preciseFormatter.format(send * price),\n toDecimals ?? null\n )\n}\n\n/**\n * Derive the limit price: `limitPrice = receiveAmount ÷ sendAmount`.\n * Returns an empty string when either input is missing or invalid.\n */\nexport function deriveLimitPrice(\n sendAmount?: string,\n receiveAmount?: string\n): string {\n const send = parsePositive(sendAmount)\n const receive = parsePositive(receiveAmount)\n if (send === undefined || receive === undefined) {\n return ''\n }\n return preciseFormatter.format(receive / send)\n}\n\n/** Invert a price for display flipping: `1 ÷ price`. */\nexport function invertPrice(price?: string): string {\n const parsed = parsePositive(price)\n if (parsed === undefined) {\n return ''\n }\n return preciseFormatter.format(1 / parsed)\n}\n\n/**\n * Apply a percentage offset to a price, e.g. `percent = 2` → `price × 1.02`.\n * Used by the quick-set buttons (+2%, +5%).\n */\nexport function applyPriceOffset(\n price: string | undefined,\n percent: number\n): string {\n const parsed = parsePositive(price)\n if (parsed === undefined) {\n return ''\n }\n return preciseFormatter.format(parsed * (1 + percent / 100))\n}\n"],"mappings":";;;AAGA,MAAa,8BAAsC,OAAU;;;;;;AAO7D,MAAM,mBAAmB,IAAI,KAAK,aAAa,MAAM;CACnD,UAAU;CACV,kBAAkB;CAClB,0BAA0B;CAC1B,uBAAuB;CACvB,aAAa;AACf,CAAC;AAED,SAAS,cAAc,OAAoC;CACzD,IAAI,CAAC,OACH;CAEF,MAAM,SAAS,OAAO,WAAW,KAAK;CACtC,IAAI,OAAO,MAAM,MAAM,KAAK,UAAU,GACpC;CAEF,OAAO;AACT;;;;;AAMA,SAAgB,oBACd,YACA,YACA,YACQ;CACR,MAAM,OAAO,cAAc,UAAU;CACrC,MAAM,QAAQ,cAAc,UAAU;CACtC,IAAI,SAAS,KAAA,KAAa,UAAU,KAAA,GAClC,OAAO;CAET,OAAO,kBACL,iBAAiB,OAAO,OAAO,KAAK,GACpC,cAAc,IAChB;AACF;;;;;AAMA,SAAgB,iBACd,YACA,eACQ;CACR,MAAM,OAAO,cAAc,UAAU;CACrC,MAAM,UAAU,cAAc,aAAa;CAC3C,IAAI,SAAS,KAAA,KAAa,YAAY,KAAA,GACpC,OAAO;CAET,OAAO,iBAAiB,OAAO,UAAU,IAAI;AAC/C;;AAGA,SAAgB,YAAY,OAAwB;CAClD,MAAM,SAAS,cAAc,KAAK;CAClC,IAAI,WAAW,KAAA,GACb,OAAO;CAET,OAAO,iBAAiB,OAAO,IAAI,MAAM;AAC3C;;;;;AAMA,SAAgB,iBACd,OACA,SACQ;CACR,MAAM,SAAS,cAAc,KAAK;CAClC,IAAI,WAAW,KAAA,GACb,OAAO;CAET,OAAO,iBAAiB,OAAO,UAAU,IAAI,UAAU,IAAI;AAC7D"}
1
+ {"version":3,"file":"limitOrder.js","names":[],"sources":["../../../src/utils/limitOrder.ts"],"sourcesContent":["import { formatInputAmount } from './format.js'\n\n/** Default order validity: 1 day, expressed as a duration in seconds. */\nexport const DEFAULT_VALID_UNTIL_SECONDS: number = 24 * 60 * 60\n\n/**\n * High-precision formatter for derived limit-order values. Mirrors the\n * approach used by `priceToTokenAmount` — these are display-level derivations;\n * the route request re-parses amounts via `parseUnits`.\n */\nconst preciseFormatter = new Intl.NumberFormat('en', {\n notation: 'standard',\n roundingPriority: 'morePrecision',\n maximumSignificantDigits: 20,\n maximumFractionDigits: 20,\n useGrouping: false,\n})\n\nfunction parsePositive(value?: string): number | undefined {\n if (!value) {\n return undefined\n }\n const parsed = Number.parseFloat(value)\n if (Number.isNaN(parsed) || parsed <= 0) {\n return undefined\n }\n return parsed\n}\n\n/**\n * Derive the receive amount: `receiveAmount = sendAmount × limitPrice`.\n * Returns an empty string when either input is missing or invalid.\n */\nexport function deriveReceiveAmount(\n sendAmount?: string,\n limitPrice?: string,\n toDecimals?: number\n): string {\n const send = parsePositive(sendAmount)\n const price = parsePositive(limitPrice)\n if (send === undefined || price === undefined) {\n return ''\n }\n return formatInputAmount(\n preciseFormatter.format(send * price),\n toDecimals ?? null\n )\n}\n\n/**\n * Derive the limit price: `limitPrice = receiveAmount ÷ sendAmount`.\n * Returns an empty string when either input is missing or invalid.\n */\nexport function deriveLimitPrice(\n sendAmount?: string,\n receiveAmount?: string\n): string {\n const send = parsePositive(sendAmount)\n const receive = parsePositive(receiveAmount)\n if (send === undefined || receive === undefined) {\n return ''\n }\n return preciseFormatter.format(receive / send)\n}\n\n/** Invert a price for display flipping: `1 ÷ price`. */\nexport function invertPrice(price?: string): string {\n const parsed = parsePositive(price)\n if (parsed === undefined) {\n return ''\n }\n return preciseFormatter.format(1 / parsed)\n}\n\n/**\n * Apply a percentage offset to a price, e.g. `percent = 2` → `price × 1.02`.\n * Used by the quick-set buttons (+2%, +5%).\n */\nexport function applyPriceOffset(\n price: string | undefined,\n percent: number\n): string {\n const parsed = parsePositive(price)\n if (parsed === undefined) {\n return ''\n }\n return preciseFormatter.format(parsed * (1 + percent / 100))\n}\n"],"mappings":";;;AAGA,MAAa,8BAAsC;;;;;;AAOnD,MAAM,mBAAmB,IAAI,KAAK,aAAa,MAAM;CACnD,UAAU;CACV,kBAAkB;CAClB,0BAA0B;CAC1B,uBAAuB;CACvB,aAAa;AACf,CAAC;AAED,SAAS,cAAc,OAAoC;CACzD,IAAI,CAAC,OACH;CAEF,MAAM,SAAS,OAAO,WAAW,KAAK;CACtC,IAAI,OAAO,MAAM,MAAM,KAAK,UAAU,GACpC;CAEF,OAAO;AACT;;;;;AAMA,SAAgB,oBACd,YACA,YACA,YACQ;CACR,MAAM,OAAO,cAAc,UAAU;CACrC,MAAM,QAAQ,cAAc,UAAU;CACtC,IAAI,SAAS,KAAA,KAAa,UAAU,KAAA,GAClC,OAAO;CAET,OAAO,kBACL,iBAAiB,OAAO,OAAO,KAAK,GACpC,cAAc,IAChB;AACF;;;;;AAMA,SAAgB,iBACd,YACA,eACQ;CACR,MAAM,OAAO,cAAc,UAAU;CACrC,MAAM,UAAU,cAAc,aAAa;CAC3C,IAAI,SAAS,KAAA,KAAa,YAAY,KAAA,GACpC,OAAO;CAET,OAAO,iBAAiB,OAAO,UAAU,IAAI;AAC/C;;AAGA,SAAgB,YAAY,OAAwB;CAClD,MAAM,SAAS,cAAc,KAAK;CAClC,IAAI,WAAW,KAAA,GACb,OAAO;CAET,OAAO,iBAAiB,OAAO,IAAI,MAAM;AAC3C;;;;;AAMA,SAAgB,iBACd,OACA,SACQ;CACR,MAAM,SAAS,cAAc,KAAK;CAClC,IAAI,WAAW,KAAA,GACb,OAAO;CAET,OAAO,iBAAiB,OAAO,UAAU,IAAI,UAAU,IAAI;AAC7D"}
@@ -1,4 +1,5 @@
1
1
  import { getConfigItemSets, isFormItemAllowed } from "./item.js";
2
+ import { ChainId } from "@lifi/sdk";
2
3
  //#region src/utils/token.ts
3
4
  /**
4
5
  * Builds per-chain sets of lowercase token addresses from the
@@ -53,7 +54,7 @@ const updateTokenInCache = (data, token) => {
53
54
  } : t)
54
55
  };
55
56
  };
56
- const filterAllowedTokens = (dataTokens, configTokens, chainsConfig, formType) => {
57
+ const filterAllowedTokens = (dataTokens, configTokens, chainsConfig, formType, nativeTokenAddresses) => {
57
58
  if (!dataTokens) return;
58
59
  const includedTokens = configTokens?.include || [];
59
60
  const allChainIds = Array.from(/* @__PURE__ */ new Set([...includedTokens.map((t) => t.chainId), ...Object.keys(dataTokens)])).map((chainId) => Number(chainId));
@@ -66,6 +67,7 @@ const filterAllowedTokens = (dataTokens, configTokens, chainsConfig, formType) =
66
67
  const includedAddresses = new Set(chainIncludedTokens.map((t) => t.address.toLowerCase()));
67
68
  const allowedAddresses = getConfigItemSets(configTokens, (tokens) => new Set(tokens.filter((t) => Number(t.chainId) === chainId).map((t) => t.address.toLowerCase())), formType);
68
69
  const verifiedAddresses = verifiedTokensSets?.get(chainId);
70
+ const nativeAddress = nativeTokenAddresses?.get(chainId)?.toLowerCase();
69
71
  const chainTokens = [...dataTokens[chainId] ?? [], ...chainIncludedTokens];
70
72
  const filtered = [];
71
73
  const seenAddresses = /* @__PURE__ */ new Set();
@@ -74,9 +76,12 @@ const filterAllowedTokens = (dataTokens, configTokens, chainsConfig, formType) =
74
76
  if (seenAddresses.has(address)) continue;
75
77
  seenAddresses.add(address);
76
78
  if (!isFormItemAllowed(token, allowedAddresses, formType, (t) => t.address.toLowerCase())) continue;
77
- if (!token.verified && (includedAddresses.has(address) || verifiedAddresses?.has(address))) filtered.push({
79
+ const markVerified = !token.verified && (includedAddresses.has(address) || verifiedAddresses?.has(address));
80
+ const markNative = !!nativeAddress && nativeAddress === address;
81
+ if (markVerified || markNative) filtered.push({
78
82
  ...token,
79
- verified: true
83
+ ...markVerified ? { verified: true } : {},
84
+ ...markNative ? { native: true } : {}
80
85
  });
81
86
  else filtered.push(token);
82
87
  }
@@ -88,7 +93,19 @@ const getExecutionToToken = (route) => ({
88
93
  ...route.steps.at(-1)?.execution?.toToken ?? route.toToken,
89
94
  amount: BigInt(route.steps.at(-1)?.execution?.toAmount ?? route.steps.at(-1)?.estimate.toAmount ?? route.toAmount)
90
95
  });
96
+ /** Capitalized name of the first provider that verified the token. */
97
+ const getTokenVerificationProvider = (token) => {
98
+ const provider = token.verificationStatusBreakdown?.find((entry) => entry.result === "verified")?.provider;
99
+ return provider ? `${provider.charAt(0).toUpperCase()}${provider.slice(1)}` : void 0;
100
+ };
101
+ /**
102
+ * Hyperliquid declares Arbitrum USDC as its native token, Lighter declares
103
+ * Ethereum USDC. Neither is a gas token you hold there. A chain-local
104
+ * stablecoin gas token, as on Arc or Stable, is genuine and stays.
105
+ */
106
+ const chainIdsWithoutNativeToken = /* @__PURE__ */ new Set([ChainId.HPL, ChainId.LTR]);
107
+ const getNativeTokenAddresses = (chains) => new Map(chains?.filter((chain) => !chainIdsWithoutNativeToken.has(chain.id)).map((chain) => [chain.id, chain.nativeToken.address]));
91
108
  //#endregion
92
- export { filterAllowedTokens, getExecutionToToken, getVerifiedTokensSets, mergeVerifiedWithSearchTokens, updateTokenInCache };
109
+ export { filterAllowedTokens, getExecutionToToken, getNativeTokenAddresses, getTokenVerificationProvider, getVerifiedTokensSets, mergeVerifiedWithSearchTokens, updateTokenInCache };
93
110
 
94
111
  //# sourceMappingURL=token.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","names":[],"sources":["../../../src/utils/token.ts"],"sourcesContent":["import type { BaseToken, RouteExtended, Token, TokenAmount } from '@lifi/sdk'\nimport type { FormType } from '../stores/form/types.js'\nimport type { TokensByChain, TokenWithVerified } from '../types/token.js'\nimport type { WidgetChains, WidgetTokens } from '../types/widget.js'\nimport { getConfigItemSets, isFormItemAllowed } from './item.js'\n\n/**\n * Builds per-chain sets of lowercase token addresses from the\n * `tokens.verified` config allowlist.\n */\nexport const getVerifiedTokensSets = (\n configTokens?: WidgetTokens\n): Map<number, Set<string>> | undefined => {\n if (!configTokens?.verified?.length) {\n return undefined\n }\n const sets = new Map<number, Set<string>>()\n for (const token of configTokens.verified) {\n const chainId = Number(token.chainId)\n let addresses = sets.get(chainId)\n if (!addresses) {\n addresses = new Set()\n sets.set(chainId, addresses)\n }\n addresses.add(token.address.toLowerCase())\n }\n return sets\n}\n\n/**\n * Merges verified tokens with search tokens.\n * Verified tokens take priority - search tokens are only added if they don't already exist.\n */\nexport const mergeVerifiedWithSearchTokens = (\n verifiedTokens?: TokensByChain,\n searchTokens?: TokensByChain\n): TokensByChain | undefined => {\n if (!verifiedTokens) {\n return searchTokens\n }\n if (!searchTokens) {\n return verifiedTokens\n }\n\n const result = { ...verifiedTokens }\n\n for (const [chainId, tokens] of Object.entries(searchTokens)) {\n const chainIdNum = Number(chainId)\n const existingTokens = result[chainIdNum] || []\n const existingAddresses = new Set(\n existingTokens.map((t) => t.address.toLowerCase())\n )\n\n const newTokens = tokens.filter(\n (t) => !existingAddresses.has(t.address.toLowerCase())\n )\n\n if (newTokens.length) {\n result[chainIdNum] = [...existingTokens, ...newTokens]\n }\n }\n\n return result\n}\n\n/**\n * Updates a token in the cache by chainId and address.\n * Returns a new cache object with the token updated, or the original if not found.\n */\nexport const updateTokenInCache = (\n data: TokensByChain | undefined,\n token: Token\n): TokensByChain | undefined => {\n if (!data) {\n return data\n }\n const chainTokens = data[token.chainId]\n if (!chainTokens) {\n return data\n }\n const index = chainTokens.findIndex((t) => t.address === token.address)\n if (index < 0) {\n return data\n }\n return {\n ...data,\n [token.chainId]: chainTokens.map((t, i) =>\n i === index ? { ...t, ...token } : t\n ),\n }\n}\n\nexport const filterAllowedTokens = (\n dataTokens: TokensByChain | undefined,\n configTokens?: WidgetTokens,\n chainsConfig?: WidgetChains,\n formType?: FormType\n): TokensByChain | undefined => {\n if (!dataTokens) {\n return\n }\n\n const includedTokens = configTokens?.include || []\n const allChainIds = Array.from(\n new Set([\n ...includedTokens.map((t) => t.chainId),\n ...Object.keys(dataTokens),\n ])\n ).map((chainId) => Number(chainId))\n\n const configChainIdsSet = getConfigItemSets(\n chainsConfig,\n (chainIds: number[]) => new Set(chainIds),\n formType\n )\n\n const allowedChainIds = configChainIdsSet\n ? allChainIds.filter((chainId) =>\n isFormItemAllowed(chainId, configChainIdsSet, formType)\n )\n : allChainIds\n\n const verifiedTokensSets = getVerifiedTokensSets(configTokens)\n\n const allowedTokensByChain: TokensByChain = {}\n for (const chainId of allowedChainIds) {\n const chainIncludedTokens = includedTokens.filter(\n (t) => Number(t.chainId) === chainId\n )\n const includedAddresses = new Set(\n chainIncludedTokens.map((t) => t.address.toLowerCase())\n )\n\n const allowedAddresses = getConfigItemSets(\n configTokens,\n (tokens: BaseToken[]) =>\n new Set(\n tokens\n .filter((t) => Number(t.chainId) === chainId)\n .map((t) => t.address.toLowerCase())\n ),\n formType\n )\n\n const verifiedAddresses = verifiedTokensSets?.get(chainId)\n\n const chainTokens: TokenWithVerified[] = [\n ...(dataTokens[chainId] ?? []),\n ...chainIncludedTokens,\n ]\n\n const filtered: TokenWithVerified[] = []\n const seenAddresses = new Set<string>()\n for (const token of chainTokens) {\n const address = token.address.toLowerCase()\n // Data tokens come first and win over include duplicates,\n // keeping their extended data\n if (seenAddresses.has(address)) {\n continue\n }\n seenAddresses.add(address)\n if (\n !isFormItemAllowed(token, allowedAddresses, formType, (t) =>\n t.address.toLowerCase()\n )\n ) {\n continue\n }\n // Include and verified-allowlist tokens are explicitly set by the\n // integrator, mark them as verified\n if (\n !token.verified &&\n (includedAddresses.has(address) || verifiedAddresses?.has(address))\n ) {\n filtered.push({ ...token, verified: true })\n } else {\n filtered.push(token)\n }\n }\n\n allowedTokensByChain[chainId] = filtered\n }\n\n return allowedTokensByChain\n}\n\nexport const getExecutionToToken = (route: RouteExtended): TokenAmount => ({\n ...(route.steps.at(-1)?.execution?.toToken ?? route.toToken),\n amount: BigInt(\n route.steps.at(-1)?.execution?.toAmount ??\n route.steps.at(-1)?.estimate.toAmount ??\n route.toAmount\n ),\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"token.js","names":[],"sources":["../../../src/utils/token.ts"],"sourcesContent":["import type {\n BaseToken,\n ExtendedChain,\n RouteExtended,\n StaticToken,\n Token,\n TokenAmount,\n} from '@lifi/sdk'\nimport { ChainId } from '@lifi/sdk'\nimport type { FormType } from '../stores/form/types.js'\nimport type { TokensByChain, TokenWithFlags } from '../types/token.js'\nimport type { WidgetChains, WidgetTokens } from '../types/widget.js'\nimport { getConfigItemSets, isFormItemAllowed } from './item.js'\n\n/**\n * Builds per-chain sets of lowercase token addresses from the\n * `tokens.verified` config allowlist.\n */\nexport const getVerifiedTokensSets = (\n configTokens?: WidgetTokens\n): Map<number, Set<string>> | undefined => {\n if (!configTokens?.verified?.length) {\n return undefined\n }\n const sets = new Map<number, Set<string>>()\n for (const token of configTokens.verified) {\n const chainId = Number(token.chainId)\n let addresses = sets.get(chainId)\n if (!addresses) {\n addresses = new Set()\n sets.set(chainId, addresses)\n }\n addresses.add(token.address.toLowerCase())\n }\n return sets\n}\n\n/**\n * Merges verified tokens with search tokens.\n * Verified tokens take priority - search tokens are only added if they don't already exist.\n */\nexport const mergeVerifiedWithSearchTokens = (\n verifiedTokens?: TokensByChain,\n searchTokens?: TokensByChain\n): TokensByChain | undefined => {\n if (!verifiedTokens) {\n return searchTokens\n }\n if (!searchTokens) {\n return verifiedTokens\n }\n\n const result = { ...verifiedTokens }\n\n for (const [chainId, tokens] of Object.entries(searchTokens)) {\n const chainIdNum = Number(chainId)\n const existingTokens = result[chainIdNum] || []\n const existingAddresses = new Set(\n existingTokens.map((t) => t.address.toLowerCase())\n )\n\n const newTokens = tokens.filter(\n (t) => !existingAddresses.has(t.address.toLowerCase())\n )\n\n if (newTokens.length) {\n result[chainIdNum] = [...existingTokens, ...newTokens]\n }\n }\n\n return result\n}\n\n/**\n * Updates a token in the cache by chainId and address.\n * Returns a new cache object with the token updated, or the original if not found.\n */\nexport const updateTokenInCache = (\n data: TokensByChain | undefined,\n token: Token\n): TokensByChain | undefined => {\n if (!data) {\n return data\n }\n const chainTokens = data[token.chainId]\n if (!chainTokens) {\n return data\n }\n const index = chainTokens.findIndex((t) => t.address === token.address)\n if (index < 0) {\n return data\n }\n return {\n ...data,\n [token.chainId]: chainTokens.map((t, i) =>\n i === index ? { ...t, ...token } : t\n ),\n }\n}\n\nexport const filterAllowedTokens = (\n dataTokens: TokensByChain | undefined,\n configTokens?: WidgetTokens,\n chainsConfig?: WidgetChains,\n formType?: FormType,\n nativeTokenAddresses?: Map<number, string>\n): TokensByChain | undefined => {\n if (!dataTokens) {\n return\n }\n\n const includedTokens = configTokens?.include || []\n const allChainIds = Array.from(\n new Set([\n ...includedTokens.map((t) => t.chainId),\n ...Object.keys(dataTokens),\n ])\n ).map((chainId) => Number(chainId))\n\n const configChainIdsSet = getConfigItemSets(\n chainsConfig,\n (chainIds: number[]) => new Set(chainIds),\n formType\n )\n\n const allowedChainIds = configChainIdsSet\n ? allChainIds.filter((chainId) =>\n isFormItemAllowed(chainId, configChainIdsSet, formType)\n )\n : allChainIds\n\n const verifiedTokensSets = getVerifiedTokensSets(configTokens)\n\n const allowedTokensByChain: TokensByChain = {}\n for (const chainId of allowedChainIds) {\n const chainIncludedTokens = includedTokens.filter(\n (t) => Number(t.chainId) === chainId\n )\n const includedAddresses = new Set(\n chainIncludedTokens.map((t) => t.address.toLowerCase())\n )\n\n const allowedAddresses = getConfigItemSets(\n configTokens,\n (tokens: BaseToken[]) =>\n new Set(\n tokens\n .filter((t) => Number(t.chainId) === chainId)\n .map((t) => t.address.toLowerCase())\n ),\n formType\n )\n\n const verifiedAddresses = verifiedTokensSets?.get(chainId)\n const nativeAddress = nativeTokenAddresses?.get(chainId)?.toLowerCase()\n\n const chainTokens: TokenWithFlags[] = [\n ...(dataTokens[chainId] ?? []),\n ...chainIncludedTokens,\n ]\n\n const filtered: TokenWithFlags[] = []\n const seenAddresses = new Set<string>()\n for (const token of chainTokens) {\n const address = token.address.toLowerCase()\n // Data tokens come first and win over include duplicates,\n // keeping their extended data\n if (seenAddresses.has(address)) {\n continue\n }\n seenAddresses.add(address)\n if (\n !isFormItemAllowed(token, allowedAddresses, formType, (t) =>\n t.address.toLowerCase()\n )\n ) {\n continue\n }\n // Include and verified-allowlist tokens are explicitly set by the\n // integrator, mark them as verified. Flags are only ever set to true,\n // so a token that earns none keeps its cached object.\n const markVerified =\n !token.verified &&\n (includedAddresses.has(address) || verifiedAddresses?.has(address))\n const markNative = !!nativeAddress && nativeAddress === address\n if (markVerified || markNative) {\n filtered.push({\n ...token,\n ...(markVerified ? { verified: true } : {}),\n ...(markNative ? { native: true } : {}),\n })\n } else {\n filtered.push(token)\n }\n }\n\n allowedTokensByChain[chainId] = filtered\n }\n\n return allowedTokensByChain\n}\n\nexport const getExecutionToToken = (route: RouteExtended): TokenAmount => ({\n ...(route.steps.at(-1)?.execution?.toToken ?? route.toToken),\n amount: BigInt(\n route.steps.at(-1)?.execution?.toAmount ??\n route.steps.at(-1)?.estimate.toAmount ??\n route.toAmount\n ),\n})\n\n/** Capitalized name of the first provider that verified the token. */\nexport const getTokenVerificationProvider = (\n token: Pick<StaticToken, 'verificationStatusBreakdown'>\n): string | undefined => {\n const provider = token.verificationStatusBreakdown?.find(\n (entry) => entry.result === 'verified'\n )?.provider\n return provider\n ? `${provider.charAt(0).toUpperCase()}${provider.slice(1)}`\n : undefined\n}\n\n/**\n * Hyperliquid declares Arbitrum USDC as its native token, Lighter declares\n * Ethereum USDC. Neither is a gas token you hold there. A chain-local\n * stablecoin gas token, as on Arc or Stable, is genuine and stays.\n */\nconst chainIdsWithoutNativeToken = new Set([ChainId.HPL, ChainId.LTR])\n\nexport const getNativeTokenAddresses = (\n chains?: ExtendedChain[]\n): Map<number, string> =>\n new Map(\n chains\n ?.filter((chain) => !chainIdsWithoutNativeToken.has(chain.id))\n .map((chain) => [chain.id, chain.nativeToken.address])\n )\n"],"mappings":"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"}
@@ -3,30 +3,32 @@ import { formatUnits } from "@lifi/sdk";
3
3
  const sortByBalances = (a, b) => Number.parseFloat(formatUnits(b.amount ?? 0n, b.decimals)) * Number.parseFloat(b.priceUSD ?? "0") - Number.parseFloat(formatUnits(a.amount ?? 0n, a.decimals)) * Number.parseFloat(a.priceUSD ?? "0");
4
4
  const sortByVolume = (a, b) => (b.volumeUSD24H ?? 0) - (a.volumeUSD24H ?? 0);
5
5
  const processTokenList = (withoutBalances, noCategories, configTokens, selectedChainId, tokens, tokensWithBalances, isPinnedToken) => {
6
- if (withoutBalances) if (noCategories) {
7
- const sortedTokens = [...tokens ?? []].sort(sortByVolume);
8
- if (isPinnedToken) {
9
- const pinned = [];
10
- const notPinned = [];
11
- for (const token of sortedTokens) if (isPinnedToken(token.chainId, token.address)) {
12
- const pinnedToken = {
13
- ...token,
14
- pinned: true
6
+ if (withoutBalances) {
7
+ if (noCategories) {
8
+ const sortedTokens = [...tokens ?? []].sort(sortByVolume);
9
+ if (isPinnedToken) {
10
+ const pinned = [];
11
+ const notPinned = [];
12
+ for (const token of sortedTokens) if (isPinnedToken(token.chainId, token.address)) {
13
+ const pinnedToken = {
14
+ ...token,
15
+ pinned: true
16
+ };
17
+ pinned.push(pinnedToken);
18
+ } else notPinned.push(token);
19
+ return {
20
+ processedTokens: [...pinned, ...notPinned],
21
+ withCategories: false,
22
+ withPinnedTokens: !!pinned.length
15
23
  };
16
- pinned.push(pinnedToken);
17
- } else notPinned.push(token);
24
+ }
18
25
  return {
19
- processedTokens: [...pinned, ...notPinned],
26
+ processedTokens: sortedTokens,
20
27
  withCategories: false,
21
- withPinnedTokens: !!pinned.length
28
+ withPinnedTokens: false
22
29
  };
23
- }
24
- return {
25
- processedTokens: sortedTokens,
26
- withCategories: false,
27
- withPinnedTokens: false
28
- };
29
- } else return processedTypedTokens(tokens ?? [], [], selectedChainId, configTokens, isPinnedToken);
30
+ } else return processedTypedTokens(tokens ?? [], [], selectedChainId, configTokens, isPinnedToken);
31
+ }
30
32
  const sortedTokensWithBalances = [...tokensWithBalances ?? []].sort(sortByBalances);
31
33
  const tokensWithBalancesSet = new Set(sortedTokensWithBalances.map((token) => `${token.chainId}-${token.address.toLowerCase()}`));
32
34
  const tokensWithoutBalances = tokens?.filter((token) => {
@@ -138,7 +140,22 @@ const isSearchMatch = (token, search) => {
138
140
  const searchLowerCase = search.toLowerCase();
139
141
  return token.name?.toLowerCase().includes(searchLowerCase) || token.symbol?.replaceAll("₮", "T").toLowerCase().includes(searchLowerCase) || token.address?.toLowerCase().includes(searchLowerCase);
140
142
  };
143
+ /**
144
+ * Moves the chain's native token to the top of its own chain's list, unless an
145
+ * external list already places it. Returns the array unchanged when nothing
146
+ * moves, so a memo downstream keeps its reference.
147
+ */
148
+ const hoistNativeToken = (tokens, selectedChainId) => {
149
+ if (!selectedChainId) return tokens;
150
+ const index = tokens.findIndex((token) => token.native && token.chainId === selectedChainId && !token.featured && !token.popular && !token.pinned && !token.verified);
151
+ if (index < 1) return tokens;
152
+ return [
153
+ tokens[index],
154
+ ...tokens.slice(0, index),
155
+ ...tokens.slice(index + 1)
156
+ ];
157
+ };
141
158
  //#endregion
142
- export { isSearchMatch, processTokenList };
159
+ export { hoistNativeToken, isSearchMatch, processTokenList };
143
160
 
144
161
  //# sourceMappingURL=tokenList.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokenList.js","names":[],"sources":["../../../src/utils/tokenList.ts"],"sourcesContent":["import { formatUnits, type TokenExtended } from '@lifi/sdk'\nimport type { TokenAmount, TokenAmountExtended } from '../types/token.js'\nimport type { WidgetTokens } from '../types/widget.js'\n\nconst sortByBalances = (a: TokenAmount, b: TokenAmount) =>\n Number.parseFloat(formatUnits(b.amount ?? 0n, b.decimals)) *\n Number.parseFloat(b.priceUSD ?? '0') -\n Number.parseFloat(formatUnits(a.amount ?? 0n, a.decimals)) *\n Number.parseFloat(a.priceUSD ?? '0')\n\nconst sortByVolume = (a: TokenExtended, b: TokenExtended) =>\n (b.volumeUSD24H ?? 0) - (a.volumeUSD24H ?? 0)\n\nexport const processTokenList = (\n withoutBalances: boolean,\n noCategories: boolean,\n configTokens?: WidgetTokens,\n selectedChainId?: number,\n tokens?: TokenExtended[],\n tokensWithBalances?: TokenAmount[],\n isPinnedToken?: (chainId: number, tokenAddress: string) => boolean\n): {\n processedTokens: TokenAmount[]\n withCategories: boolean\n withPinnedTokens: boolean\n} => {\n if (withoutBalances) {\n if (noCategories) {\n const sortedTokens = [...(tokens ?? [])].sort(sortByVolume)\n // Separate pinned tokens\n if (isPinnedToken) {\n const pinned: TokenAmount[] = []\n const notPinned: TokenAmount[] = []\n for (const token of sortedTokens) {\n if (isPinnedToken(token.chainId, token.address)) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinned.push(pinnedToken)\n } else {\n notPinned.push(token)\n }\n }\n return {\n processedTokens: [...pinned, ...notPinned],\n withCategories: false,\n withPinnedTokens: !!pinned.length,\n }\n }\n return {\n processedTokens: sortedTokens,\n withCategories: false,\n withPinnedTokens: false,\n }\n } else {\n return processedTypedTokens(\n tokens ?? [],\n [],\n selectedChainId,\n configTokens,\n isPinnedToken\n )\n }\n }\n\n const sortedTokensWithBalances = [...(tokensWithBalances ?? [])].sort(\n sortByBalances\n )\n\n const tokensWithBalancesSet = new Set(\n sortedTokensWithBalances.map(\n (token) => `${token.chainId}-${token.address.toLowerCase()}`\n )\n )\n const tokensWithoutBalances =\n tokens\n ?.filter((token) => {\n const tokenKey = `${token.chainId}-${token.address.toLowerCase()}`\n return !tokensWithBalancesSet.has(tokenKey)\n })\n .sort(sortByVolume) ?? []\n\n if (noCategories) {\n // Separate pinned tokens\n if (isPinnedToken) {\n const pinnedWithBalances: TokenAmount[] = []\n const notPinnedWithBalances: TokenAmount[] = []\n const pinnedWithoutBalances: TokenAmount[] = []\n const notPinnedWithoutBalances: TokenAmount[] = []\n\n for (const token of sortedTokensWithBalances) {\n if (isPinnedToken(token.chainId, token.address)) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinnedWithBalances.push(pinnedToken)\n } else {\n notPinnedWithBalances.push(token)\n }\n }\n\n for (const token of tokensWithoutBalances) {\n if (isPinnedToken(token.chainId, token.address)) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinnedWithoutBalances.push(pinnedToken)\n } else {\n notPinnedWithoutBalances.push(token)\n }\n }\n\n return {\n processedTokens: [\n ...pinnedWithBalances,\n ...pinnedWithoutBalances,\n ...notPinnedWithBalances,\n ...notPinnedWithoutBalances,\n ],\n withCategories: false,\n withPinnedTokens:\n !!pinnedWithBalances.length || !!pinnedWithoutBalances.length,\n }\n }\n return {\n processedTokens: [...sortedTokensWithBalances, ...tokensWithoutBalances],\n withCategories: false,\n withPinnedTokens: false,\n }\n } else {\n return processedTypedTokens(\n tokensWithoutBalances,\n sortedTokensWithBalances,\n selectedChainId,\n configTokens,\n isPinnedToken\n )\n }\n}\n\n// NB: only for non-all-networks\nconst processedTypedTokens = (\n tokens: TokenAmount[],\n tokensWithBalances: TokenAmount[],\n selectedChainId?: number,\n configTokens?: WidgetTokens,\n isPinnedToken?: (chainId: number, tokenAddress: string) => boolean\n) => {\n const filteredTokensMap = new Map(\n tokens.map((token) => [token.address, token])\n )\n\n const pinnedTokens: TokenAmount[] = []\n const featuredTokensFromConfig: TokenAmount[] = []\n const popularTokensFromConfig: TokenAmount[] = []\n\n ;(['popular', 'featured'] as const).forEach((tokenType) => {\n const typedTokens = configTokens?.[tokenType]?.filter(\n (token) => token.chainId === selectedChainId\n )\n\n typedTokens?.forEach((token) => {\n const tokenAmount = { ...token } as TokenAmount\n tokenAmount[tokenType] = true\n // Config tokens are explicitly set by integrator, mark as verified\n tokenAmount.verified = true\n\n const match = filteredTokensMap.get(token.address)\n if (match?.priceUSD) {\n tokenAmount.priceUSD = match.priceUSD\n }\n if (!token.logoURI && match?.logoURI) {\n tokenAmount.logoURI = match.logoURI\n }\n\n if (tokenType === 'popular') {\n popularTokensFromConfig.push(tokenAmount)\n } else {\n featuredTokensFromConfig.push(tokenAmount)\n }\n\n // Additionally add to pinned tokens if it is pinned\n const isPinned =\n isPinnedToken && selectedChainId\n ? isPinnedToken(selectedChainId, token.address)\n : false\n if (isPinned) {\n const pinnedToken = { ...tokenAmount, pinned: true } as TokenAmount\n pinnedTokens.push(pinnedToken)\n }\n })\n })\n\n // Filter out config-added tokens from main list\n const configTokenAddresses = new Set(\n [...popularTokensFromConfig, ...featuredTokensFromConfig].map((t) =>\n t.address.toLowerCase()\n )\n )\n\n const remainingTokens = tokens.filter(\n (token) => !configTokenAddresses.has(token.address.toLowerCase())\n )\n\n const otherTokens: TokenAmount[] = []\n\n // Separate pinned tokens and categorize remaining tokens\n for (const token of remainingTokens) {\n const isPinned =\n isPinnedToken && selectedChainId\n ? isPinnedToken(selectedChainId, token.address)\n : false\n\n if (isPinned) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinnedTokens.push(pinnedToken)\n } else if (token.featured) {\n featuredTokensFromConfig.push(token)\n } else if (token.popular) {\n popularTokensFromConfig.push(token)\n } else {\n otherTokens.push(token)\n }\n }\n\n // Also check tokens with balances for pinned status\n const pinnedTokensWithBalances: TokenAmount[] = []\n const nonPinnedTokensWithBalances: TokenAmount[] = []\n\n if (isPinnedToken && selectedChainId) {\n for (const token of tokensWithBalances) {\n if (isPinnedToken(selectedChainId, token.address)) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinnedTokensWithBalances.push(pinnedToken)\n } else {\n nonPinnedTokensWithBalances.push(token)\n }\n }\n } else {\n nonPinnedTokensWithBalances.push(...tokensWithBalances)\n }\n\n const sortedPinnedTokens = [\n ...pinnedTokens,\n ...pinnedTokensWithBalances,\n ].sort(sortByVolume)\n const sortedFeaturedTokens = [...featuredTokensFromConfig].sort(sortByVolume)\n const sortedPopularTokens = [...popularTokensFromConfig].sort(sortByVolume)\n const sortedOtherTokens = [...otherTokens].sort(sortByVolume)\n\n return {\n processedTokens: [\n ...sortedPinnedTokens,\n ...sortedFeaturedTokens,\n ...nonPinnedTokensWithBalances,\n ...sortedPopularTokens,\n ...sortedOtherTokens,\n ],\n withCategories:\n !!featuredTokensFromConfig.length || !!popularTokensFromConfig.length,\n withPinnedTokens: !!sortedPinnedTokens.length,\n }\n}\n\nexport const isSearchMatch = (\n token: TokenExtended | TokenAmountExtended,\n search?: string\n): boolean => {\n if (!search) {\n return true\n }\n\n const searchLowerCase = search.toLowerCase()\n return (\n token.name?.toLowerCase().includes(searchLowerCase) ||\n token.symbol\n ?.replaceAll('₮', 'T')\n .toLowerCase()\n .includes(searchLowerCase) ||\n token.address?.toLowerCase().includes(searchLowerCase)\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tokenList.js","names":[],"sources":["../../../src/utils/tokenList.ts"],"sourcesContent":["import { formatUnits, type TokenExtended } from '@lifi/sdk'\nimport type { TokenAmount, TokenAmountExtended } from '../types/token.js'\nimport type { WidgetTokens } from '../types/widget.js'\n\nconst sortByBalances = (a: TokenAmount, b: TokenAmount) =>\n Number.parseFloat(formatUnits(b.amount ?? 0n, b.decimals)) *\n Number.parseFloat(b.priceUSD ?? '0') -\n Number.parseFloat(formatUnits(a.amount ?? 0n, a.decimals)) *\n Number.parseFloat(a.priceUSD ?? '0')\n\nconst sortByVolume = (a: TokenExtended, b: TokenExtended) =>\n (b.volumeUSD24H ?? 0) - (a.volumeUSD24H ?? 0)\n\nexport const processTokenList = (\n withoutBalances: boolean,\n noCategories: boolean,\n configTokens?: WidgetTokens,\n selectedChainId?: number,\n tokens?: TokenExtended[],\n tokensWithBalances?: TokenAmount[],\n isPinnedToken?: (chainId: number, tokenAddress: string) => boolean\n): {\n processedTokens: TokenAmount[]\n withCategories: boolean\n withPinnedTokens: boolean\n} => {\n if (withoutBalances) {\n if (noCategories) {\n const sortedTokens = [...(tokens ?? [])].sort(sortByVolume)\n // Separate pinned tokens\n if (isPinnedToken) {\n const pinned: TokenAmount[] = []\n const notPinned: TokenAmount[] = []\n for (const token of sortedTokens) {\n if (isPinnedToken(token.chainId, token.address)) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinned.push(pinnedToken)\n } else {\n notPinned.push(token)\n }\n }\n return {\n processedTokens: [...pinned, ...notPinned],\n withCategories: false,\n withPinnedTokens: !!pinned.length,\n }\n }\n return {\n processedTokens: sortedTokens,\n withCategories: false,\n withPinnedTokens: false,\n }\n } else {\n return processedTypedTokens(\n tokens ?? [],\n [],\n selectedChainId,\n configTokens,\n isPinnedToken\n )\n }\n }\n\n const sortedTokensWithBalances = [...(tokensWithBalances ?? [])].sort(\n sortByBalances\n )\n\n const tokensWithBalancesSet = new Set(\n sortedTokensWithBalances.map(\n (token) => `${token.chainId}-${token.address.toLowerCase()}`\n )\n )\n const tokensWithoutBalances =\n tokens\n ?.filter((token) => {\n const tokenKey = `${token.chainId}-${token.address.toLowerCase()}`\n return !tokensWithBalancesSet.has(tokenKey)\n })\n .sort(sortByVolume) ?? []\n\n if (noCategories) {\n // Separate pinned tokens\n if (isPinnedToken) {\n const pinnedWithBalances: TokenAmount[] = []\n const notPinnedWithBalances: TokenAmount[] = []\n const pinnedWithoutBalances: TokenAmount[] = []\n const notPinnedWithoutBalances: TokenAmount[] = []\n\n for (const token of sortedTokensWithBalances) {\n if (isPinnedToken(token.chainId, token.address)) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinnedWithBalances.push(pinnedToken)\n } else {\n notPinnedWithBalances.push(token)\n }\n }\n\n for (const token of tokensWithoutBalances) {\n if (isPinnedToken(token.chainId, token.address)) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinnedWithoutBalances.push(pinnedToken)\n } else {\n notPinnedWithoutBalances.push(token)\n }\n }\n\n return {\n processedTokens: [\n ...pinnedWithBalances,\n ...pinnedWithoutBalances,\n ...notPinnedWithBalances,\n ...notPinnedWithoutBalances,\n ],\n withCategories: false,\n withPinnedTokens:\n !!pinnedWithBalances.length || !!pinnedWithoutBalances.length,\n }\n }\n return {\n processedTokens: [...sortedTokensWithBalances, ...tokensWithoutBalances],\n withCategories: false,\n withPinnedTokens: false,\n }\n } else {\n return processedTypedTokens(\n tokensWithoutBalances,\n sortedTokensWithBalances,\n selectedChainId,\n configTokens,\n isPinnedToken\n )\n }\n}\n\n// NB: only for non-all-networks\nconst processedTypedTokens = (\n tokens: TokenAmount[],\n tokensWithBalances: TokenAmount[],\n selectedChainId?: number,\n configTokens?: WidgetTokens,\n isPinnedToken?: (chainId: number, tokenAddress: string) => boolean\n) => {\n const filteredTokensMap = new Map(\n tokens.map((token) => [token.address, token])\n )\n\n const pinnedTokens: TokenAmount[] = []\n const featuredTokensFromConfig: TokenAmount[] = []\n const popularTokensFromConfig: TokenAmount[] = []\n\n ;(['popular', 'featured'] as const).forEach((tokenType) => {\n const typedTokens = configTokens?.[tokenType]?.filter(\n (token) => token.chainId === selectedChainId\n )\n\n typedTokens?.forEach((token) => {\n const tokenAmount = { ...token } as TokenAmount\n tokenAmount[tokenType] = true\n // Config tokens are explicitly set by integrator, mark as verified\n tokenAmount.verified = true\n\n const match = filteredTokensMap.get(token.address)\n if (match?.priceUSD) {\n tokenAmount.priceUSD = match.priceUSD\n }\n if (!token.logoURI && match?.logoURI) {\n tokenAmount.logoURI = match.logoURI\n }\n\n if (tokenType === 'popular') {\n popularTokensFromConfig.push(tokenAmount)\n } else {\n featuredTokensFromConfig.push(tokenAmount)\n }\n\n // Additionally add to pinned tokens if it is pinned\n const isPinned =\n isPinnedToken && selectedChainId\n ? isPinnedToken(selectedChainId, token.address)\n : false\n if (isPinned) {\n const pinnedToken = { ...tokenAmount, pinned: true } as TokenAmount\n pinnedTokens.push(pinnedToken)\n }\n })\n })\n\n // Filter out config-added tokens from main list\n const configTokenAddresses = new Set(\n [...popularTokensFromConfig, ...featuredTokensFromConfig].map((t) =>\n t.address.toLowerCase()\n )\n )\n\n const remainingTokens = tokens.filter(\n (token) => !configTokenAddresses.has(token.address.toLowerCase())\n )\n\n const otherTokens: TokenAmount[] = []\n\n // Separate pinned tokens and categorize remaining tokens\n for (const token of remainingTokens) {\n const isPinned =\n isPinnedToken && selectedChainId\n ? isPinnedToken(selectedChainId, token.address)\n : false\n\n if (isPinned) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinnedTokens.push(pinnedToken)\n } else if (token.featured) {\n featuredTokensFromConfig.push(token)\n } else if (token.popular) {\n popularTokensFromConfig.push(token)\n } else {\n otherTokens.push(token)\n }\n }\n\n // Also check tokens with balances for pinned status\n const pinnedTokensWithBalances: TokenAmount[] = []\n const nonPinnedTokensWithBalances: TokenAmount[] = []\n\n if (isPinnedToken && selectedChainId) {\n for (const token of tokensWithBalances) {\n if (isPinnedToken(selectedChainId, token.address)) {\n const pinnedToken = { ...token, pinned: true } as TokenAmount\n pinnedTokensWithBalances.push(pinnedToken)\n } else {\n nonPinnedTokensWithBalances.push(token)\n }\n }\n } else {\n nonPinnedTokensWithBalances.push(...tokensWithBalances)\n }\n\n const sortedPinnedTokens = [\n ...pinnedTokens,\n ...pinnedTokensWithBalances,\n ].sort(sortByVolume)\n const sortedFeaturedTokens = [...featuredTokensFromConfig].sort(sortByVolume)\n const sortedPopularTokens = [...popularTokensFromConfig].sort(sortByVolume)\n const sortedOtherTokens = [...otherTokens].sort(sortByVolume)\n\n return {\n processedTokens: [\n ...sortedPinnedTokens,\n ...sortedFeaturedTokens,\n ...nonPinnedTokensWithBalances,\n ...sortedPopularTokens,\n ...sortedOtherTokens,\n ],\n withCategories:\n !!featuredTokensFromConfig.length || !!popularTokensFromConfig.length,\n withPinnedTokens: !!sortedPinnedTokens.length,\n }\n}\n\nexport const isSearchMatch = (\n token: TokenExtended | TokenAmountExtended,\n search?: string\n): boolean => {\n if (!search) {\n return true\n }\n\n const searchLowerCase = search.toLowerCase()\n return (\n token.name?.toLowerCase().includes(searchLowerCase) ||\n token.symbol\n ?.replaceAll('₮', 'T')\n .toLowerCase()\n .includes(searchLowerCase) ||\n token.address?.toLowerCase().includes(searchLowerCase)\n )\n}\n\n/**\n * Moves the chain's native token to the top of its own chain's list, unless an\n * external list already places it. Returns the array unchanged when nothing\n * moves, so a memo downstream keeps its reference.\n */\nexport const hoistNativeToken = (\n tokens: TokenAmount[],\n selectedChainId?: number\n): TokenAmount[] => {\n if (!selectedChainId) {\n return tokens\n }\n const index = tokens.findIndex(\n (token) =>\n token.native &&\n token.chainId === selectedChainId &&\n !token.featured &&\n !token.popular &&\n !token.pinned &&\n !token.verified\n )\n // -1 is no native token, 0 is already first\n if (index < 1) {\n return tokens\n }\n return [tokens[index], ...tokens.slice(0, index), ...tokens.slice(index + 1)]\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/widget",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "LI.FI Widget for cross-chain bridging and swapping. It will drive your multi-chain strategy and attract new users from everywhere.",
5
5
  "type": "module",
6
6
  "main": "./dist/esm/index.js",
@@ -52,22 +52,22 @@
52
52
  "dependencies": {
53
53
  "@emotion/react": "^11.14.0",
54
54
  "@emotion/styled": "^11.14.1",
55
- "@lifi/sdk": "^4.3.0",
56
- "@mui/icons-material": "^9.2.0",
57
- "@mui/material": "^9.2.0",
58
- "@mui/system": "^9.2.0",
59
- "@tanstack/react-router": "^1.170.17",
60
- "@tanstack/react-virtual": "^3.14.5",
55
+ "@lifi/sdk": "^4.6.1",
56
+ "@lifi/wallet-management": "4.2.0",
57
+ "@lifi/widget-provider": "4.4.0",
58
+ "@mui/icons-material": "^9.4.0",
59
+ "@mui/material": "^9.4.0",
60
+ "@mui/system": "^9.4.0",
61
+ "@tanstack/react-router": "^1.170.32",
62
+ "@tanstack/react-virtual": "^3.14.10",
61
63
  "eventemitter3": "^5.0.4",
62
- "i18next": "^26.3.6",
63
- "microdiff": "^1.5.0",
64
- "motion": "^12.42.2",
65
- "react-i18next": "^17.0.9",
66
- "react-intersection-observer": "^10.1.0",
64
+ "i18next": "^26.4.1",
65
+ "microdiff": "^1.6.0",
66
+ "motion": "^13.2.0",
67
+ "react-i18next": "^17.0.13",
68
+ "react-intersection-observer": "^11.0.1",
67
69
  "react-transition-group": "^4.4.5",
68
- "zustand": "^5.0.14",
69
- "@lifi/widget-provider": "4.3.1",
70
- "@lifi/wallet-management": "4.1.3"
70
+ "zustand": "^5.0.15"
71
71
  },
72
72
  "peerDependencies": {
73
73
  "@tanstack/react-query": ">=5.90.0",
@@ -4,7 +4,7 @@ import type React from 'react'
4
4
  import { type JSX, memo } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
6
  import { useAvailableChains } from '../../hooks/useAvailableChains.js'
7
- import { useGasRecommendation } from '../../hooks/useGasRecommendation.js'
7
+ import { useMaxSendAmount } from '../../hooks/useMaxSendAmount.js'
8
8
  import { useTokenAddressBalance } from '../../hooks/useTokenAddressBalance.js'
9
9
  import type { FormTypeProps } from '../../stores/form/types.js'
10
10
  import { FormKeyHelper } from '../../stores/form/types.js'
@@ -29,28 +29,10 @@ export const AmountInputEndAdornment: React.NamedExoticComponent<FormTypeProps>
29
29
  const chain = getChainById(chainId)
30
30
  const { account } = useAccount({ chainType: chain?.chainType })
31
31
 
32
- // We get gas recommendations for the source chain to make sure that after pressing the Max button
33
- // the user will have enough funds remaining to cover gas costs
34
- const { data } = useGasRecommendation(chainId)
35
-
36
32
  const { token } = useTokenAddressBalance(chainId, tokenAddress)
37
-
38
- const getMaxAmount = () => {
39
- if (!token?.amount) {
40
- return 0n
41
- }
42
- let maxAmount = token.amount
43
- if (chain?.nativeToken.address === tokenAddress && data?.recommended) {
44
- const recommendedAmount = BigInt(data.recommended.amount)
45
- if (token.amount > recommendedAmount) {
46
- maxAmount = token.amount - recommendedAmount
47
- }
48
- }
49
- return maxAmount
50
- }
33
+ const maxAmount = useMaxSendAmount(chainId, tokenAddress)
51
34
 
52
35
  const handlePercentage = (percentage: number) => {
53
- const maxAmount = getMaxAmount()
54
36
  if (maxAmount && token?.decimals) {
55
37
  const percentageAmount = (maxAmount * BigInt(percentage)) / 100n
56
38
  setFieldValue(
@@ -64,7 +46,6 @@ export const AmountInputEndAdornment: React.NamedExoticComponent<FormTypeProps>
64
46
  }
65
47
 
66
48
  const handleMax = () => {
67
- const maxAmount = getMaxAmount()
68
49
  if (maxAmount && token?.decimals) {
69
50
  setFieldValue(
70
51
  FormKeyHelper.getAmountKey(formType),
@@ -3,8 +3,8 @@ import { useAccount } from '@lifi/wallet-management'
3
3
  import { type JSX, memo } from 'react'
4
4
  import { useTranslation } from 'react-i18next'
5
5
  import { useAvailableChains } from '../../hooks/useAvailableChains.js'
6
- import { useGasRecommendation } from '../../hooks/useGasRecommendation.js'
7
6
  import { useLinkedLimitFields } from '../../hooks/useLinkedLimitFields.js'
7
+ import { useMaxSendAmount } from '../../hooks/useMaxSendAmount.js'
8
8
  import { useTokenAddressBalance } from '../../hooks/useTokenAddressBalance.js'
9
9
  import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
10
10
  import type { FormTypeProps } from '../../stores/form/types.js'
@@ -33,22 +33,8 @@ export const PercentageChips: React.NamedExoticComponent<FormTypeProps> = memo(
33
33
  const chain = getChainById(chainId)
34
34
  const { account } = useAccount({ chainType: chain?.chainType })
35
35
 
36
- const { data } = useGasRecommendation(chainId)
37
36
  const { token } = useTokenAddressBalance(chainId, tokenAddress)
38
-
39
- const getMaxAmount = (): bigint => {
40
- if (!token?.amount) {
41
- return 0n
42
- }
43
- let maxAmount = token.amount
44
- if (chain?.nativeToken.address === tokenAddress && data?.recommended) {
45
- const recommendedAmount = BigInt(data.recommended.amount)
46
- if (token.amount > recommendedAmount) {
47
- maxAmount = token.amount - recommendedAmount
48
- }
49
- }
50
- return maxAmount
51
- }
37
+ const maxAmount = useMaxSendAmount(chainId, tokenAddress)
52
38
 
53
39
  // In limit mode the send amount must flow through the linked-field
54
40
  // derivation so the receive amount recomputes; otherwise it is a plain
@@ -62,7 +48,6 @@ export const PercentageChips: React.NamedExoticComponent<FormTypeProps> = memo(
62
48
  }
63
49
 
64
50
  const handlePercentage = (percentage: number): void => {
65
- const maxAmount = getMaxAmount()
66
51
  if (maxAmount && token?.decimals) {
67
52
  const percentageAmount = (maxAmount * BigInt(percentage)) / 100n
68
53
  applyAmount(formatUnits(percentageAmount, token.decimals))
@@ -70,7 +55,6 @@ export const PercentageChips: React.NamedExoticComponent<FormTypeProps> = memo(
70
55
  }
71
56
 
72
57
  const handleMax = (): void => {
73
- const maxAmount = getMaxAmount()
74
58
  if (maxAmount && token?.decimals) {
75
59
  applyAmount(formatUnits(maxAmount, token.decimals))
76
60
  }
@@ -44,6 +44,12 @@ const chainTypeIcons = [
44
44
  icon: 'https://lifinance.github.io/types/src/assets/icons/chains/tron.svg',
45
45
  defaultChainId: ChainId.TRN,
46
46
  },
47
+ {
48
+ name: 'Stellar',
49
+ chainType: ChainType.STL,
50
+ icon: 'https://lifinance.github.io/types/src/assets/icons/chains/stellar.svg',
51
+ defaultChainId: ChainId.XLM,
52
+ },
47
53
  ]
48
54
 
49
55
  const maxChainAvatarsCount = 4
@@ -93,7 +99,7 @@ export const AllChainsAvatar: React.NamedExoticComponent<AllChainsAvatarProps> =
93
99
  }
94
100
  )
95
101
 
96
- if (existingChainTypeIcons.length === maxChainAvatarsCount) {
102
+ if (existingChainTypeIcons.length >= maxChainAvatarsCount) {
97
103
  return existingChainTypeIcons
98
104
  }
99
105
 
@@ -0,0 +1,24 @@
1
+ import WarningRounded from '@mui/icons-material/WarningRounded'
2
+ import type { BoxProps } from '@mui/material'
3
+ import { Typography } from '@mui/material'
4
+ import { useTranslation } from 'react-i18next'
5
+ import { AlertMessage } from './AlertMessage.js'
6
+
7
+ export const UnsupportedToAddressMessage: React.FC<BoxProps> = ({
8
+ ...props
9
+ }) => {
10
+ const { t } = useTranslation()
11
+ return (
12
+ <AlertMessage
13
+ title={
14
+ <Typography variant="body2" sx={{ px: 1, color: 'text.primary' }}>
15
+ {t('info.message.unsupportedToAddressMessage')}
16
+ </Typography>
17
+ }
18
+ icon={<WarningRounded />}
19
+ severity="warning"
20
+ multiline
21
+ {...props}
22
+ />
23
+ )
24
+ }
@@ -7,6 +7,7 @@ import { FundsSufficiencyMessage } from './FundsSufficiencyMessage.js'
7
7
  import { GasSufficiencyMessage } from './GasSufficiencyMessage.js'
8
8
  import { MinFromAmountUSDMessage } from './MinFromAmountUSDMessage.js'
9
9
  import { MissingRouteRequiredAccountMessage } from './MissingRouteRequiredAccountMessage.js'
10
+ import { UnsupportedToAddressMessage } from './UnsupportedToAddressMessage.js'
10
11
  import { useMessageQueue } from './useMessageQueue.js'
11
12
 
12
13
  type WarningMessagesProps = BoxProps & {
@@ -51,6 +52,8 @@ export const WarningMessages: React.FC<WarningMessagesProps> = ({
51
52
  return <AccountNotDeployedMessage {...props} />
52
53
  case 'ACCOUNT_DEPLOYED':
53
54
  return <AccountDeployedMessage {...props} />
55
+ case 'UNSUPPORTED_TO_ADDRESS':
56
+ return <UnsupportedToAddressMessage {...props} />
54
57
  default:
55
58
  return null
56
59
  }
@@ -22,6 +22,7 @@ export const useMessageQueue = (
22
22
  } => {
23
23
  const {
24
24
  requiredToAddress,
25
+ unsupportedReceiverBlocking,
25
26
  accountNotDeployedAtDestination,
26
27
  accountDeployedAtDestination,
27
28
  isLoading: isToAddressRequirementsLoading,
@@ -37,6 +38,13 @@ export const useMessageQueue = (
37
38
  const messageQueue = useMemo(() => {
38
39
  const queue: QueuedMessage[] = []
39
40
 
41
+ if (unsupportedReceiverBlocking) {
42
+ queue.push({
43
+ id: 'UNSUPPORTED_TO_ADDRESS',
44
+ priority: 7,
45
+ })
46
+ }
47
+
40
48
  if (missingChain) {
41
49
  queue.push({
42
50
  id: 'MISSING_ROUTE_REQUIRED_ACCOUNT',
@@ -94,6 +102,7 @@ export const useMessageQueue = (
94
102
  accountDeployedAtDestination,
95
103
  accountNotDeployedAtDestination,
96
104
  requiredToAddress,
105
+ unsupportedReceiverBlocking,
97
106
  missingChain,
98
107
  missingAccountAddress,
99
108
  belowMinFromAmountUSD,
@@ -17,7 +17,8 @@ export const LimitOrderRoutes: React.FC<CardProps> = (props) => {
17
17
  'selectedRouteId'
18
18
  )
19
19
  const { setFieldValue } = useFieldActions()
20
- const { requiredToAddress } = useToAddressRequirements()
20
+ const { requiredToAddress, unsupportedReceiverBlocking } =
21
+ useToAddressRequirements()
21
22
 
22
23
  const currentRoute = routes?.[0]
23
24
 
@@ -25,7 +26,9 @@ export const LimitOrderRoutes: React.FC<CardProps> = (props) => {
25
26
  return null
26
27
  }
27
28
 
28
- const toAddressUnsatisfied = currentRoute && requiredToAddress && !toAddress
29
+ const toAddressUnsatisfied =
30
+ currentRoute &&
31
+ (unsupportedReceiverBlocking || (requiredToAddress && !toAddress))
29
32
  const allowInteraction = !toAddressUnsatisfied
30
33
 
31
34
  const activeRouteId =
@@ -43,12 +43,15 @@ export const RoutesContent: React.NamedExoticComponent<RoutesContentProps> =
43
43
 
44
44
  const { account } = useAccount({ chainType: fromChain?.chainType })
45
45
  const [toAddress] = useFieldValues('toAddress')
46
- const { requiredToAddress } = useToAddressRequirements()
46
+ const { requiredToAddress, unsupportedReceiverBlocking } =
47
+ useToAddressRequirements()
47
48
 
48
49
  const currentRoute = routes?.[0]
49
50
 
50
51
  const routeNotFound = !currentRoute && !isLoading && !isFetching
51
- const toAddressUnsatisfied = currentRoute && requiredToAddress && !toAddress
52
+ const toAddressUnsatisfied =
53
+ currentRoute &&
54
+ (unsupportedReceiverBlocking || (requiredToAddress && !toAddress))
52
55
  const allowInteraction = account.isConnected && !toAddressUnsatisfied
53
56
 
54
57
  const title =
@@ -16,12 +16,13 @@ export const SendToWalletExpandButton = (): JSX.Element => {
16
16
  const { toAddresses, disabledUI, hiddenUI } = useWidgetConfig()
17
17
  const hiddenToAddress = hiddenUI?.toAddress
18
18
  const disabledToAddress = disabledUI?.toAddress
19
- const { requiredToAddress } = useToAddressRequirements()
19
+ const { requiredToAddress, unsupportedToAddress } = useToAddressRequirements()
20
20
  const [toAddressValue] = useFieldValues('toAddress')
21
21
 
22
22
  const visible =
23
23
  !hiddenToAddress &&
24
24
  !disabledToAddress &&
25
+ !unsupportedToAddress &&
25
26
  !toAddressValue &&
26
27
  !requiredToAddress
27
28