@naculus/connect-appkit-react 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +144 -0
- package/dist/client.d.ts +144 -0
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2270 -0
- package/dist/index.d.ts +2270 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +30 -12
- package/api-extractor.json +0 -21
- package/src/chain-abstraction/index.ts +0 -8
- package/src/chain-abstraction/useCompareCosts.ts +0 -137
- package/src/chain-abstraction/useExecuteRoute.ts +0 -129
- package/src/chain-abstraction/useRouteQuote.ts +0 -168
- package/src/client.test.ts +0 -242
- package/src/client.ts +0 -265
- package/src/hooks/useAccount.test.ts +0 -67
- package/src/hooks/useAccount.ts +0 -37
- package/src/hooks/useBalance.test.ts +0 -459
- package/src/hooks/useBalance.ts +0 -170
- package/src/hooks/useChain.test.ts +0 -160
- package/src/hooks/useChain.ts +0 -53
- package/src/hooks/useConnect.test.ts +0 -112
- package/src/hooks/useConnect.ts +0 -29
- package/src/hooks/useDisconnect.test.ts +0 -75
- package/src/hooks/useDisconnect.ts +0 -32
- package/src/hooks/useERC20Allowance.ts +0 -93
- package/src/hooks/useERC20Approve.ts +0 -220
- package/src/hooks/useERC20Transfer.ts +0 -134
- package/src/hooks/useERC20TransferSimulation.ts +0 -147
- package/src/hooks/useEmbeddedWallet.test.ts +0 -266
- package/src/hooks/useEmbeddedWallet.ts +0 -181
- package/src/hooks/useLastTx.test.ts +0 -117
- package/src/hooks/useLastTx.ts +0 -73
- package/src/hooks/useLookupAddress.ts +0 -131
- package/src/hooks/useNotification.ts +0 -308
- package/src/hooks/useResolveName.ts +0 -128
- package/src/hooks/useSIWxLogin.test.ts +0 -307
- package/src/hooks/useSIWxLogin.ts +0 -163
- package/src/hooks/useSIWxSession.test.ts +0 -481
- package/src/hooks/useSIWxSession.ts +0 -290
- package/src/hooks/useSendCalls.ts +0 -103
- package/src/hooks/useSendTransaction.test.ts +0 -175
- package/src/hooks/useSendTransaction.ts +0 -80
- package/src/hooks/useSendUserOperation.ts +0 -313
- package/src/hooks/useSession.ts +0 -96
- package/src/hooks/useSessionKeys.ts +0 -356
- package/src/hooks/useSignInWithEthereum.test.ts +0 -224
- package/src/hooks/useSignInWithEthereum.ts +0 -46
- package/src/hooks/useSignInWithX.test.ts +0 -210
- package/src/hooks/useSignInWithX.ts +0 -141
- package/src/hooks/useSignMessage.test.ts +0 -151
- package/src/hooks/useSignMessage.ts +0 -65
- package/src/hooks/useSimulateTransfer.ts +0 -165
- package/src/hooks/useSiwxAuthSession.test.ts +0 -456
- package/src/hooks/useSiwxAuthSession.ts +0 -163
- package/src/hooks/useSmartAccount.ts +0 -194
- package/src/hooks/useSwitchChain.ts +0 -68
- package/src/hooks/useTokenBalance.test.ts +0 -337
- package/src/hooks/useTokenBalance.ts +0 -210
- package/src/hooks/useTransactionSimulation.ts +0 -401
- package/src/hooks/useTxHistory.test.ts +0 -108
- package/src/hooks/useTxHistory.ts +0 -83
- package/src/hooks/useTxMonitor.test.ts +0 -127
- package/src/hooks/useTxMonitor.ts +0 -178
- package/src/hooks/useUserOpStatus.ts +0 -245
- package/src/hooks/useValidateDestination.test.ts +0 -96
- package/src/hooks/useValidateDestination.ts +0 -72
- package/src/hooks/useViemClient.test.ts +0 -155
- package/src/hooks/useViemClient.ts +0 -65
- package/src/hooks/useWallet.test.ts +0 -133
- package/src/hooks/useWallet.ts +0 -39
- package/src/hooks/useWeb3ErrorHandler.ts +0 -102
- package/src/index.ts +0 -186
- package/src/provider/Web3ConnectProvider.test.tsx +0 -178
- package/src/provider/Web3ConnectProvider.tsx +0 -530
- package/src/token-list/TokenSelector.tsx +0 -351
- package/src/token-list/defaults.ts +0 -39
- package/src/token-list/index.ts +0 -14
- package/src/token-list/useTokenList.ts +0 -119
- package/src/token-list/useTokenSearch.ts +0 -99
- package/src/types/index.ts +0 -99
- package/src/utils/chains.ts +0 -115
- package/src/utils/errorMessages.ts +0 -142
- package/src/utils/i18n.ts +0 -90
- package/src/wc-generated/components.ts +0 -377
- package/test/setup.ts +0 -17
- package/test/test-utils.tsx +0 -78
- package/tsup.config.ts +0 -18
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TokenSelector — chain-aware, searchable token selection component.
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - Loads token list for the given chain
|
|
6
|
-
* - Search by symbol, name, or address
|
|
7
|
-
* - Displays token logo, symbol, name, and decimals
|
|
8
|
-
* - Calls onSelect when a token is chosen
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import React, { useCallback } from "react";
|
|
12
|
-
import type { TokenListEntry } from "@naculus/connect-core";
|
|
13
|
-
import { useTokenList } from "./useTokenList";
|
|
14
|
-
import { useTokenSearch } from "./useTokenSearch";
|
|
15
|
-
|
|
16
|
-
export interface TokenSelectorProps {
|
|
17
|
-
/** Chain ID in CAIP-2 format (e.g. "eip155:1") */
|
|
18
|
-
chainId: string;
|
|
19
|
-
/** Called when a token is selected */
|
|
20
|
-
onSelect: (token: TokenListEntry) => void;
|
|
21
|
-
/** Called to close/dismiss the selector */
|
|
22
|
-
onClose: () => void;
|
|
23
|
-
/** Optional configuration */
|
|
24
|
-
options?: {
|
|
25
|
-
/** Show popular/recommended tokens (default: true) */
|
|
26
|
-
showRecommendations?: boolean;
|
|
27
|
-
/** Custom filter for tokens */
|
|
28
|
-
filter?: (token: TokenListEntry) => boolean;
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* TokenSelector — a dropdown/modal component for selecting tokens.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* <TokenSelector
|
|
37
|
-
* chainId="eip155:1"
|
|
38
|
-
* onSelect={(token) => setSelectedToken(token)}
|
|
39
|
-
* onClose={() => setOpen(false)}
|
|
40
|
-
* />
|
|
41
|
-
*/
|
|
42
|
-
export function TokenSelector({
|
|
43
|
-
chainId,
|
|
44
|
-
onSelect,
|
|
45
|
-
onClose,
|
|
46
|
-
options,
|
|
47
|
-
}: TokenSelectorProps) {
|
|
48
|
-
const [searchQuery, setSearchQuery] = React.useState("");
|
|
49
|
-
|
|
50
|
-
const {
|
|
51
|
-
tokens: allTokens,
|
|
52
|
-
isLoading,
|
|
53
|
-
error,
|
|
54
|
-
} = useTokenList(chainId);
|
|
55
|
-
|
|
56
|
-
const { results, isSearching } = useTokenSearch(
|
|
57
|
-
searchQuery,
|
|
58
|
-
searchQuery ? chainId : undefined,
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const showRecommendations = options?.showRecommendations ?? true;
|
|
62
|
-
|
|
63
|
-
// Display tokens: when searching, show results; otherwise show the full list
|
|
64
|
-
const displayTokens = React.useMemo(() => {
|
|
65
|
-
if (searchQuery.trim()) {
|
|
66
|
-
const combined = [...results.exact, ...results.fuzzy].map((m) => m.token);
|
|
67
|
-
return options?.filter
|
|
68
|
-
? combined.filter(options.filter)
|
|
69
|
-
: combined;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const tokens = allTokens;
|
|
73
|
-
return options?.filter
|
|
74
|
-
? tokens.filter(options.filter)
|
|
75
|
-
: tokens;
|
|
76
|
-
}, [searchQuery, results, allTokens, options?.filter]);
|
|
77
|
-
|
|
78
|
-
// Popular tokens for recommendation section
|
|
79
|
-
const popularTokens = React.useMemo(() => {
|
|
80
|
-
if (!showRecommendations || searchQuery.trim()) return [];
|
|
81
|
-
return allTokens.slice(0, 6); // Top 6
|
|
82
|
-
}, [allTokens, showRecommendations, searchQuery]);
|
|
83
|
-
|
|
84
|
-
const handleSelect = useCallback(
|
|
85
|
-
(token: TokenListEntry) => {
|
|
86
|
-
onSelect(token);
|
|
87
|
-
onClose();
|
|
88
|
-
},
|
|
89
|
-
[onSelect, onClose],
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
return (
|
|
93
|
-
<div
|
|
94
|
-
style={{
|
|
95
|
-
background: "hsl(var(--background))",
|
|
96
|
-
borderRadius: "12px",
|
|
97
|
-
padding: "16px",
|
|
98
|
-
color: "hsl(var(--foreground))",
|
|
99
|
-
minWidth: "320px",
|
|
100
|
-
maxWidth: "400px",
|
|
101
|
-
fontFamily: "system-ui, sans-serif",
|
|
102
|
-
}}
|
|
103
|
-
>
|
|
104
|
-
{/* Header */}
|
|
105
|
-
<div
|
|
106
|
-
style={{
|
|
107
|
-
display: "flex",
|
|
108
|
-
justifyContent: "space-between",
|
|
109
|
-
alignItems: "center",
|
|
110
|
-
marginBottom: "12px",
|
|
111
|
-
}}
|
|
112
|
-
>
|
|
113
|
-
<h3 style={{ margin: 0, fontSize: "16px", fontWeight: 600 }}>
|
|
114
|
-
Select Token
|
|
115
|
-
</h3>
|
|
116
|
-
<button
|
|
117
|
-
onClick={onClose}
|
|
118
|
-
style={{
|
|
119
|
-
background: "none",
|
|
120
|
-
border: "none",
|
|
121
|
-
color: "hsl(var(--muted-foreground))",
|
|
122
|
-
cursor: "pointer",
|
|
123
|
-
fontSize: "18px",
|
|
124
|
-
padding: "4px 8px",
|
|
125
|
-
}}
|
|
126
|
-
>
|
|
127
|
-
✕
|
|
128
|
-
</button>
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
{/* Search Input */}
|
|
132
|
-
<input
|
|
133
|
-
type="text"
|
|
134
|
-
placeholder="Search token name or paste address"
|
|
135
|
-
value={searchQuery}
|
|
136
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
137
|
-
style={{
|
|
138
|
-
width: "100%",
|
|
139
|
-
padding: "10px 12px",
|
|
140
|
-
borderRadius: "8px",
|
|
141
|
-
border: "1px solid #333",
|
|
142
|
-
background: "hsl(var(--card))",
|
|
143
|
-
color: "hsl(var(--foreground))",
|
|
144
|
-
fontSize: "14px",
|
|
145
|
-
outline: "none",
|
|
146
|
-
boxSizing: "border-box",
|
|
147
|
-
marginBottom: "12px",
|
|
148
|
-
}}
|
|
149
|
-
autoFocus
|
|
150
|
-
/>
|
|
151
|
-
|
|
152
|
-
{/* Loading / Error States */}
|
|
153
|
-
{isLoading && !isSearching && (
|
|
154
|
-
<div style={{ textAlign: "center", padding: "20px", color: "hsl(var(--muted-foreground))" }}>
|
|
155
|
-
Loading tokens...
|
|
156
|
-
</div>
|
|
157
|
-
)}
|
|
158
|
-
{error && (
|
|
159
|
-
<div
|
|
160
|
-
style={{
|
|
161
|
-
textAlign: "center",
|
|
162
|
-
padding: "12px",
|
|
163
|
-
color: "hsl(var(--destructive))",
|
|
164
|
-
fontSize: "13px",
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
Error: {error.message}
|
|
168
|
-
</div>
|
|
169
|
-
)}
|
|
170
|
-
|
|
171
|
-
{/* Popular / Recommended Section */}
|
|
172
|
-
{showRecommendations && popularTokens.length > 0 && !searchQuery.trim() && (
|
|
173
|
-
<div style={{ marginBottom: "12px" }}>
|
|
174
|
-
<div
|
|
175
|
-
style={{
|
|
176
|
-
fontSize: "11px",
|
|
177
|
-
color: "hsl(var(--muted-foreground))",
|
|
178
|
-
textTransform: "uppercase",
|
|
179
|
-
letterSpacing: "0.5px",
|
|
180
|
-
marginBottom: "8px",
|
|
181
|
-
}}
|
|
182
|
-
>
|
|
183
|
-
Popular
|
|
184
|
-
</div>
|
|
185
|
-
<div style={{ display: "flex", gap: "6px", flexWrap: "wrap" }}>
|
|
186
|
-
{popularTokens.map((token) => (
|
|
187
|
-
<button
|
|
188
|
-
key={`${token.chainId}:${token.address}`}
|
|
189
|
-
onClick={() => handleSelect(token)}
|
|
190
|
-
style={{
|
|
191
|
-
background: "hsl(var(--card))",
|
|
192
|
-
border: "1px solid #333",
|
|
193
|
-
borderRadius: "20px",
|
|
194
|
-
padding: "6px 14px",
|
|
195
|
-
color: "hsl(var(--foreground))",
|
|
196
|
-
cursor: "pointer",
|
|
197
|
-
fontSize: "13px",
|
|
198
|
-
display: "flex",
|
|
199
|
-
alignItems: "center",
|
|
200
|
-
gap: "4px",
|
|
201
|
-
}}
|
|
202
|
-
>
|
|
203
|
-
{token.logoURI ? (
|
|
204
|
-
<img
|
|
205
|
-
src={token.logoURI}
|
|
206
|
-
alt=""
|
|
207
|
-
style={{ width: "16px", height: "16px", borderRadius: "50%" }}
|
|
208
|
-
onError={(e) => {
|
|
209
|
-
(e.target as HTMLImageElement).style.display = "none";
|
|
210
|
-
}}
|
|
211
|
-
/>
|
|
212
|
-
) : null}
|
|
213
|
-
{token.symbol}
|
|
214
|
-
</button>
|
|
215
|
-
))}
|
|
216
|
-
</div>
|
|
217
|
-
</div>
|
|
218
|
-
)}
|
|
219
|
-
|
|
220
|
-
{/* Search Result Count */}
|
|
221
|
-
{searchQuery.trim() && !isLoading && (
|
|
222
|
-
<div
|
|
223
|
-
style={{
|
|
224
|
-
fontSize: "11px",
|
|
225
|
-
color: "hsl(var(--muted-foreground))",
|
|
226
|
-
marginBottom: "8px",
|
|
227
|
-
}}
|
|
228
|
-
>
|
|
229
|
-
{displayTokens.length > 0 ? (
|
|
230
|
-
<>
|
|
231
|
-
{results.exact.length} exact, {results.fuzzy.length} fuzzy
|
|
232
|
-
{results.hasMore ? " (more available)" : ""}
|
|
233
|
-
</>
|
|
234
|
-
) : (
|
|
235
|
-
"No results found"
|
|
236
|
-
)}
|
|
237
|
-
</div>
|
|
238
|
-
)}
|
|
239
|
-
|
|
240
|
-
{/* Token List */}
|
|
241
|
-
<div
|
|
242
|
-
style={{
|
|
243
|
-
maxHeight: "320px",
|
|
244
|
-
overflowY: "auto",
|
|
245
|
-
display: "flex",
|
|
246
|
-
flexDirection: "column",
|
|
247
|
-
gap: "2px",
|
|
248
|
-
}}
|
|
249
|
-
>
|
|
250
|
-
{displayTokens.map((token) => (
|
|
251
|
-
<button
|
|
252
|
-
key={`${token.chainId}:${token.address}`}
|
|
253
|
-
onClick={() => handleSelect(token)}
|
|
254
|
-
style={{
|
|
255
|
-
display: "flex",
|
|
256
|
-
alignItems: "center",
|
|
257
|
-
gap: "10px",
|
|
258
|
-
padding: "10px 8px",
|
|
259
|
-
background: "transparent",
|
|
260
|
-
border: "none",
|
|
261
|
-
borderRadius: "8px",
|
|
262
|
-
color: "hsl(var(--foreground))",
|
|
263
|
-
cursor: "pointer",
|
|
264
|
-
textAlign: "left",
|
|
265
|
-
width: "100%",
|
|
266
|
-
fontSize: "14px",
|
|
267
|
-
}}
|
|
268
|
-
onMouseEnter={(e) => {
|
|
269
|
-
(e.target as HTMLElement).style.background = "hsl(var(--card))";
|
|
270
|
-
}}
|
|
271
|
-
onMouseLeave={(e) => {
|
|
272
|
-
(e.target as HTMLElement).style.background = "transparent";
|
|
273
|
-
}}
|
|
274
|
-
>
|
|
275
|
-
{/* Logo */}
|
|
276
|
-
<div
|
|
277
|
-
style={{
|
|
278
|
-
width: "32px",
|
|
279
|
-
height: "32px",
|
|
280
|
-
borderRadius: "50%",
|
|
281
|
-
background: "hsl(var(--border))",
|
|
282
|
-
display: "flex",
|
|
283
|
-
alignItems: "center",
|
|
284
|
-
justifyContent: "center",
|
|
285
|
-
fontSize: "12px",
|
|
286
|
-
fontWeight: 600,
|
|
287
|
-
flexShrink: 0,
|
|
288
|
-
overflow: "hidden",
|
|
289
|
-
}}
|
|
290
|
-
>
|
|
291
|
-
{token.logoURI ? (
|
|
292
|
-
<img
|
|
293
|
-
src={token.logoURI}
|
|
294
|
-
alt={token.symbol}
|
|
295
|
-
style={{ width: "100%", height: "100%" }}
|
|
296
|
-
onError={(e) => {
|
|
297
|
-
const img = e.target as HTMLImageElement;
|
|
298
|
-
img.style.display = "none";
|
|
299
|
-
(img.parentElement as HTMLElement).textContent =
|
|
300
|
-
token.symbol.slice(0, 2);
|
|
301
|
-
}}
|
|
302
|
-
/>
|
|
303
|
-
) : (
|
|
304
|
-
token.symbol.slice(0, 2)
|
|
305
|
-
)}
|
|
306
|
-
</div>
|
|
307
|
-
|
|
308
|
-
{/* Token info */}
|
|
309
|
-
<div style={{ flex: 1, minWidth: 0 }}>
|
|
310
|
-
<div
|
|
311
|
-
style={{
|
|
312
|
-
fontWeight: 600,
|
|
313
|
-
fontSize: "14px",
|
|
314
|
-
overflow: "hidden",
|
|
315
|
-
textOverflow: "ellipsis",
|
|
316
|
-
whiteSpace: "nowrap",
|
|
317
|
-
}}
|
|
318
|
-
>
|
|
319
|
-
{token.symbol}
|
|
320
|
-
</div>
|
|
321
|
-
<div
|
|
322
|
-
style={{
|
|
323
|
-
fontSize: "12px",
|
|
324
|
-
color: "hsl(var(--muted-foreground))",
|
|
325
|
-
overflow: "hidden",
|
|
326
|
-
textOverflow: "ellipsis",
|
|
327
|
-
whiteSpace: "nowrap",
|
|
328
|
-
}}
|
|
329
|
-
>
|
|
330
|
-
{token.name}
|
|
331
|
-
</div>
|
|
332
|
-
</div>
|
|
333
|
-
|
|
334
|
-
{/* Decimals & Chain Info */}
|
|
335
|
-
<div
|
|
336
|
-
style={{
|
|
337
|
-
fontSize: "11px",
|
|
338
|
-
color: "#666",
|
|
339
|
-
textAlign: "right",
|
|
340
|
-
flexShrink: 0,
|
|
341
|
-
}}
|
|
342
|
-
>
|
|
343
|
-
<div>{token.decimals} decimals</div>
|
|
344
|
-
<div>Chain {token.chainId}</div>
|
|
345
|
-
</div>
|
|
346
|
-
</button>
|
|
347
|
-
))}
|
|
348
|
-
</div>
|
|
349
|
-
</div>
|
|
350
|
-
);
|
|
351
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default token list sources for React hooks.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { TokenListSource } from "@naculus/connect-core";
|
|
6
|
-
import {
|
|
7
|
-
DEFAULT_SOURCES,
|
|
8
|
-
ETHEREUM_MAINNET_TOKENS,
|
|
9
|
-
POLYGON_TOKENS,
|
|
10
|
-
OPTIMISM_TOKENS,
|
|
11
|
-
ARBITRUM_TOKENS,
|
|
12
|
-
BASE_TOKENS,
|
|
13
|
-
} from "@naculus/connect-core";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Get default sources for useTokenList / useTokenSearch hooks.
|
|
17
|
-
* Includes built-in tokens and remote source URLs.
|
|
18
|
-
*/
|
|
19
|
-
export function getDefaultSources(): TokenListSource[] {
|
|
20
|
-
return [
|
|
21
|
-
{
|
|
22
|
-
name: "built-in",
|
|
23
|
-
tokens: [
|
|
24
|
-
...ETHEREUM_MAINNET_TOKENS,
|
|
25
|
-
...POLYGON_TOKENS,
|
|
26
|
-
...OPTIMISM_TOKENS,
|
|
27
|
-
...ARBITRUM_TOKENS,
|
|
28
|
-
...BASE_TOKENS,
|
|
29
|
-
],
|
|
30
|
-
enabled: true,
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: "uniswap-default",
|
|
34
|
-
url: "https://tokens.uniswap.org",
|
|
35
|
-
enabled: true,
|
|
36
|
-
refreshInterval: 24 * 60 * 60 * 1000,
|
|
37
|
-
},
|
|
38
|
-
];
|
|
39
|
-
}
|
package/src/token-list/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token Discovery — React hooks and components.
|
|
3
|
-
*
|
|
4
|
-
* @see docs/features/token-discovery.md
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export { useTokenList } from "./useTokenList";
|
|
8
|
-
export type { UseTokenListOptions, UseTokenListReturn } from "./useTokenList";
|
|
9
|
-
|
|
10
|
-
export { useTokenSearch } from "./useTokenSearch";
|
|
11
|
-
export type { UseTokenSearchReturn } from "./useTokenSearch";
|
|
12
|
-
|
|
13
|
-
export { TokenSelector } from "./TokenSelector";
|
|
14
|
-
export type { TokenSelectorProps } from "./TokenSelector";
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useTokenList — React hook for loading token lists.
|
|
3
|
-
*
|
|
4
|
-
* Manages TokenListManager lifecycle and provides reactive
|
|
5
|
-
* token data scoped to a specific chain.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useState, useEffect, useCallback, useRef } from "react";
|
|
9
|
-
import { TokenListManager } from "@naculus/connect-core";
|
|
10
|
-
import type { TokenListEntry, TokenListSource, TokenListManagerConfig } from "@naculus/connect-core";
|
|
11
|
-
|
|
12
|
-
export interface UseTokenListOptions {
|
|
13
|
-
/** Auto-load on mount (default: true) */
|
|
14
|
-
autoLoad?: boolean;
|
|
15
|
-
/** Custom sources (overrides defaults) */
|
|
16
|
-
sources?: TokenListSource[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface UseTokenListReturn {
|
|
20
|
-
/** Loaded tokens for the specified chain */
|
|
21
|
-
tokens: TokenListEntry[];
|
|
22
|
-
/** Whether the initial load is in progress */
|
|
23
|
-
isLoading: boolean;
|
|
24
|
-
/** Whether tokens have been loaded at least once */
|
|
25
|
-
isLoaded: boolean;
|
|
26
|
-
/** Last error, if any */
|
|
27
|
-
error: Error | null;
|
|
28
|
-
/** Force a full refresh from all sources */
|
|
29
|
-
refetch: () => Promise<void>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* React hook for accessing token lists for a specific chain.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* const { tokens, isLoading } = useTokenList("eip155:1");
|
|
37
|
-
* // tokens → [WETH, USDC, USDT, DAI, ...]
|
|
38
|
-
*/
|
|
39
|
-
export function useTokenList(
|
|
40
|
-
chainId?: string,
|
|
41
|
-
options?: UseTokenListOptions,
|
|
42
|
-
): UseTokenListReturn {
|
|
43
|
-
const managerRef = useRef<TokenListManager | null>(null);
|
|
44
|
-
const [tokens, setTokens] = useState<TokenListEntry[]>([]);
|
|
45
|
-
const [isLoading, setIsLoading] = useState(options?.autoLoad !== false);
|
|
46
|
-
const [isLoaded, setIsLoaded] = useState(false);
|
|
47
|
-
const [error, setError] = useState<Error | null>(null);
|
|
48
|
-
|
|
49
|
-
// Initialize manager once
|
|
50
|
-
if (!managerRef.current) {
|
|
51
|
-
const config: Partial<TokenListManagerConfig> = {};
|
|
52
|
-
if (options?.sources) {
|
|
53
|
-
config.sources = options.sources;
|
|
54
|
-
}
|
|
55
|
-
managerRef.current = new TokenListManager(config);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const load = useCallback(async () => {
|
|
59
|
-
const manager = managerRef.current;
|
|
60
|
-
if (!manager) return;
|
|
61
|
-
|
|
62
|
-
setIsLoading(true);
|
|
63
|
-
setError(null);
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
await manager.load();
|
|
67
|
-
const numericChainId = chainId ? parseChainId(chainId) : undefined;
|
|
68
|
-
const loaded = manager.getTokens(
|
|
69
|
-
numericChainId !== undefined ? { chainId: numericChainId } : undefined,
|
|
70
|
-
);
|
|
71
|
-
setTokens(loaded);
|
|
72
|
-
setIsLoaded(true);
|
|
73
|
-
} catch (err) {
|
|
74
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
75
|
-
} finally {
|
|
76
|
-
setIsLoading(false);
|
|
77
|
-
}
|
|
78
|
-
}, [chainId]);
|
|
79
|
-
|
|
80
|
-
const refetch = useCallback(async () => {
|
|
81
|
-
const manager = managerRef.current;
|
|
82
|
-
if (!manager) return;
|
|
83
|
-
|
|
84
|
-
setIsLoading(true);
|
|
85
|
-
setError(null);
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
await manager.refresh();
|
|
89
|
-
const numericChainId = chainId ? parseChainId(chainId) : undefined;
|
|
90
|
-
const loaded = manager.getTokens(
|
|
91
|
-
numericChainId !== undefined ? { chainId: numericChainId } : undefined,
|
|
92
|
-
);
|
|
93
|
-
setTokens(loaded);
|
|
94
|
-
setIsLoaded(true);
|
|
95
|
-
} catch (err) {
|
|
96
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
97
|
-
} finally {
|
|
98
|
-
setIsLoading(false);
|
|
99
|
-
}
|
|
100
|
-
}, [chainId]);
|
|
101
|
-
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (options?.autoLoad !== false) {
|
|
104
|
-
load();
|
|
105
|
-
}
|
|
106
|
-
}, [load, options?.autoLoad]);
|
|
107
|
-
|
|
108
|
-
return { tokens, isLoading, isLoaded, error, refetch };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Parse a CAIP-2 chain ID like "eip155:1" into a numeric chain ID (1).
|
|
113
|
-
* Returns undefined for non-numeric chains.
|
|
114
|
-
*/
|
|
115
|
-
function parseChainId(chainId: string): number | undefined {
|
|
116
|
-
const parts = chainId.split(":");
|
|
117
|
-
const numeric = parseInt(parts[parts.length - 1], 10);
|
|
118
|
-
return isNaN(numeric) ? undefined : numeric;
|
|
119
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useTokenSearch — React hook for searching tokens.
|
|
3
|
-
*
|
|
4
|
-
* Debounced search over loaded token lists.
|
|
5
|
-
* Returns structured results (exact + fuzzy matches).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useState, useEffect, useRef, useMemo } from "react";
|
|
9
|
-
import { TokenListManager } from "@naculus/connect-core";
|
|
10
|
-
import type { TokenSearchResult, TokenListSource, TokenListManagerConfig } from "@naculus/connect-core";
|
|
11
|
-
import { getDefaultSources } from "./defaults";
|
|
12
|
-
|
|
13
|
-
const DEBOUNCE_MS = 300;
|
|
14
|
-
|
|
15
|
-
export interface UseTokenSearchReturn {
|
|
16
|
-
/** Structured search results */
|
|
17
|
-
results: TokenSearchResult;
|
|
18
|
-
/** Whether a search is currently running */
|
|
19
|
-
isSearching: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Shared manager instance across hooks
|
|
23
|
-
let sharedManager: TokenListManager | null = null;
|
|
24
|
-
|
|
25
|
-
function getManager(): TokenListManager {
|
|
26
|
-
if (!sharedManager) {
|
|
27
|
-
sharedManager = new TokenListManager({
|
|
28
|
-
sources: getDefaultSources(),
|
|
29
|
-
});
|
|
30
|
-
// Start loading in background
|
|
31
|
-
sharedManager.load().catch(() => {});
|
|
32
|
-
}
|
|
33
|
-
return sharedManager;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* React hook for searching tokens by symbol, name, or address.
|
|
38
|
-
*
|
|
39
|
-
* Debounced 300ms to avoid excessive re-renders during typing.
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* const { results, isSearching } = useTokenSearch("USDC");
|
|
43
|
-
* // results.exact → [USDC on ETH]
|
|
44
|
-
* // results.fuzzy → [USDC.e on AVAX]
|
|
45
|
-
*/
|
|
46
|
-
export function useTokenSearch(
|
|
47
|
-
query: string,
|
|
48
|
-
chainId?: string,
|
|
49
|
-
): UseTokenSearchReturn {
|
|
50
|
-
const [results, setResults] = useState<TokenSearchResult>({
|
|
51
|
-
exact: [],
|
|
52
|
-
fuzzy: [],
|
|
53
|
-
hasMore: false,
|
|
54
|
-
});
|
|
55
|
-
const [isSearching, setIsSearching] = useState(false);
|
|
56
|
-
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
57
|
-
|
|
58
|
-
const trimmedQuery = query.trim();
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (!trimmedQuery) {
|
|
62
|
-
setResults({ exact: [], fuzzy: [], hasMore: false });
|
|
63
|
-
setIsSearching(false);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Debounce
|
|
68
|
-
if (debounceRef.current) {
|
|
69
|
-
clearTimeout(debounceRef.current);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
setIsSearching(true);
|
|
73
|
-
|
|
74
|
-
debounceRef.current = setTimeout(() => {
|
|
75
|
-
const manager = getManager();
|
|
76
|
-
const numericChainId = chainId ? parseChainId(chainId) : undefined;
|
|
77
|
-
const searchResults = manager.search(trimmedQuery, {
|
|
78
|
-
chainId: numericChainId,
|
|
79
|
-
limit: 20,
|
|
80
|
-
});
|
|
81
|
-
setResults(searchResults);
|
|
82
|
-
setIsSearching(false);
|
|
83
|
-
}, DEBOUNCE_MS);
|
|
84
|
-
|
|
85
|
-
return () => {
|
|
86
|
-
if (debounceRef.current) {
|
|
87
|
-
clearTimeout(debounceRef.current);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
}, [trimmedQuery, chainId]);
|
|
91
|
-
|
|
92
|
-
return { results, isSearching };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function parseChainId(chainId: string): number | undefined {
|
|
96
|
-
const parts = chainId.split(":");
|
|
97
|
-
const numeric = parseInt(parts[parts.length - 1], 10);
|
|
98
|
-
return isNaN(numeric) ? undefined : numeric;
|
|
99
|
-
}
|