@aurora-is-near/intents-swap-widget 3.17.1 → 3.17.3

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 (166) hide show
  1. package/dist/{config-CQ4V08as.js → config-VpsP2eLV.js} +959 -880
  2. package/dist/config-VpsP2eLV.js.map +1 -0
  3. package/dist/config.js +2 -2
  4. package/dist/errors.js +1 -1
  5. package/dist/ext/alchemy/index.js +1 -1
  6. package/dist/ext/index.js +1 -1
  7. package/dist/features/BalanceRpcLoader/TokenBalanceLoader.js +1 -1
  8. package/dist/features/BalanceRpcLoader/index.js +1 -1
  9. package/dist/features/BalanceRpcLoader/useTokenBalanceRpc.js +1 -1
  10. package/dist/features/ChainsDropdown/index.js +1 -1
  11. package/dist/features/DepositMethodSwitcher.js +1 -1
  12. package/dist/features/ErrorBoundary.js +1 -1
  13. package/dist/features/ExternalDeposit.js +2 -2
  14. package/dist/features/SendAddress/index.js +1 -1
  15. package/dist/features/SendAddress/useNotification.js +1 -1
  16. package/dist/features/SubmitButton/index.js +1 -1
  17. package/dist/features/SuccessScreen/index.js +1 -1
  18. package/dist/features/SwapDirectionSwitcher.js +1 -1
  19. package/dist/features/SwapQuote/SwapQuote.js +1 -1
  20. package/dist/features/SwapQuote/index.js +1 -1
  21. package/dist/features/TokenInput/TokenInput.js +1 -1
  22. package/dist/features/TokenInput/TokenInputEmpty.js +1 -1
  23. package/dist/features/TokenInput/TokenInputSource.js +1 -1
  24. package/dist/features/TokenInput/TokenInputTarget.js +1 -1
  25. package/dist/features/TokenInput/WalletBalance.js +1 -1
  26. package/dist/features/TokenInput/hooks/index.js +1 -1
  27. package/dist/features/TokenInput/hooks/useTokenInputBalance.js +1 -1
  28. package/dist/features/TokenInput/index.js +1 -1
  29. package/dist/features/TokensList/TokenItem.d.ts +2 -2
  30. package/dist/features/TokensList/TokenItem.js +1 -2
  31. package/dist/features/TokensList/TokensList.js +10 -2
  32. package/dist/features/TokensList/TokensList.js.map +1 -1
  33. package/dist/features/TokensList/constants.d.ts +4 -0
  34. package/dist/features/TokensList/constants.js +8 -0
  35. package/dist/features/TokensList/constants.js.map +1 -0
  36. package/dist/features/TokensList/hooks/index.d.ts +1 -0
  37. package/dist/features/TokensList/hooks/index.js +5 -0
  38. package/dist/features/TokensList/hooks/index.js.map +1 -0
  39. package/dist/features/TokensList/hooks/useFocusOnList.d.ts +9 -0
  40. package/dist/features/TokensList/hooks/useFocusOnList.js +33 -0
  41. package/dist/features/TokensList/hooks/useFocusOnList.js.map +1 -0
  42. package/dist/features/TokensList/index.d.ts +2 -1
  43. package/dist/features/TokensList/index.js +1 -1
  44. package/dist/features/TokensList/types.d.ts +14 -0
  45. package/dist/features/TokensList/types.js +2 -0
  46. package/dist/features/TokensList/types.js.map +1 -0
  47. package/dist/features/TokensList/utils/getFirstGroupItemTotalIndex.d.ts +9 -0
  48. package/dist/features/TokensList/utils/getFirstGroupItemTotalIndex.js +8 -0
  49. package/dist/features/TokensList/utils/getFirstGroupItemTotalIndex.js.map +1 -0
  50. package/dist/features/TokensList/utils/getGroupHeadersTotalIndexes.d.ts +8 -0
  51. package/dist/features/TokensList/utils/getGroupHeadersTotalIndexes.js +5 -0
  52. package/dist/features/TokensList/utils/getGroupHeadersTotalIndexes.js.map +1 -0
  53. package/dist/features/TokensList/utils/getListItemsTotalCount.d.ts +8 -0
  54. package/dist/features/TokensList/utils/getListItemsTotalCount.js +8 -0
  55. package/dist/features/TokensList/utils/getListItemsTotalCount.js.map +1 -0
  56. package/dist/features/TokensList/utils/getListState.d.ts +9 -0
  57. package/dist/features/TokensList/utils/getListState.js +5 -0
  58. package/dist/features/TokensList/utils/getListState.js.map +1 -0
  59. package/dist/features/TokensList/utils/getListTotalHeight.d.ts +8 -0
  60. package/dist/features/TokensList/utils/getListTotalHeight.js +12 -0
  61. package/dist/features/TokensList/utils/getListTotalHeight.js.map +1 -0
  62. package/dist/features/TokensList/utils/getTokenByTotalIndex.d.ts +9 -0
  63. package/dist/features/TokensList/utils/getTokenByTotalIndex.js +7 -0
  64. package/dist/features/TokensList/utils/getTokenByTotalIndex.js.map +1 -0
  65. package/dist/features/TokensList/utils/index.d.ts +6 -0
  66. package/dist/features/TokensList/utils/index.js +15 -0
  67. package/dist/features/TokensList/utils/index.js.map +1 -0
  68. package/dist/features/TokensModal.js +2 -2
  69. package/dist/features/WalletCompatibilityCheck/WalletCompatibilityModal.js +2 -2
  70. package/dist/features/WalletCompatibilityCheck/index.js +1 -1
  71. package/dist/features/index.js +1 -1
  72. package/dist/hooks/index.js +1 -1
  73. package/dist/hooks/useAllTokens.js +1 -1
  74. package/dist/hooks/useChains.js +1 -1
  75. package/dist/hooks/useCompatibilityCheck.js +1 -1
  76. package/dist/hooks/useDefaultToken.js +1 -1
  77. package/dist/hooks/useExternalDepositStatus/index.js +1 -1
  78. package/dist/hooks/useExternalDepositStatus/usePoaExternalDepositStatus.js +1 -1
  79. package/dist/hooks/useIntentsBalance.js +1 -1
  80. package/dist/hooks/useIsCompatibilityCheckRequired.js +1 -1
  81. package/dist/hooks/useMakeDepositAddress.js +1 -1
  82. package/dist/hooks/useMakeIntentsTransfer.js +4 -3
  83. package/dist/hooks/useMakeIntentsTransfer.js.map +1 -1
  84. package/dist/hooks/useMakeNEARFtTransferCall.js +4 -2
  85. package/dist/hooks/useMakeNEARFtTransferCall.js.map +1 -1
  86. package/dist/hooks/useMakeQuote.js +1 -1
  87. package/dist/hooks/useMakeQuoteTransfer.js +3 -2
  88. package/dist/hooks/useMakeQuoteTransfer.js.map +1 -1
  89. package/dist/hooks/useMakeSolanaTransfer.js +1 -1
  90. package/dist/hooks/useMakeTransfer.js +1 -1
  91. package/dist/hooks/useMergedBalance.js +1 -1
  92. package/dist/hooks/useSwitchChain.js +1 -1
  93. package/dist/hooks/useTheme.js +1 -1
  94. package/dist/hooks/useTokenInputPair.js +1 -1
  95. package/dist/hooks/useTokens.js +1 -1
  96. package/dist/hooks/useTokensFiltered.js +1 -1
  97. package/dist/hooks/useTokensIntentsUnique.js +1 -1
  98. package/dist/{index-Ce9bzNHX.js → index-JGjxyZXI.js} +70 -70
  99. package/dist/index-JGjxyZXI.js.map +1 -0
  100. package/dist/index.browser.esm-COWShN0a.js.map +1 -1
  101. package/dist/index.js +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/tokenSelect.js +1 -1
  112. package/dist/machine/events/validateInputAndMoveTo.js +1 -1
  113. package/dist/machine/events/validateInputs.js +1 -1
  114. package/dist/machine/index.js +1 -1
  115. package/dist/machine/snap.js +1 -1
  116. package/dist/machine/subscriptions/checkers/isSendAddressAsConnected.js +1 -1
  117. package/dist/machine/subscriptions/index.js +1 -1
  118. package/dist/styles.css +1 -1
  119. package/dist/theme/ThemeProvider.js +1 -1
  120. package/dist/utils/checkers/isUserDeniedSigning.d.ts +2 -1
  121. package/dist/utils/checkers/isUserDeniedSigning.js +6 -4
  122. package/dist/utils/checkers/isUserDeniedSigning.js.map +1 -1
  123. package/dist/utils/index.d.ts +1 -0
  124. package/dist/utils/index.js +26 -24
  125. package/dist/utils/index.js.map +1 -1
  126. package/dist/utils/intents/signers/near.d.ts +1 -1
  127. package/dist/utils/intents/signers/near.js +2 -2
  128. package/dist/utils/intents/signers/privy.d.ts +1 -1
  129. package/dist/utils/intents/signers/privy.js +1 -1
  130. package/dist/utils/intents/signers/solana.d.ts +1 -1
  131. package/dist/utils/intents/signers/solana.js.map +1 -1
  132. package/dist/utils/isErrorLikeObject.d.ts +4 -0
  133. package/dist/utils/isErrorLikeObject.js +5 -0
  134. package/dist/utils/isErrorLikeObject.js.map +1 -0
  135. package/dist/utils/near/getNearNep141StorageBalance.js +1 -1
  136. package/dist/widgets/WidgetDeposit/WidgetDepositContent.js +2 -2
  137. package/dist/widgets/WidgetDeposit/WidgetDepositSkeleton.js +1 -1
  138. package/dist/widgets/WidgetSwap/WidgetSwapContent.js +2 -2
  139. package/dist/widgets/WidgetSwap/WidgetSwapSkeleton.js +1 -1
  140. package/dist/widgets/WidgetWithdraw/WidgetWithdrawContent.js +2 -2
  141. package/dist/widgets/WidgetWithdraw/WidgetWithdrawSkeleton.js +1 -1
  142. package/package.json +2 -2
  143. package/src/features/TokensList/TokenItem.tsx +7 -3
  144. package/src/features/TokensList/TokensList.tsx +151 -57
  145. package/src/features/TokensList/constants.ts +4 -0
  146. package/src/features/TokensList/hooks/index.ts +1 -0
  147. package/src/features/TokensList/hooks/useFocusOnList.ts +56 -0
  148. package/src/features/TokensList/types.ts +28 -0
  149. package/src/features/TokensList/utils/getFirstGroupItemTotalIndex.ts +28 -0
  150. package/src/features/TokensList/utils/getGroupHeadersTotalIndexes.ts +21 -0
  151. package/src/features/TokensList/utils/getListItemsTotalCount.ts +14 -0
  152. package/src/features/TokensList/utils/getListState.ts +16 -0
  153. package/src/features/TokensList/utils/getListTotalHeight.ts +25 -0
  154. package/src/features/TokensList/utils/getTokenByTotalIndex.ts +19 -0
  155. package/src/features/TokensList/utils/index.ts +6 -0
  156. package/src/hooks/useMakeIntentsTransfer.ts +40 -29
  157. package/src/hooks/useMakeNEARFtTransferCall.ts +10 -0
  158. package/src/hooks/useMakeQuoteTransfer.ts +2 -1
  159. package/src/utils/checkers/isUserDeniedSigning.ts +11 -3
  160. package/src/utils/index.ts +1 -0
  161. package/src/utils/intents/signers/near.ts +1 -1
  162. package/src/utils/intents/signers/privy.ts +1 -1
  163. package/src/utils/intents/signers/solana.ts +1 -1
  164. package/src/utils/isErrorLikeObject.ts +11 -0
  165. package/dist/config-CQ4V08as.js.map +0 -1
  166. package/dist/index-Ce9bzNHX.js.map +0 -1
@@ -6,8 +6,6 @@ import { TinyNumber } from '@/components/TinyNumber';
6
6
  import { getUsdDisplayBalance } from '@/utils/formatters/getUsdDisplayBalance';
7
7
  import type { Token, TokenBalance } from '@/types/token';
8
8
 
9
- export const TOKEN_ITEM_HEIGHT = 58;
10
-
11
9
  type Msg = { type: 'on_select_token'; token: Token };
12
10
 
13
11
  type Props = {
@@ -15,6 +13,7 @@ type Props = {
15
13
  balance: TokenBalance;
16
14
  showBalance?: boolean;
17
15
  isNotSelectable?: boolean;
16
+ isFocused?: boolean;
18
17
  className?: string;
19
18
  onMsg: (msg: Msg) => void;
20
19
  };
@@ -24,6 +23,7 @@ export const TokenItem = ({
24
23
  balance,
25
24
  showBalance = true,
26
25
  isNotSelectable,
26
+ isFocused,
27
27
  className,
28
28
  onMsg,
29
29
  }: Props) => {
@@ -43,6 +43,7 @@ export const TokenItem = ({
43
43
  {
44
44
  'cursor-not-allowed': isNotSelectable,
45
45
  'cursor-pointer hover:bg-sw-gray-800': !isNotSelectable,
46
+ 'bg-sw-gray-800': isFocused && !isNotSelectable,
46
47
  },
47
48
  className,
48
49
  )}
@@ -52,7 +53,10 @@ export const TokenItem = ({
52
53
  <TokenIcon
53
54
  token={token}
54
55
  chainShowIcon={!token.isIntent}
55
- className="border-sw-gray-900 group-hover:border-sw-gray-800 transition-colors"
56
+ className={cn('group-hover:border-sw-gray-800 transition-colors', {
57
+ 'border-sw-gray-800': isFocused,
58
+ 'border-sw-gray-900': !isFocused,
59
+ })}
56
60
  />
57
61
 
58
62
  <div className="gap-sw-xs mr-auto flex flex-col">
@@ -1,8 +1,24 @@
1
- import { VList } from 'virtua';
2
- import { Fragment, useMemo } from 'react';
1
+ import { VList, VListHandle } from 'virtua';
2
+ import { useCallback, useMemo, useRef, useState } from 'react';
3
3
 
4
- import { TOKEN_ITEM_HEIGHT, TokenItem } from './TokenItem';
4
+ import { TokenItem } from './TokenItem';
5
+ import { useFocusOnList } from './hooks';
5
6
  import { TokensListPlaceholder } from './TokensListPlaceholder';
7
+ import {
8
+ LIST_CONTAINER_ID,
9
+ MAX_LIST_VIEW_AREA_HEIGHT,
10
+ TOKEN_ITEM_HEIGHT,
11
+ } from './constants';
12
+ import {
13
+ getFirstGroupItemTotalIndex,
14
+ getGroupHeadersTotalIndexes,
15
+ getListItemsTotalCount,
16
+ getListState,
17
+ getListTotalHeight,
18
+ getTokenByTotalIndex,
19
+ } from './utils';
20
+ import type { ListGroup } from './types';
21
+
6
22
  import { cn } from '@/utils/cn';
7
23
  import { Hr } from '@/components/Hr';
8
24
  import { useUnsafeSnapshot } from '@/machine/snap';
@@ -31,18 +47,6 @@ type Props = {
31
47
  onMsg: (msg: Msg) => void;
32
48
  };
33
49
 
34
- const useListState = (tokens: ReadonlyArray<Token>, search: string) => {
35
- if (tokens.length === 0 && search) {
36
- return 'EMPTY_SEARCH';
37
- }
38
-
39
- if (tokens.length === 0 && !search) {
40
- return 'NO_TOKENS';
41
- }
42
-
43
- return 'HAS_TOKENS';
44
- };
45
-
46
50
  export const TokensList = ({
47
51
  variant,
48
52
  search,
@@ -68,37 +72,52 @@ export const TokensList = ({
68
72
  });
69
73
 
70
74
  const areTokensGrouped = ctx.walletAddress ? groupTokens : false;
71
- const tokensListState = useListState(filteredTokens.all, search);
75
+ const tokensListState = getListState(filteredTokens.all, search);
72
76
 
73
- const tokensUngrouped = useMemo(
74
- () => [{ label: null, tokens: filteredTokens.all }],
77
+ const ref = useRef<VListHandle>(null);
78
+ const [focusedIndex, setFocusedIndex] = useState(-1);
79
+
80
+ const tokensUngrouped = useMemo<ListGroup<1>>(
81
+ () => [{ tokens: filteredTokens.all }],
75
82
  [filteredTokens.all],
76
83
  );
77
84
 
78
- const tokensBySection = useMemo(
79
- () => [
80
- { label: appName, tokens: filteredTokens.intents },
81
- {
82
- label: chainIsNotSupported ? null : 'Connected wallet',
83
- tokens: filteredTokens.wallet,
84
- },
85
- ],
86
- [filteredTokens.wallet, filteredTokens.intents, chainIsNotSupported],
87
- );
85
+ const tokensBySection = useMemo(() => {
86
+ return [
87
+ ...(filteredTokens.intents.length > 0
88
+ ? [
89
+ { label: appName, count: filteredTokens.intents.length },
90
+ { tokens: filteredTokens.intents },
91
+ ]
92
+ : []),
93
+ ...(filteredTokens.wallet.length > 0
94
+ ? [
95
+ {
96
+ label: chainIsNotSupported ? null : 'Connected wallet',
97
+ count: filteredTokens.wallet.length,
98
+ },
99
+ { tokens: filteredTokens.wallet },
100
+ ]
101
+ : []),
102
+ ].filter(Boolean) as ListGroup<0 | 2 | 4>;
103
+ }, [filteredTokens.wallet, filteredTokens.intents, chainIsNotSupported]);
88
104
 
89
- const tokensCount = useMemo(() => {
90
- return (areTokensGrouped ? tokensBySection : tokensUngrouped).reduce(
91
- (acc, group) => acc + group.tokens.length,
92
- 0,
93
- );
94
- }, [tokensBySection, tokensUngrouped, areTokensGrouped]);
105
+ const tokensList = areTokensGrouped ? tokensBySection : tokensUngrouped;
95
106
 
96
- // <offset> - user defined offset e.g. page header height + space
97
- // 152px - height of TokenModal elements like search and paddings
98
- // 48px - minimal offset from the bottom screen edge
99
- // Total: 152 + 48 = 200px
100
- // const maxHeight = `calc(100vh - (${topScreenOffset ?? '0px'} + ${offset ?? '0px'} + 200px))`;
101
- const maxHeight = '450px';
107
+ const listHeight = getListTotalHeight(tokensList);
108
+ const totalItems = getListItemsTotalCount(tokensList);
109
+ const headerIndexes = getGroupHeadersTotalIndexes(tokensList);
110
+
111
+ const handleBlur = useCallback(() => {
112
+ setFocusedIndex(-1);
113
+ }, []);
114
+
115
+ useFocusOnList({
116
+ listRef: ref.current,
117
+ initialFocusedIndex: areTokensGrouped ? 1 : 0,
118
+ onFocus: (index) => setFocusedIndex(index),
119
+ onBlur: handleBlur,
120
+ });
102
121
 
103
122
  switch (tokensListState) {
104
123
  case 'EMPTY_SEARCH':
@@ -121,26 +140,98 @@ export const TokensList = ({
121
140
  return (
122
141
  <div className={cn('gap-sw-lg flex flex-col', className)}>
123
142
  <VList
143
+ ref={ref}
144
+ tabIndex={0}
145
+ id={LIST_CONTAINER_ID}
146
+ itemSize={TOKEN_ITEM_HEIGHT}
124
147
  className="hide-scrollbar"
125
148
  style={{
126
- maxHeight,
127
149
  minHeight: 200,
128
- height: tokensCount
129
- ? tokensCount * TOKEN_ITEM_HEIGHT +
130
- (areTokensGrouped ? tokensBySection.length * 62 : 0)
131
- : TOKEN_ITEM_HEIGHT * 2,
150
+ height: listHeight,
151
+ maxHeight: MAX_LIST_VIEW_AREA_HEIGHT,
152
+ overflowAnchor: 'none',
153
+ outline: 'none',
154
+ }}
155
+ onKeyDown={(e) => {
156
+ if (!ref.current) {
157
+ return;
158
+ }
159
+
160
+ switch (e.code) {
161
+ case 'ArrowUp': {
162
+ e.preventDefault();
163
+ let prevIndex = Math.max(focusedIndex - 1, 0);
164
+
165
+ if (areTokensGrouped && headerIndexes.includes(prevIndex)) {
166
+ prevIndex = prevIndex === 0 ? 1 : prevIndex - 1;
167
+ }
168
+
169
+ setFocusedIndex(prevIndex);
170
+ ref.current?.scrollToIndex(prevIndex, {
171
+ align: 'center',
172
+ smooth: true,
173
+ });
174
+
175
+ break;
176
+ }
177
+
178
+ case 'ArrowDown': {
179
+ e.preventDefault();
180
+ let nextIndex = Math.min(focusedIndex + 1, totalItems - 1);
181
+
182
+ if (areTokensGrouped && headerIndexes.includes(nextIndex)) {
183
+ nextIndex = nextIndex === 0 ? 1 : nextIndex + 1;
184
+ }
185
+
186
+ setFocusedIndex(nextIndex);
187
+ ref.current?.scrollToIndex(nextIndex, {
188
+ align: 'center',
189
+ smooth: true,
190
+ });
191
+
192
+ break;
193
+ }
194
+
195
+ case 'Enter': {
196
+ e.preventDefault();
197
+ const token = getTokenByTotalIndex(tokensList, focusedIndex);
198
+
199
+ if (token) {
200
+ onMsg({ type: 'on_select_token', token });
201
+ }
202
+
203
+ break;
204
+ }
205
+
206
+ default:
207
+ break;
208
+ }
132
209
  }}>
133
- {(areTokensGrouped ? tokensBySection : tokensUngrouped).map(
134
- ({ label, tokens: tokensToDisplay }) => (
135
- <Fragment key={label ?? 'ungrouped-tokens'}>
136
- {tokensToDisplay.length && label ? (
137
- <header className="pb-sw-lg flex flex-col">
210
+ {tokensList.map(
211
+ ({ label, count, tokens: tokensToDisplay }, groupIndex) => {
212
+ const firstItemInGroupIndex = getFirstGroupItemTotalIndex(
213
+ tokensList,
214
+ groupIndex,
215
+ );
216
+
217
+ if (label !== undefined) {
218
+ if (!label) {
219
+ // must be rendered for calculations even if label is null
220
+ return <header key={groupIndex} />;
221
+ }
222
+
223
+ return (
224
+ <header
225
+ key={label}
226
+ className="pb-sw-lg pt-sw-sm flex flex-col">
138
227
  <Hr />
139
- <span className="text-sw-label-sm pt-sw-xl text-sw-gray-100">{`${label} — ${tokensToDisplay.length}`}</span>
228
+ <span className="text-sw-label-sm pt-sw-xl text-sw-gray-100">{`${label} — ${count}`}</span>
140
229
  </header>
141
- ) : null}
230
+ );
231
+ }
142
232
 
143
- {tokensToDisplay.map((token) => {
233
+ if (tokensToDisplay) {
234
+ return tokensToDisplay.map((token, tokenIndex) => {
144
235
  const tokenBalanceKey = getTokenBalanceKey(token);
145
236
 
146
237
  return (
@@ -149,17 +240,20 @@ export const TokensList = ({
149
240
  key={tokenBalanceKey}
150
241
  showBalance={showBalances}
151
242
  balance={mergedBalance[tokenBalanceKey]}
243
+ isFocused={
244
+ focusedIndex === firstItemInGroupIndex + tokenIndex
245
+ }
152
246
  isNotSelectable={
153
247
  chainIsNotSupported && !!ctx.walletAddress
154
248
  }
155
249
  onMsg={onMsg}
156
250
  />
157
251
  );
158
- })}
252
+ });
253
+ }
159
254
 
160
- {tokensToDisplay.length ? <div className="h-sw-2xl" /> : null}
161
- </Fragment>
162
- ),
255
+ return null;
256
+ },
163
257
  )}
164
258
  </VList>
165
259
  </div>
@@ -0,0 +1,4 @@
1
+ export const MAX_LIST_VIEW_AREA_HEIGHT = '450px';
2
+ export const LIST_CONTAINER_ID = 'virtual-tokens-list';
3
+ export const TOKEN_ITEM_HEIGHT = 58;
4
+ export const LIST_SECTION_HEADER_HEIGHT = 62;
@@ -0,0 +1 @@
1
+ export { useFocusOnList } from './useFocusOnList';
@@ -0,0 +1,56 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ import type { VListHandle } from 'virtua';
3
+
4
+ import { LIST_CONTAINER_ID } from '../constants';
5
+
6
+ import { useHandleKeyDown } from '@/hooks/useHandleKeyDown';
7
+
8
+ type Args = {
9
+ initialFocusedIndex: number;
10
+ listRef: VListHandle | null;
11
+ onFocus: (index: number) => void;
12
+ onBlur: () => void;
13
+ };
14
+
15
+ export const useFocusOnList = ({
16
+ initialFocusedIndex,
17
+ listRef,
18
+ onFocus,
19
+ onBlur,
20
+ }: Args) => {
21
+ useHandleKeyDown('ArrowDown', () => {
22
+ const virtualListDiv = document.getElementById(LIST_CONTAINER_ID);
23
+
24
+ if (virtualListDiv && document.activeElement !== virtualListDiv) {
25
+ onFocus(initialFocusedIndex);
26
+ listRef?.scrollToIndex(initialFocusedIndex, { align: 'nearest' });
27
+
28
+ // required to prevent initial scroll on focus
29
+ // which causes list's jump
30
+ setTimeout(() => {
31
+ virtualListDiv.focus({ preventScroll: true });
32
+ }, 0);
33
+ }
34
+ });
35
+
36
+ const handleBlur = useCallback(
37
+ (event: FocusEvent) => {
38
+ const virtualListDiv = document.getElementById(LIST_CONTAINER_ID);
39
+
40
+ if (event.target === virtualListDiv) {
41
+ onBlur();
42
+ }
43
+ },
44
+ [onBlur],
45
+ );
46
+
47
+ useEffect(() => {
48
+ const virtualListDiv = document.getElementById(LIST_CONTAINER_ID);
49
+
50
+ virtualListDiv?.addEventListener('blur', handleBlur);
51
+
52
+ return () => {
53
+ virtualListDiv?.removeEventListener('blur', handleBlur);
54
+ };
55
+ }, [handleBlur]);
56
+ };
@@ -0,0 +1,28 @@
1
+ import type { Token } from '@/types/token';
2
+
3
+ type OddGroup = {
4
+ label: string | null;
5
+ count: number;
6
+ tokens?: never;
7
+ };
8
+
9
+ type EvenGroup = {
10
+ label?: never;
11
+ count?: never;
12
+ tokens: Token[];
13
+ };
14
+
15
+ // covers 3 sections max (extend if needed)
16
+ export type ListGroup<N extends number = 6> = N extends 0
17
+ ? []
18
+ : N extends 1
19
+ ? [EvenGroup]
20
+ : N extends 2
21
+ ? [OddGroup, EvenGroup]
22
+ : N extends 4
23
+ ? [OddGroup, EvenGroup, OddGroup, EvenGroup]
24
+ : N extends 6
25
+ ? [OddGroup, EvenGroup, OddGroup, EvenGroup, OddGroup, EvenGroup]
26
+ : never;
27
+
28
+ export type AnyListGroup = ListGroup<0 | 1 | 2 | 4 | 6>;
@@ -0,0 +1,28 @@
1
+ import type { AnyListGroup } from '../types';
2
+
3
+ /**
4
+ * Get index across all groups of the first item in a given group
5
+ *
6
+ * @param tokensList - The list of tokens (grouped or ungrouped)
7
+ * @param groupIndex - The index of the group
8
+ * @returns The total index of the first item in the group
9
+ */
10
+ export const getFirstGroupItemTotalIndex = (
11
+ tokensList: AnyListGroup,
12
+ groupIndex: number,
13
+ ) => {
14
+ const tokensGroupIndex = groupIndex % 2 !== 0 ? groupIndex : 0;
15
+
16
+ if (tokensGroupIndex <= 1) {
17
+ return tokensGroupIndex;
18
+ }
19
+
20
+ return (
21
+ tokensList.reduce((acc, group, index) => {
22
+ return (
23
+ acc +
24
+ (group.tokens && index < tokensGroupIndex ? group.tokens.length + 1 : 0)
25
+ );
26
+ }, 0) + 1
27
+ ); // +1 for current group's header
28
+ };
@@ -0,0 +1,21 @@
1
+ import type { AnyListGroup } from '../types';
2
+
3
+ /**
4
+ * Get positions of all group headers in the combined list
5
+ *
6
+ * @param tokensList - The list of tokens (grouped or ungrouped)
7
+ * @returns List of positions of all group headers in the combined list
8
+ */
9
+ export const getGroupHeadersTotalIndexes = (tokensList: AnyListGroup) => {
10
+ return tokensList.reduce<number[]>((acc, group, index) => {
11
+ if (index === 0 && group.label) {
12
+ return [0];
13
+ }
14
+
15
+ if (group.tokens && index < tokensList.length - 1) {
16
+ return [...acc, (acc[acc.length - 1] ?? 0) + group.tokens.length + 1];
17
+ }
18
+
19
+ return acc;
20
+ }, []);
21
+ };
@@ -0,0 +1,14 @@
1
+ import type { AnyListGroup } from '../types';
2
+
3
+ /**
4
+ * Get the total number of RENDERED items in the list (including group headers)
5
+ *
6
+ * @param tokensList - The list of tokens (grouped or ungrouped)
7
+ * @returns The total number of items in the list
8
+ */
9
+ export const getListItemsTotalCount = (tokensList: AnyListGroup) => {
10
+ return tokensList.reduce(
11
+ (acc, group) => acc + (group.tokens?.length ?? 0) + (group.label ? 1 : 0),
12
+ 0,
13
+ );
14
+ };
@@ -0,0 +1,16 @@
1
+ import type { Token } from '@/types/token';
2
+
3
+ /**
4
+ * Get the state of the list
5
+ *
6
+ * @param tokens - The list of tokens (grouped or ungrouped)
7
+ * @param search - The search string
8
+ * @returns The state of the list: 'EMPTY_SEARCH', 'NO_TOKENS', 'HAS_TOKENS'
9
+ */
10
+ export const getListState = (tokens: ReadonlyArray<Token>, search: string) => {
11
+ if (tokens.length) {
12
+ return 'HAS_TOKENS';
13
+ }
14
+
15
+ return !search ? 'NO_TOKENS' : 'EMPTY_SEARCH';
16
+ };
@@ -0,0 +1,25 @@
1
+ import { LIST_SECTION_HEADER_HEIGHT, TOKEN_ITEM_HEIGHT } from '../constants';
2
+ import type { AnyListGroup } from '../types';
3
+
4
+ const getTokensTotalCount = (tokensList: AnyListGroup) => {
5
+ return tokensList.reduce(
6
+ (acc, group) => acc + (group.tokens?.length ?? 0),
7
+ 0,
8
+ );
9
+ };
10
+
11
+ /**
12
+ * Get the total height of the list
13
+ *
14
+ * @param tokensList - The list of tokens (grouped or ungrouped)
15
+ * @returns The total height of the list
16
+ */
17
+ export const getListTotalHeight = (tokensList: AnyListGroup) => {
18
+ const tokensCount = getTokensTotalCount(tokensList);
19
+
20
+ return tokensCount
21
+ ? tokensCount * TOKEN_ITEM_HEIGHT +
22
+ tokensList.filter((group) => !!group.label).length *
23
+ LIST_SECTION_HEADER_HEIGHT
24
+ : TOKEN_ITEM_HEIGHT * 2;
25
+ };
@@ -0,0 +1,19 @@
1
+ import type { AnyListGroup } from '../types';
2
+
3
+ /**
4
+ * Get the token by total index across all groups and headers
5
+ *
6
+ * @param tokensList - The list of tokens (grouped or ungrouped)
7
+ * @param totalIndex - The total index of the token
8
+ * @returns The token
9
+ */
10
+ export const getTokenByTotalIndex = (
11
+ tokensList: AnyListGroup,
12
+ totalIndex: number,
13
+ ) => {
14
+ const flatList = tokensList.flatMap((group) =>
15
+ 'label' in group ? [null] : group.tokens,
16
+ );
17
+
18
+ return flatList[totalIndex];
19
+ };
@@ -0,0 +1,6 @@
1
+ export { getGroupHeadersTotalIndexes } from './getGroupHeadersTotalIndexes';
2
+ export { getFirstGroupItemTotalIndex } from './getFirstGroupItemTotalIndex';
3
+ export { getListItemsTotalCount } from './getListItemsTotalCount';
4
+ export { getTokenByTotalIndex } from './getTokenByTotalIndex';
5
+ export { getListTotalHeight } from './getListTotalHeight';
6
+ export { getListState } from './getListState';
@@ -1,12 +1,12 @@
1
1
  import {
2
- BridgeSDK,
3
2
  createIntentSignerNEP413,
4
3
  createInternalTransferRoute,
5
4
  createNearWithdrawalRoute,
6
5
  FeeExceedsAmountError,
6
+ IntentsSDK,
7
7
  MinWithdrawalAmountError,
8
8
  type RouteConfig,
9
- } from '@defuse-protocol/bridge-sdk';
9
+ } from '@defuse-protocol/intents-sdk';
10
10
  import type { NearWalletBase as NearWallet } from '@hot-labs/near-connect/build/types/wallet';
11
11
  import { snakeCase } from 'change-case';
12
12
  import { generateRandomBytes } from '../utils/near/getRandomBytes';
@@ -18,6 +18,7 @@ import { TransferError } from '@/errors';
18
18
  import { INTENTS_CONTRACT } from '@/constants';
19
19
  import { CHAIN_IDS_MAP } from '@/constants/chains';
20
20
  import { notReachable } from '@/utils/notReachable';
21
+ import { isErrorLikeObject } from '@/utils/isErrorLikeObject';
21
22
  import { localStorageTyped } from '@/utils/localstorage';
22
23
  import { queryContract } from '@/utils/near/queryContract';
23
24
  import { IntentSignerPrivy } from '@/utils/intents/signers/privy';
@@ -245,7 +246,7 @@ export const useMakeIntentsTransfer = ({ providers }: IntentsTransferArgs) => {
245
246
  notReachable(intentsAccountType);
246
247
  }
247
248
 
248
- const sdk = new BridgeSDK({ referral: snakeCase(appName) });
249
+ const sdk = new IntentsSDK({ referral: snakeCase(appName) });
249
250
 
250
251
  sdk.setIntentSigner(signer);
251
252
 
@@ -261,35 +262,48 @@ export const useMakeIntentsTransfer = ({ providers }: IntentsTransferArgs) => {
261
262
  routeConfig = createInternalTransferRoute();
262
263
  }
263
264
 
264
- const withdrawal = sdk.createWithdrawal({
265
- withdrawalParams: {
266
- assetId: ctx.sourceToken.assetId,
267
- amount: BigInt(ctx.sourceTokenAmount),
268
- destinationAddress: getDestinationAddress(
269
- ctx,
270
- isDirectNearTokenWithdrawal || isDirectNonNearWithdrawal,
271
- ),
272
- destinationMemo: undefined,
273
- feeInclusive: false,
274
- routeConfig,
275
- },
276
- });
265
+ const withdrawalParams = {
266
+ assetId: ctx.sourceToken.assetId,
267
+ amount: BigInt(ctx.sourceTokenAmount),
268
+ destinationAddress: getDestinationAddress(
269
+ ctx,
270
+ isDirectNearTokenWithdrawal || isDirectNonNearWithdrawal,
271
+ ),
272
+ destinationMemo: undefined,
273
+ feeInclusive: false,
274
+ routeConfig,
275
+ };
276
+
277
+ onPending('WAITING_CONFIRMATION');
277
278
 
278
279
  try {
279
- await withdrawal.estimateFee();
280
+ const feeEstimation = await sdk.estimateWithdrawalFee({
281
+ withdrawalParams,
282
+ });
280
283
 
281
- onPending('WAITING_CONFIRMATION');
282
- const txIntent = await withdrawal.signAndSendIntent();
284
+ const { intentHash } = await sdk.signAndSendWithdrawalIntent({
285
+ withdrawalParams,
286
+ feeEstimation,
287
+ });
283
288
 
284
289
  onPending('PROCESSING');
285
- const tx = await withdrawal.waitForIntentSettlement();
290
+ const intentTx = await sdk.waitForIntentSettlement({ intentHash });
286
291
 
287
- await withdrawal.waitForWithdrawalCompletion();
292
+ const completionResult = await sdk.waitForWithdrawalCompletion({
293
+ withdrawalParams,
294
+ intentTx,
295
+ });
288
296
 
289
297
  return {
290
- hash: tx.hash,
291
- transactionLink: getTransactionLink(CHAIN_IDS_MAP.near, tx.hash),
292
- intent: txIntent,
298
+ intent: intentTx.hash,
299
+ // no hash means completion not trackable for this bridge
300
+ hash: completionResult.hash ?? '',
301
+ transactionLink: completionResult.hash
302
+ ? getTransactionLink(
303
+ CHAIN_IDS_MAP[ctx.targetToken.blockchain],
304
+ completionResult.hash,
305
+ )
306
+ : '',
293
307
  };
294
308
  } catch (e: unknown) {
295
309
  logger.error('[TRANSFER ERROR]', e);
@@ -313,7 +327,7 @@ export const useMakeIntentsTransfer = ({ providers }: IntentsTransferArgs) => {
313
327
  });
314
328
  }
315
329
 
316
- if (e instanceof Error) {
330
+ if (isErrorLikeObject(e)) {
317
331
  if (e.message.includes('Fee is not estimated')) {
318
332
  throw new TransferError({
319
333
  code: 'FEES_NOT_ESTIMATED',
@@ -321,10 +335,7 @@ export const useMakeIntentsTransfer = ({ providers }: IntentsTransferArgs) => {
321
335
  }
322
336
 
323
337
  // User rejected
324
- if (
325
- isUserDeniedSigning(e.message) ||
326
- isUserDeniedSigning(`${e.cause}`)
327
- ) {
338
+ if (isUserDeniedSigning(e)) {
328
339
  return undefined;
329
340
  }
330
341
  }