@cfxdevkit/defi-react 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,684 @@
1
+ // src/contracts.ts
2
+ var AUTOMATION_MANAGER_ADDRESS = process.env.NEXT_PUBLIC_AUTOMATION_MANAGER_ADDRESS ?? "0x0000000000000000000000000000000000000000";
3
+ var ERC20_ABI = [
4
+ {
5
+ type: "function",
6
+ name: "allowance",
7
+ stateMutability: "view",
8
+ inputs: [
9
+ { name: "owner", type: "address" },
10
+ { name: "spender", type: "address" }
11
+ ],
12
+ outputs: [{ name: "", type: "uint256" }]
13
+ },
14
+ {
15
+ type: "function",
16
+ name: "approve",
17
+ stateMutability: "nonpayable",
18
+ inputs: [
19
+ { name: "spender", type: "address" },
20
+ { name: "amount", type: "uint256" }
21
+ ],
22
+ outputs: [{ name: "", type: "bool" }]
23
+ }
24
+ ];
25
+ var WCFX_ABI = [
26
+ {
27
+ type: "function",
28
+ name: "balanceOf",
29
+ stateMutability: "view",
30
+ inputs: [{ name: "account", type: "address" }],
31
+ outputs: [{ name: "", type: "uint256" }]
32
+ },
33
+ {
34
+ type: "function",
35
+ name: "allowance",
36
+ stateMutability: "view",
37
+ inputs: [
38
+ { name: "owner", type: "address" },
39
+ { name: "spender", type: "address" }
40
+ ],
41
+ outputs: [{ name: "", type: "uint256" }]
42
+ },
43
+ {
44
+ type: "function",
45
+ name: "approve",
46
+ stateMutability: "nonpayable",
47
+ inputs: [
48
+ { name: "spender", type: "address" },
49
+ { name: "amount", type: "uint256" }
50
+ ],
51
+ outputs: [{ name: "", type: "bool" }]
52
+ },
53
+ {
54
+ type: "function",
55
+ name: "deposit",
56
+ stateMutability: "payable",
57
+ inputs: [],
58
+ outputs: []
59
+ },
60
+ {
61
+ type: "function",
62
+ name: "withdraw",
63
+ stateMutability: "nonpayable",
64
+ inputs: [{ name: "wad", type: "uint256" }],
65
+ outputs: []
66
+ }
67
+ ];
68
+ var MAX_UINT256 = 2n ** 256n - 1n;
69
+ var AUTOMATION_MANAGER_ABI = [
70
+ // ─── Custom Errors ─────────────────────────────────────────────────────────
71
+ {
72
+ type: "error",
73
+ name: "DCACompleted",
74
+ inputs: [{ name: "jobId", type: "bytes32" }]
75
+ },
76
+ {
77
+ type: "error",
78
+ name: "DCAIntervalNotReached",
79
+ inputs: [{ name: "nextExecution", type: "uint256" }]
80
+ },
81
+ { type: "error", name: "EnforcedPause", inputs: [] },
82
+ { type: "error", name: "ExpectedPause", inputs: [] },
83
+ {
84
+ type: "error",
85
+ name: "InvalidParams",
86
+ inputs: [{ name: "reason", type: "string" }]
87
+ },
88
+ {
89
+ type: "error",
90
+ name: "JobExpiredError",
91
+ inputs: [{ name: "jobId", type: "bytes32" }]
92
+ },
93
+ {
94
+ type: "error",
95
+ name: "JobNotActive",
96
+ inputs: [{ name: "jobId", type: "bytes32" }]
97
+ },
98
+ {
99
+ type: "error",
100
+ name: "JobNotFound",
101
+ inputs: [{ name: "jobId", type: "bytes32" }]
102
+ },
103
+ {
104
+ type: "error",
105
+ name: "OwnableInvalidOwner",
106
+ inputs: [{ name: "owner", type: "address" }]
107
+ },
108
+ {
109
+ type: "error",
110
+ name: "OwnableUnauthorizedAccount",
111
+ inputs: [{ name: "account", type: "address" }]
112
+ },
113
+ {
114
+ type: "error",
115
+ name: "PriceConditionNotMet",
116
+ inputs: [{ name: "jobId", type: "bytes32" }]
117
+ },
118
+ { type: "error", name: "ReentrancyGuardReentrantCall", inputs: [] },
119
+ {
120
+ type: "error",
121
+ name: "SafeERC20FailedOperation",
122
+ inputs: [{ name: "token", type: "address" }]
123
+ },
124
+ {
125
+ type: "error",
126
+ name: "SlippageTooHigh",
127
+ inputs: [
128
+ { name: "requested", type: "uint256" },
129
+ { name: "maxAllowed", type: "uint256" }
130
+ ]
131
+ },
132
+ {
133
+ type: "error",
134
+ name: "TooManyJobs",
135
+ inputs: [{ name: "user", type: "address" }]
136
+ },
137
+ { type: "error", name: "Unauthorized", inputs: [] },
138
+ { type: "error", name: "ZeroAddress", inputs: [] },
139
+ // ─── Events ────────────────────────────────────────────────────────────────
140
+ {
141
+ type: "event",
142
+ name: "JobCreated",
143
+ anonymous: false,
144
+ inputs: [
145
+ { indexed: true, name: "jobId", type: "bytes32" },
146
+ { indexed: true, name: "owner", type: "address" },
147
+ { indexed: false, name: "jobType", type: "uint8" }
148
+ ]
149
+ },
150
+ // ─── createLimitOrder ──────────────────────────────────────────────────────
151
+ {
152
+ type: "function",
153
+ name: "createLimitOrder",
154
+ stateMutability: "nonpayable",
155
+ inputs: [
156
+ {
157
+ name: "params",
158
+ type: "tuple",
159
+ components: [
160
+ { name: "tokenIn", type: "address" },
161
+ { name: "tokenOut", type: "address" },
162
+ { name: "amountIn", type: "uint256" },
163
+ { name: "minAmountOut", type: "uint256" },
164
+ { name: "targetPrice", type: "uint256" },
165
+ { name: "triggerAbove", type: "bool" }
166
+ ]
167
+ },
168
+ { name: "slippageBps", type: "uint256" },
169
+ { name: "expiresAt", type: "uint256" }
170
+ ],
171
+ outputs: [{ name: "jobId", type: "bytes32" }]
172
+ },
173
+ // ─── createDCAJob ──────────────────────────────────────────────────────────
174
+ {
175
+ type: "function",
176
+ name: "createDCAJob",
177
+ stateMutability: "nonpayable",
178
+ inputs: [
179
+ {
180
+ name: "params",
181
+ type: "tuple",
182
+ components: [
183
+ { name: "tokenIn", type: "address" },
184
+ { name: "tokenOut", type: "address" },
185
+ { name: "amountPerSwap", type: "uint256" },
186
+ { name: "intervalSeconds", type: "uint256" },
187
+ { name: "totalSwaps", type: "uint256" },
188
+ { name: "swapsCompleted", type: "uint256" },
189
+ { name: "nextExecution", type: "uint256" }
190
+ ]
191
+ },
192
+ { name: "slippageBps", type: "uint256" },
193
+ { name: "expiresAt", type: "uint256" }
194
+ ],
195
+ outputs: [{ name: "jobId", type: "bytes32" }]
196
+ },
197
+ // ─── cancelJob ─────────────────────────────────────────────────────────────
198
+ {
199
+ type: "function",
200
+ name: "cancelJob",
201
+ stateMutability: "nonpayable",
202
+ inputs: [{ name: "jobId", type: "bytes32" }],
203
+ outputs: []
204
+ },
205
+ // ─── JobCancelled event ────────────────────────────────────────────────────
206
+ {
207
+ type: "event",
208
+ name: "JobCancelled",
209
+ anonymous: false,
210
+ inputs: [
211
+ { indexed: true, name: "jobId", type: "bytes32" },
212
+ { indexed: true, name: "canceller", type: "address" }
213
+ ]
214
+ }
215
+ ];
216
+
217
+ // src/usePoolTokens.ts
218
+ import { useCallback, useEffect, useRef, useState } from "react";
219
+ import { createPublicClient, formatUnits, http } from "viem";
220
+ import { confluxESpace, confluxESpaceTestnet } from "viem/chains";
221
+ var CFX_NATIVE_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
222
+ var WCFX_ADDRESSES = {
223
+ testnet: "0x2ED3dddae5B2F321AF0806181FBFA6D049Be47d8",
224
+ mainnet: "0x14b2D3bC65e74DAE1030EAFd8ac30c533c976A9b"
225
+ // EIP-55 checksum: last char is lowercase b
226
+ };
227
+ function wcfxAddress(network = process.env.NEXT_PUBLIC_NETWORK ?? "testnet") {
228
+ return WCFX_ADDRESSES[network] ?? WCFX_ADDRESSES.testnet;
229
+ }
230
+ function resolveTokenInAddress(address) {
231
+ if (address.toLowerCase() === CFX_NATIVE_ADDRESS.toLowerCase()) {
232
+ return wcfxAddress();
233
+ }
234
+ return address;
235
+ }
236
+ var CACHE_KEY = "cas_pool_meta_v2";
237
+ var CACHE_TTL = 10 * 60 * 1e3;
238
+ function readCache() {
239
+ if (typeof window === "undefined") return null;
240
+ try {
241
+ const raw = localStorage.getItem(CACHE_KEY);
242
+ if (!raw) return null;
243
+ const c = JSON.parse(raw);
244
+ c.tokens = c.tokens.map((t) => ({
245
+ ...t,
246
+ address: t.address.toLowerCase()
247
+ }));
248
+ c.pairs = c.pairs.map((p) => ({
249
+ ...p,
250
+ address: p.address.toLowerCase(),
251
+ token0: p.token0.toLowerCase(),
252
+ token1: p.token1.toLowerCase()
253
+ }));
254
+ if (Date.now() - c.cachedAt > CACHE_TTL) return null;
255
+ return c;
256
+ } catch {
257
+ return null;
258
+ }
259
+ }
260
+ function readCacheIgnoreTTL() {
261
+ if (typeof window === "undefined") return null;
262
+ try {
263
+ const raw = localStorage.getItem(CACHE_KEY);
264
+ if (!raw) return null;
265
+ const c = JSON.parse(raw);
266
+ c.tokens = c.tokens.map((t) => ({
267
+ ...t,
268
+ address: t.address.toLowerCase()
269
+ }));
270
+ c.pairs = c.pairs.map((p) => ({
271
+ ...p,
272
+ address: p.address.toLowerCase(),
273
+ token0: p.token0.toLowerCase(),
274
+ token1: p.token1.toLowerCase()
275
+ }));
276
+ return c;
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+ function writeCache(tokens, pairs) {
282
+ try {
283
+ localStorage.setItem(
284
+ CACHE_KEY,
285
+ JSON.stringify({
286
+ tokens,
287
+ pairs,
288
+ cachedAt: Date.now()
289
+ })
290
+ );
291
+ } catch {
292
+ }
293
+ }
294
+ function mergeTokens(known, fresh) {
295
+ for (const t of fresh) {
296
+ known.set(t.address.toLowerCase(), {
297
+ ...t,
298
+ address: t.address.toLowerCase()
299
+ });
300
+ }
301
+ }
302
+ function mergePairs(known, fresh) {
303
+ for (const p of fresh) {
304
+ const t0 = p.token0.toLowerCase();
305
+ const t1 = p.token1.toLowerCase();
306
+ const key = [t0, t1].sort().join("|");
307
+ known.set(key, {
308
+ address: p.address.toLowerCase(),
309
+ token0: t0,
310
+ token1: t1
311
+ });
312
+ }
313
+ }
314
+ var BALANCE_OF_ABI = [
315
+ {
316
+ name: "balanceOf",
317
+ type: "function",
318
+ stateMutability: "view",
319
+ inputs: [{ name: "account", type: "address" }],
320
+ outputs: [{ name: "", type: "uint256" }]
321
+ }
322
+ ];
323
+ function getPairedTokens(pairs, allTokens, tokenInAddress) {
324
+ if (!tokenInAddress) return allTokens;
325
+ const incoming = tokenInAddress.toLowerCase();
326
+ const wcfx = wcfxAddress().toLowerCase();
327
+ const cfxNative = CFX_NATIVE_ADDRESS.toLowerCase();
328
+ const resolved = incoming === cfxNative ? wcfx : incoming;
329
+ const counterparts = /* @__PURE__ */ new Set();
330
+ for (const p of pairs) {
331
+ const t0 = p.token0.toLowerCase();
332
+ const t1 = p.token1.toLowerCase();
333
+ if (t0 === resolved) counterparts.add(t1);
334
+ if (t1 === resolved) counterparts.add(t0);
335
+ }
336
+ const wCfxPaired = counterparts.has(wcfx);
337
+ counterparts.delete(wcfx);
338
+ counterparts.delete(cfxNative);
339
+ const results = allTokens.filter(
340
+ (t) => counterparts.has(t.address.toLowerCase()) && t.address.toLowerCase() !== cfxNative && t.address.toLowerCase() !== wcfx
341
+ );
342
+ if (wCfxPaired && incoming !== cfxNative) {
343
+ const cfxEntry = allTokens.find(
344
+ (t) => t.address.toLowerCase() === cfxNative
345
+ );
346
+ if (cfxEntry) return [cfxEntry, ...results];
347
+ }
348
+ return results;
349
+ }
350
+ function metaToTokens(rawTokens) {
351
+ const wcfx = wcfxAddress().toLowerCase();
352
+ return rawTokens.map((t) => ({
353
+ ...t,
354
+ address: t.address.toLowerCase(),
355
+ symbol: t.address.toLowerCase() === wcfx ? "wCFX" : t.symbol,
356
+ name: t.address.toLowerCase() === wcfx ? "Wrapped CFX" : t.name,
357
+ logoURI: t.logoURI,
358
+ balanceWei: "0",
359
+ balanceFormatted: "0"
360
+ }));
361
+ }
362
+ var CFX_ENTRY_ZERO = {
363
+ address: CFX_NATIVE_ADDRESS,
364
+ symbol: "CFX",
365
+ name: "Conflux (native)",
366
+ decimals: 18,
367
+ balanceWei: "0",
368
+ balanceFormatted: "0"
369
+ };
370
+ function cfxEntryFrom(knownTokens) {
371
+ const wcfx = wcfxAddress().toLowerCase();
372
+ const logo = knownTokens.get(wcfx)?.logoURI;
373
+ return logo ? { ...CFX_ENTRY_ZERO, logoURI: logo } : CFX_ENTRY_ZERO;
374
+ }
375
+ function withTimeout(promise, ms, silent = false) {
376
+ return new Promise((resolve) => {
377
+ const t = setTimeout(() => {
378
+ if (!silent)
379
+ console.warn("[usePoolTokens] RPC call timed out after", ms, "ms");
380
+ resolve(null);
381
+ }, ms);
382
+ promise.then(
383
+ (v) => {
384
+ clearTimeout(t);
385
+ resolve(v);
386
+ },
387
+ (err) => {
388
+ clearTimeout(t);
389
+ if (!silent) {
390
+ const msg = err?.message ?? String(err);
391
+ console.warn("[usePoolTokens] RPC call rejected:", msg);
392
+ }
393
+ resolve(null);
394
+ }
395
+ );
396
+ });
397
+ }
398
+ function usePoolTokens(userAddress) {
399
+ const [tokens, setTokens] = useState([]);
400
+ const [pairs, setPairs] = useState([]);
401
+ const [loading, setLoading] = useState(true);
402
+ const [balancesLoading, setBalancesLoading] = useState(false);
403
+ const [error, setError] = useState(null);
404
+ const [rpcWarning, setRpcWarning] = useState(null);
405
+ const [_tick, setTick] = useState(0);
406
+ const refresh = useCallback(() => setTick((t) => t + 1), []);
407
+ useEffect(() => {
408
+ if (!userAddress) return;
409
+ const id = setInterval(() => setTick((t) => t + 1), 3e4);
410
+ return () => clearInterval(id);
411
+ }, [userAddress]);
412
+ const knownTokensRef = useRef(/* @__PURE__ */ new Map());
413
+ const knownPairsRef = useRef(/* @__PURE__ */ new Map());
414
+ const applyKnown = useCallback(() => {
415
+ const tokenArr = Array.from(knownTokensRef.current.values());
416
+ const pairArr = Array.from(knownPairsRef.current.values());
417
+ setPairs(pairArr);
418
+ return { tokenArr, pairArr };
419
+ }, []);
420
+ const fetchBalances = useCallback(
421
+ async (owner, signal) => {
422
+ const pairAddrs = new Set(
423
+ Array.from(knownPairsRef.current.values()).map(
424
+ (p) => p.address.toLowerCase()
425
+ )
426
+ );
427
+ const rawTokens = Array.from(knownTokensRef.current.values()).filter(
428
+ (t) => !pairAddrs.has(t.address.toLowerCase())
429
+ );
430
+ setBalancesLoading(true);
431
+ try {
432
+ const network = process.env.NEXT_PUBLIC_NETWORK ?? "testnet";
433
+ const rpcUrl = network === "testnet" ? "https://evmtestnet.confluxrpc.com" : "https://evm.confluxrpc.com";
434
+ const chain = network === "testnet" ? confluxESpaceTestnet : confluxESpace;
435
+ const client = createPublicClient({ chain, transport: http(rpcUrl) });
436
+ const CALL_TIMEOUT = 3e4;
437
+ console.log("[usePoolTokens] fetchBalances start", {
438
+ owner,
439
+ network,
440
+ rpcUrl,
441
+ ercTokenCount: rawTokens.length
442
+ });
443
+ const nativeResult = await withTimeout(
444
+ client.getBalance({ address: owner }),
445
+ CALL_TIMEOUT
446
+ );
447
+ if (signal.aborted) return;
448
+ console.log(
449
+ "[usePoolTokens] fetchBalances nativeResult:",
450
+ nativeResult?.toString() ?? "null (failed)"
451
+ );
452
+ const MULTICALL3 = "0xcA11bde05977b3631167028862bE2a173976CA11";
453
+ let ercResults = [];
454
+ let multicallResult = null;
455
+ if (rawTokens.length > 0) {
456
+ multicallResult = await withTimeout(
457
+ client.multicall({
458
+ contracts: rawTokens.map((t) => ({
459
+ address: t.address,
460
+ abi: BALANCE_OF_ABI,
461
+ functionName: "balanceOf",
462
+ args: [owner]
463
+ })),
464
+ allowFailure: true,
465
+ multicallAddress: MULTICALL3
466
+ }),
467
+ CALL_TIMEOUT,
468
+ /* silent */
469
+ true
470
+ );
471
+ ercResults = multicallResult ? multicallResult.map(
472
+ (r) => r.status === "success" ? r.result : null
473
+ ) : rawTokens.map(() => null);
474
+ const successCount = ercResults.filter((r) => r !== null).length;
475
+ console.log(
476
+ `[usePoolTokens] fetchBalances multicall: ${successCount}/${rawTokens.length} succeeded, full timeout: ${multicallResult === null}`
477
+ );
478
+ }
479
+ if (signal.aborted) return;
480
+ if (nativeResult === null) {
481
+ const msg = "CFX balance fetch failed \u2014 RPC may be unavailable.";
482
+ console.warn("[usePoolTokens] fetchBalances:", msg);
483
+ setRpcWarning(msg);
484
+ } else if (rawTokens.length > 0 && multicallResult === null) {
485
+ const msg = "Token balance fetch timed out \u2014 RPC may be overloaded. Showing last known balances.";
486
+ console.warn("[usePoolTokens] fetchBalances:", msg);
487
+ setRpcWarning(msg);
488
+ } else {
489
+ setRpcWarning(null);
490
+ }
491
+ const wcfx = wcfxAddress().toLowerCase();
492
+ const enriched = rawTokens.map((t, i) => {
493
+ const raw = ercResults[i];
494
+ return {
495
+ ...t,
496
+ symbol: t.address.toLowerCase() === wcfx ? "wCFX" : t.symbol,
497
+ name: t.address.toLowerCase() === wcfx ? "Wrapped CFX" : t.name,
498
+ // null means the call timed out — balanceWei stays '0' for now;
499
+ // the merge step below reinstates any previously-known balance.
500
+ balanceWei: raw !== null ? raw.toString() : "0",
501
+ balanceFormatted: raw !== null ? formatUnits(raw, t.decimals ?? 18) : "0"
502
+ };
503
+ });
504
+ enriched.sort((a, b) => {
505
+ const diff = BigInt(b.balanceWei) - BigInt(a.balanceWei);
506
+ return diff > 0n ? 1 : diff < 0n ? -1 : a.symbol.localeCompare(b.symbol);
507
+ });
508
+ const nativeBal = nativeResult ?? 0n;
509
+ const cfxWithBalance = {
510
+ ...cfxEntryFrom(knownTokensRef.current),
511
+ balanceWei: nativeBal.toString(),
512
+ balanceFormatted: formatUnits(nativeBal, 18)
513
+ };
514
+ if (!signal.aborted) {
515
+ setTokens((prev) => {
516
+ const prevMap = new Map(
517
+ prev.map((t) => [t.address.toLowerCase(), t.balanceWei])
518
+ );
519
+ const timedOut = ercResults.map((r) => r === null);
520
+ const merged = [
521
+ // CFX: keep previous balance if native call timed out
522
+ nativeResult !== null ? cfxWithBalance : prev.find((t) => t.address === CFX_NATIVE_ADDRESS) ?? cfxWithBalance,
523
+ ...enriched.map((t, i) => {
524
+ if (timedOut[i]) {
525
+ const prevBal = prevMap.get(t.address.toLowerCase()) ?? "0";
526
+ if (prevBal !== "0") {
527
+ return {
528
+ ...t,
529
+ balanceWei: prevBal,
530
+ balanceFormatted: formatUnits(
531
+ BigInt(prevBal),
532
+ t.decimals ?? 18
533
+ )
534
+ };
535
+ }
536
+ }
537
+ return t;
538
+ })
539
+ ];
540
+ const [cfxEntry, ...rest] = merged;
541
+ rest.sort((a, b) => {
542
+ const diff = BigInt(b.balanceWei) - BigInt(a.balanceWei);
543
+ return diff > 0n ? 1 : diff < 0n ? -1 : a.symbol.localeCompare(b.symbol);
544
+ });
545
+ return [cfxEntry, ...rest];
546
+ });
547
+ }
548
+ } catch (err) {
549
+ console.error(
550
+ "[usePoolTokens] fetchBalances threw:",
551
+ err?.message ?? err
552
+ );
553
+ setRpcWarning("Balance fetch failed \u2014 see browser console for details");
554
+ } finally {
555
+ if (!signal.aborted) setBalancesLoading(false);
556
+ }
557
+ },
558
+ []
559
+ );
560
+ const tickMountedRef = useRef(false);
561
+ useEffect(() => {
562
+ if (!tickMountedRef.current) {
563
+ tickMountedRef.current = true;
564
+ return;
565
+ }
566
+ if (!userAddress) return;
567
+ const ctrl = new AbortController();
568
+ void fetchBalances(userAddress, ctrl.signal);
569
+ return () => ctrl.abort();
570
+ }, [_tick, userAddress, fetchBalances]);
571
+ useEffect(() => {
572
+ const abortCtrl = new AbortController();
573
+ const { signal } = abortCtrl;
574
+ const persisted = readCacheIgnoreTTL();
575
+ if (persisted) {
576
+ mergeTokens(knownTokensRef.current, persisted.tokens);
577
+ mergePairs(knownPairsRef.current, persisted.pairs);
578
+ }
579
+ const cached = readCache();
580
+ if (cached) {
581
+ const { tokenArr } = applyKnown();
582
+ const cfxBase = cfxEntryFrom(knownTokensRef.current);
583
+ const freshMeta = metaToTokens(tokenArr);
584
+ setTokens((prev) => {
585
+ if (prev.length === 0) {
586
+ return [cfxBase, ...freshMeta];
587
+ }
588
+ const existing = new Map(prev.map((t) => [t.address.toLowerCase(), t]));
589
+ const prevCfx = existing.get(CFX_NATIVE_ADDRESS.toLowerCase());
590
+ const cfxEntry = prevCfx ? {
591
+ ...cfxBase,
592
+ balanceWei: prevCfx.balanceWei,
593
+ balanceFormatted: prevCfx.balanceFormatted
594
+ } : cfxBase;
595
+ const rest = freshMeta.map(
596
+ (t) => existing.get(t.address.toLowerCase()) ?? t
597
+ );
598
+ rest.sort((a, b) => {
599
+ const diff = BigInt(b.balanceWei) - BigInt(a.balanceWei);
600
+ return diff > 0n ? 1 : diff < 0n ? -1 : a.symbol.localeCompare(b.symbol);
601
+ });
602
+ return [cfxEntry, ...rest];
603
+ });
604
+ setLoading(false);
605
+ if (userAddress) {
606
+ fetchBalances(userAddress, signal);
607
+ }
608
+ }
609
+ (async () => {
610
+ try {
611
+ const res = await fetch("/api/pools", { signal });
612
+ if (!res.ok) throw new Error(`Backend returned ${res.status}`);
613
+ const data = await res.json();
614
+ if (signal.aborted) return;
615
+ mergeTokens(knownTokensRef.current, data.tokens);
616
+ mergePairs(knownPairsRef.current, data.pairs);
617
+ const mergedTokens = Array.from(knownTokensRef.current.values());
618
+ const mergedPairs = Array.from(knownPairsRef.current.values());
619
+ writeCache(mergedTokens, mergedPairs);
620
+ setPairs(mergedPairs);
621
+ const freshMeta = metaToTokens(mergedTokens);
622
+ const cfxBase = cfxEntryFrom(knownTokensRef.current);
623
+ setTokens((prev) => {
624
+ if (prev.length === 0) {
625
+ return [cfxBase, ...freshMeta];
626
+ }
627
+ const existing = new Map(
628
+ prev.map((t) => [t.address.toLowerCase(), t])
629
+ );
630
+ const prevCfx = existing.get(CFX_NATIVE_ADDRESS.toLowerCase());
631
+ const cfxEntry = prevCfx ? {
632
+ ...cfxBase,
633
+ balanceWei: prevCfx.balanceWei,
634
+ balanceFormatted: prevCfx.balanceFormatted
635
+ } : cfxBase;
636
+ const rest = freshMeta.map(
637
+ (t) => existing.get(t.address.toLowerCase()) ?? t
638
+ );
639
+ rest.sort((a, b) => {
640
+ const diff = BigInt(b.balanceWei) - BigInt(a.balanceWei);
641
+ return diff > 0n ? 1 : diff < 0n ? -1 : a.symbol.localeCompare(b.symbol);
642
+ });
643
+ return [cfxEntry, ...rest];
644
+ });
645
+ setLoading(false);
646
+ if (!userAddress) return;
647
+ await fetchBalances(userAddress, signal);
648
+ } catch (err) {
649
+ if (signal.aborted) return;
650
+ const msg = err?.message ?? String(err);
651
+ console.warn("[usePoolTokens] /api/pools fetch failed:", msg);
652
+ if (cached || persisted) {
653
+ setLoading(false);
654
+ } else {
655
+ setError(msg);
656
+ setLoading(false);
657
+ }
658
+ }
659
+ })();
660
+ return () => abortCtrl.abort();
661
+ }, [userAddress, applyKnown, fetchBalances]);
662
+ return {
663
+ tokens,
664
+ pairs,
665
+ loading,
666
+ balancesLoading,
667
+ error,
668
+ rpcWarning,
669
+ refresh
670
+ };
671
+ }
672
+ export {
673
+ AUTOMATION_MANAGER_ABI,
674
+ AUTOMATION_MANAGER_ADDRESS,
675
+ CFX_NATIVE_ADDRESS,
676
+ ERC20_ABI,
677
+ MAX_UINT256,
678
+ WCFX_ABI,
679
+ getPairedTokens,
680
+ resolveTokenInAddress,
681
+ usePoolTokens,
682
+ wcfxAddress
683
+ };
684
+ //# sourceMappingURL=index.js.map