@stableflow/pay-widgets 0.1.0 → 0.2.0-beta-90dffc3d2f-20261012
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 +6 -2
- package/dist/token-select/index.d.ts +37 -35
- package/dist/token-select/index.js +985 -799
- package/dist/token-select/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -1,35 +1,26 @@
|
|
|
1
1
|
// src/token-select/context.tsx
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
3
4
|
|
|
4
5
|
// src/token-select/balances.ts
|
|
5
6
|
import { useSyncExternalStore } from "react";
|
|
6
7
|
|
|
7
8
|
// src/token-select/config.ts
|
|
8
9
|
var TOKEN_BALANCE_POLL_MS = 6e4;
|
|
9
|
-
var CONFIG_STALE_MS = 30 * 60 * 1e3;
|
|
10
10
|
var ALL_CHAIN_FILTER = "all";
|
|
11
|
-
var
|
|
11
|
+
var DEFAULT_STABLE_SYMBOLS = ["USDT", "USDC"];
|
|
12
12
|
var POPULAR_SECTION_TITLE = "Popular on your network";
|
|
13
13
|
var TOKEN_SELECT_PREFS_STORAGE_KEY = "stableflow-pay:token-select-prefs:v1";
|
|
14
14
|
var TOKEN_SELECT_PREFS_VERSION = 2;
|
|
15
15
|
var MAX_RECENT_BLOCKCHAINS = 16;
|
|
16
16
|
var MAX_RECENT_ASSETS = 3;
|
|
17
|
-
var
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"base",
|
|
25
|
-
"pol",
|
|
26
|
-
"avax",
|
|
27
|
-
"op",
|
|
28
|
-
"bera",
|
|
29
|
-
"gnosis",
|
|
30
|
-
"xlayer",
|
|
31
|
-
"scroll"
|
|
32
|
-
];
|
|
17
|
+
var CHAIN_KIND_LABELS = {
|
|
18
|
+
evm: "EVM",
|
|
19
|
+
near: "Near",
|
|
20
|
+
solana: "Solana",
|
|
21
|
+
tron: "Tron",
|
|
22
|
+
zec: "Zcash"
|
|
23
|
+
};
|
|
33
24
|
var DEFAULT_CHAIN_KINDS = {
|
|
34
25
|
eth: "evm",
|
|
35
26
|
base: "evm",
|
|
@@ -52,7 +43,23 @@ var DEFAULT_CHAIN_KINDS = {
|
|
|
52
43
|
var listeners = /* @__PURE__ */ new Set();
|
|
53
44
|
var balances = {};
|
|
54
45
|
var reader = null;
|
|
46
|
+
var syncReader = null;
|
|
47
|
+
var tokensById = /* @__PURE__ */ new Map();
|
|
55
48
|
var chainInflight = /* @__PURE__ */ new Map();
|
|
49
|
+
function configureBalanceReaders(options) {
|
|
50
|
+
reader = options.readBalances ?? null;
|
|
51
|
+
if (!options.readBalances && options.getBalance) {
|
|
52
|
+
syncReader = {
|
|
53
|
+
getBalance: options.getBalance,
|
|
54
|
+
isBalanceLoading: options.isBalanceLoading ?? (() => false)
|
|
55
|
+
};
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
syncReader = null;
|
|
59
|
+
}
|
|
60
|
+
function setBalanceTokens(tokens) {
|
|
61
|
+
tokensById = new Map(tokens.map((token) => [token.assetId, token]));
|
|
62
|
+
}
|
|
56
63
|
function emit() {
|
|
57
64
|
balances = { ...balances };
|
|
58
65
|
for (const listener of listeners) listener();
|
|
@@ -87,15 +94,25 @@ function withStaleOnError(prev, next) {
|
|
|
87
94
|
if (prev?.raw == null && prev?.formatted == null) return next;
|
|
88
95
|
return { ...next, raw: prev.raw, formatted: prev.formatted, updatedAt: prev.updatedAt };
|
|
89
96
|
}
|
|
90
|
-
function setBalanceReader(next) {
|
|
91
|
-
reader = next;
|
|
92
|
-
}
|
|
93
97
|
function clearPayTokenBalances() {
|
|
94
98
|
balances = {};
|
|
95
99
|
emit();
|
|
96
100
|
}
|
|
101
|
+
function syncBalance(owner, assetId) {
|
|
102
|
+
if (!syncReader) return void 0;
|
|
103
|
+
const token = tokensById.get(assetId);
|
|
104
|
+
if (!token) return void 0;
|
|
105
|
+
const loading = syncReader.isBalanceLoading(token, owner);
|
|
106
|
+
const formatted = syncReader.getBalance(token, owner);
|
|
107
|
+
if (loading && (formatted == null || formatted === "")) {
|
|
108
|
+
return { raw: null, formatted: null, status: "loading", error: null, updatedAt: null };
|
|
109
|
+
}
|
|
110
|
+
if (formatted == null || formatted === "") return void 0;
|
|
111
|
+
return { raw: null, formatted, status: "success", error: null, updatedAt: null };
|
|
112
|
+
}
|
|
97
113
|
function getPayTokenBalance(owner, assetId) {
|
|
98
114
|
if (!owner || !assetId) return void 0;
|
|
115
|
+
if (syncReader) return syncBalance(owner, assetId);
|
|
99
116
|
return balances[`${owner}:${assetId}`] ?? balances[`${owner.toLowerCase()}:${assetId}`];
|
|
100
117
|
}
|
|
101
118
|
function usePayTokenBalance(owner, assetId) {
|
|
@@ -198,6 +215,7 @@ async function fetchChain(owner, tokens, force) {
|
|
|
198
215
|
});
|
|
199
216
|
}
|
|
200
217
|
async function ensurePayTokenBalances(owners, tokens, opts) {
|
|
218
|
+
if (syncReader || !reader) return;
|
|
201
219
|
const force = Boolean(opts?.force);
|
|
202
220
|
const groups = groupByChain(tokens);
|
|
203
221
|
await Promise.all(Array.from(groups.values()).map((group) => {
|
|
@@ -208,306 +226,66 @@ async function ensurePayTokenBalances(owners, tokens, opts) {
|
|
|
208
226
|
}));
|
|
209
227
|
}
|
|
210
228
|
|
|
211
|
-
// src/token-select/pay-config.ts
|
|
212
|
-
import { useCallback, useEffect, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
213
|
-
|
|
214
|
-
// src/token-select/logos.ts
|
|
215
|
-
var LOGO_HOST = "https://assets.dapdap.net";
|
|
216
|
-
var CHAIN_LOGO_ALIAS = {
|
|
217
|
-
eth: "ethereum",
|
|
218
|
-
ethereum: "ethereum",
|
|
219
|
-
base: "base",
|
|
220
|
-
arb: "arbitrum",
|
|
221
|
-
arbitrum: "arbitrum",
|
|
222
|
-
op: "optimism",
|
|
223
|
-
optimism: "optimism",
|
|
224
|
-
pol: "polygon",
|
|
225
|
-
polygon: "polygon",
|
|
226
|
-
bsc: "bsc",
|
|
227
|
-
avax: "avalanche",
|
|
228
|
-
avalanche: "avalanche",
|
|
229
|
-
gnosis: "gnosis",
|
|
230
|
-
scroll: "scroll",
|
|
231
|
-
xlayer: "xlayer",
|
|
232
|
-
bera: "berachain",
|
|
233
|
-
berachain: "berachain",
|
|
234
|
-
near: "near",
|
|
235
|
-
sol: "solana",
|
|
236
|
-
solana: "solana",
|
|
237
|
-
tron: "tron",
|
|
238
|
-
zec: "zcash",
|
|
239
|
-
zcash: "zcash"
|
|
240
|
-
};
|
|
241
|
-
function logoPath(path) {
|
|
242
|
-
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
243
|
-
return `${LOGO_HOST}${normalized}`;
|
|
244
|
-
}
|
|
245
|
-
function evmTypeLogoUrl() {
|
|
246
|
-
return logoPath("/stableflow/networks/type-evm.svg");
|
|
247
|
-
}
|
|
248
|
-
function chainLogoUrl(blockchainOrNetwork) {
|
|
249
|
-
const key = String(blockchainOrNetwork || "").toLowerCase();
|
|
250
|
-
const file = CHAIN_LOGO_ALIAS[key] || key.replace(/\s+/g, "");
|
|
251
|
-
return logoPath(`/stableflow/networks/${file}.png`);
|
|
252
|
-
}
|
|
253
|
-
function tokenLogoUrl(symbol) {
|
|
254
|
-
const upper = String(symbol || "").toUpperCase();
|
|
255
|
-
const file = upper === "USDT0" ? "usdt" : upper.toLowerCase();
|
|
256
|
-
return logoPath(`/stableflow/tokens/${file}.png`);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// src/token-select/pay-config.ts
|
|
260
|
-
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
261
|
-
var WRAP_NEAR_CONTRACT = "wrap.near";
|
|
262
|
-
var listeners2 = /* @__PURE__ */ new Set();
|
|
263
|
-
var chainKindOverrides = {};
|
|
264
|
-
var snapshot = {
|
|
265
|
-
apiHost: DEFAULT_API_HOST,
|
|
266
|
-
chains: [],
|
|
267
|
-
tokens: [],
|
|
268
|
-
source: null,
|
|
269
|
-
status: "idle",
|
|
270
|
-
error: null,
|
|
271
|
-
fetchedAt: null
|
|
272
|
-
};
|
|
273
|
-
var inflight = null;
|
|
274
|
-
function emit2(next) {
|
|
275
|
-
snapshot = next;
|
|
276
|
-
for (const listener of listeners2) listener();
|
|
277
|
-
}
|
|
278
|
-
function subscribe2(listener) {
|
|
279
|
-
listeners2.add(listener);
|
|
280
|
-
return () => listeners2.delete(listener);
|
|
281
|
-
}
|
|
282
|
-
function getPayConfigSnapshot() {
|
|
283
|
-
return snapshot;
|
|
284
|
-
}
|
|
285
|
-
function setChainKindOverrides(overrides) {
|
|
286
|
-
chainKindOverrides = overrides ?? {};
|
|
287
|
-
if (snapshot.source) {
|
|
288
|
-
const normalized = normalizePayConfig(snapshot.source, chainKindOverrides);
|
|
289
|
-
emit2({ ...snapshot, chains: normalized.chains, tokens: normalized.tokens });
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
function asRecord(value) {
|
|
293
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
294
|
-
return value;
|
|
295
|
-
}
|
|
296
|
-
function apiText(value) {
|
|
297
|
-
if (typeof value === "string") return value.trim();
|
|
298
|
-
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
299
|
-
return "";
|
|
300
|
-
}
|
|
301
|
-
function apiNumber(value) {
|
|
302
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
303
|
-
if (typeof value === "string" && value.trim()) {
|
|
304
|
-
const parsed = Number(value);
|
|
305
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
306
|
-
}
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
function asBoolean(value, fallback = true) {
|
|
310
|
-
if (typeof value === "boolean") return value;
|
|
311
|
-
if (typeof value === "string") {
|
|
312
|
-
const lower = value.trim().toLowerCase();
|
|
313
|
-
if (lower === "true") return true;
|
|
314
|
-
if (lower === "false") return false;
|
|
315
|
-
}
|
|
316
|
-
return fallback;
|
|
317
|
-
}
|
|
318
|
-
function mapSourceChain(raw) {
|
|
319
|
-
const row = asRecord(raw) ?? {};
|
|
320
|
-
return {
|
|
321
|
-
network: apiText(row.network),
|
|
322
|
-
chainId: apiText(row.chain_id ?? row.chainId),
|
|
323
|
-
chainName: apiText(row.chain_name ?? row.chainName),
|
|
324
|
-
logo: apiText(row.logo),
|
|
325
|
-
explorer: apiText(row.explorer)
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
function mapSourceToken(raw) {
|
|
329
|
-
const row = asRecord(raw) ?? {};
|
|
330
|
-
return {
|
|
331
|
-
symbol: apiText(row.symbol),
|
|
332
|
-
network: apiText(row.network),
|
|
333
|
-
decimals: apiNumber(row.decimals) ?? 0,
|
|
334
|
-
contractAddress: apiText(row.contract_address ?? row.contractAddress),
|
|
335
|
-
price: apiText(row.price),
|
|
336
|
-
supportPayment: asBoolean(row.support_payment ?? row.supportPayment),
|
|
337
|
-
supportReceive: asBoolean(row.support_receive ?? row.supportReceive)
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
function configBody(raw) {
|
|
341
|
-
const row = asRecord(raw) ?? {};
|
|
342
|
-
const data = asRecord(row.data);
|
|
343
|
-
if (typeof row.code === "number" && data) return data;
|
|
344
|
-
return row;
|
|
345
|
-
}
|
|
346
|
-
function mapPayConfigSource(raw) {
|
|
347
|
-
const row = configBody(raw);
|
|
348
|
-
const chains = Array.isArray(row.chains) ? row.chains.map(mapSourceChain) : [];
|
|
349
|
-
const tokens = Array.isArray(row.tokens) ? row.tokens.map(mapSourceToken) : [];
|
|
350
|
-
return { chains, tokens };
|
|
351
|
-
}
|
|
352
|
-
function chainKindFor(network, overrides = chainKindOverrides) {
|
|
353
|
-
const key = network.trim().toLowerCase();
|
|
354
|
-
if (!key) return null;
|
|
355
|
-
return overrides[key] ?? DEFAULT_CHAIN_KINDS[key] ?? null;
|
|
356
|
-
}
|
|
357
|
-
function normalizeSymbol(symbol) {
|
|
358
|
-
const upper = symbol.trim().toUpperCase();
|
|
359
|
-
if (!upper) return null;
|
|
360
|
-
if (upper === "USDT0") return "USDT";
|
|
361
|
-
if (upper === "WNEAR") return "NEAR";
|
|
362
|
-
return upper;
|
|
363
|
-
}
|
|
364
|
-
function tokenAssetId(network, symbol, contractAddress) {
|
|
365
|
-
const addr = String(contractAddress || "").trim();
|
|
366
|
-
return `${network}:${symbol}:${addr || "native"}`;
|
|
367
|
-
}
|
|
368
|
-
function isNativeAddress(contractAddress) {
|
|
369
|
-
const addr = String(contractAddress || "").trim();
|
|
370
|
-
if (!addr) return true;
|
|
371
|
-
const lower = addr.toLowerCase();
|
|
372
|
-
return lower === "native" || lower === ZERO_ADDRESS;
|
|
373
|
-
}
|
|
374
|
-
function normalizePayConfig(source, overrides = chainKindOverrides) {
|
|
375
|
-
const chains = [];
|
|
376
|
-
for (const chain of source.chains) {
|
|
377
|
-
const chainKind = chainKindFor(chain.network, overrides);
|
|
378
|
-
if (!chainKind || !chain.network) continue;
|
|
379
|
-
chains.push({
|
|
380
|
-
blockchain: chain.network,
|
|
381
|
-
chainName: chain.chainName || chain.network,
|
|
382
|
-
chainKind,
|
|
383
|
-
chainId: chain.chainId || void 0,
|
|
384
|
-
logo: chain.logo || chainLogoUrl(chain.network),
|
|
385
|
-
explorer: chain.explorer
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
const chainByCode = new Map(chains.map((chain) => [chain.blockchain, chain]));
|
|
389
|
-
const tokens = [];
|
|
390
|
-
for (const token of source.tokens) {
|
|
391
|
-
const chain = chainByCode.get(token.network);
|
|
392
|
-
if (!chain) continue;
|
|
393
|
-
const symbol = normalizeSymbol(token.symbol);
|
|
394
|
-
if (!symbol) continue;
|
|
395
|
-
if (!Number.isInteger(token.decimals) || token.decimals < 0) continue;
|
|
396
|
-
const contractAddress = token.contractAddress.trim() || null;
|
|
397
|
-
tokens.push({
|
|
398
|
-
assetId: tokenAssetId(token.network, symbol, contractAddress),
|
|
399
|
-
symbol,
|
|
400
|
-
providerSymbol: token.symbol,
|
|
401
|
-
blockchain: token.network,
|
|
402
|
-
decimals: token.decimals,
|
|
403
|
-
contractAddress,
|
|
404
|
-
price: Number(token.price || 1),
|
|
405
|
-
logo: tokenLogoUrl(symbol),
|
|
406
|
-
supportPayment: token.supportPayment,
|
|
407
|
-
supportReceive: token.supportReceive,
|
|
408
|
-
chain
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
const hasNearWrap = tokens.some((token) => token.symbol === "NEAR" && token.blockchain === "near" && token.contractAddress?.toLowerCase() === WRAP_NEAR_CONTRACT);
|
|
412
|
-
const visible = hasNearWrap ? tokens.filter((token) => !(token.symbol === "NEAR" && token.blockchain === "near" && isNativeAddress(token.contractAddress))) : tokens;
|
|
413
|
-
return { chains, tokens: visible };
|
|
414
|
-
}
|
|
415
|
-
function configUrl(apiHost) {
|
|
416
|
-
return `${apiHost.replace(/\/$/, "")}/v1/pay/config`;
|
|
417
|
-
}
|
|
418
|
-
async function load(apiHost, force) {
|
|
419
|
-
const fresh = snapshot.apiHost === apiHost && snapshot.status === "success" && snapshot.fetchedAt != null && Date.now() - snapshot.fetchedAt < CONFIG_STALE_MS;
|
|
420
|
-
if (!force && fresh) return;
|
|
421
|
-
if (inflight && !force) return inflight;
|
|
422
|
-
emit2({ ...snapshot, apiHost, status: snapshot.tokens.length > 0 ? snapshot.status : "loading", error: null });
|
|
423
|
-
const run = (async () => {
|
|
424
|
-
try {
|
|
425
|
-
const response = await fetch(configUrl(apiHost));
|
|
426
|
-
if (!response.ok) throw new Error(`Config request failed (${response.status})`);
|
|
427
|
-
const source = mapPayConfigSource(await response.json());
|
|
428
|
-
const normalized = normalizePayConfig(source, chainKindOverrides);
|
|
429
|
-
emit2({
|
|
430
|
-
apiHost,
|
|
431
|
-
source,
|
|
432
|
-
chains: normalized.chains,
|
|
433
|
-
tokens: normalized.tokens,
|
|
434
|
-
status: "success",
|
|
435
|
-
error: null,
|
|
436
|
-
fetchedAt: Date.now()
|
|
437
|
-
});
|
|
438
|
-
} catch (cause) {
|
|
439
|
-
const message = cause instanceof Error ? cause.message : "Failed to load config";
|
|
440
|
-
emit2({
|
|
441
|
-
...snapshot,
|
|
442
|
-
apiHost,
|
|
443
|
-
status: snapshot.source ? "success" : "error",
|
|
444
|
-
error: message
|
|
445
|
-
});
|
|
446
|
-
} finally {
|
|
447
|
-
inflight = null;
|
|
448
|
-
}
|
|
449
|
-
})();
|
|
450
|
-
inflight = run;
|
|
451
|
-
return run;
|
|
452
|
-
}
|
|
453
|
-
function refetchPayConfig(apiHost = snapshot.apiHost) {
|
|
454
|
-
return load(apiHost, true);
|
|
455
|
-
}
|
|
456
|
-
function usePayConfigSnapshot() {
|
|
457
|
-
return useSyncExternalStore2(subscribe2, getPayConfigSnapshot, getPayConfigSnapshot);
|
|
458
|
-
}
|
|
459
|
-
function usePayConfig(options) {
|
|
460
|
-
const apiHost = options?.apiHost?.trim() || DEFAULT_API_HOST;
|
|
461
|
-
const current = useSyncExternalStore2(subscribe2, getPayConfigSnapshot, getPayConfigSnapshot);
|
|
462
|
-
const refetch = useCallback(() => {
|
|
463
|
-
void refetchPayConfig(apiHost);
|
|
464
|
-
}, [apiHost]);
|
|
465
|
-
useEffect(() => {
|
|
466
|
-
void load(apiHost, false);
|
|
467
|
-
}, [apiHost]);
|
|
468
|
-
return {
|
|
469
|
-
chains: current.apiHost === apiHost ? current.chains : [],
|
|
470
|
-
tokens: current.apiHost === apiHost ? current.tokens : [],
|
|
471
|
-
source: current.apiHost === apiHost ? current.source : null,
|
|
472
|
-
status: current.apiHost === apiHost ? current.status : "loading",
|
|
473
|
-
error: current.apiHost === apiHost ? current.error : null,
|
|
474
|
-
refetch
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
|
|
478
229
|
// src/token-select/context.tsx
|
|
479
230
|
import { jsx } from "react/jsx-runtime";
|
|
480
231
|
var PayWidgetsContext = createContext(null);
|
|
232
|
+
function isProduction() {
|
|
233
|
+
const env = globalThis.process?.env?.NODE_ENV;
|
|
234
|
+
return env === "production";
|
|
235
|
+
}
|
|
236
|
+
function warnBalanceMode(readBalances, getBalance) {
|
|
237
|
+
if (isProduction()) return;
|
|
238
|
+
if (readBalances && getBalance) {
|
|
239
|
+
console.warn("PayWidgetsProvider: both readBalances and getBalance were passed; using readBalances.");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (!readBalances && !getBalance) {
|
|
243
|
+
console.warn("PayWidgetsProvider: pass readBalances or getBalance so token balances can load.");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
481
246
|
function PayWidgetsProvider(props) {
|
|
482
247
|
const {
|
|
483
|
-
|
|
248
|
+
tokens,
|
|
249
|
+
loading = false,
|
|
484
250
|
popularTokens = [],
|
|
485
251
|
wallet,
|
|
486
252
|
readBalances,
|
|
487
|
-
|
|
253
|
+
getBalance,
|
|
254
|
+
isBalanceLoading,
|
|
488
255
|
onCopyAddress,
|
|
489
|
-
|
|
256
|
+
stableSymbols = [...DEFAULT_STABLE_SYMBOLS],
|
|
257
|
+
chainKindLabels = {},
|
|
490
258
|
children
|
|
491
259
|
} = props;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
warnBalanceMode(readBalances, getBalance);
|
|
262
|
+
configureBalanceReaders({
|
|
263
|
+
readBalances: readBalances ?? null,
|
|
264
|
+
getBalance: getBalance ?? null,
|
|
265
|
+
isBalanceLoading: isBalanceLoading ?? null
|
|
266
|
+
});
|
|
267
|
+
return () => configureBalanceReaders({});
|
|
268
|
+
}, [readBalances, getBalance, isBalanceLoading]);
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
setBalanceTokens(tokens);
|
|
271
|
+
}, [tokens]);
|
|
503
272
|
const connected = Object.values(wallet.accounts).some((account) => Boolean(account?.address?.trim()));
|
|
504
|
-
|
|
273
|
+
useEffect(() => {
|
|
505
274
|
if (!connected) clearPayTokenBalances();
|
|
506
275
|
}, [connected]);
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
276
|
+
const balanceMode = readBalances ? "async" : getBalance ? "sync" : "none";
|
|
277
|
+
const value = {
|
|
278
|
+
tokens,
|
|
279
|
+
loading,
|
|
280
|
+
wallet,
|
|
281
|
+
popularTokens,
|
|
282
|
+
stableSymbols,
|
|
283
|
+
chainKindLabels,
|
|
284
|
+
onCopyAddress,
|
|
285
|
+
balanceMode,
|
|
286
|
+
getBalance: balanceMode === "sync" ? getBalance ?? null : null,
|
|
287
|
+
isBalanceLoading: balanceMode === "sync" ? isBalanceLoading ?? null : null
|
|
288
|
+
};
|
|
511
289
|
return /* @__PURE__ */ jsx(PayWidgetsContext.Provider, { value, children });
|
|
512
290
|
}
|
|
513
291
|
function usePayWidgets() {
|
|
@@ -519,83 +297,23 @@ function usePayWidgets() {
|
|
|
519
297
|
}
|
|
520
298
|
|
|
521
299
|
// src/token-select/TokenSelectDialog.tsx
|
|
522
|
-
import { useEffect as useEffect3, useMemo as
|
|
300
|
+
import { useEffect as useEffect3, useLayoutEffect, useMemo, useRef as useRef2, useState as useState2, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
523
301
|
import { Dialog } from "@stableflow/pay-ui/dialog";
|
|
524
|
-
import { SearchInput
|
|
302
|
+
import { SearchInput } from "@stableflow/pay-ui/search-input";
|
|
525
303
|
|
|
526
|
-
// src/token-select/
|
|
527
|
-
import {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
function rememberRecentBlockchain(recent, blockchain) {
|
|
535
|
-
const code = blockchain.trim();
|
|
536
|
-
if (!code) return recent;
|
|
537
|
-
return [code, ...recent.filter((item) => item !== code)].slice(0, MAX_RECENT_BLOCKCHAINS);
|
|
538
|
-
}
|
|
539
|
-
function migrateTokenSelectPrefs(persisted, version) {
|
|
540
|
-
const state = persisted ?? {};
|
|
541
|
-
const recentBlockchains = version >= 1 ? Array.isArray(state.recentBlockchains) ? state.recentBlockchains : [] : typeof state.lastBlockchain === "string" && state.lastBlockchain ? [state.lastBlockchain] : [];
|
|
542
|
-
const recentAssetIds = version >= TOKEN_SELECT_PREFS_VERSION && Array.isArray(state.recentAssetIds) ? state.recentAssetIds.filter((id) => typeof id === "string" && id.trim()).slice(0, MAX_RECENT_ASSETS) : typeof state.lastAssetId === "string" && state.lastAssetId ? [state.lastAssetId] : [];
|
|
543
|
-
return { recentAssetIds, recentBlockchains };
|
|
544
|
-
}
|
|
545
|
-
function readStorage() {
|
|
546
|
-
if (typeof localStorage === "undefined") return EMPTY_PREFS;
|
|
547
|
-
try {
|
|
548
|
-
const raw = localStorage.getItem(TOKEN_SELECT_PREFS_STORAGE_KEY);
|
|
549
|
-
if (!raw) return EMPTY_PREFS;
|
|
550
|
-
const parsed = JSON.parse(raw);
|
|
551
|
-
const version = typeof parsed.version === "number" ? parsed.version : 0;
|
|
552
|
-
const payload = parsed && typeof parsed === "object" && "state" in parsed ? parsed.state : parsed;
|
|
553
|
-
return migrateTokenSelectPrefs(payload, version);
|
|
554
|
-
} catch {
|
|
555
|
-
return EMPTY_PREFS;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
function writeStorage(state) {
|
|
559
|
-
if (typeof localStorage === "undefined") return;
|
|
560
|
-
localStorage.setItem(TOKEN_SELECT_PREFS_STORAGE_KEY, JSON.stringify({
|
|
561
|
-
state,
|
|
562
|
-
version: TOKEN_SELECT_PREFS_VERSION
|
|
563
|
-
}));
|
|
564
|
-
}
|
|
565
|
-
var prefs = readStorage();
|
|
566
|
-
var listeners3 = /* @__PURE__ */ new Set();
|
|
567
|
-
function emit3(next) {
|
|
568
|
-
prefs = next;
|
|
569
|
-
writeStorage(next);
|
|
570
|
-
for (const listener of listeners3) listener();
|
|
571
|
-
}
|
|
572
|
-
function subscribe3(listener) {
|
|
573
|
-
listeners3.add(listener);
|
|
574
|
-
return () => listeners3.delete(listener);
|
|
575
|
-
}
|
|
576
|
-
function getTokenSelectPrefs() {
|
|
577
|
-
return prefs;
|
|
578
|
-
}
|
|
579
|
-
function setLastToken(assetId, blockchain) {
|
|
580
|
-
emit3({
|
|
581
|
-
recentAssetIds: rememberRecentAsset(prefs.recentAssetIds, assetId),
|
|
582
|
-
recentBlockchains: rememberRecentBlockchain(prefs.recentBlockchains, blockchain)
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
function setLastBlockchain(blockchain) {
|
|
586
|
-
emit3({
|
|
587
|
-
...prefs,
|
|
588
|
-
recentBlockchains: rememberRecentBlockchain(prefs.recentBlockchains, blockchain)
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
function useTokenSelectPrefs() {
|
|
592
|
-
return useSyncExternalStore3(subscribe3, getTokenSelectPrefs, getTokenSelectPrefs);
|
|
593
|
-
}
|
|
304
|
+
// src/token-select/chain-pane.tsx
|
|
305
|
+
import { IconChevron } from "@stableflow/pay-ui/icons/chevron";
|
|
306
|
+
import { Tooltip } from "@stableflow/pay-ui/tooltip";
|
|
307
|
+
|
|
308
|
+
// src/token-select/chain-wallet-status.tsx
|
|
309
|
+
import { IconCopy } from "@stableflow/pay-ui/icons/copy";
|
|
310
|
+
import { IconLogout } from "@stableflow/pay-ui/icons/logout";
|
|
594
311
|
|
|
595
312
|
// src/token-select/format.ts
|
|
596
313
|
import { clsx } from "clsx";
|
|
597
314
|
import { twMerge } from "tailwind-merge";
|
|
598
315
|
var fontSans = "[font-family:Montserrat,PingFang_SC,system-ui,sans-serif]";
|
|
316
|
+
var thinScrollbar = "[&::-webkit-scrollbar]:w-[4px] [&::-webkit-scrollbar-thumb]:rounded-[12px] [&::-webkit-scrollbar-thumb]:bg-[#d9d9d9] [&::-webkit-scrollbar-track]:bg-transparent supports-[not_selector(::-webkit-scrollbar)]:[scrollbar-width:thin] [scrollbar-color:#d9d9d9_transparent]";
|
|
599
317
|
function cx(...inputs) {
|
|
600
318
|
return twMerge(clsx(inputs));
|
|
601
319
|
}
|
|
@@ -605,13 +323,8 @@ function formatAddress(address, prefix = 4, suffix = 5) {
|
|
|
605
323
|
if (address.length <= prefix + suffix) return address;
|
|
606
324
|
return `${address.slice(0, prefix)}...${address.slice(-suffix)}`;
|
|
607
325
|
}
|
|
608
|
-
function chainKindLabel(kind) {
|
|
609
|
-
|
|
610
|
-
if (kind === "near") return "Near";
|
|
611
|
-
if (kind === "solana") return "Solana";
|
|
612
|
-
if (kind === "tron") return "Tron";
|
|
613
|
-
if (kind === "zec") return "Zcash";
|
|
614
|
-
return kind;
|
|
326
|
+
function chainKindLabel(kind, labels) {
|
|
327
|
+
return labels?.[kind] ?? CHAIN_KIND_LABELS[kind] ?? kind;
|
|
615
328
|
}
|
|
616
329
|
function formatDisplayAmount(value, options = {}) {
|
|
617
330
|
const maxDecimals = options.maxDecimals ?? 2;
|
|
@@ -633,14 +346,7 @@ function formatDisplayAmount(value, options = {}) {
|
|
|
633
346
|
return `${prefix}${negative ? "-" : ""}${body}`;
|
|
634
347
|
}
|
|
635
348
|
|
|
636
|
-
// src/token-select/chain-pane.tsx
|
|
637
|
-
import { useState } from "react";
|
|
638
|
-
import { IconArrowDown } from "@stableflow/pay-ui/icons/arrow-down";
|
|
639
|
-
import { Tooltip } from "@stableflow/pay-ui/tooltip";
|
|
640
|
-
|
|
641
349
|
// src/token-select/chain-wallet-status.tsx
|
|
642
|
-
import { IconCopy } from "@stableflow/pay-ui/icons/copy";
|
|
643
|
-
import { IconLogout } from "@stableflow/pay-ui/icons/logout";
|
|
644
350
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
645
351
|
function stop(event) {
|
|
646
352
|
event.preventDefault();
|
|
@@ -710,22 +416,14 @@ function ChainWalletStatus({ kind }) {
|
|
|
710
416
|
}
|
|
711
417
|
|
|
712
418
|
// src/token-select/utils.ts
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if (chain.chainKind === "evm") {
|
|
720
|
-
if (!placed) {
|
|
721
|
-
entries.push({ kind: "evm", chains: evm });
|
|
722
|
-
placed = true;
|
|
723
|
-
}
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
entries.push({ kind: "chain", chain });
|
|
419
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
420
|
+
function chainsFromTokens(tokens) {
|
|
421
|
+
const byCode = /* @__PURE__ */ new Map();
|
|
422
|
+
for (const token of tokens) {
|
|
423
|
+
const code = token.chain.blockchain || token.blockchain;
|
|
424
|
+
if (!byCode.has(code)) byCode.set(code, token.chain);
|
|
727
425
|
}
|
|
728
|
-
return
|
|
426
|
+
return Array.from(byCode.values());
|
|
729
427
|
}
|
|
730
428
|
function tokenBalanceUsd(token, formatted) {
|
|
731
429
|
if (formatted == null || formatted === "") return -1;
|
|
@@ -741,10 +439,6 @@ function tokenMatchesSearch(token, query) {
|
|
|
741
439
|
if (token.providerSymbol.toLowerCase().includes(q)) return true;
|
|
742
440
|
return Boolean(token.contractAddress?.toLowerCase().includes(q));
|
|
743
441
|
}
|
|
744
|
-
function matchesChainFilter(token, chainFilter) {
|
|
745
|
-
if (chainFilter === ALL_CHAIN_FILTER) return true;
|
|
746
|
-
return token.blockchain === chainFilter;
|
|
747
|
-
}
|
|
748
442
|
function formattedBalancePositive(formatted) {
|
|
749
443
|
if (formatted == null || formatted === "") return false;
|
|
750
444
|
const amount = Number(formatted);
|
|
@@ -764,9 +458,6 @@ function chainBalanceUsd(blockchain, tokens, getBalanceUsd) {
|
|
|
764
458
|
}
|
|
765
459
|
return total;
|
|
766
460
|
}
|
|
767
|
-
var DISCONNECTED_CHAIN_RANK = new Map(
|
|
768
|
-
DISCONNECTED_CHAIN_ORDER.map((code, index) => [code, index])
|
|
769
|
-
);
|
|
770
461
|
function parsePopularTokens(raw) {
|
|
771
462
|
if (!raw?.trim()) return [];
|
|
772
463
|
const refs = [];
|
|
@@ -790,16 +481,10 @@ function popularTokensForWallets(tokens, configured, isChainConnected) {
|
|
|
790
481
|
}
|
|
791
482
|
return result;
|
|
792
483
|
}
|
|
484
|
+
function sortChainsAlpha(chains) {
|
|
485
|
+
return chains.slice().sort((left, right) => left.chainName.localeCompare(right.chainName));
|
|
486
|
+
}
|
|
793
487
|
function sortChainsForSidebar(chains, tokens, options) {
|
|
794
|
-
if (!options.walletConnected) {
|
|
795
|
-
const tail = DISCONNECTED_CHAIN_ORDER.length;
|
|
796
|
-
return chains.map((chain, index) => ({ chain, index })).sort((left, right) => {
|
|
797
|
-
const leftRank = DISCONNECTED_CHAIN_RANK.get(left.chain.blockchain) ?? tail;
|
|
798
|
-
const rightRank = DISCONNECTED_CHAIN_RANK.get(right.chain.blockchain) ?? tail;
|
|
799
|
-
if (leftRank !== rightRank) return leftRank - rightRank;
|
|
800
|
-
return left.index - right.index;
|
|
801
|
-
}).map((item) => item.chain);
|
|
802
|
-
}
|
|
803
488
|
if (!options.showBalances) return chains.slice();
|
|
804
489
|
return chains.slice().sort((left, right) => {
|
|
805
490
|
const leftUsd = chainBalanceUsd(left.blockchain, tokens, options.getBalanceUsd);
|
|
@@ -808,6 +493,13 @@ function sortChainsForSidebar(chains, tokens, options) {
|
|
|
808
493
|
return left.chainName.localeCompare(right.chainName);
|
|
809
494
|
});
|
|
810
495
|
}
|
|
496
|
+
function payerRailChains(chains, owners, fundedSet) {
|
|
497
|
+
return chains.filter((chain) => {
|
|
498
|
+
if (!owners[chain.chainKind]) return false;
|
|
499
|
+
if (chain.chainKind === "evm" && !fundedSet.has(chain.blockchain)) return false;
|
|
500
|
+
return true;
|
|
501
|
+
});
|
|
502
|
+
}
|
|
811
503
|
function initialChainFilter(lockChainKind, recentBlockchains, chains) {
|
|
812
504
|
if (!lockChainKind) return ALL_CHAIN_FILTER;
|
|
813
505
|
const matching = chains.filter((chain) => chain.chainKind === lockChainKind);
|
|
@@ -848,9 +540,6 @@ function sortTokensByBalance(tokens, getBalanceUsd, isLoading) {
|
|
|
848
540
|
return compareTokensBySymbol(left, right);
|
|
849
541
|
});
|
|
850
542
|
}
|
|
851
|
-
function sortTokensBySymbol(tokens) {
|
|
852
|
-
return tokens.slice().sort(compareTokensBySymbol);
|
|
853
|
-
}
|
|
854
543
|
function positiveBalanceTokens(tokens, getBalanceUsd) {
|
|
855
544
|
return sortTokensByBalance(
|
|
856
545
|
tokens.filter((token) => getBalanceUsd(token) > 0),
|
|
@@ -858,6 +547,90 @@ function positiveBalanceTokens(tokens, getBalanceUsd) {
|
|
|
858
547
|
() => false
|
|
859
548
|
);
|
|
860
549
|
}
|
|
550
|
+
function isGasToken(token) {
|
|
551
|
+
const addr = String(token.contractAddress || "").trim().toLowerCase();
|
|
552
|
+
if (!addr || addr === "native" || addr === ZERO_ADDRESS) return true;
|
|
553
|
+
return token.blockchain === "near" && token.symbol.toUpperCase() === "NEAR";
|
|
554
|
+
}
|
|
555
|
+
function stableSymbolIndex(symbol, stableSymbols) {
|
|
556
|
+
const upper = symbol.toUpperCase();
|
|
557
|
+
return stableSymbols.findIndex((item) => item.toUpperCase() === upper);
|
|
558
|
+
}
|
|
559
|
+
function tokenPriority(token, stableSymbols = DEFAULT_STABLE_SYMBOLS) {
|
|
560
|
+
if (stableSymbolIndex(token.symbol, stableSymbols) >= 0) return 0;
|
|
561
|
+
if (isGasToken(token)) return 1;
|
|
562
|
+
return 2;
|
|
563
|
+
}
|
|
564
|
+
function compareTokensByPriority(left, right, stableSymbols = DEFAULT_STABLE_SYMBOLS) {
|
|
565
|
+
const leftPriority = tokenPriority(left, stableSymbols);
|
|
566
|
+
const rightPriority = tokenPriority(right, stableSymbols);
|
|
567
|
+
if (leftPriority !== rightPriority) return leftPriority - rightPriority;
|
|
568
|
+
if (leftPriority === 0) {
|
|
569
|
+
const leftIndex = stableSymbolIndex(left.symbol, stableSymbols);
|
|
570
|
+
const rightIndex = stableSymbolIndex(right.symbol, stableSymbols);
|
|
571
|
+
if (leftIndex !== rightIndex) return leftIndex - rightIndex;
|
|
572
|
+
}
|
|
573
|
+
return left.symbol.localeCompare(right.symbol) || left.chain.chainName.localeCompare(right.chain.chainName);
|
|
574
|
+
}
|
|
575
|
+
function sortTokensForChain(tokens, options) {
|
|
576
|
+
const stableSymbols = options.stableSymbols ?? DEFAULT_STABLE_SYMBOLS;
|
|
577
|
+
if (!options.withBalances) {
|
|
578
|
+
return tokens.slice().sort((left, right) => compareTokensByPriority(left, right, stableSymbols));
|
|
579
|
+
}
|
|
580
|
+
return tokens.slice().sort((left, right) => {
|
|
581
|
+
const leftRank = tokenBalanceRank(options.usdOf(left), options.loadingOf(left));
|
|
582
|
+
const rightRank = tokenBalanceRank(options.usdOf(right), options.loadingOf(right));
|
|
583
|
+
if (leftRank !== rightRank) return leftRank - rightRank;
|
|
584
|
+
if (leftRank === BALANCE_RANK_FUNDED) {
|
|
585
|
+
const byUsd = options.usdOf(right) - options.usdOf(left);
|
|
586
|
+
if (byUsd !== 0) return byUsd;
|
|
587
|
+
}
|
|
588
|
+
return compareTokensByPriority(left, right, stableSymbols);
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
var EVM_SEARCH_GROUP = "evm";
|
|
592
|
+
var EVM_SEARCH_GROUP_NAME = "EVM-based";
|
|
593
|
+
function groupSearchResults(tokens, query, options) {
|
|
594
|
+
const matched = tokens.filter((token) => tokenMatchesSearch(token, query));
|
|
595
|
+
const groups = /* @__PURE__ */ new Map();
|
|
596
|
+
for (const token of matched) {
|
|
597
|
+
const key = token.chain.chainKind === EVM_SEARCH_GROUP ? EVM_SEARCH_GROUP : token.blockchain;
|
|
598
|
+
const list = groups.get(key) ?? [];
|
|
599
|
+
list.push(token);
|
|
600
|
+
groups.set(key, list);
|
|
601
|
+
}
|
|
602
|
+
const stableSymbols = options.stableSymbols ?? DEFAULT_STABLE_SYMBOLS;
|
|
603
|
+
const built = Array.from(groups.entries()).map(([key, groupTokens]) => {
|
|
604
|
+
const chain = groupTokens[0].chain;
|
|
605
|
+
const folded = key === EVM_SEARCH_GROUP;
|
|
606
|
+
const sorted = options.role === "payer" ? sortTokensForChain(groupTokens, {
|
|
607
|
+
withBalances: options.hasOwner(chain.chainKind),
|
|
608
|
+
usdOf: options.usdOf,
|
|
609
|
+
loadingOf: options.loadingOf,
|
|
610
|
+
stableSymbols
|
|
611
|
+
}) : groupTokens.slice().sort((left, right) => compareTokensByPriority(left, right, stableSymbols));
|
|
612
|
+
return {
|
|
613
|
+
blockchain: folded ? EVM_SEARCH_GROUP : key,
|
|
614
|
+
chainName: folded ? EVM_SEARCH_GROUP_NAME : chain.chainName,
|
|
615
|
+
chainKind: chain.chainKind,
|
|
616
|
+
tokens: sorted,
|
|
617
|
+
usd: sorted.reduce((total, token) => {
|
|
618
|
+
const usd = options.usdOf(token);
|
|
619
|
+
return usd > 0 ? total + usd : total;
|
|
620
|
+
}, 0)
|
|
621
|
+
};
|
|
622
|
+
});
|
|
623
|
+
built.sort((left, right) => {
|
|
624
|
+
if (options.role === "payer" && right.usd !== left.usd) return right.usd - left.usd;
|
|
625
|
+
return left.chainName.localeCompare(right.chainName);
|
|
626
|
+
});
|
|
627
|
+
return built.map(({ blockchain, chainName, chainKind, tokens: groupTokens }) => ({
|
|
628
|
+
blockchain,
|
|
629
|
+
chainName,
|
|
630
|
+
chainKind,
|
|
631
|
+
tokens: groupTokens
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
861
634
|
function recentTokensInOrder(tokens, recentAssetIds) {
|
|
862
635
|
const byId = new Map(tokens.map((token) => [token.assetId, token]));
|
|
863
636
|
const result = [];
|
|
@@ -870,109 +643,86 @@ function recentTokensInOrder(tokens, recentAssetIds) {
|
|
|
870
643
|
|
|
871
644
|
// src/token-select/chain-pane.tsx
|
|
872
645
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
873
|
-
function lockReason(lockChainKind) {
|
|
874
|
-
return `Recipient address is on ${chainKindLabel(lockChainKind)}; edit the recipient to change chain`;
|
|
646
|
+
function lockReason(lockChainKind, labels) {
|
|
647
|
+
return `Recipient address is on ${chainKindLabel(lockChainKind, labels)}; edit the recipient to change chain`;
|
|
875
648
|
}
|
|
876
649
|
function ChainPane(props) {
|
|
877
650
|
const {
|
|
878
651
|
chains,
|
|
879
|
-
fundedBlockchains,
|
|
880
652
|
showWalletStatus = true,
|
|
881
|
-
|
|
653
|
+
onSelectChain,
|
|
882
654
|
lockChainKind = null,
|
|
883
655
|
disabledBlockchains = null,
|
|
884
|
-
disabledReason
|
|
656
|
+
disabledReason,
|
|
657
|
+
scrollRef
|
|
885
658
|
} = props;
|
|
886
|
-
const
|
|
659
|
+
const { chainKindLabels } = usePayWidgets();
|
|
887
660
|
function wrapDisabled(disabled, reason, content) {
|
|
888
661
|
if (!disabled || !reason) return content;
|
|
889
662
|
return /* @__PURE__ */ jsx3(Tooltip, { side: "right", triggerClassName: "min-w-0 flex-1", content: reason, children: content });
|
|
890
663
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
664
|
+
return /* @__PURE__ */ jsx3("div", { className: "flex h-full min-h-0 flex-col", children: /* @__PURE__ */ jsx3("div", { ref: scrollRef, className: cx("min-h-0 flex-1 overflow-y-auto", thinScrollbar), children: /* @__PURE__ */ jsxs2("div", { className: "px-2.5 pt-[17px] pb-3", children: [
|
|
665
|
+
/* @__PURE__ */ jsx3("p", { className: cx("px-3.5 text-sm font-medium text-[#aaa]", fontSans), children: "All Networks" }),
|
|
666
|
+
/* @__PURE__ */ jsx3("div", { className: "mt-1", children: chains.map((chain) => {
|
|
667
|
+
const locked = isChainKindLocked(chain.chainKind, lockChainKind);
|
|
668
|
+
const chainDisabled = isBlockchainDisabled(chain.blockchain, disabledBlockchains);
|
|
669
|
+
const disabled = locked || chainDisabled;
|
|
670
|
+
const reason = chainDisabled ? disabledReason : locked && lockChainKind ? lockReason(lockChainKind, chainKindLabels) : void 0;
|
|
671
|
+
return /* @__PURE__ */ jsxs2(
|
|
672
|
+
"div",
|
|
673
|
+
{
|
|
674
|
+
className: cx(
|
|
675
|
+
"flex h-[66px] w-full items-center gap-4 rounded-[12px] px-3.5",
|
|
676
|
+
disabled ? "opacity-40" : "hover:bg-[#f6f6f6]"
|
|
677
|
+
),
|
|
678
|
+
children: [
|
|
679
|
+
wrapDisabled(
|
|
680
|
+
disabled,
|
|
681
|
+
reason,
|
|
682
|
+
/* @__PURE__ */ jsxs2(
|
|
683
|
+
"button",
|
|
684
|
+
{
|
|
685
|
+
type: "button",
|
|
686
|
+
disabled,
|
|
687
|
+
onClick: () => {
|
|
688
|
+
if (disabled) return;
|
|
689
|
+
onSelectChain(chain.blockchain);
|
|
690
|
+
},
|
|
691
|
+
className: cx(
|
|
692
|
+
"flex min-w-0 flex-1 items-center gap-3 border-0 bg-transparent text-left",
|
|
693
|
+
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
|
694
|
+
),
|
|
695
|
+
children: [
|
|
696
|
+
/* @__PURE__ */ jsx3("img", { src: chain.logo, alt: "", className: "size-8 shrink-0 object-cover" }),
|
|
697
|
+
/* @__PURE__ */ jsx3("span", { className: cx("truncate text-sm font-medium text-black", fontSans), children: chain.chainName })
|
|
698
|
+
]
|
|
699
|
+
}
|
|
700
|
+
)
|
|
701
|
+
),
|
|
702
|
+
showWalletStatus ? /* @__PURE__ */ jsx3(ChainWalletStatus, { kind: chain.chainKind }) : /* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1" }),
|
|
703
|
+
/* @__PURE__ */ jsx3(
|
|
909
704
|
"button",
|
|
910
705
|
{
|
|
911
706
|
type: "button",
|
|
707
|
+
"aria-label": `Open ${chain.chainName}`,
|
|
912
708
|
disabled,
|
|
913
709
|
onClick: () => {
|
|
914
710
|
if (disabled) return;
|
|
915
|
-
|
|
711
|
+
onSelectChain(chain.blockchain);
|
|
916
712
|
},
|
|
917
713
|
className: cx(
|
|
918
|
-
"flex
|
|
714
|
+
"inline-flex shrink-0 border-0 bg-transparent p-0 text-[#606060]",
|
|
919
715
|
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
|
920
716
|
),
|
|
921
|
-
children:
|
|
922
|
-
/* @__PURE__ */ jsxs2("span", { className: "relative size-8 shrink-0", children: [
|
|
923
|
-
/* @__PURE__ */ jsx3("img", { src: chain.logo, alt: "", className: "size-8 object-cover" }),
|
|
924
|
-
fundedBlockchains.has(chain.blockchain) ? /* @__PURE__ */ jsx3("span", { className: "absolute -top-0.5 -right-0.5 size-2 rounded-full border border-white bg-[#06f] md:size-2.5" }) : null
|
|
925
|
-
] }),
|
|
926
|
-
/* @__PURE__ */ jsx3("span", { className: cx("truncate text-sm font-medium text-black md:text-base", fontSans), children: chain.chainName })
|
|
927
|
-
]
|
|
717
|
+
children: /* @__PURE__ */ jsx3(IconChevron, {})
|
|
928
718
|
}
|
|
929
719
|
)
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
chain.blockchain
|
|
935
|
-
);
|
|
936
|
-
}
|
|
937
|
-
return /* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 flex-1 flex-col", children: [
|
|
938
|
-
/* @__PURE__ */ jsx3("p", { className: cx("shrink-0 text-sm font-medium text-[#aaa]", fontSans), children: "All Networks" }),
|
|
939
|
-
/* @__PURE__ */ jsx3("div", { className: "mt-3 flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto", children: groupEvmChains(chains).map((entry) => {
|
|
940
|
-
if (entry.kind === "chain") return chainRow(entry.chain, false);
|
|
941
|
-
return /* @__PURE__ */ jsxs2("div", { className: "rounded-[12px] px-2 md:px-3 py-2 hover:bg-[rgba(185,215,255,0.1)]", children: [
|
|
942
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
|
|
943
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3", children: [
|
|
944
|
-
/* @__PURE__ */ jsx3("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-[10px] bg-[#627EEA]", children: /* @__PURE__ */ jsx3("img", { src: evmTypeLogoUrl(), alt: "", className: "size-5 object-contain" }) }),
|
|
945
|
-
/* @__PURE__ */ jsx3("span", { className: cx("truncate text-sm font-medium text-black md:text-base", fontSans), children: "EVM-based" })
|
|
946
|
-
] }),
|
|
947
|
-
showWalletStatus ? /* @__PURE__ */ jsx3(ChainWalletStatus, { kind: "evm" }) : /* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1" })
|
|
948
|
-
] }),
|
|
949
|
-
/* @__PURE__ */ jsxs2("div", { className: "mt-2 flex items-center gap-2", children: [
|
|
950
|
-
evmExpanded ? /* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1" }) : /* @__PURE__ */ jsx3("span", { className: "flex min-w-0 flex-1 overflow-hidden", children: entry.chains.map((chain, index) => /* @__PURE__ */ jsx3(
|
|
951
|
-
"img",
|
|
952
|
-
{
|
|
953
|
-
src: chain.logo,
|
|
954
|
-
alt: "",
|
|
955
|
-
style: { zIndex: entry.chains.length - index },
|
|
956
|
-
className: index === 0 ? "relative size-4 shrink-0 rounded-[4px] border border-white object-cover" : "relative -ml-1.5 size-4 shrink-0 rounded-[4px] border border-white object-cover"
|
|
957
|
-
},
|
|
958
|
-
chain.blockchain
|
|
959
|
-
)) }),
|
|
960
|
-
/* @__PURE__ */ jsx3(
|
|
961
|
-
"button",
|
|
962
|
-
{
|
|
963
|
-
type: "button",
|
|
964
|
-
"aria-expanded": evmExpanded,
|
|
965
|
-
"aria-label": evmExpanded ? "Collapse EVM-based" : "Expand EVM-based",
|
|
966
|
-
onClick: () => setEvmExpanded((open) => !open),
|
|
967
|
-
className: "ml-auto inline-flex shrink-0 cursor-pointer border-0 bg-transparent p-1 text-[#606060]",
|
|
968
|
-
children: /* @__PURE__ */ jsx3(IconArrowDown, { className: cx("size-3 transition-transform", evmExpanded ? "rotate-180" : "") })
|
|
969
|
-
}
|
|
970
|
-
)
|
|
971
|
-
] }),
|
|
972
|
-
evmExpanded ? /* @__PURE__ */ jsx3("div", { className: "mt-2 border-t border-[#e3e3e3] pt-1", children: entry.chains.map((chain) => chainRow(chain, true)) }) : null
|
|
973
|
-
] }, "evm");
|
|
720
|
+
]
|
|
721
|
+
},
|
|
722
|
+
chain.blockchain
|
|
723
|
+
);
|
|
974
724
|
}) })
|
|
975
|
-
] });
|
|
725
|
+
] }) }) });
|
|
976
726
|
}
|
|
977
727
|
|
|
978
728
|
// src/token-select/network-rail.tsx
|
|
@@ -982,99 +732,257 @@ import { Tooltip as Tooltip2 } from "@stableflow/pay-ui/tooltip";
|
|
|
982
732
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
983
733
|
function chipClass(selected, disabled) {
|
|
984
734
|
return cx(
|
|
985
|
-
"relative flex size-
|
|
735
|
+
"relative flex size-[50px] shrink-0 items-center justify-center rounded-[12px] border border-solid",
|
|
986
736
|
selected ? "border-[#06f] bg-[rgba(0,102,255,0.1)]" : "border-transparent bg-transparent",
|
|
987
737
|
disabled ? "cursor-not-allowed opacity-40" : "cursor-pointer hover:bg-black/5"
|
|
988
738
|
);
|
|
989
739
|
}
|
|
990
|
-
function NetworkRail(props) {
|
|
991
|
-
const {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
740
|
+
function NetworkRail(props) {
|
|
741
|
+
const {
|
|
742
|
+
role,
|
|
743
|
+
walletConnected,
|
|
744
|
+
selection,
|
|
745
|
+
chains,
|
|
746
|
+
fundedBlockchains,
|
|
747
|
+
lockChainKind = null,
|
|
748
|
+
disabledBlockchains = null,
|
|
749
|
+
disabledReason,
|
|
750
|
+
onSelectWallet,
|
|
751
|
+
onSelectAll,
|
|
752
|
+
onSelectNetworks,
|
|
753
|
+
onSelectChain,
|
|
754
|
+
className
|
|
755
|
+
} = props;
|
|
756
|
+
const { chainKindLabels } = usePayWidgets();
|
|
757
|
+
const payer = role === "payer";
|
|
758
|
+
const networksSelected = selection.kind === "networks";
|
|
759
|
+
const allSelected = selection.kind === "all";
|
|
760
|
+
const walletSelected = selection.kind === "wallet";
|
|
761
|
+
function wrapDisabled(disabled, reason, content) {
|
|
762
|
+
if (!disabled || !reason) return content;
|
|
763
|
+
return /* @__PURE__ */ jsx4(Tooltip2, { side: "right", content: reason, children: content });
|
|
764
|
+
}
|
|
765
|
+
function chainButton(chain) {
|
|
766
|
+
const locked = isChainKindLocked(chain.chainKind, lockChainKind);
|
|
767
|
+
const chainDisabled = isBlockchainDisabled(chain.blockchain, disabledBlockchains);
|
|
768
|
+
const disabled = locked || chainDisabled;
|
|
769
|
+
const reason = chainDisabled ? disabledReason : locked ? `Recipient address is on ${chainKindLabel(lockChainKind, chainKindLabels)}; edit the recipient to change chain` : void 0;
|
|
770
|
+
const selected = selection.kind === "chain" && selection.blockchain === chain.blockchain;
|
|
771
|
+
return /* @__PURE__ */ jsx4("span", { children: wrapDisabled(
|
|
772
|
+
disabled,
|
|
773
|
+
reason,
|
|
774
|
+
/* @__PURE__ */ jsxs3(
|
|
775
|
+
"button",
|
|
776
|
+
{
|
|
777
|
+
type: "button",
|
|
778
|
+
"aria-label": chain.chainName,
|
|
779
|
+
disabled,
|
|
780
|
+
onClick: () => {
|
|
781
|
+
if (disabled) return;
|
|
782
|
+
onSelectChain(chain.blockchain);
|
|
783
|
+
},
|
|
784
|
+
className: chipClass(selected, disabled),
|
|
785
|
+
children: [
|
|
786
|
+
/* @__PURE__ */ jsx4("img", { src: chain.logo, alt: "", className: "size-8 object-cover" }),
|
|
787
|
+
fundedBlockchains.has(chain.blockchain) ? /* @__PURE__ */ jsx4("span", { className: "absolute top-1 right-1 size-2.5 rounded-full border border-white bg-[#06f]" }) : null
|
|
788
|
+
]
|
|
789
|
+
}
|
|
790
|
+
)
|
|
791
|
+
) }, chain.blockchain);
|
|
792
|
+
}
|
|
793
|
+
return /* @__PURE__ */ jsxs3("div", { className: cx("box-border flex h-full w-[76px] shrink-0 flex-col items-center border-r border-[#e3e3e3] pt-3", className), children: [
|
|
794
|
+
payer && walletConnected ? /* @__PURE__ */ jsx4("button", { type: "button", "aria-label": "Wallet", onClick: onSelectWallet, className: chipClass(walletSelected, false), children: /* @__PURE__ */ jsx4(IconWallet, { className: walletSelected ? "size-5 text-[#06f]" : "size-5 text-black" }) }) : null,
|
|
795
|
+
payer && !walletConnected ? /* @__PURE__ */ jsx4("button", { type: "button", "aria-label": "All networks", onClick: onSelectNetworks, className: chipClass(networksSelected, false), children: /* @__PURE__ */ jsx4(IconAllNetworks, { className: "size-5 text-black" }) }) : null,
|
|
796
|
+
!payer ? /* @__PURE__ */ jsx4("button", { type: "button", "aria-label": "All", onClick: onSelectAll, className: chipClass(allSelected, false), children: /* @__PURE__ */ jsx4("span", { className: cx("text-sm font-medium text-black", fontSans), children: "All" }) }) : null,
|
|
797
|
+
payer && walletConnected ? /* @__PURE__ */ jsxs3("div", { className: cx("mt-3.5 flex min-h-0 w-full flex-1 flex-col items-center gap-3.5 overflow-y-auto", thinScrollbar), children: [
|
|
798
|
+
chains.map((chain) => chainButton(chain)),
|
|
799
|
+
/* @__PURE__ */ jsx4("div", { className: "h-px w-8 shrink-0 bg-[#e3e3e3]" }),
|
|
800
|
+
/* @__PURE__ */ jsx4("button", { type: "button", "aria-label": "All networks", onClick: onSelectNetworks, className: chipClass(networksSelected, false), children: /* @__PURE__ */ jsx4(IconAllNetworks, { className: "size-5 text-black" }) }),
|
|
801
|
+
/* @__PURE__ */ jsx4("div", { className: "pb-3" })
|
|
802
|
+
] }) : null,
|
|
803
|
+
!payer ? /* @__PURE__ */ jsxs3("div", { className: cx("mt-3.5 flex min-h-0 w-full flex-1 flex-col items-center gap-3.5 overflow-y-auto", thinScrollbar), children: [
|
|
804
|
+
chains.map((chain) => chainButton(chain)),
|
|
805
|
+
/* @__PURE__ */ jsx4("div", { className: "pb-3" })
|
|
806
|
+
] }) : null
|
|
807
|
+
] });
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/token-select/pane-transition.tsx
|
|
811
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
812
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
813
|
+
var EASE = [0.32, 0.72, 0, 1];
|
|
814
|
+
var variants = {
|
|
815
|
+
enter: (direction) => ({ x: direction > 0 ? 24 : -24, opacity: 0 }),
|
|
816
|
+
center: { x: 0, opacity: 1 },
|
|
817
|
+
exit: (direction) => ({ x: direction > 0 ? -24 : 24, opacity: 0 })
|
|
818
|
+
};
|
|
819
|
+
function PaneTransition(props) {
|
|
820
|
+
const reduce = useReducedMotion();
|
|
821
|
+
if (reduce) {
|
|
822
|
+
return /* @__PURE__ */ jsx5("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: props.children });
|
|
823
|
+
}
|
|
824
|
+
return /* @__PURE__ */ jsx5("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsx5(AnimatePresence, { initial: false, custom: props.direction, children: /* @__PURE__ */ jsx5(
|
|
825
|
+
motion.div,
|
|
826
|
+
{
|
|
827
|
+
custom: props.direction,
|
|
828
|
+
variants,
|
|
829
|
+
initial: "enter",
|
|
830
|
+
animate: "center",
|
|
831
|
+
exit: "exit",
|
|
832
|
+
transition: { duration: 0.22, ease: EASE },
|
|
833
|
+
className: "absolute inset-0 flex min-h-0 flex-col",
|
|
834
|
+
children: props.children
|
|
835
|
+
},
|
|
836
|
+
props.paneKey
|
|
837
|
+
) }) });
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/token-select/prefs.ts
|
|
841
|
+
import { useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
842
|
+
var EMPTY_PREFS = { recentAssetIds: [], recentBlockchains: [] };
|
|
843
|
+
function rememberRecentAsset(recent, assetId) {
|
|
844
|
+
const id = assetId.trim();
|
|
845
|
+
if (!id) return recent;
|
|
846
|
+
return [id, ...recent.filter((item) => item !== id)].slice(0, MAX_RECENT_ASSETS);
|
|
847
|
+
}
|
|
848
|
+
function rememberRecentBlockchain(recent, blockchain) {
|
|
849
|
+
const code = blockchain.trim();
|
|
850
|
+
if (!code) return recent;
|
|
851
|
+
return [code, ...recent.filter((item) => item !== code)].slice(0, MAX_RECENT_BLOCKCHAINS);
|
|
852
|
+
}
|
|
853
|
+
function migrateTokenSelectPrefs(persisted, version) {
|
|
854
|
+
const state = persisted ?? {};
|
|
855
|
+
const recentBlockchains = version >= 1 ? Array.isArray(state.recentBlockchains) ? state.recentBlockchains : [] : typeof state.lastBlockchain === "string" && state.lastBlockchain ? [state.lastBlockchain] : [];
|
|
856
|
+
const recentAssetIds = version >= TOKEN_SELECT_PREFS_VERSION && Array.isArray(state.recentAssetIds) ? state.recentAssetIds.filter((id) => typeof id === "string" && id.trim()).slice(0, MAX_RECENT_ASSETS) : typeof state.lastAssetId === "string" && state.lastAssetId ? [state.lastAssetId] : [];
|
|
857
|
+
return { recentAssetIds, recentBlockchains };
|
|
858
|
+
}
|
|
859
|
+
function readStorage() {
|
|
860
|
+
if (typeof localStorage === "undefined") return EMPTY_PREFS;
|
|
861
|
+
try {
|
|
862
|
+
const raw = localStorage.getItem(TOKEN_SELECT_PREFS_STORAGE_KEY);
|
|
863
|
+
if (!raw) return EMPTY_PREFS;
|
|
864
|
+
const parsed = JSON.parse(raw);
|
|
865
|
+
const version = typeof parsed.version === "number" ? parsed.version : 0;
|
|
866
|
+
const payload = parsed && typeof parsed === "object" && "state" in parsed ? parsed.state : parsed;
|
|
867
|
+
return migrateTokenSelectPrefs(payload, version);
|
|
868
|
+
} catch {
|
|
869
|
+
return EMPTY_PREFS;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function writeStorage(state) {
|
|
873
|
+
if (typeof localStorage === "undefined") return;
|
|
874
|
+
localStorage.setItem(TOKEN_SELECT_PREFS_STORAGE_KEY, JSON.stringify({
|
|
875
|
+
state,
|
|
876
|
+
version: TOKEN_SELECT_PREFS_VERSION
|
|
877
|
+
}));
|
|
878
|
+
}
|
|
879
|
+
var prefs = readStorage();
|
|
880
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
881
|
+
function emit2(next) {
|
|
882
|
+
prefs = next;
|
|
883
|
+
writeStorage(next);
|
|
884
|
+
for (const listener of listeners2) listener();
|
|
885
|
+
}
|
|
886
|
+
function subscribe2(listener) {
|
|
887
|
+
listeners2.add(listener);
|
|
888
|
+
return () => listeners2.delete(listener);
|
|
889
|
+
}
|
|
890
|
+
function getTokenSelectPrefs() {
|
|
891
|
+
return prefs;
|
|
892
|
+
}
|
|
893
|
+
function setLastToken(assetId, blockchain) {
|
|
894
|
+
emit2({
|
|
895
|
+
recentAssetIds: rememberRecentAsset(prefs.recentAssetIds, assetId),
|
|
896
|
+
recentBlockchains: rememberRecentBlockchain(prefs.recentBlockchains, blockchain)
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
function setLastBlockchain(blockchain) {
|
|
900
|
+
emit2({
|
|
901
|
+
...prefs,
|
|
902
|
+
recentBlockchains: rememberRecentBlockchain(prefs.recentBlockchains, blockchain)
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
function useTokenSelectPrefs() {
|
|
906
|
+
return useSyncExternalStore2(subscribe2, getTokenSelectPrefs, getTokenSelectPrefs);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// src/token-select/token-pane.tsx
|
|
910
|
+
import { IconChevron as IconChevron2 } from "@stableflow/pay-ui/icons/chevron";
|
|
911
|
+
import { Skeleton } from "@stableflow/pay-ui/skeleton";
|
|
912
|
+
|
|
913
|
+
// src/token-select/lazy-image.tsx
|
|
914
|
+
import { useEffect as useEffect2, useRef, useState } from "react";
|
|
915
|
+
import { useInView } from "motion/react";
|
|
916
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
917
|
+
function LazyImage(props) {
|
|
918
|
+
const { src, fallbackSrc, alt = "", className } = props;
|
|
919
|
+
const ref = useRef(null);
|
|
920
|
+
const inView = useInView(ref, { once: true });
|
|
921
|
+
const [failedSrc, setFailedSrc] = useState(null);
|
|
922
|
+
useEffect2(() => {
|
|
923
|
+
setFailedSrc(null);
|
|
924
|
+
}, [src]);
|
|
925
|
+
const requested = src.trim();
|
|
926
|
+
const useFallback = requested.length === 0 || failedSrc === requested;
|
|
927
|
+
const shown = useFallback ? fallbackSrc : requested;
|
|
928
|
+
return /* @__PURE__ */ jsx6("span", { ref, className: "block size-full", children: inView ? /* @__PURE__ */ jsx6(
|
|
929
|
+
"img",
|
|
930
|
+
{
|
|
931
|
+
src: shown,
|
|
932
|
+
alt,
|
|
933
|
+
className,
|
|
934
|
+
onError: () => {
|
|
935
|
+
if (!useFallback) setFailedSrc(requested);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
) : null });
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// src/token-select/logos.ts
|
|
942
|
+
var LOGO_HOST = "https://assets.dapdap.net";
|
|
943
|
+
var CHAIN_LOGO_ALIAS = {
|
|
944
|
+
eth: "ethereum",
|
|
945
|
+
ethereum: "ethereum",
|
|
946
|
+
base: "base",
|
|
947
|
+
arb: "arbitrum",
|
|
948
|
+
arbitrum: "arbitrum",
|
|
949
|
+
op: "optimism",
|
|
950
|
+
optimism: "optimism",
|
|
951
|
+
pol: "polygon",
|
|
952
|
+
polygon: "polygon",
|
|
953
|
+
bsc: "bsc",
|
|
954
|
+
avax: "avalanche",
|
|
955
|
+
avalanche: "avalanche",
|
|
956
|
+
gnosis: "gnosis",
|
|
957
|
+
scroll: "scroll",
|
|
958
|
+
xlayer: "xlayer",
|
|
959
|
+
bera: "berachain",
|
|
960
|
+
berachain: "berachain",
|
|
961
|
+
near: "near",
|
|
962
|
+
sol: "solana",
|
|
963
|
+
solana: "solana",
|
|
964
|
+
tron: "tron",
|
|
965
|
+
zec: "zcash",
|
|
966
|
+
zcash: "zcash"
|
|
967
|
+
};
|
|
968
|
+
var DEFAULT_TOKEN_LOGO = `${LOGO_HOST}/tokens/default_icon.png`;
|
|
969
|
+
function logoPath(path) {
|
|
970
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
971
|
+
return `${LOGO_HOST}${normalized}`;
|
|
972
|
+
}
|
|
973
|
+
function chainLogoUrl(blockchainOrNetwork) {
|
|
974
|
+
const key = String(blockchainOrNetwork || "").toLowerCase();
|
|
975
|
+
const file = CHAIN_LOGO_ALIAS[key] || key.replace(/\s+/g, "");
|
|
976
|
+
return logoPath(`/stableflow/networks/${file}.png`);
|
|
977
|
+
}
|
|
978
|
+
function tokenLogoUrl(symbol) {
|
|
979
|
+
const upper = String(symbol || "").toUpperCase();
|
|
980
|
+
const file = upper === "USDT0" ? "usdt" : upper.toLowerCase();
|
|
981
|
+
return logoPath(`/stableflow/tokens/${file}.png`);
|
|
1072
982
|
}
|
|
1073
983
|
|
|
1074
984
|
// src/token-select/token-pane.tsx
|
|
1075
|
-
import {
|
|
1076
|
-
import { Skeleton } from "@stableflow/pay-ui/skeleton";
|
|
1077
|
-
import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
985
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1078
986
|
function TokenPane(props) {
|
|
1079
987
|
const {
|
|
1080
988
|
sections,
|
|
@@ -1082,65 +990,67 @@ function TokenPane(props) {
|
|
|
1082
990
|
loading,
|
|
1083
991
|
showBalances = false,
|
|
1084
992
|
showWalletStatus = true,
|
|
993
|
+
header = null,
|
|
1085
994
|
getBalance,
|
|
1086
995
|
isBalanceLoading,
|
|
1087
996
|
onSelectToken,
|
|
1088
997
|
isTokenDisabled,
|
|
1089
|
-
search = "",
|
|
1090
|
-
onSearchChange,
|
|
1091
|
-
showSearch = false,
|
|
1092
|
-
searchClassName,
|
|
1093
998
|
rowClassName
|
|
1094
999
|
} = props;
|
|
1095
1000
|
const visibleSections = sections.filter((section) => section.title || section.tokens.length > 0);
|
|
1096
1001
|
const hasTokens = sections.some((section) => section.tokens.length > 0);
|
|
1097
|
-
return /* @__PURE__ */
|
|
1098
|
-
|
|
1099
|
-
|
|
1002
|
+
return /* @__PURE__ */ jsx7("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx7("div", { className: cx("min-h-0 flex-1 overflow-y-auto", thinScrollbar), children: /* @__PURE__ */ jsxs4("div", { className: "px-2.5 pt-[17px] pb-3", children: [
|
|
1003
|
+
header ? /* @__PURE__ */ jsx7(ChainListHeader, { header, showWalletStatus }) : null,
|
|
1004
|
+
visibleSections.map((section) => /* @__PURE__ */ jsxs4("section", { className: "mb-2", children: [
|
|
1005
|
+
section.title ? /* @__PURE__ */ jsxs4("div", { className: "mb-1 flex items-center justify-between gap-2 px-3.5", children: [
|
|
1006
|
+
/* @__PURE__ */ jsx7("p", { className: cx("min-w-0 truncate text-sm font-medium text-[#aaa]", fontSans), children: section.title }),
|
|
1007
|
+
showWalletStatus && section.walletKind ? /* @__PURE__ */ jsx7(ChainWalletStatus, { kind: section.walletKind }) : null
|
|
1008
|
+
] }) : null,
|
|
1009
|
+
section.tokens.map((token) => /* @__PURE__ */ jsx7(
|
|
1010
|
+
TokenRow,
|
|
1011
|
+
{
|
|
1012
|
+
token,
|
|
1013
|
+
selected: token.assetId === selectedAssetId,
|
|
1014
|
+
recent: token.assetId === section.badgeAssetId,
|
|
1015
|
+
showBalances,
|
|
1016
|
+
formatted: showBalances ? getBalance(token) : null,
|
|
1017
|
+
loadingBalance: showBalances && isBalanceLoading(token),
|
|
1018
|
+
disabled: Boolean(isTokenDisabled?.(token)),
|
|
1019
|
+
onSelect: () => onSelectToken(token),
|
|
1020
|
+
className: rowClassName
|
|
1021
|
+
},
|
|
1022
|
+
`${section.id}-${token.assetId}`
|
|
1023
|
+
))
|
|
1024
|
+
] }, section.id)),
|
|
1025
|
+
loading && !hasTokens ? /* @__PURE__ */ jsx7(TokenListSkeleton, {}) : null,
|
|
1026
|
+
!loading && !hasTokens ? /* @__PURE__ */ jsx7("p", { className: cx("px-3.5 py-4 text-[13px] text-[#606060]", fontSans), children: "No tokens found" }) : null
|
|
1027
|
+
] }) }) });
|
|
1028
|
+
}
|
|
1029
|
+
function ChainListHeader(props) {
|
|
1030
|
+
const { header, showWalletStatus } = props;
|
|
1031
|
+
const walletKind = header.walletKind;
|
|
1032
|
+
return /* @__PURE__ */ jsxs4("div", { className: "mb-1 flex items-center justify-between gap-3 px-3.5", children: [
|
|
1033
|
+
header.kind === "back" ? /* @__PURE__ */ jsxs4(
|
|
1034
|
+
"button",
|
|
1100
1035
|
{
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
)
|
|
1036
|
+
type: "button",
|
|
1037
|
+
onClick: header.onBack,
|
|
1038
|
+
className: cx("flex min-w-0 cursor-pointer items-center gap-3 border-0 bg-transparent p-0 text-sm font-medium text-[#aaa]", fontSans),
|
|
1039
|
+
children: [
|
|
1040
|
+
/* @__PURE__ */ jsx7(IconChevron2, { className: "rotate-180 text-[#aaa]" }),
|
|
1041
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate", children: header.title })
|
|
1042
|
+
]
|
|
1109
1043
|
}
|
|
1110
|
-
) :
|
|
1111
|
-
/* @__PURE__ */
|
|
1112
|
-
visibleSections.map((section) => /* @__PURE__ */ jsxs4("section", { className: "mb-2", children: [
|
|
1113
|
-
section.title ? /* @__PURE__ */ jsxs4("div", { className: "mb-1 flex items-center justify-between gap-2 px-2 md:px-3.5", children: [
|
|
1114
|
-
/* @__PURE__ */ jsx5("p", { className: cx("min-w-0 truncate text-sm font-medium text-[#aaa]", fontSans), children: section.title }),
|
|
1115
|
-
showWalletStatus && section.walletKind ? /* @__PURE__ */ jsx5(ChainWalletStatus, { kind: section.walletKind }) : null
|
|
1116
|
-
] }) : null,
|
|
1117
|
-
section.tokens.map((token) => /* @__PURE__ */ jsx5(
|
|
1118
|
-
TokenRow,
|
|
1119
|
-
{
|
|
1120
|
-
token,
|
|
1121
|
-
selected: token.assetId === selectedAssetId,
|
|
1122
|
-
recent: token.assetId === section.badgeAssetId,
|
|
1123
|
-
showBalances,
|
|
1124
|
-
formatted: showBalances ? getBalance(token) : null,
|
|
1125
|
-
loadingBalance: showBalances && isBalanceLoading(token),
|
|
1126
|
-
disabled: Boolean(isTokenDisabled?.(token)),
|
|
1127
|
-
onSelect: () => onSelectToken(token),
|
|
1128
|
-
className: rowClassName
|
|
1129
|
-
},
|
|
1130
|
-
`${section.id}-${token.assetId}`
|
|
1131
|
-
))
|
|
1132
|
-
] }, section.id)),
|
|
1133
|
-
loading && !hasTokens ? /* @__PURE__ */ jsx5(TokenListSkeleton, {}) : null,
|
|
1134
|
-
!loading && !hasTokens ? /* @__PURE__ */ jsx5("p", { className: cx("px-1 py-4 text-[13px] text-[#606060]", fontSans), children: "No tokens found" }) : null
|
|
1135
|
-
] })
|
|
1044
|
+
) : /* @__PURE__ */ jsx7("p", { className: cx("min-w-0 truncate text-sm font-medium text-[#aaa]", fontSans), children: header.title }),
|
|
1045
|
+
showWalletStatus && walletKind ? /* @__PURE__ */ jsx7(ChainWalletStatus, { kind: walletKind }) : null
|
|
1136
1046
|
] });
|
|
1137
1047
|
}
|
|
1138
1048
|
function TokenListSkeleton() {
|
|
1139
|
-
return /* @__PURE__ */
|
|
1140
|
-
/* @__PURE__ */
|
|
1049
|
+
return /* @__PURE__ */ jsx7("div", { "aria-hidden": true, children: Array.from({ length: 6 }, (_, index) => /* @__PURE__ */ jsxs4("div", { className: "flex h-[66px] items-center gap-2.5 px-3.5", children: [
|
|
1050
|
+
/* @__PURE__ */ jsx7(Skeleton, { className: "size-8 shrink-0 rounded-full" }),
|
|
1141
1051
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
|
|
1142
|
-
/* @__PURE__ */
|
|
1143
|
-
/* @__PURE__ */
|
|
1052
|
+
/* @__PURE__ */ jsx7(Skeleton, { className: "h-3.5 w-24" }),
|
|
1053
|
+
/* @__PURE__ */ jsx7(Skeleton, { className: "h-3 w-16" })
|
|
1144
1054
|
] })
|
|
1145
1055
|
] }, index)) });
|
|
1146
1056
|
}
|
|
@@ -1154,16 +1064,23 @@ function TokenRow(props) {
|
|
|
1154
1064
|
disabled,
|
|
1155
1065
|
onClick: onSelect,
|
|
1156
1066
|
className: cx(
|
|
1157
|
-
"flex w-full items-center justify-between gap-2 rounded-[12px] border-0 bg-transparent px-
|
|
1158
|
-
selected && "bg-[#
|
|
1067
|
+
"flex h-[66px] w-full cursor-pointer items-center justify-between gap-2 rounded-[12px] border-0 bg-transparent px-3.5 text-left hover:bg-[#f6f6f6]",
|
|
1068
|
+
selected && "bg-[#f6f6f6]",
|
|
1159
1069
|
disabled && "cursor-not-allowed opacity-40 hover:bg-transparent",
|
|
1160
1070
|
className
|
|
1161
1071
|
),
|
|
1162
1072
|
children: [
|
|
1163
1073
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-2.5", children: [
|
|
1164
1074
|
/* @__PURE__ */ jsxs4("span", { className: "relative size-8 shrink-0", children: [
|
|
1165
|
-
/* @__PURE__ */
|
|
1166
|
-
|
|
1075
|
+
/* @__PURE__ */ jsx7(
|
|
1076
|
+
LazyImage,
|
|
1077
|
+
{
|
|
1078
|
+
src: token.logo,
|
|
1079
|
+
fallbackSrc: DEFAULT_TOKEN_LOGO,
|
|
1080
|
+
className: "size-8 rounded-[12px] object-cover"
|
|
1081
|
+
}
|
|
1082
|
+
),
|
|
1083
|
+
/* @__PURE__ */ jsx7(
|
|
1167
1084
|
"img",
|
|
1168
1085
|
{
|
|
1169
1086
|
src: token.chain.logo,
|
|
@@ -1174,37 +1091,78 @@ function TokenRow(props) {
|
|
|
1174
1091
|
] }),
|
|
1175
1092
|
/* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
|
|
1176
1093
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
1177
|
-
/* @__PURE__ */
|
|
1178
|
-
recent ? /* @__PURE__ */
|
|
1094
|
+
/* @__PURE__ */ jsx7("span", { className: cx("truncate text-sm font-semibold text-black", fontSans), children: token.symbol }),
|
|
1095
|
+
recent ? /* @__PURE__ */ jsx7("span", { className: cx("shrink-0 rounded-[9px] bg-[#06f]/10 px-1.5 text-[10px] font-medium leading-[18px] text-[#06f]", fontSans), children: "Recently Used" }) : null
|
|
1179
1096
|
] }),
|
|
1180
|
-
/* @__PURE__ */
|
|
1097
|
+
/* @__PURE__ */ jsx7("span", { className: cx("mt-0.5 block truncate text-xs text-[#606060]", fontSans), children: token.chain.chainName })
|
|
1181
1098
|
] })
|
|
1182
1099
|
] }),
|
|
1183
|
-
showBalances ? /* @__PURE__ */
|
|
1100
|
+
showBalances ? /* @__PURE__ */ jsx7("span", { className: "shrink-0 text-right", children: loadingBalance ? /* @__PURE__ */ jsx7(
|
|
1184
1101
|
"span",
|
|
1185
1102
|
{
|
|
1186
1103
|
className: "inline-block size-3.5 animate-spin rounded-full border-2 border-[#606060] border-r-transparent",
|
|
1187
1104
|
"aria-label": "Loading balance"
|
|
1188
1105
|
}
|
|
1189
1106
|
) : formatted != null ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1190
|
-
/* @__PURE__ */
|
|
1191
|
-
usd >= 0 ? /* @__PURE__ */
|
|
1192
|
-
] }) : /* @__PURE__ */
|
|
1107
|
+
/* @__PURE__ */ jsx7("span", { className: cx("block text-sm font-medium text-black", fontSans), children: formatDisplayAmount(formatted, { prefix: "", maxDecimals: 4 }) }),
|
|
1108
|
+
usd >= 0 ? /* @__PURE__ */ jsx7("span", { className: cx("block text-xs text-[#606060]", fontSans), children: formatDisplayAmount(usd, { prefix: "$", showDust: true }) }) : null
|
|
1109
|
+
] }) : /* @__PURE__ */ jsx7("span", { className: cx("text-sm font-medium text-black", fontSans), children: "-" }) }) : null
|
|
1193
1110
|
]
|
|
1194
1111
|
}
|
|
1195
1112
|
);
|
|
1196
1113
|
}
|
|
1197
1114
|
|
|
1115
|
+
// src/token-select/search-results.tsx
|
|
1116
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1117
|
+
function SearchResults(props) {
|
|
1118
|
+
const {
|
|
1119
|
+
groups,
|
|
1120
|
+
selectedAssetId,
|
|
1121
|
+
loading,
|
|
1122
|
+
showBalances = false,
|
|
1123
|
+
showWalletStatus = true,
|
|
1124
|
+
getBalance,
|
|
1125
|
+
isBalanceLoading,
|
|
1126
|
+
onSelectToken,
|
|
1127
|
+
isTokenDisabled,
|
|
1128
|
+
rowClassName
|
|
1129
|
+
} = props;
|
|
1130
|
+
const hasTokens = groups.some((group) => group.tokens.length > 0);
|
|
1131
|
+
return /* @__PURE__ */ jsx8("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx8("div", { className: cx("min-h-0 flex-1 overflow-y-auto", thinScrollbar), children: /* @__PURE__ */ jsxs5("div", { className: "px-2.5 pt-[17px] pb-3", children: [
|
|
1132
|
+
groups.map((group) => /* @__PURE__ */ jsxs5("section", { className: "mb-2", children: [
|
|
1133
|
+
/* @__PURE__ */ jsxs5("div", { className: "mb-1 flex items-center justify-between gap-2 px-3.5", children: [
|
|
1134
|
+
/* @__PURE__ */ jsx8("p", { className: cx("min-w-0 truncate text-sm font-medium text-[#aaa]", fontSans), children: group.chainName }),
|
|
1135
|
+
showWalletStatus ? /* @__PURE__ */ jsx8(ChainWalletStatus, { kind: group.chainKind }) : null
|
|
1136
|
+
] }),
|
|
1137
|
+
group.tokens.map((token) => /* @__PURE__ */ jsx8(
|
|
1138
|
+
TokenRow,
|
|
1139
|
+
{
|
|
1140
|
+
token,
|
|
1141
|
+
selected: token.assetId === selectedAssetId,
|
|
1142
|
+
recent: false,
|
|
1143
|
+
showBalances,
|
|
1144
|
+
formatted: showBalances ? getBalance(token) : null,
|
|
1145
|
+
loadingBalance: showBalances && isBalanceLoading(token),
|
|
1146
|
+
disabled: Boolean(isTokenDisabled?.(token)),
|
|
1147
|
+
onSelect: () => onSelectToken(token),
|
|
1148
|
+
className: rowClassName
|
|
1149
|
+
},
|
|
1150
|
+
token.assetId
|
|
1151
|
+
))
|
|
1152
|
+
] }, group.blockchain)),
|
|
1153
|
+
!loading && !hasTokens ? /* @__PURE__ */ jsx8("p", { className: cx("px-3.5 py-4 text-[13px] text-[#606060]", fontSans), children: "No tokens found" }) : null
|
|
1154
|
+
] }) }) });
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// src/token-select/types.ts
|
|
1158
|
+
var KNOWN_CHAIN_KINDS = ["evm", "near", "solana", "tron", "zec"];
|
|
1159
|
+
function ownerKey2(owners) {
|
|
1160
|
+
return Object.keys(owners).sort().map((kind) => `${kind}:${owners[kind] ?? ""}`).join("|");
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1198
1163
|
// src/token-select/TokenSelectDialog.tsx
|
|
1199
|
-
import { jsx as
|
|
1164
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1200
1165
|
var SEARCH_INPUT_CLASS = "h-[36px] rounded-[18px] border-[#e3e3e3] bg-[#f6f6f6] text-sm placeholder:text-black/30";
|
|
1201
|
-
function chainsFromTokens(tokens) {
|
|
1202
|
-
const byCode = /* @__PURE__ */ new Map();
|
|
1203
|
-
for (const token of tokens) {
|
|
1204
|
-
if (!byCode.has(token.blockchain)) byCode.set(token.blockchain, token.chain);
|
|
1205
|
-
}
|
|
1206
|
-
return Array.from(byCode.values());
|
|
1207
|
-
}
|
|
1208
1166
|
function ownerForToken(owners, token) {
|
|
1209
1167
|
return owners[token.chain.chainKind] ?? null;
|
|
1210
1168
|
}
|
|
@@ -1215,6 +1173,15 @@ function isNativeToken(token) {
|
|
|
1215
1173
|
const addr = String(token.contractAddress || "").trim().toLowerCase();
|
|
1216
1174
|
return !addr || addr === "native" || addr === "0x0000000000000000000000000000000000000000";
|
|
1217
1175
|
}
|
|
1176
|
+
function railSelection(view) {
|
|
1177
|
+
if (view.kind === "wallet") return { kind: "wallet" };
|
|
1178
|
+
if (view.kind === "all") return { kind: "all" };
|
|
1179
|
+
if (view.kind === "rail-chain") return { kind: "chain", blockchain: view.blockchain };
|
|
1180
|
+
return { kind: "networks" };
|
|
1181
|
+
}
|
|
1182
|
+
function chainByCode(chains, blockchain) {
|
|
1183
|
+
return chains.find((chain) => chain.blockchain === blockchain);
|
|
1184
|
+
}
|
|
1218
1185
|
function TokenSelectDialog(props) {
|
|
1219
1186
|
const {
|
|
1220
1187
|
open,
|
|
@@ -1231,6 +1198,7 @@ function TokenSelectDialog(props) {
|
|
|
1231
1198
|
disabledReason,
|
|
1232
1199
|
className,
|
|
1233
1200
|
titleClassName,
|
|
1201
|
+
headerClassName,
|
|
1234
1202
|
closeClassName,
|
|
1235
1203
|
contentClassName,
|
|
1236
1204
|
searchClassName,
|
|
@@ -1239,11 +1207,19 @@ function TokenSelectDialog(props) {
|
|
|
1239
1207
|
} = props;
|
|
1240
1208
|
const payer = role === "payer";
|
|
1241
1209
|
const showBalances = payer;
|
|
1242
|
-
const {
|
|
1243
|
-
|
|
1210
|
+
const {
|
|
1211
|
+
wallet,
|
|
1212
|
+
popularTokens: popularRefs,
|
|
1213
|
+
tokens,
|
|
1214
|
+
loading,
|
|
1215
|
+
stableSymbols,
|
|
1216
|
+
balanceMode,
|
|
1217
|
+
getBalance: syncGetBalance,
|
|
1218
|
+
isBalanceLoading: syncIsBalanceLoading
|
|
1219
|
+
} = usePayWidgets();
|
|
1244
1220
|
const prefs2 = useTokenSelectPrefs();
|
|
1245
|
-
const balanceEntries =
|
|
1246
|
-
const connectedOwners =
|
|
1221
|
+
const balanceEntries = useSyncExternalStore3(subscribePayTokenBalances, getPayTokenBalances, getPayTokenBalances);
|
|
1222
|
+
const connectedOwners = useMemo(() => {
|
|
1247
1223
|
const owners2 = {};
|
|
1248
1224
|
for (const [kind, account] of Object.entries(wallet.accounts)) {
|
|
1249
1225
|
const address = account?.address?.trim();
|
|
@@ -1251,53 +1227,57 @@ function TokenSelectDialog(props) {
|
|
|
1251
1227
|
}
|
|
1252
1228
|
return owners2;
|
|
1253
1229
|
}, [wallet.accounts]);
|
|
1254
|
-
const owners =
|
|
1230
|
+
const owners = useMemo(
|
|
1231
|
+
() => showBalances ? { ...connectedOwners, ...balanceOwners } : {},
|
|
1232
|
+
[showBalances, connectedOwners, balanceOwners]
|
|
1233
|
+
);
|
|
1255
1234
|
const walletConnected = hasAnyOwner(connectedOwners);
|
|
1256
|
-
const walletWasConnected = useRef(null);
|
|
1257
1235
|
const [search, setSearch] = useState2("");
|
|
1258
|
-
const [
|
|
1259
|
-
const [
|
|
1260
|
-
const
|
|
1236
|
+
const [view, setView] = useState2(payer ? { kind: "networks" } : { kind: "all" });
|
|
1237
|
+
const [slideDirection, setSlideDirection] = useState2(1);
|
|
1238
|
+
const networksScrollRef = useRef2(null);
|
|
1239
|
+
const savedNetworksScroll = useRef2(0);
|
|
1240
|
+
const walletConnectedRef = useRef2(walletConnected);
|
|
1241
|
+
const payerRef = useRef2(payer);
|
|
1242
|
+
const lockRef = useRef2(lockChainKind);
|
|
1243
|
+
const tokensRef = useRef2(tokens);
|
|
1244
|
+
walletConnectedRef.current = walletConnected;
|
|
1245
|
+
payerRef.current = payer;
|
|
1246
|
+
lockRef.current = lockChainKind;
|
|
1247
|
+
tokensRef.current = tokens;
|
|
1261
1248
|
useEffect3(() => {
|
|
1262
1249
|
if (!open) return;
|
|
1263
1250
|
setSearch("");
|
|
1264
|
-
|
|
1265
|
-
|
|
1251
|
+
const lock = lockRef.current;
|
|
1252
|
+
if (lock) {
|
|
1253
|
+
const filter = initialChainFilter(
|
|
1254
|
+
lock,
|
|
1255
|
+
getTokenSelectPrefs().recentBlockchains,
|
|
1256
|
+
chainsFromTokens(tokensRef.current)
|
|
1257
|
+
);
|
|
1258
|
+
if (filter !== ALL_CHAIN_FILTER) {
|
|
1259
|
+
setView({ kind: "rail-chain", blockchain: filter });
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1266
1262
|
return;
|
|
1267
1263
|
}
|
|
1268
|
-
if (!
|
|
1269
|
-
setView("
|
|
1270
|
-
|
|
1264
|
+
if (!payerRef.current) {
|
|
1265
|
+
setView({ kind: "all" });
|
|
1266
|
+
return;
|
|
1271
1267
|
}
|
|
1272
|
-
|
|
1268
|
+
setView(walletConnectedRef.current ? { kind: "wallet" } : { kind: "networks" });
|
|
1269
|
+
}, [open]);
|
|
1273
1270
|
useEffect3(() => {
|
|
1274
|
-
if (!open || !lockChainKind
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
chainsFromTokens(tokens)
|
|
1279
|
-
));
|
|
1271
|
+
if (!open || !lockChainKind) return;
|
|
1272
|
+
const filter = initialChainFilter(lockChainKind, getTokenSelectPrefs().recentBlockchains, chainsFromTokens(tokens));
|
|
1273
|
+
if (filter === ALL_CHAIN_FILTER) return;
|
|
1274
|
+
setView((current) => current.kind === "rail-chain" ? current : { kind: "rail-chain", blockchain: filter });
|
|
1280
1275
|
}, [open, lockChainKind, tokens]);
|
|
1281
|
-
|
|
1282
|
-
if (!open) {
|
|
1283
|
-
walletWasConnected.current = null;
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
if (lockChainKind || !payer) {
|
|
1287
|
-
walletWasConnected.current = walletConnected;
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
const previous = walletWasConnected.current;
|
|
1291
|
-
walletWasConnected.current = walletConnected;
|
|
1292
|
-
if (previous !== null && previous === walletConnected) return;
|
|
1293
|
-
setView(walletConnected ? "token" : "network");
|
|
1294
|
-
setChainFilter(ALL_CHAIN_FILTER);
|
|
1295
|
-
}, [open, lockChainKind, payer, walletConnected]);
|
|
1296
|
-
const allowed = useMemo2(() => {
|
|
1276
|
+
const allowed = useMemo(() => {
|
|
1297
1277
|
if (!allowedBlockchains || allowedBlockchains.length === 0) return null;
|
|
1298
1278
|
return new Set(allowedBlockchains.map((code) => code.toLowerCase()));
|
|
1299
1279
|
}, [allowedBlockchains]);
|
|
1300
|
-
const scopedTokens =
|
|
1280
|
+
const scopedTokens = useMemo(() => {
|
|
1301
1281
|
return tokens.filter((token) => {
|
|
1302
1282
|
if (allowed && !allowed.has(token.blockchain.toLowerCase())) return false;
|
|
1303
1283
|
if (excludeNative && isNativeToken(token)) return false;
|
|
@@ -1306,22 +1286,25 @@ function TokenSelectDialog(props) {
|
|
|
1306
1286
|
return true;
|
|
1307
1287
|
});
|
|
1308
1288
|
}, [tokens, allowed, excludeNative, payer]);
|
|
1309
|
-
const
|
|
1310
|
-
const tokenKey =
|
|
1311
|
-
const ownersRef =
|
|
1312
|
-
const
|
|
1289
|
+
const ownersKey = ownerKey2(owners);
|
|
1290
|
+
const tokenKey = useMemo(() => scopedTokens.map((token) => token.assetId).join("|"), [scopedTokens]);
|
|
1291
|
+
const ownersRef = useRef2(owners);
|
|
1292
|
+
const scopedRef = useRef2(scopedTokens);
|
|
1313
1293
|
ownersRef.current = owners;
|
|
1314
|
-
|
|
1294
|
+
scopedRef.current = scopedTokens;
|
|
1315
1295
|
useEffect3(() => {
|
|
1316
|
-
if (!showBalances || !open || !hasAnyOwner(ownersRef.current) || !tokenKey) return;
|
|
1317
|
-
void ensurePayTokenBalances(ownersRef.current,
|
|
1296
|
+
if (balanceMode !== "async" || !showBalances || !open || !hasAnyOwner(ownersRef.current) || !tokenKey) return;
|
|
1297
|
+
void ensurePayTokenBalances(ownersRef.current, scopedRef.current);
|
|
1318
1298
|
const id = window.setInterval(() => {
|
|
1319
|
-
void ensurePayTokenBalances(ownersRef.current,
|
|
1299
|
+
void ensurePayTokenBalances(ownersRef.current, scopedRef.current, { force: true });
|
|
1320
1300
|
}, TOKEN_BALANCE_POLL_MS);
|
|
1321
1301
|
return () => window.clearInterval(id);
|
|
1322
|
-
}, [showBalances, open,
|
|
1302
|
+
}, [balanceMode, showBalances, open, ownersKey, tokenKey]);
|
|
1323
1303
|
function balanceOf(token) {
|
|
1324
|
-
|
|
1304
|
+
const owner = ownerForToken(owners, token);
|
|
1305
|
+
if (!showBalances || !owner) return void 0;
|
|
1306
|
+
if (balanceMode === "sync" && syncGetBalance) return syncGetBalance(token, owner) ?? void 0;
|
|
1307
|
+
return getPayTokenBalance(owner, token.assetId)?.formatted;
|
|
1325
1308
|
}
|
|
1326
1309
|
function usdOf(token) {
|
|
1327
1310
|
if (!showBalances) return -1;
|
|
@@ -1331,52 +1314,50 @@ function TokenSelectDialog(props) {
|
|
|
1331
1314
|
if (!showBalances) return false;
|
|
1332
1315
|
const owner = ownerForToken(owners, token);
|
|
1333
1316
|
if (!owner) return false;
|
|
1317
|
+
const formatted = balanceOf(token);
|
|
1318
|
+
if (formatted != null && formatted !== "") return false;
|
|
1319
|
+
if (balanceMode === "sync") return syncIsBalanceLoading?.(token, owner) ?? false;
|
|
1334
1320
|
const entry = getPayTokenBalance(owner, token.assetId);
|
|
1335
|
-
return
|
|
1321
|
+
return !entry || entry.status === "loading";
|
|
1336
1322
|
}
|
|
1337
|
-
const availableChains =
|
|
1338
|
-
|
|
1339
|
-
const byCode = /* @__PURE__ */ new Map();
|
|
1340
|
-
for (const token of scopedTokens) {
|
|
1341
|
-
if (codes.has(token.blockchain) && !byCode.has(token.blockchain)) byCode.set(token.blockchain, token.chain);
|
|
1342
|
-
}
|
|
1343
|
-
return Array.from(byCode.values());
|
|
1344
|
-
}, [scopedTokens]);
|
|
1345
|
-
const sortedChains = useMemo2(
|
|
1346
|
-
() => sortChainsForSidebar(availableChains, scopedTokens, {
|
|
1347
|
-
showBalances,
|
|
1348
|
-
walletConnected,
|
|
1349
|
-
getBalanceUsd: (token) => tokenBalanceUsd(token, getPayTokenBalance(ownerForToken(owners, token), token.assetId)?.formatted)
|
|
1350
|
-
}),
|
|
1351
|
-
[availableChains, scopedTokens, showBalances, walletConnected, owners, balanceEntries]
|
|
1352
|
-
);
|
|
1353
|
-
const fundedBlockchains = useMemo2(() => {
|
|
1323
|
+
const availableChains = useMemo(() => chainsFromTokens(scopedTokens), [scopedTokens]);
|
|
1324
|
+
const fundedBlockchains = useMemo(() => {
|
|
1354
1325
|
const funded = /* @__PURE__ */ new Set();
|
|
1355
1326
|
if (!showBalances) return funded;
|
|
1356
1327
|
for (const chain of availableChains) {
|
|
1357
|
-
if (chainHasBalance(chain.blockchain, scopedTokens, (token) =>
|
|
1328
|
+
if (chainHasBalance(chain.blockchain, scopedTokens, (token) => balanceOf(token))) {
|
|
1358
1329
|
funded.add(chain.blockchain);
|
|
1359
1330
|
}
|
|
1360
1331
|
}
|
|
1361
1332
|
return funded;
|
|
1362
|
-
}, [availableChains, owners, scopedTokens, showBalances, balanceEntries]);
|
|
1363
|
-
const
|
|
1333
|
+
}, [availableChains, owners, scopedTokens, showBalances, balanceEntries, balanceMode, syncGetBalance, syncIsBalanceLoading]);
|
|
1334
|
+
const railChains = useMemo(() => {
|
|
1335
|
+
if (!payer) return sortChainsAlpha(availableChains);
|
|
1336
|
+
const connected = payerRailChains(availableChains, owners, fundedBlockchains);
|
|
1337
|
+
return sortChainsForSidebar(connected, scopedTokens, {
|
|
1338
|
+
showBalances,
|
|
1339
|
+
getBalanceUsd: (token) => tokenBalanceUsd(token, balanceOf(token))
|
|
1340
|
+
});
|
|
1341
|
+
}, [payer, availableChains, owners, fundedBlockchains, scopedTokens, showBalances, balanceEntries, balanceMode, syncGetBalance]);
|
|
1342
|
+
const networkChains = useMemo(() => sortChainsAlpha(availableChains), [availableChains]);
|
|
1343
|
+
useEffect3(() => {
|
|
1344
|
+
if (!open || !payer || lockChainKind) return;
|
|
1345
|
+
setView((current) => {
|
|
1346
|
+
if (current.kind === "wallet" && !walletConnected) return { kind: "networks" };
|
|
1347
|
+
if (current.kind === "rail-chain" && !railChains.some((chain) => chain.blockchain === current.blockchain)) {
|
|
1348
|
+
return walletConnected ? { kind: "wallet" } : { kind: "networks" };
|
|
1349
|
+
}
|
|
1350
|
+
return current;
|
|
1351
|
+
});
|
|
1352
|
+
}, [open, payer, lockChainKind, walletConnected, railChains]);
|
|
1353
|
+
const popularTokens = useMemo(
|
|
1364
1354
|
() => popularTokensForWallets(scopedTokens, popularRefs, (kind) => Boolean(connectedOwners[kind])),
|
|
1365
1355
|
[scopedTokens, popularRefs, connectedOwners]
|
|
1366
1356
|
);
|
|
1367
1357
|
const newestAssetId = showBalances ? prefs2.recentAssetIds[0] ?? null : null;
|
|
1368
|
-
const
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
if (showBalances && chainFilter === ALL_CHAIN_FILTER && searchingAll) {
|
|
1372
|
-
const matched2 = scopedTokens.filter((token) => tokenMatchesSearch(token, search));
|
|
1373
|
-
return [{
|
|
1374
|
-
id: "search",
|
|
1375
|
-
tokens: sortTokensByBalance(matched2, usdOf, loadingOf),
|
|
1376
|
-
badgeAssetId: newestAssetId
|
|
1377
|
-
}];
|
|
1378
|
-
}
|
|
1379
|
-
if (showBalances && chainFilter === ALL_CHAIN_FILTER) {
|
|
1358
|
+
const searching = search.trim().length > 0;
|
|
1359
|
+
const sections = useMemo(() => {
|
|
1360
|
+
if (view.kind === "wallet") {
|
|
1380
1361
|
const next = [];
|
|
1381
1362
|
const recent = recentTokensInOrder(scopedTokens, prefs2.recentAssetIds);
|
|
1382
1363
|
if (recent.length > 0) {
|
|
@@ -1396,42 +1377,87 @@ function TokenSelectDialog(props) {
|
|
|
1396
1377
|
}
|
|
1397
1378
|
return next;
|
|
1398
1379
|
}
|
|
1399
|
-
|
|
1400
|
-
if (!showBalances && chainFilter === ALL_CHAIN_FILTER && !search.trim()) {
|
|
1380
|
+
if (view.kind === "all") {
|
|
1401
1381
|
const next = [];
|
|
1402
1382
|
if (popularTokens.length > 0) {
|
|
1403
1383
|
next.push({ id: "popular", title: POPULAR_SECTION_TITLE, tokens: popularTokens });
|
|
1404
1384
|
}
|
|
1405
|
-
|
|
1385
|
+
const sorted = scopedTokens.slice().sort((left, right) => compareTokensByPriority(left, right, stableSymbols));
|
|
1386
|
+
next.push({ id: "all", tokens: sorted });
|
|
1406
1387
|
return next;
|
|
1407
1388
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1389
|
+
if (view.kind === "rail-chain" || view.kind === "network-chain") {
|
|
1390
|
+
const matched = scopedTokens.filter((token) => token.blockchain === view.blockchain);
|
|
1391
|
+
if (!payer) {
|
|
1392
|
+
const next = [];
|
|
1393
|
+
const popular = popularTokens.filter((token) => token.blockchain === view.blockchain);
|
|
1394
|
+
if (popular.length > 0) {
|
|
1395
|
+
next.push({ id: "popular", title: POPULAR_SECTION_TITLE, tokens: popular });
|
|
1396
|
+
}
|
|
1397
|
+
next.push({
|
|
1398
|
+
id: view.blockchain,
|
|
1399
|
+
tokens: matched.slice().sort((left, right) => compareTokensByPriority(left, right, stableSymbols))
|
|
1400
|
+
});
|
|
1401
|
+
return next;
|
|
1402
|
+
}
|
|
1403
|
+
const chain = chainByCode(availableChains, view.blockchain);
|
|
1404
|
+
return [{
|
|
1405
|
+
id: view.blockchain,
|
|
1406
|
+
tokens: sortTokensForChain(matched, {
|
|
1407
|
+
withBalances: Boolean(chain && owners[chain.chainKind]),
|
|
1408
|
+
usdOf,
|
|
1409
|
+
loadingOf,
|
|
1410
|
+
stableSymbols
|
|
1411
|
+
}),
|
|
1412
|
+
badgeAssetId: newestAssetId
|
|
1413
|
+
}];
|
|
1414
|
+
}
|
|
1415
|
+
return [];
|
|
1416
1416
|
}, [
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
searchingAll,
|
|
1420
|
-
search,
|
|
1417
|
+
view,
|
|
1418
|
+
payer,
|
|
1421
1419
|
scopedTokens,
|
|
1422
1420
|
prefs2.recentAssetIds,
|
|
1423
1421
|
newestAssetId,
|
|
1424
|
-
walletKind,
|
|
1425
1422
|
balanceEntries,
|
|
1426
1423
|
owners,
|
|
1427
1424
|
popularTokens,
|
|
1428
|
-
availableChains
|
|
1425
|
+
availableChains,
|
|
1426
|
+
stableSymbols,
|
|
1427
|
+
balanceMode,
|
|
1428
|
+
syncGetBalance,
|
|
1429
|
+
syncIsBalanceLoading
|
|
1429
1430
|
]);
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1431
|
+
const searchGroups = useMemo(() => {
|
|
1432
|
+
if (!searching) return [];
|
|
1433
|
+
return groupSearchResults(scopedTokens, search, {
|
|
1434
|
+
role: payer ? "payer" : "receiver",
|
|
1435
|
+
usdOf,
|
|
1436
|
+
loadingOf,
|
|
1437
|
+
hasOwner: (kind) => Boolean(owners[kind]),
|
|
1438
|
+
stableSymbols
|
|
1439
|
+
});
|
|
1440
|
+
}, [searching, scopedTokens, search, payer, owners, stableSymbols, balanceEntries, balanceMode, syncGetBalance, syncIsBalanceLoading]);
|
|
1441
|
+
function handleSelectChain(blockchain, fromNetworks) {
|
|
1442
|
+
setLastBlockchain(blockchain);
|
|
1443
|
+
if (fromNetworks) {
|
|
1444
|
+
savedNetworksScroll.current = networksScrollRef.current?.scrollTop ?? 0;
|
|
1445
|
+
setSlideDirection(1);
|
|
1446
|
+
setView({ kind: "network-chain", blockchain });
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
setView({ kind: "rail-chain", blockchain });
|
|
1434
1450
|
}
|
|
1451
|
+
function handleBackToNetworks() {
|
|
1452
|
+
setSlideDirection(-1);
|
|
1453
|
+
setView({ kind: "networks" });
|
|
1454
|
+
}
|
|
1455
|
+
useLayoutEffect(() => {
|
|
1456
|
+
if (view.kind !== "networks" || slideDirection !== -1) return;
|
|
1457
|
+
const node = networksScrollRef.current;
|
|
1458
|
+
if (!node) return;
|
|
1459
|
+
node.scrollTop = savedNetworksScroll.current;
|
|
1460
|
+
}, [view, slideDirection]);
|
|
1435
1461
|
function handleSelectToken(token) {
|
|
1436
1462
|
if (isBlockchainDisabled(token.blockchain, disabledBlockchains)) return;
|
|
1437
1463
|
if (isChainKindLocked(token.chain.chainKind, lockChainKind)) return;
|
|
@@ -1440,8 +1466,28 @@ function TokenSelectDialog(props) {
|
|
|
1440
1466
|
onSelect({ token });
|
|
1441
1467
|
onClose();
|
|
1442
1468
|
}
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1469
|
+
const activeChain = view.kind === "rail-chain" || view.kind === "network-chain" ? chainByCode(availableChains, view.blockchain) : void 0;
|
|
1470
|
+
const chainHeader = view.kind === "network-chain" ? { kind: "back", title: activeChain?.chainName ?? view.blockchain, onBack: handleBackToNetworks, walletKind: activeChain?.chainKind } : view.kind === "rail-chain" ? { kind: "chain", title: activeChain?.chainName ?? view.blockchain, walletKind: activeChain?.chainKind } : null;
|
|
1471
|
+
const awaitingBalances = showBalances && sections.every((section) => section.tokens.length === 0) && scopedTokens.some((token) => loadingOf(token));
|
|
1472
|
+
const tokenDisabled = (token) => isBlockchainDisabled(token.blockchain, disabledBlockchains) || isChainKindLocked(token.chain.chainKind, lockChainKind);
|
|
1473
|
+
const sliding = view.kind === "networks" || view.kind === "network-chain";
|
|
1474
|
+
const tokenPane = /* @__PURE__ */ jsx9(
|
|
1475
|
+
TokenPane,
|
|
1476
|
+
{
|
|
1477
|
+
sections,
|
|
1478
|
+
header: chainHeader,
|
|
1479
|
+
selectedAssetId,
|
|
1480
|
+
loading: loading || awaitingBalances,
|
|
1481
|
+
showBalances,
|
|
1482
|
+
showWalletStatus: payer,
|
|
1483
|
+
getBalance: balanceOf,
|
|
1484
|
+
isBalanceLoading: loadingOf,
|
|
1485
|
+
onSelectToken: handleSelectToken,
|
|
1486
|
+
isTokenDisabled: tokenDisabled,
|
|
1487
|
+
rowClassName
|
|
1488
|
+
}
|
|
1489
|
+
);
|
|
1490
|
+
return /* @__PURE__ */ jsx9(
|
|
1445
1491
|
Dialog,
|
|
1446
1492
|
{
|
|
1447
1493
|
open,
|
|
@@ -1452,10 +1498,11 @@ function TokenSelectDialog(props) {
|
|
|
1452
1498
|
fontSans,
|
|
1453
1499
|
titleClassName
|
|
1454
1500
|
),
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1501
|
+
headerClassName: cx("h-14 border-b border-[#e3e3e3] px-[22px] pr-4", headerClassName),
|
|
1502
|
+
closeClassName: cx("md:ml-0", closeClassName),
|
|
1503
|
+
contentClassName: cx("overflow-hidden", contentClassName),
|
|
1504
|
+
headerAction: /* @__PURE__ */ jsx9(
|
|
1505
|
+
SearchInput,
|
|
1459
1506
|
{
|
|
1460
1507
|
value: search,
|
|
1461
1508
|
onChange: setSearch,
|
|
@@ -1463,80 +1510,219 @@ function TokenSelectDialog(props) {
|
|
|
1463
1510
|
className: "ml-auto hidden w-[215px] shrink-0 md:block",
|
|
1464
1511
|
inputClassName: cx(SEARCH_INPUT_CLASS, searchClassName)
|
|
1465
1512
|
}
|
|
1466
|
-
)
|
|
1467
|
-
cardClassName: cx(
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1513
|
+
),
|
|
1514
|
+
cardClassName: cx(
|
|
1515
|
+
"w-full max-h-[90vh] gap-0 rounded-[12px] border border-[#e0e0e0] bg-[#fdfdfd] p-0 shadow-[0_0_40px_0_rgba(0,0,0,0.06)] md:w-[474px] md:rounded-[12px] md:p-0",
|
|
1516
|
+
className
|
|
1517
|
+
),
|
|
1518
|
+
children: /* @__PURE__ */ jsxs6("div", { className: "flex h-[min(520px,62vh)] min-h-0 flex-col overflow-hidden md:h-[min(555px,70vh)]", children: [
|
|
1519
|
+
/* @__PURE__ */ jsx9(
|
|
1520
|
+
SearchInput,
|
|
1471
1521
|
{
|
|
1472
1522
|
value: search,
|
|
1473
1523
|
onChange: setSearch,
|
|
1474
1524
|
placeholder: "search or paste address",
|
|
1475
|
-
className: "mb-3 shrink-0 md:hidden",
|
|
1525
|
+
className: "mb-3 shrink-0 px-[22px] md:hidden",
|
|
1476
1526
|
inputClassName: cx(SEARCH_INPUT_CLASS, searchClassName)
|
|
1477
1527
|
}
|
|
1478
|
-
)
|
|
1479
|
-
/* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
1528
|
+
),
|
|
1529
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 flex-1", children: [
|
|
1530
|
+
/* @__PURE__ */ jsx9(
|
|
1481
1531
|
NetworkRail,
|
|
1482
1532
|
{
|
|
1483
|
-
|
|
1484
|
-
chainFilter,
|
|
1485
|
-
chips: sortedChains,
|
|
1486
|
-
fundedBlockchains,
|
|
1533
|
+
role: payer ? "payer" : "receiver",
|
|
1487
1534
|
walletConnected,
|
|
1488
|
-
|
|
1535
|
+
selection: railSelection(view),
|
|
1536
|
+
chains: railChains,
|
|
1537
|
+
fundedBlockchains,
|
|
1489
1538
|
lockChainKind,
|
|
1490
1539
|
disabledBlockchains,
|
|
1491
1540
|
disabledReason,
|
|
1492
|
-
|
|
1493
|
-
|
|
1541
|
+
onSelectWallet: () => setView({ kind: "wallet" }),
|
|
1542
|
+
onSelectAll: () => setView({ kind: "all" }),
|
|
1543
|
+
onSelectNetworks: () => setView({ kind: "networks" }),
|
|
1544
|
+
onSelectChain: (blockchain) => handleSelectChain(blockchain, false),
|
|
1494
1545
|
className: railClassName
|
|
1495
1546
|
}
|
|
1496
1547
|
),
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
{
|
|
1500
|
-
chains: sortedChains,
|
|
1501
|
-
fundedBlockchains,
|
|
1502
|
-
showWalletStatus: payer,
|
|
1503
|
-
onSelectFilter: handleSelectFilter,
|
|
1504
|
-
lockChainKind,
|
|
1505
|
-
disabledBlockchains,
|
|
1506
|
-
disabledReason
|
|
1507
|
-
}
|
|
1508
|
-
) }) : /* @__PURE__ */ jsx6("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col pl-3 md:pl-4", children: /* @__PURE__ */ jsx6(
|
|
1509
|
-
TokenPane,
|
|
1548
|
+
searching ? /* @__PURE__ */ jsx9(
|
|
1549
|
+
SearchResults,
|
|
1510
1550
|
{
|
|
1511
|
-
|
|
1551
|
+
groups: searchGroups,
|
|
1512
1552
|
selectedAssetId,
|
|
1513
|
-
loading
|
|
1553
|
+
loading,
|
|
1514
1554
|
showBalances,
|
|
1515
1555
|
showWalletStatus: payer,
|
|
1516
1556
|
getBalance: balanceOf,
|
|
1517
1557
|
isBalanceLoading: loadingOf,
|
|
1518
1558
|
onSelectToken: handleSelectToken,
|
|
1519
|
-
isTokenDisabled:
|
|
1520
|
-
searchClassName,
|
|
1559
|
+
isTokenDisabled: tokenDisabled,
|
|
1521
1560
|
rowClassName
|
|
1522
1561
|
}
|
|
1523
|
-
)
|
|
1562
|
+
) : sliding ? /* @__PURE__ */ jsx9(
|
|
1563
|
+
PaneTransition,
|
|
1564
|
+
{
|
|
1565
|
+
paneKey: view.kind === "network-chain" ? `network-chain:${view.blockchain}` : "networks",
|
|
1566
|
+
direction: slideDirection,
|
|
1567
|
+
children: view.kind === "networks" ? /* @__PURE__ */ jsx9(
|
|
1568
|
+
ChainPane,
|
|
1569
|
+
{
|
|
1570
|
+
chains: networkChains,
|
|
1571
|
+
showWalletStatus: payer,
|
|
1572
|
+
onSelectChain: (blockchain) => handleSelectChain(blockchain, true),
|
|
1573
|
+
lockChainKind,
|
|
1574
|
+
disabledBlockchains,
|
|
1575
|
+
disabledReason,
|
|
1576
|
+
scrollRef: networksScrollRef
|
|
1577
|
+
}
|
|
1578
|
+
) : tokenPane
|
|
1579
|
+
}
|
|
1580
|
+
) : /* @__PURE__ */ jsx9("div", { className: "relative min-h-0 flex-1", children: tokenPane })
|
|
1524
1581
|
] })
|
|
1525
1582
|
] })
|
|
1526
1583
|
}
|
|
1527
1584
|
);
|
|
1528
1585
|
}
|
|
1586
|
+
|
|
1587
|
+
// src/token-select/pay-config.ts
|
|
1588
|
+
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
1589
|
+
var WRAP_NEAR_CONTRACT = "wrap.near";
|
|
1590
|
+
function asRecord(value) {
|
|
1591
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1592
|
+
return value;
|
|
1593
|
+
}
|
|
1594
|
+
function apiText(value) {
|
|
1595
|
+
if (typeof value === "string") return value.trim();
|
|
1596
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
1597
|
+
return "";
|
|
1598
|
+
}
|
|
1599
|
+
function apiNumber(value) {
|
|
1600
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1601
|
+
if (typeof value === "string" && value.trim()) {
|
|
1602
|
+
const parsed = Number(value);
|
|
1603
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
1604
|
+
}
|
|
1605
|
+
return null;
|
|
1606
|
+
}
|
|
1607
|
+
function asBoolean(value, fallback = true) {
|
|
1608
|
+
if (typeof value === "boolean") return value;
|
|
1609
|
+
if (typeof value === "string") {
|
|
1610
|
+
const lower = value.trim().toLowerCase();
|
|
1611
|
+
if (lower === "true") return true;
|
|
1612
|
+
if (lower === "false") return false;
|
|
1613
|
+
}
|
|
1614
|
+
return fallback;
|
|
1615
|
+
}
|
|
1616
|
+
function mapSourceChain(raw) {
|
|
1617
|
+
const row = asRecord(raw) ?? {};
|
|
1618
|
+
return {
|
|
1619
|
+
network: apiText(row.network),
|
|
1620
|
+
chainId: apiText(row.chain_id ?? row.chainId),
|
|
1621
|
+
chainName: apiText(row.chain_name ?? row.chainName),
|
|
1622
|
+
logo: apiText(row.logo),
|
|
1623
|
+
explorer: apiText(row.explorer)
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
function mapSourceToken(raw) {
|
|
1627
|
+
const row = asRecord(raw) ?? {};
|
|
1628
|
+
return {
|
|
1629
|
+
symbol: apiText(row.symbol),
|
|
1630
|
+
network: apiText(row.network),
|
|
1631
|
+
decimals: apiNumber(row.decimals) ?? 0,
|
|
1632
|
+
contractAddress: apiText(row.contract_address ?? row.contractAddress),
|
|
1633
|
+
price: apiText(row.price),
|
|
1634
|
+
supportPayment: asBoolean(row.support_payment ?? row.supportPayment),
|
|
1635
|
+
supportReceive: asBoolean(row.support_receive ?? row.supportReceive)
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
function configBody(raw) {
|
|
1639
|
+
const row = asRecord(raw) ?? {};
|
|
1640
|
+
const data = asRecord(row.data);
|
|
1641
|
+
if (typeof row.code === "number" && data) return data;
|
|
1642
|
+
return row;
|
|
1643
|
+
}
|
|
1644
|
+
function mapPayConfigSource(raw) {
|
|
1645
|
+
const row = configBody(raw);
|
|
1646
|
+
const chains = Array.isArray(row.chains) ? row.chains.map(mapSourceChain) : [];
|
|
1647
|
+
const tokens = Array.isArray(row.tokens) ? row.tokens.map(mapSourceToken) : [];
|
|
1648
|
+
return { chains, tokens };
|
|
1649
|
+
}
|
|
1650
|
+
function chainKindFor(network, overrides = {}) {
|
|
1651
|
+
const key = network.trim().toLowerCase();
|
|
1652
|
+
if (!key) return null;
|
|
1653
|
+
return overrides[key] ?? DEFAULT_CHAIN_KINDS[key] ?? null;
|
|
1654
|
+
}
|
|
1655
|
+
function normalizeSymbol(symbol) {
|
|
1656
|
+
const upper = symbol.trim().toUpperCase();
|
|
1657
|
+
if (!upper) return null;
|
|
1658
|
+
if (upper === "USDT0") return "USDT";
|
|
1659
|
+
if (upper === "WNEAR") return "NEAR";
|
|
1660
|
+
return upper;
|
|
1661
|
+
}
|
|
1662
|
+
function tokenAssetId(network, symbol, contractAddress) {
|
|
1663
|
+
const addr = String(contractAddress || "").trim();
|
|
1664
|
+
return `${network}:${symbol}:${addr || "native"}`;
|
|
1665
|
+
}
|
|
1666
|
+
function isNativeAddress(contractAddress) {
|
|
1667
|
+
const addr = String(contractAddress || "").trim();
|
|
1668
|
+
if (!addr) return true;
|
|
1669
|
+
const lower = addr.toLowerCase();
|
|
1670
|
+
return lower === "native" || lower === ZERO_ADDRESS2;
|
|
1671
|
+
}
|
|
1672
|
+
function normalizePayConfig(source, options) {
|
|
1673
|
+
const overrides = options?.chainKinds ?? {};
|
|
1674
|
+
const chains = [];
|
|
1675
|
+
for (const chain of source.chains) {
|
|
1676
|
+
const chainKind = chainKindFor(chain.network, overrides);
|
|
1677
|
+
if (!chainKind || !chain.network) continue;
|
|
1678
|
+
chains.push({
|
|
1679
|
+
blockchain: chain.network,
|
|
1680
|
+
chainName: chain.chainName || chain.network,
|
|
1681
|
+
chainKind,
|
|
1682
|
+
chainId: chain.chainId || void 0,
|
|
1683
|
+
logo: chain.logo || chainLogoUrl(chain.network),
|
|
1684
|
+
explorer: chain.explorer
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
const chainByCode2 = new Map(chains.map((chain) => [chain.blockchain, chain]));
|
|
1688
|
+
const tokens = [];
|
|
1689
|
+
for (const token of source.tokens) {
|
|
1690
|
+
const chain = chainByCode2.get(token.network);
|
|
1691
|
+
if (!chain) continue;
|
|
1692
|
+
const symbol = normalizeSymbol(token.symbol);
|
|
1693
|
+
if (!symbol) continue;
|
|
1694
|
+
if (!Number.isInteger(token.decimals) || token.decimals < 0) continue;
|
|
1695
|
+
const contractAddress = token.contractAddress.trim() || null;
|
|
1696
|
+
tokens.push({
|
|
1697
|
+
assetId: tokenAssetId(token.network, symbol, contractAddress),
|
|
1698
|
+
symbol,
|
|
1699
|
+
providerSymbol: token.symbol,
|
|
1700
|
+
blockchain: token.network,
|
|
1701
|
+
decimals: token.decimals,
|
|
1702
|
+
contractAddress,
|
|
1703
|
+
price: Number(token.price || 1),
|
|
1704
|
+
logo: tokenLogoUrl(symbol),
|
|
1705
|
+
supportPayment: token.supportPayment,
|
|
1706
|
+
supportReceive: token.supportReceive,
|
|
1707
|
+
chain
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
const hasNearWrap = tokens.some((token) => token.symbol === "NEAR" && token.blockchain === "near" && token.contractAddress?.toLowerCase() === WRAP_NEAR_CONTRACT);
|
|
1711
|
+
if (!hasNearWrap) return tokens;
|
|
1712
|
+
return tokens.filter((token) => !(token.symbol === "NEAR" && token.blockchain === "near" && isNativeAddress(token.contractAddress)));
|
|
1713
|
+
}
|
|
1529
1714
|
export {
|
|
1530
|
-
|
|
1715
|
+
KNOWN_CHAIN_KINDS,
|
|
1531
1716
|
PayWidgetsProvider,
|
|
1532
1717
|
TokenSelectDialog,
|
|
1718
|
+
chainsFromTokens,
|
|
1533
1719
|
clearPayTokenBalances,
|
|
1534
1720
|
mapPayConfigSource,
|
|
1535
1721
|
normalizePayConfig,
|
|
1722
|
+
ownerKey2 as ownerKey,
|
|
1536
1723
|
parsePopularTokens,
|
|
1537
1724
|
refreshPayTokenBalance,
|
|
1538
|
-
|
|
1539
|
-
usePayConfigSnapshot,
|
|
1725
|
+
tokenAssetId,
|
|
1540
1726
|
usePayTokenBalance
|
|
1541
1727
|
};
|
|
1542
1728
|
//# sourceMappingURL=index.js.map
|