@aurora-is-near/intents-swap-widget 3.18.2 → 3.19.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 (182) hide show
  1. package/dist/components/Accordion.js +31 -32
  2. package/dist/components/Accordion.js.map +1 -1
  3. package/dist/components/AllNetworksIcon.js +11 -0
  4. package/dist/components/AllNetworksIcon.js.map +1 -0
  5. package/dist/components/Button.d.ts +8 -0
  6. package/dist/components/Button.js +53 -43
  7. package/dist/components/Button.js.map +1 -1
  8. package/dist/components/ChainShortcut.d.ts +15 -0
  9. package/dist/components/ChainShortcut.js +57 -0
  10. package/dist/components/ChainShortcut.js.map +1 -0
  11. package/dist/components/CopyButton.d.ts +5 -2
  12. package/dist/components/CopyButton.js +11 -7
  13. package/dist/components/CopyButton.js.map +1 -1
  14. package/dist/components/Notes.js +1 -1
  15. package/dist/components/Notes.js.map +1 -1
  16. package/dist/components/index.d.ts +2 -0
  17. package/dist/components/index.js +21 -17
  18. package/dist/components/index.js.map +1 -1
  19. package/dist/{config-CgCb5P7j.js → config-C47AYcMK.js} +1006 -992
  20. package/dist/config-C47AYcMK.js.map +1 -0
  21. package/dist/config.d.ts +2 -0
  22. package/dist/config.js +9 -10
  23. package/dist/config.js.map +1 -1
  24. package/dist/errors.js +1 -1
  25. package/dist/ext/alchemy/index.js +1 -1
  26. package/dist/ext/index.js +1 -1
  27. package/dist/features/BalanceRpcLoader/TokenBalanceLoader.js +1 -1
  28. package/dist/features/BalanceRpcLoader/index.js +1 -1
  29. package/dist/features/BalanceRpcLoader/useTokenBalanceRpc.js +1 -1
  30. package/dist/features/ChainsDropdown/ChainItem.d.ts +1 -0
  31. package/dist/features/ChainsDropdown/ChainItem.js +23 -14
  32. package/dist/features/ChainsDropdown/ChainItem.js.map +1 -1
  33. package/dist/features/ChainsDropdown/index.js +61 -94
  34. package/dist/features/ChainsDropdown/index.js.map +1 -1
  35. package/dist/features/ChainsSelector/index.d.ts +15 -0
  36. package/dist/features/ChainsSelector/index.js +108 -0
  37. package/dist/features/ChainsSelector/index.js.map +1 -0
  38. package/dist/features/DepositMethodSwitcher.js +1 -1
  39. package/dist/features/ErrorBoundary.js +1 -1
  40. package/dist/features/ExternalDeposit.js +4 -3
  41. package/dist/features/ExternalDeposit.js.map +1 -1
  42. package/dist/features/SendAddress/index.js +1 -1
  43. package/dist/features/SendAddress/useNotification.js +1 -1
  44. package/dist/features/SubmitButton/index.js +1 -1
  45. package/dist/features/SuccessScreen/CopyableValue.d.ts +5 -0
  46. package/dist/features/SuccessScreen/CopyableValue.js +11 -0
  47. package/dist/features/SuccessScreen/CopyableValue.js.map +1 -0
  48. package/dist/features/SuccessScreen/TokenRow.d.ts +8 -0
  49. package/dist/features/SuccessScreen/TokenRow.js +20 -0
  50. package/dist/features/SuccessScreen/TokenRow.js.map +1 -0
  51. package/dist/features/SuccessScreen/index.d.ts +5 -4
  52. package/dist/features/SuccessScreen/index.js +151 -49
  53. package/dist/features/SuccessScreen/index.js.map +1 -1
  54. package/dist/features/SuccessScreen/useSummaryItemsCount.d.ts +1 -0
  55. package/dist/features/SuccessScreen/useSummaryItemsCount.js +14 -0
  56. package/dist/features/SuccessScreen/useSummaryItemsCount.js.map +1 -0
  57. package/dist/features/SwapDirectionSwitcher.js +1 -1
  58. package/dist/features/SwapQuote/SwapQuote.js +1 -1
  59. package/dist/features/SwapQuote/index.js +1 -1
  60. package/dist/features/TokenInput/TokenInput.js +1 -1
  61. package/dist/features/TokenInput/TokenInputEmpty.js +1 -1
  62. package/dist/features/TokenInput/TokenInputSource.js +1 -1
  63. package/dist/features/TokenInput/TokenInputTarget.js +2 -2
  64. package/dist/features/TokenInput/WalletBalance.js +1 -1
  65. package/dist/features/TokenInput/hooks/index.js +1 -1
  66. package/dist/features/TokenInput/hooks/useTokenInputBalance.js +1 -1
  67. package/dist/features/TokenInput/index.js +1 -1
  68. package/dist/features/TokensList/TokenItem.js +2 -2
  69. package/dist/features/TokensList/TokensList.js +1 -1
  70. package/dist/features/TokensList/index.js +1 -1
  71. package/dist/features/TokensModal.js +62 -64
  72. package/dist/features/TokensModal.js.map +1 -1
  73. package/dist/features/WalletCompatibilityCheck/WalletCompatibilityModal.js +5 -4
  74. package/dist/features/WalletCompatibilityCheck/WalletCompatibilityModal.js.map +1 -1
  75. package/dist/features/WalletCompatibilityCheck/index.js +1 -1
  76. package/dist/features/index.js +1 -1
  77. package/dist/hooks/index.js +1 -1
  78. package/dist/hooks/useAllTokens.js +1 -1
  79. package/dist/hooks/useChains.js +1 -1
  80. package/dist/hooks/useCompatibilityCheck.js +1 -1
  81. package/dist/hooks/useDefaultToken.js +1 -1
  82. package/dist/hooks/useExternalDepositStatus/index.js +1 -1
  83. package/dist/hooks/useExternalDepositStatus/usePoaExternalDepositStatus.js +1 -1
  84. package/dist/hooks/useIntentsBalance.js +1 -1
  85. package/dist/hooks/useIsCompatibilityCheckRequired.js +1 -1
  86. package/dist/hooks/useMakeDepositAddress.js +1 -1
  87. package/dist/hooks/useMakeIntentsTransfer.js +1 -1
  88. package/dist/hooks/useMakeNEARFtTransferCall.js +1 -1
  89. package/dist/hooks/useMakeQuote.js +1 -1
  90. package/dist/hooks/useMakeQuoteTransfer.js +1 -1
  91. package/dist/hooks/useMakeTransfer.js +1 -1
  92. package/dist/hooks/useMergedBalance.js +1 -1
  93. package/dist/hooks/useSwitchChain.js +1 -1
  94. package/dist/hooks/useTheme.js +1 -1
  95. package/dist/hooks/useTokenInputPair.js +1 -1
  96. package/dist/hooks/useTokens.js +1 -1
  97. package/dist/hooks/useTokensFiltered.d.ts +3 -1
  98. package/dist/hooks/useTokensFiltered.js +1 -1
  99. package/dist/hooks/useTokensIntentsUnique.js +1 -1
  100. package/dist/index.js +74 -70
  101. package/dist/index.js.map +1 -1
  102. package/dist/machine/effects/index.js +1 -1
  103. package/dist/machine/effects/useAlchemyBalanceEffect.js +1 -1
  104. package/dist/machine/effects/useBalancesUpdateEffect.js +1 -1
  105. package/dist/machine/effects/useMakeQuoteEffect.js +1 -1
  106. package/dist/machine/effects/useSelectedTokensEffect.js +1 -1
  107. package/dist/machine/effects/useSetTokenBalanceEffect.js +1 -1
  108. package/dist/machine/effects/useSetTokenIntentsTargetEffect.js +1 -1
  109. package/dist/machine/effects/useWalletConnEffect.js +1 -1
  110. package/dist/machine/events/index.js +1 -1
  111. package/dist/machine/events/reset.d.ts +1 -0
  112. package/dist/machine/events/reset.js +2 -2
  113. package/dist/machine/events/reset.js.map +1 -1
  114. package/dist/machine/events/tokenSelect.js +1 -1
  115. package/dist/machine/events/validateInputAndMoveTo.js +1 -1
  116. package/dist/machine/events/validateInputs.js +1 -1
  117. package/dist/machine/index.js +1 -1
  118. package/dist/machine/snap.js +1 -1
  119. package/dist/machine/subscriptions/checkers/isSendAddressAsConnected.js +1 -1
  120. package/dist/machine/subscriptions/index.js +1 -1
  121. package/dist/styles.css +1 -1
  122. package/dist/theme/ThemeProvider.js +1 -1
  123. package/dist/types/config.d.ts +3 -0
  124. package/dist/types/localisation.d.ts +1 -1
  125. package/dist/utils/intents/signers/near.js +1 -1
  126. package/dist/utils/intents/signers/privy.js +1 -1
  127. package/dist/utils/near/getNearNep141StorageBalance.js +1 -1
  128. package/dist/utils/tokens/sort.d.ts +2 -1
  129. package/dist/utils/tokens/sort.js +55 -24
  130. package/dist/utils/tokens/sort.js.map +1 -1
  131. package/dist/widgets/WidgetDeposit/WidgetDepositContent.js +74 -73
  132. package/dist/widgets/WidgetDeposit/WidgetDepositContent.js.map +1 -1
  133. package/dist/widgets/WidgetDeposit/WidgetDepositSkeleton.js +1 -1
  134. package/dist/widgets/WidgetSwap/WidgetSwapContent.js +70 -68
  135. package/dist/widgets/WidgetSwap/WidgetSwapContent.js.map +1 -1
  136. package/dist/widgets/WidgetSwap/WidgetSwapSkeleton.js +1 -1
  137. package/dist/widgets/WidgetWithdraw/WidgetWithdrawContent.js +64 -62
  138. package/dist/widgets/WidgetWithdraw/WidgetWithdrawContent.js.map +1 -1
  139. package/dist/widgets/WidgetWithdraw/WidgetWithdrawSkeleton.js +1 -1
  140. package/package.json +1 -1
  141. package/src/components/Accordion.tsx +5 -6
  142. package/src/components/AllNetworksIcon.tsx +8 -0
  143. package/src/components/Button.tsx +17 -1
  144. package/src/components/ChainShortcut.tsx +67 -0
  145. package/src/components/CopyButton.tsx +12 -2
  146. package/src/components/Notes.tsx +1 -1
  147. package/src/components/index.ts +2 -0
  148. package/src/config.tsx +13 -0
  149. package/src/features/ChainsDropdown/ChainItem.tsx +17 -4
  150. package/src/features/ChainsDropdown/index.tsx +10 -41
  151. package/src/features/ChainsSelector/index.tsx +113 -0
  152. package/src/features/SuccessScreen/CopyableValue.tsx +13 -0
  153. package/src/features/SuccessScreen/TokenRow.tsx +26 -0
  154. package/src/features/SuccessScreen/index.tsx +161 -55
  155. package/src/features/SuccessScreen/useSummaryItemsCount.ts +33 -0
  156. package/src/features/TokensList/TokensList.tsx +2 -1
  157. package/src/features/TokensModal.tsx +29 -29
  158. package/src/hooks/useTokensFiltered.ts +4 -0
  159. package/src/machine/events/reset.ts +6 -3
  160. package/src/types/config.ts +8 -0
  161. package/src/types/localisation.ts +11 -3
  162. package/src/utils/tokens/sort.ts +107 -20
  163. package/src/widgets/WidgetDeposit/WidgetDepositContent.tsx +6 -4
  164. package/src/widgets/WidgetSwap/WidgetSwapContent.tsx +7 -4
  165. package/src/widgets/WidgetWithdraw/WidgetWithdrawContent.tsx +7 -4
  166. package/dist/config-CgCb5P7j.js.map +0 -1
  167. package/dist/features/ChainsDropdown/AllNetworksIcon.js +0 -7
  168. package/dist/features/ChainsDropdown/AllNetworksIcon.js.map +0 -1
  169. package/dist/features/SuccessScreen/CheckIcon.d.ts +0 -1
  170. package/dist/features/SuccessScreen/CheckIcon.js +0 -7
  171. package/dist/features/SuccessScreen/CheckIcon.js.map +0 -1
  172. package/dist/features/SuccessScreen/ExternalAction.d.ts +0 -5
  173. package/dist/features/SuccessScreen/ExternalAction.js +0 -16
  174. package/dist/features/SuccessScreen/ExternalAction.js.map +0 -1
  175. package/dist/features/SuccessScreen/SummaryItem.d.ts +0 -8
  176. package/dist/features/SuccessScreen/SummaryItem.js +0 -21
  177. package/dist/features/SuccessScreen/SummaryItem.js.map +0 -1
  178. package/src/features/ChainsDropdown/AllNetworksIcon.tsx +0 -7
  179. package/src/features/SuccessScreen/CheckIcon.tsx +0 -7
  180. package/src/features/SuccessScreen/ExternalAction.tsx +0 -15
  181. package/src/features/SuccessScreen/SummaryItem.tsx +0 -28
  182. /package/dist/{features/ChainsDropdown → components}/AllNetworksIcon.d.ts +0 -0
@@ -1,79 +1,185 @@
1
- import { Fragment } from 'react';
1
+ import {
2
+ ArrowDownward,
3
+ Check,
4
+ OpenInNew,
5
+ } from '@material-symbols-svg/react-rounded/w700';
2
6
 
3
- import { CheckIcon } from './CheckIcon';
4
- import { SummaryItem } from './SummaryItem';
5
- import { Hr } from '@/components/Hr';
6
- import { Card } from '@/components/Card';
7
+ import { TokenRow } from './TokenRow';
8
+ import { CopyableValue } from './CopyableValue';
9
+ import { useSummaryItemsCount } from './useSummaryItemsCount';
10
+
11
+ import { Notes } from '@/components/Notes';
12
+ import { Button } from '@/components/Button';
7
13
  import { CloseButton } from '@/components/CloseButton';
8
- import type { TransferResult } from '@/types/transfer';
14
+ import { Accordion } from '@/components/Accordion';
9
15
 
10
- import { fireEvent, useUnsafeSnapshot } from '@/machine';
16
+ import { guardStates } from '@/machine/guards';
17
+ import { useUnsafeSnapshot } from '@/machine/snap';
18
+ import { formatUsdAmount } from '@/utils/formatters/formatUsdAmount';
19
+ import { formatTinyNumber } from '@/utils/formatters/formatTinyNumber';
20
+ import { formatBigToHuman } from '@/utils/formatters/formatBigToHuman';
11
21
  import { useTypedTranslation } from '@/localisation';
22
+ import { useHandleKeyDown } from '@/hooks';
23
+ import { logger } from '@/logger';
24
+
25
+ import type { TransferResult } from '@/types/transfer';
26
+
27
+ const NOTES_ITEM_HEIGHT = 44;
12
28
 
13
29
  type Msg = { type: 'on_dismiss_success' };
14
30
 
15
31
  type Props = TransferResult & {
16
- message: string | string[];
17
- onMsg?: (msg: Msg) => void;
18
- hideHeader?: boolean;
32
+ title: string;
33
+ message?: string;
34
+ showTargetToken?: boolean;
35
+ onMsg: (msg: Msg) => void;
19
36
  };
20
37
 
21
38
  export const SuccessScreen = ({
22
- intent,
23
- transactionLink,
24
- hash: txHash,
39
+ title,
25
40
  message,
41
+ showTargetToken = true,
42
+ transactionLink,
26
43
  onMsg,
27
- hideHeader,
44
+ ...transferResult
28
45
  }: Props) => {
29
- const { ctx } = useUnsafeSnapshot();
30
46
  const { t } = useTypedTranslation();
31
- const lines = Array.isArray(message) ? message : [message];
47
+ const { ctx } = useUnsafeSnapshot();
48
+
49
+ const isValidState = guardStates(ctx, ['transfer_success']);
50
+ const handleClose = () => onMsg({ type: 'on_dismiss_success' });
51
+ const summaryItemsCount = useSummaryItemsCount(!!transferResult.intent);
52
+
53
+ useHandleKeyDown('Escape', handleClose);
32
54
 
33
- const onDismiss = () => {
34
- fireEvent('reset', { clearWalletAddress: false }).moveTo(
35
- ctx.walletAddress ? 'initial_wallet' : 'initial_dry',
55
+ if (!isValidState) {
56
+ logger.warn(
57
+ '[WIDGET] Success screen can be rendered only in transfer_success state',
36
58
  );
37
- onMsg?.({ type: 'on_dismiss_success' });
38
- };
59
+
60
+ return null;
61
+ }
62
+
63
+ const sourceAmount = formatBigToHuman(
64
+ ctx.quote?.amountIn ?? ctx.sourceTokenAmount,
65
+ ctx.sourceToken.decimals,
66
+ );
67
+
68
+ const targetAmount = formatBigToHuman(
69
+ ctx.quote?.amountOut ?? ctx.targetTokenAmount,
70
+ ctx.targetToken.decimals,
71
+ );
72
+
73
+ const sourceAmountUsd = ctx.quote?.amountInUsd
74
+ ? parseFloat(ctx.quote.amountInUsd)
75
+ : ctx.sourceToken.price * parseFloat(sourceAmount);
76
+
77
+ const targetAmountUsd = ctx.quote?.amountOutUsd
78
+ ? parseFloat(ctx.quote.amountOutUsd)
79
+ : ctx.targetToken.price * parseFloat(targetAmount);
80
+
81
+ const targetTokenUnitPrice = targetAmountUsd / parseFloat(targetAmount);
82
+
83
+ const sourceTokenUnitAmount = formatTinyNumber(
84
+ targetTokenUnitPrice / (parseFloat(sourceAmount) / sourceAmountUsd),
85
+ );
39
86
 
40
87
  return (
41
- <Card className="w-full">
42
- {!hideHeader && (
43
- <>
44
- <header className="flex justify-between">
45
- <CheckIcon />
46
- <CloseButton onClick={onDismiss} />
47
- </header>
48
- <span className="text-sw-label-lg text-sw-gray-50">
49
- {t('transfer.success.title', 'All done!')}
50
- </span>
51
- </>
88
+ <div className="flex flex-col gap-sw-2xl w-full">
89
+ <header className="flex items-center gap-sw-lg">
90
+ <div className="flex items-center justify-center p-sw-md bg-sw-status-success rounded-sw-md">
91
+ <Check size={20} className="text-sw-gray-900" />
92
+ </div>
93
+ <span className="text-sw-label-lg text-sw-status-success mr-auto">
94
+ {title}
95
+ </span>
96
+ <CloseButton onClick={handleClose} />
97
+ </header>
98
+
99
+ {!!message && (
100
+ <p className="text-sw-body-md text-sw-gray-200">{message}</p>
52
101
  )}
53
- <p className="mt-sw-sm text-sw-body-md text-sw-gray-400">
54
- {lines.map((line, idx) => (
55
- <Fragment key={idx}>
56
- {line}
57
- {idx !== lines.length - 1 && <br />}
58
- </Fragment>
59
- ))}
60
- </p>
61
- <Hr className="mt-sw-xl mb-sw-md" />
62
- <ul className="mt-sw-xl flex flex-col">
63
- <SummaryItem
64
- hasCopyAction
65
- value={txHash}
66
- externalUrl={transactionLink}
67
- label={t('transfer.success.hash.label', 'Transaction hash')}
102
+
103
+ <div className="flex flex-col">
104
+ <TokenRow
105
+ token={ctx.sourceToken}
106
+ amount={sourceAmount}
107
+ amountUsd={sourceAmountUsd}
68
108
  />
69
- {!!intent && (
70
- <SummaryItem
71
- hasCopyAction
72
- label={t('transfer.success.intent.label', 'Intent')}
73
- value={intent}
74
- />
109
+ {showTargetToken && (
110
+ <>
111
+ <div className="flex items-center justify-center w-full h-[12px] z-1">
112
+ <div className="flex items-center justify-center p-sw-md bg-sw-gray-950 rounded-sw-md w-fit">
113
+ <ArrowDownward size={18} className="text-sw-gray-200" />
114
+ </div>
115
+ </div>
116
+ <TokenRow
117
+ token={ctx.targetToken}
118
+ amount={targetAmount}
119
+ amountUsd={targetAmountUsd}
120
+ />
121
+ </>
75
122
  )}
76
- </ul>
77
- </Card>
123
+ </div>
124
+
125
+ <Accordion
126
+ expandedByDefault={false}
127
+ expandedHeightPx={summaryItemsCount * NOTES_ITEM_HEIGHT}
128
+ title={t('transfer.success.details.label', 'Transaction details')}>
129
+ <Notes>
130
+ {ctx.sourceToken.symbol !== ctx.targetToken.symbol && (
131
+ <Notes.Item
132
+ label={t('transfer.success.details.rate', 'Rate')}
133
+ value={
134
+ <span
135
+ className="text-sw-gray-50"
136
+ style={{ borderBottomWidth: '2px', borderStyle: 'dotted' }}>
137
+ {`1 ${ctx.targetToken.symbol} ≈ `} {sourceTokenUnitAmount}{' '}
138
+ {`${ctx.sourceToken.symbol}`}
139
+ <span>{` (${formatUsdAmount(targetTokenUnitPrice)})`}</span>
140
+ </span>
141
+ }
142
+ />
143
+ )}
144
+ {/* send address is missing if target token is on intents */}
145
+ {!!ctx.sendAddress && (
146
+ <Notes.Item
147
+ label={t(
148
+ 'transfer.success.details.recipient',
149
+ 'Recipient address',
150
+ )}
151
+ value={<CopyableValue value={ctx.sendAddress} />}
152
+ />
153
+ )}
154
+ {!!transferResult.intent && (
155
+ <Notes.Item
156
+ label={t('transfer.success.details.intent', 'Intent hash')}
157
+ value={<CopyableValue value={transferResult.intent} />}
158
+ />
159
+ )}
160
+ <Notes.Item
161
+ label={t('transfer.success.details.hash', 'Transaction hash')}
162
+ value={<CopyableValue value={transferResult.hash} />}
163
+ />
164
+ </Notes>
165
+ </Accordion>
166
+
167
+ <div className="flex flex-col gap-sw-lg">
168
+ <Button
169
+ fluid
170
+ as="a"
171
+ size="lg"
172
+ target="_blank"
173
+ variant="primary"
174
+ iconPosition="tail"
175
+ href={transactionLink}
176
+ icon={OpenInNew}>
177
+ {t('transfer.success.action.viewOnExplorer', 'View in explorer')}
178
+ </Button>
179
+ <Button fluid size="lg" variant="outlined" onClick={handleClose}>
180
+ {t('transfer.success.action.backToSwap', 'Back to swap')}
181
+ </Button>
182
+ </div>
183
+ </div>
78
184
  );
79
185
  };
@@ -0,0 +1,33 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { guardStates } from '@/machine/guards';
4
+ import { useUnsafeSnapshot } from '@/machine/snap';
5
+
6
+ export const useSummaryItemsCount = (hasIntentHash: boolean) => {
7
+ const { ctx } = useUnsafeSnapshot();
8
+ const isValidState = guardStates(ctx, ['transfer_success']);
9
+
10
+ const summaryItemsCount = useMemo(() => {
11
+ let count = 1;
12
+
13
+ if (!isValidState) {
14
+ return 0;
15
+ }
16
+
17
+ if (ctx.sourceToken.symbol !== ctx.targetToken.symbol) {
18
+ count += 1;
19
+ }
20
+
21
+ if (ctx.sendAddress) {
22
+ count += 1;
23
+ }
24
+
25
+ if (hasIntentHash) {
26
+ count += 1;
27
+ }
28
+
29
+ return count;
30
+ }, [hasIntentHash, ctx.sendAddress, ctx.sourceToken, ctx.targetToken]);
31
+
32
+ return summaryItemsCount;
33
+ };
@@ -60,7 +60,7 @@ export const TokensList = ({
60
60
  }: Props) => {
61
61
  const { t } = useTypedTranslation();
62
62
  const { ctx } = useUnsafeSnapshot();
63
- const { walletSupportedChains, appName } = useConfig();
63
+ const { walletSupportedChains, appName, priorityAssets = [] } = useConfig();
64
64
  const { mergedBalance } = useMergedBalance();
65
65
 
66
66
  const filteredTokens = useTokensFiltered({
@@ -69,6 +69,7 @@ export const TokensList = ({
69
69
  chainsFilter,
70
70
  selectedChain,
71
71
  walletSupportedChains,
72
+ priorityAssets,
72
73
  });
73
74
 
74
75
  const areTokensGrouped = ctx.walletAddress ? groupTokens : false;
@@ -1,9 +1,10 @@
1
- import { SearchW700 as Search } from '@material-symbols-svg/react-rounded/icons/search';
2
1
  import { useRef, useState } from 'react';
2
+ import { SearchW700 as Search } from '@material-symbols-svg/react-rounded/icons/search';
3
3
 
4
- import { TokensList } from './TokensList';
5
- import { ChainsDropdown } from './ChainsDropdown';
6
4
  import { useChains } from '../hooks';
5
+ import { TokensList } from './TokensList';
6
+ import { ChainsSelector } from './ChainsSelector';
7
+
7
8
  import { Hr } from '@/components/Hr';
8
9
  import { Card } from '@/components/Card';
9
10
  import { Input } from '@/components/Input';
@@ -96,33 +97,32 @@ export const TokensModal = ({
96
97
  <CloseButton onClick={handleClose} />
97
98
  </header>
98
99
 
99
- <div className="gap-sw-xl flex items-center">
100
- <Input
101
- focusOnMount
102
- icon={Search}
103
- ref={searchInputRef}
104
- defaultValue={search}
105
- className="w-full"
106
- placeholder="Search or paste address"
107
- onChange={(e) => setSearch(e.target.value.trim())}
100
+ <Input
101
+ focusOnMount
102
+ icon={Search}
103
+ ref={searchInputRef}
104
+ defaultValue={search}
105
+ className="w-full"
106
+ placeholder="Search or paste address"
107
+ onChange={(e) => setSearch(e.target.value.trim())}
108
+ />
109
+
110
+ {showChainsSelector && (
111
+ <ChainsSelector
112
+ variant={variant}
113
+ chainsFilter={chainsFilter}
114
+ selectedChain={selectedChain}
115
+ onMsg={(msg) => {
116
+ switch (msg.type) {
117
+ case 'on_select_chain':
118
+ setSelectedChain(msg.chain);
119
+ break;
120
+ default:
121
+ notReachable(msg.type);
122
+ }
123
+ }}
108
124
  />
109
- {showChainsSelector && (
110
- <ChainsDropdown
111
- variant={variant}
112
- selected={selectedChain}
113
- chainsFilter={chainsFilter}
114
- onMsg={(msg) => {
115
- switch (msg.type) {
116
- case 'on_click_chain':
117
- setSelectedChain(msg.chain);
118
- break;
119
- default:
120
- notReachable(msg.type, { throwError: false });
121
- }
122
- }}
123
- />
124
- )}
125
- </div>
125
+ )}
126
126
 
127
127
  {chainIsNotSupported && !!ctx.walletAddress && (
128
128
  <>
@@ -8,6 +8,7 @@ import { createFilterByIntents } from '@/utils/tokens/filterByIntents';
8
8
  import { createFilterBySearch } from '@/utils/tokens/filterBySearchString';
9
9
  import { createFilterBySelectedChain } from '@/utils/tokens/filterBySelectedChain';
10
10
  import type { Chains, ChainsFilter } from '@/types/chain';
11
+ import type { PriorityAssets } from '@/types/config';
11
12
 
12
13
  export type TokensFilterOptions = {
13
14
  variant: 'source' | 'target';
@@ -15,6 +16,7 @@ export type TokensFilterOptions = {
15
16
  selectedChain: Chains | 'all' | 'intents';
16
17
  chainsFilter: ChainsFilter;
17
18
  walletSupportedChains: ReadonlyArray<Chains>;
19
+ priorityAssets: PriorityAssets;
18
20
  };
19
21
 
20
22
  // TODO: memoize
@@ -24,6 +26,7 @@ export const useTokensFiltered = ({
24
26
  selectedChain,
25
27
  chainsFilter,
26
28
  walletSupportedChains,
29
+ priorityAssets,
27
30
  }: TokensFilterOptions) => {
28
31
  const chains = useChains(variant);
29
32
  const { tokens } = useTokens(variant);
@@ -35,6 +38,7 @@ export const useTokensFiltered = ({
35
38
  mergedBalance,
36
39
  walletSupportedChains,
37
40
  search,
41
+ priorityAssets,
38
42
  );
39
43
 
40
44
  const filteredTokens = tokens
@@ -2,15 +2,13 @@ import type { Machine } from '@/machine';
2
2
  import type { Context } from '@/machine/context';
3
3
 
4
4
  export type ResetPayload = {
5
+ keepSelectedTokens?: boolean;
5
6
  clearWalletAddress: boolean;
6
7
  };
7
8
 
8
9
  export const reset = (ctx: Context, payload: ResetPayload, m: Machine) => {
9
- ctx.sourceToken = undefined;
10
10
  ctx.sourceTokenBalance = undefined;
11
11
  ctx.sourceTokenAmount = '';
12
-
13
- ctx.targetToken = undefined;
14
12
  ctx.targetTokenAmount = '';
15
13
 
16
14
  ctx.sendAddress = undefined;
@@ -22,6 +20,11 @@ export const reset = (ctx: Context, payload: ResetPayload, m: Machine) => {
22
20
  ctx.quoteStatus = 'idle';
23
21
  ctx.transferStatus = { status: 'idle' };
24
22
 
23
+ if (!payload.keepSelectedTokens) {
24
+ ctx.sourceToken = undefined;
25
+ ctx.targetToken = undefined;
26
+ }
27
+
25
28
  if (payload.clearWalletAddress) {
26
29
  ctx.walletAddress = undefined;
27
30
  }
@@ -9,6 +9,10 @@ export type WalletAddresses = Partial<
9
9
 
10
10
  export type IntentsAccountType = 'evm' | 'near' | 'sol';
11
11
 
12
+ export type PriorityAssets =
13
+ | ReadonlyArray<string>
14
+ | ReadonlyArray<readonly [Chains, string]>;
15
+
12
16
  export type WidgetConfig = {
13
17
  // Application metadata
14
18
  appName: string;
@@ -38,6 +42,7 @@ export type WidgetConfig = {
38
42
  allowedTokensList?: string[]; // assetIDs
39
43
  allowedSourceTokensList?: string[];
40
44
  allowedTargetTokensList?: string[];
45
+ priorityAssets?: PriorityAssets;
41
46
  filterTokens: (token: Token) => boolean;
42
47
 
43
48
  // Chains filtering
@@ -45,6 +50,9 @@ export type WidgetConfig = {
45
50
  allowedChainsList?: Chains[];
46
51
  allowedSourceChainsList?: Chains[];
47
52
  allowedTargetChainsList?: Chains[];
53
+ topChainShortcuts?: (
54
+ intentsAccountType?: IntentsAccountType,
55
+ ) => [Chains, Chains, Chains, Chains];
48
56
  chainsFilter: {
49
57
  source: ChainsFilter;
50
58
  target: ChainsFilter;
@@ -1,6 +1,7 @@
1
1
  export type LocalisationKeys =
2
2
  // chain
3
3
  | 'chain.all.label'
4
+ | 'chain.more.label'
4
5
  // wallet
5
6
  | 'wallet.recipient.placeholder'
6
7
  | 'wallet.recipient.info.selectToken'
@@ -14,9 +15,16 @@ export type LocalisationKeys =
14
15
  | 'wallet.recipient.message.receiveFunds'
15
16
  | 'wallet.connected.error.notSupportedChain'
16
17
  // transfer
17
- | 'transfer.success.title'
18
- | 'transfer.success.hash.label'
19
- | 'transfer.success.intent.label'
18
+ | 'transfer.success.swap.title'
19
+ | 'transfer.success.withdrawal.title'
20
+ | 'transfer.success.deposit.title'
21
+ | 'transfer.success.details.label'
22
+ | 'transfer.success.details.hash'
23
+ | 'transfer.success.details.rate'
24
+ | 'transfer.success.details.intent'
25
+ | 'transfer.success.details.recipient'
26
+ | 'transfer.success.action.viewOnExplorer'
27
+ | 'transfer.success.action.backToSwap'
20
28
  // quote
21
29
  | 'quote.result.maxSlippage.label'
22
30
  | 'quote.result.processingTime.label'
@@ -2,8 +2,73 @@ import { formatUnits } from 'ethers';
2
2
 
3
3
  import { getTokenBalanceKey } from '../intents/getTokenBalanceKey';
4
4
  import type { Token, TokenBalances } from '@/types/token';
5
+ import type { PriorityAssets } from '@/types/config';
5
6
  import type { Chains } from '@/types/chain';
6
7
 
8
+ const compareByPriority = (
9
+ a: Token,
10
+ b: Token,
11
+ priorityAssets: PriorityAssets,
12
+ ): number | null => {
13
+ if (a.isIntent || b.isIntent || priorityAssets.length === 0) {
14
+ return null;
15
+ }
16
+
17
+ const isPriorityToken = (token: Token): boolean => {
18
+ // by assetId
19
+ if (typeof priorityAssets[0] === 'string') {
20
+ return (priorityAssets as ReadonlyArray<string>).includes(token.assetId);
21
+ }
22
+
23
+ // by blockchain and symbol
24
+ return (priorityAssets as ReadonlyArray<readonly [Chains, string]>).some(
25
+ ([blockchain, symbol]) =>
26
+ token.blockchain === blockchain &&
27
+ token.symbol.toLowerCase() === symbol.toLowerCase(),
28
+ );
29
+ };
30
+
31
+ const getPriorityIndex = (token: Token): number => {
32
+ // by assetId
33
+ if (typeof priorityAssets[0] === 'string') {
34
+ return (priorityAssets as ReadonlyArray<string>).indexOf(token.assetId);
35
+ }
36
+
37
+ // by blockchain and symbol
38
+ return (
39
+ priorityAssets as ReadonlyArray<readonly [Chains, string]>
40
+ ).findIndex(
41
+ ([blockchain, symbol]) =>
42
+ token.blockchain === blockchain &&
43
+ token.symbol.toLowerCase() === symbol.toLowerCase(),
44
+ );
45
+ };
46
+
47
+ const aIsPriority = isPriorityToken(a);
48
+ const bIsPriority = isPriorityToken(b);
49
+
50
+ if (aIsPriority && !bIsPriority) {
51
+ return -1;
52
+ }
53
+
54
+ if (!aIsPriority && bIsPriority) {
55
+ return 1;
56
+ }
57
+
58
+ // If both are priority tokens, maintain their relative order based on the priority list
59
+ if (aIsPriority && bIsPriority) {
60
+ const aIndex = getPriorityIndex(a);
61
+ const bIndex = getPriorityIndex(b);
62
+
63
+ if (aIndex !== bIndex) {
64
+ return aIndex - bIndex;
65
+ }
66
+ }
67
+
68
+ // Return null to indicate no priority difference, continue with other sorting criteria
69
+ return null;
70
+ };
71
+
7
72
  const compareWithSearch = (
8
73
  a: Token,
9
74
  b: Token,
@@ -73,8 +138,9 @@ const compareWithSearch = (
73
138
  return a.chainName.toLowerCase().localeCompare(b.chainName.toLowerCase());
74
139
  };
75
140
 
76
- const sortTokensByUsdBalance = (
141
+ const sortTokens = (
77
142
  walletSupportedChains: ReadonlyArray<Chains>,
143
+ priorityAssets: PriorityAssets,
78
144
  usdBalanceA: number | undefined,
79
145
  usdBalanceB: number | undefined,
80
146
  searchStr: string | undefined,
@@ -82,42 +148,61 @@ const sortTokensByUsdBalance = (
82
148
  b: Token,
83
149
  ): number => {
84
150
  const isIntent = a.isIntent || b.isIntent;
151
+ const hasBalanceA = usdBalanceA !== undefined && usdBalanceA > 0;
152
+ const hasBalanceB = usdBalanceB !== undefined && usdBalanceB > 0;
85
153
 
86
- // 0. Sort supported chains first
87
- const aSupported = walletSupportedChains.includes(a.blockchain);
88
- const bSupported = walletSupportedChains.includes(b.blockchain);
89
-
90
- if (!isIntent && aSupported && !bSupported) {
154
+ // 1. Tokens with balance always come first
155
+ if (hasBalanceA && !hasBalanceB) {
91
156
  return -1;
92
157
  }
93
158
 
94
- if (!isIntent && !aSupported && bSupported) {
159
+ if (!hasBalanceA && hasBalanceB) {
95
160
  return 1;
96
161
  }
97
162
 
98
- if (!isIntent && !aSupported && !bSupported) {
99
- return compareWithSearch(a, b, searchStr);
100
- }
163
+ // 2. If both have balance, sort by balance amount and other criteria
164
+ if (hasBalanceA && hasBalanceB) {
165
+ // Sort supported chains first
166
+ const aSupported = walletSupportedChains.includes(a.blockchain);
167
+ const bSupported = walletSupportedChains.includes(b.blockchain);
168
+
169
+ if (!isIntent && aSupported && !bSupported) {
170
+ return -1;
171
+ }
172
+
173
+ if (!isIntent && !aSupported && bSupported) {
174
+ return 1;
175
+ }
176
+
177
+ // Compare non-zero balances
178
+ if (usdBalanceA !== usdBalanceB) {
179
+ return (usdBalanceB ?? 0) - (usdBalanceA ?? 0);
180
+ }
101
181
 
102
- // 1. Handle zero balances
103
- if (!usdBalanceA && !usdBalanceB) {
182
+ // If balances equal, sort by search/name
104
183
  return compareWithSearch(a, b, searchStr);
105
184
  }
106
185
 
107
- if (!usdBalanceA && usdBalanceB) {
108
- return 1;
186
+ // 3. Both have no balance - prioritize by asset IDs or blockchain-symbol pairs (only for non-intent tokens)
187
+ const priorityComparison = compareByPriority(a, b, priorityAssets);
188
+
189
+ if (priorityComparison !== null) {
190
+ return priorityComparison;
109
191
  }
110
192
 
111
- if (!usdBalanceB && usdBalanceA) {
193
+ // 4. For tokens without balance and not in priority list, sort by supported chains
194
+ const aSupported = walletSupportedChains.includes(a.blockchain);
195
+ const bSupported = walletSupportedChains.includes(b.blockchain);
196
+
197
+ if (!isIntent && aSupported && !bSupported) {
112
198
  return -1;
113
199
  }
114
200
 
115
- // 3. Compare non-zero balances
116
- if (usdBalanceA !== usdBalanceB) {
117
- return (usdBalanceB ?? 0) - (usdBalanceA ?? 0);
201
+ if (!isIntent && !aSupported && bSupported) {
202
+ return 1;
118
203
  }
119
204
 
120
- // 4. If balances equal, sort by market cap and then name
205
+ // 5. Finally, sort alphabetically by search/name
121
206
  return compareWithSearch(a, b, searchStr);
122
207
  };
123
208
 
@@ -125,6 +210,7 @@ export const createTokenSorter = (
125
210
  tokensBalance: TokenBalances,
126
211
  walletSupportedChains: ReadonlyArray<Chains>,
127
212
  searchStr?: string | undefined,
213
+ priorityAssets: PriorityAssets = [],
128
214
  ) => {
129
215
  return (a: Token, b: Token) => {
130
216
  const recordA = tokensBalance[getTokenBalanceKey(a)];
@@ -145,8 +231,9 @@ export const createTokenSorter = (
145
231
 
146
232
  const searchLower = searchStr?.trim().toLowerCase() ?? undefined;
147
233
 
148
- return sortTokensByUsdBalance(
234
+ return sortTokens(
149
235
  walletSupportedChains,
236
+ priorityAssets,
150
237
  usdBalanceA,
151
238
  usdBalanceB,
152
239
  searchLower,