@stableflow/pay-widgets 0.1.0 → 0.2.0-beta-e820becee0-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 +933 -787
- 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
|
|
300
|
+
import { useEffect as useEffect2, useLayoutEffect, useMemo, useRef, useState, 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,184 @@ 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
740
|
function NetworkRail(props) {
|
|
991
741
|
const {
|
|
992
|
-
|
|
993
|
-
chainFilter,
|
|
994
|
-
chips,
|
|
995
|
-
fundedBlockchains,
|
|
742
|
+
role,
|
|
996
743
|
walletConnected,
|
|
997
|
-
|
|
744
|
+
selection,
|
|
745
|
+
chains,
|
|
746
|
+
fundedBlockchains,
|
|
998
747
|
lockChainKind = null,
|
|
999
748
|
disabledBlockchains = null,
|
|
1000
749
|
disabledReason,
|
|
1001
|
-
|
|
1002
|
-
|
|
750
|
+
onSelectWallet,
|
|
751
|
+
onSelectAll,
|
|
752
|
+
onSelectNetworks,
|
|
753
|
+
onSelectChain,
|
|
1003
754
|
className
|
|
1004
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";
|
|
1005
761
|
function wrapDisabled(disabled, reason, content) {
|
|
1006
762
|
if (!disabled || !reason) return content;
|
|
1007
763
|
return /* @__PURE__ */ jsx4(Tooltip2, { side: "right", content: reason, children: content });
|
|
1008
764
|
}
|
|
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
|
-
className: chipClass(selected, disabled),
|
|
1053
|
-
children: [
|
|
1054
|
-
/* @__PURE__ */ jsx4("img", { src: chain.logo, alt: "", className: "size-6 object-cover md:size-8" }),
|
|
1055
|
-
fundedBlockchains.has(chain.blockchain) ? /* @__PURE__ */ jsx4("span", { className: "absolute top-1 right-1 size-2 rounded-full border border-white bg-[#06f] md:size-2.5" }) : null
|
|
1056
|
-
]
|
|
1057
|
-
}
|
|
1058
|
-
)
|
|
1059
|
-
) }, chain.blockchain);
|
|
1060
|
-
}) }),
|
|
1061
|
-
/* @__PURE__ */ jsx4("div", { className: "mt-2 flex w-full shrink-0 justify-center border-t border-[#e3e3e3] pt-2", children: /* @__PURE__ */ jsx4(
|
|
1062
|
-
"button",
|
|
1063
|
-
{
|
|
1064
|
-
type: "button",
|
|
1065
|
-
"aria-label": "All networks",
|
|
1066
|
-
onClick: onOpenNetworks,
|
|
1067
|
-
className: chipClass(view === "network", false),
|
|
1068
|
-
children: /* @__PURE__ */ jsx4(IconAllNetworks, { className: "size-4 text-black" })
|
|
1069
|
-
}
|
|
1070
|
-
) })
|
|
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
|
|
1071
807
|
] });
|
|
1072
808
|
}
|
|
1073
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
|
+
|
|
1074
909
|
// src/token-select/token-pane.tsx
|
|
1075
|
-
import {
|
|
910
|
+
import { IconChevron as IconChevron2 } from "@stableflow/pay-ui/icons/chevron";
|
|
1076
911
|
import { Skeleton } from "@stableflow/pay-ui/skeleton";
|
|
1077
|
-
import { Fragment, jsx as
|
|
912
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1078
913
|
function TokenPane(props) {
|
|
1079
914
|
const {
|
|
1080
915
|
sections,
|
|
@@ -1082,65 +917,67 @@ function TokenPane(props) {
|
|
|
1082
917
|
loading,
|
|
1083
918
|
showBalances = false,
|
|
1084
919
|
showWalletStatus = true,
|
|
920
|
+
header = null,
|
|
1085
921
|
getBalance,
|
|
1086
922
|
isBalanceLoading,
|
|
1087
923
|
onSelectToken,
|
|
1088
924
|
isTokenDisabled,
|
|
1089
|
-
search = "",
|
|
1090
|
-
onSearchChange,
|
|
1091
|
-
showSearch = false,
|
|
1092
|
-
searchClassName,
|
|
1093
925
|
rowClassName
|
|
1094
926
|
} = props;
|
|
1095
927
|
const visibleSections = sections.filter((section) => section.title || section.tokens.length > 0);
|
|
1096
928
|
const hasTokens = sections.some((section) => section.tokens.length > 0);
|
|
1097
|
-
return /* @__PURE__ */
|
|
1098
|
-
|
|
1099
|
-
|
|
929
|
+
return /* @__PURE__ */ jsx6("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: /* @__PURE__ */ jsx6("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: [
|
|
930
|
+
header ? /* @__PURE__ */ jsx6(ChainListHeader, { header, showWalletStatus }) : null,
|
|
931
|
+
visibleSections.map((section) => /* @__PURE__ */ jsxs4("section", { className: "mb-2", children: [
|
|
932
|
+
section.title ? /* @__PURE__ */ jsxs4("div", { className: "mb-1 flex items-center justify-between gap-2 px-3.5", children: [
|
|
933
|
+
/* @__PURE__ */ jsx6("p", { className: cx("min-w-0 truncate text-sm font-medium text-[#aaa]", fontSans), children: section.title }),
|
|
934
|
+
showWalletStatus && section.walletKind ? /* @__PURE__ */ jsx6(ChainWalletStatus, { kind: section.walletKind }) : null
|
|
935
|
+
] }) : null,
|
|
936
|
+
section.tokens.map((token) => /* @__PURE__ */ jsx6(
|
|
937
|
+
TokenRow,
|
|
938
|
+
{
|
|
939
|
+
token,
|
|
940
|
+
selected: token.assetId === selectedAssetId,
|
|
941
|
+
recent: token.assetId === section.badgeAssetId,
|
|
942
|
+
showBalances,
|
|
943
|
+
formatted: showBalances ? getBalance(token) : null,
|
|
944
|
+
loadingBalance: showBalances && isBalanceLoading(token),
|
|
945
|
+
disabled: Boolean(isTokenDisabled?.(token)),
|
|
946
|
+
onSelect: () => onSelectToken(token),
|
|
947
|
+
className: rowClassName
|
|
948
|
+
},
|
|
949
|
+
`${section.id}-${token.assetId}`
|
|
950
|
+
))
|
|
951
|
+
] }, section.id)),
|
|
952
|
+
loading && !hasTokens ? /* @__PURE__ */ jsx6(TokenListSkeleton, {}) : null,
|
|
953
|
+
!loading && !hasTokens ? /* @__PURE__ */ jsx6("p", { className: cx("px-3.5 py-4 text-[13px] text-[#606060]", fontSans), children: "No tokens found" }) : null
|
|
954
|
+
] }) }) });
|
|
955
|
+
}
|
|
956
|
+
function ChainListHeader(props) {
|
|
957
|
+
const { header, showWalletStatus } = props;
|
|
958
|
+
const walletKind = header.walletKind;
|
|
959
|
+
return /* @__PURE__ */ jsxs4("div", { className: "mb-1 flex items-center justify-between gap-3 px-3.5", children: [
|
|
960
|
+
header.kind === "back" ? /* @__PURE__ */ jsxs4(
|
|
961
|
+
"button",
|
|
1100
962
|
{
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
)
|
|
963
|
+
type: "button",
|
|
964
|
+
onClick: header.onBack,
|
|
965
|
+
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),
|
|
966
|
+
children: [
|
|
967
|
+
/* @__PURE__ */ jsx6(IconChevron2, { className: "rotate-180 text-[#aaa]" }),
|
|
968
|
+
/* @__PURE__ */ jsx6("span", { className: "truncate", children: header.title })
|
|
969
|
+
]
|
|
1109
970
|
}
|
|
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
|
-
] })
|
|
971
|
+
) : /* @__PURE__ */ jsx6("p", { className: cx("min-w-0 truncate text-sm font-medium text-[#aaa]", fontSans), children: header.title }),
|
|
972
|
+
showWalletStatus && walletKind ? /* @__PURE__ */ jsx6(ChainWalletStatus, { kind: walletKind }) : null
|
|
1136
973
|
] });
|
|
1137
974
|
}
|
|
1138
975
|
function TokenListSkeleton() {
|
|
1139
|
-
return /* @__PURE__ */
|
|
1140
|
-
/* @__PURE__ */
|
|
976
|
+
return /* @__PURE__ */ jsx6("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: [
|
|
977
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "size-8 shrink-0 rounded-full" }),
|
|
1141
978
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
|
|
1142
|
-
/* @__PURE__ */
|
|
1143
|
-
/* @__PURE__ */
|
|
979
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "h-3.5 w-24" }),
|
|
980
|
+
/* @__PURE__ */ jsx6(Skeleton, { className: "h-3 w-16" })
|
|
1144
981
|
] })
|
|
1145
982
|
] }, index)) });
|
|
1146
983
|
}
|
|
@@ -1154,16 +991,16 @@ function TokenRow(props) {
|
|
|
1154
991
|
disabled,
|
|
1155
992
|
onClick: onSelect,
|
|
1156
993
|
className: cx(
|
|
1157
|
-
"flex w-full items-center justify-between gap-2 rounded-[12px] border-0 bg-transparent px-
|
|
1158
|
-
selected && "bg-[#
|
|
994
|
+
"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]",
|
|
995
|
+
selected && "bg-[#f6f6f6]",
|
|
1159
996
|
disabled && "cursor-not-allowed opacity-40 hover:bg-transparent",
|
|
1160
997
|
className
|
|
1161
998
|
),
|
|
1162
999
|
children: [
|
|
1163
1000
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-2.5", children: [
|
|
1164
1001
|
/* @__PURE__ */ jsxs4("span", { className: "relative size-8 shrink-0", children: [
|
|
1165
|
-
/* @__PURE__ */
|
|
1166
|
-
/* @__PURE__ */
|
|
1002
|
+
/* @__PURE__ */ jsx6("img", { src: token.logo, alt: "", className: "size-8 rounded-[12px] object-cover" }),
|
|
1003
|
+
/* @__PURE__ */ jsx6(
|
|
1167
1004
|
"img",
|
|
1168
1005
|
{
|
|
1169
1006
|
src: token.chain.logo,
|
|
@@ -1174,37 +1011,78 @@ function TokenRow(props) {
|
|
|
1174
1011
|
] }),
|
|
1175
1012
|
/* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
|
|
1176
1013
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
1177
|
-
/* @__PURE__ */
|
|
1178
|
-
recent ? /* @__PURE__ */
|
|
1014
|
+
/* @__PURE__ */ jsx6("span", { className: cx("truncate text-sm font-semibold text-black", fontSans), children: token.symbol }),
|
|
1015
|
+
recent ? /* @__PURE__ */ jsx6("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
1016
|
] }),
|
|
1180
|
-
/* @__PURE__ */
|
|
1017
|
+
/* @__PURE__ */ jsx6("span", { className: cx("mt-0.5 block truncate text-xs text-[#606060]", fontSans), children: token.chain.chainName })
|
|
1181
1018
|
] })
|
|
1182
1019
|
] }),
|
|
1183
|
-
showBalances ? /* @__PURE__ */
|
|
1020
|
+
showBalances ? /* @__PURE__ */ jsx6("span", { className: "shrink-0 text-right", children: loadingBalance ? /* @__PURE__ */ jsx6(
|
|
1184
1021
|
"span",
|
|
1185
1022
|
{
|
|
1186
1023
|
className: "inline-block size-3.5 animate-spin rounded-full border-2 border-[#606060] border-r-transparent",
|
|
1187
1024
|
"aria-label": "Loading balance"
|
|
1188
1025
|
}
|
|
1189
1026
|
) : formatted != null ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1190
|
-
/* @__PURE__ */
|
|
1191
|
-
usd >= 0 ? /* @__PURE__ */
|
|
1192
|
-
] }) : /* @__PURE__ */
|
|
1027
|
+
/* @__PURE__ */ jsx6("span", { className: cx("block text-sm font-medium text-black", fontSans), children: formatDisplayAmount(formatted, { prefix: "", maxDecimals: 4 }) }),
|
|
1028
|
+
usd >= 0 ? /* @__PURE__ */ jsx6("span", { className: cx("block text-xs text-[#606060]", fontSans), children: formatDisplayAmount(usd, { prefix: "$", showDust: true }) }) : null
|
|
1029
|
+
] }) : /* @__PURE__ */ jsx6("span", { className: cx("text-sm font-medium text-black", fontSans), children: "-" }) }) : null
|
|
1193
1030
|
]
|
|
1194
1031
|
}
|
|
1195
1032
|
);
|
|
1196
1033
|
}
|
|
1197
1034
|
|
|
1035
|
+
// src/token-select/search-results.tsx
|
|
1036
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1037
|
+
function SearchResults(props) {
|
|
1038
|
+
const {
|
|
1039
|
+
groups,
|
|
1040
|
+
selectedAssetId,
|
|
1041
|
+
loading,
|
|
1042
|
+
showBalances = false,
|
|
1043
|
+
showWalletStatus = true,
|
|
1044
|
+
getBalance,
|
|
1045
|
+
isBalanceLoading,
|
|
1046
|
+
onSelectToken,
|
|
1047
|
+
isTokenDisabled,
|
|
1048
|
+
rowClassName
|
|
1049
|
+
} = props;
|
|
1050
|
+
const hasTokens = groups.some((group) => group.tokens.length > 0);
|
|
1051
|
+
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__ */ jsxs5("div", { className: "px-2.5 pt-[17px] pb-3", children: [
|
|
1052
|
+
groups.map((group) => /* @__PURE__ */ jsxs5("section", { className: "mb-2", children: [
|
|
1053
|
+
/* @__PURE__ */ jsxs5("div", { className: "mb-1 flex items-center justify-between gap-2 px-3.5", children: [
|
|
1054
|
+
/* @__PURE__ */ jsx7("p", { className: cx("min-w-0 truncate text-sm font-medium text-[#aaa]", fontSans), children: group.chainName }),
|
|
1055
|
+
showWalletStatus ? /* @__PURE__ */ jsx7(ChainWalletStatus, { kind: group.chainKind }) : null
|
|
1056
|
+
] }),
|
|
1057
|
+
group.tokens.map((token) => /* @__PURE__ */ jsx7(
|
|
1058
|
+
TokenRow,
|
|
1059
|
+
{
|
|
1060
|
+
token,
|
|
1061
|
+
selected: token.assetId === selectedAssetId,
|
|
1062
|
+
recent: false,
|
|
1063
|
+
showBalances,
|
|
1064
|
+
formatted: showBalances ? getBalance(token) : null,
|
|
1065
|
+
loadingBalance: showBalances && isBalanceLoading(token),
|
|
1066
|
+
disabled: Boolean(isTokenDisabled?.(token)),
|
|
1067
|
+
onSelect: () => onSelectToken(token),
|
|
1068
|
+
className: rowClassName
|
|
1069
|
+
},
|
|
1070
|
+
token.assetId
|
|
1071
|
+
))
|
|
1072
|
+
] }, group.blockchain)),
|
|
1073
|
+
!loading && !hasTokens ? /* @__PURE__ */ jsx7("p", { className: cx("px-3.5 py-4 text-[13px] text-[#606060]", fontSans), children: "No tokens found" }) : null
|
|
1074
|
+
] }) }) });
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// src/token-select/types.ts
|
|
1078
|
+
var KNOWN_CHAIN_KINDS = ["evm", "near", "solana", "tron", "zec"];
|
|
1079
|
+
function ownerKey2(owners) {
|
|
1080
|
+
return Object.keys(owners).sort().map((kind) => `${kind}:${owners[kind] ?? ""}`).join("|");
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1198
1083
|
// src/token-select/TokenSelectDialog.tsx
|
|
1199
|
-
import { jsx as
|
|
1084
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1200
1085
|
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
1086
|
function ownerForToken(owners, token) {
|
|
1209
1087
|
return owners[token.chain.chainKind] ?? null;
|
|
1210
1088
|
}
|
|
@@ -1215,6 +1093,15 @@ function isNativeToken(token) {
|
|
|
1215
1093
|
const addr = String(token.contractAddress || "").trim().toLowerCase();
|
|
1216
1094
|
return !addr || addr === "native" || addr === "0x0000000000000000000000000000000000000000";
|
|
1217
1095
|
}
|
|
1096
|
+
function railSelection(view) {
|
|
1097
|
+
if (view.kind === "wallet") return { kind: "wallet" };
|
|
1098
|
+
if (view.kind === "all") return { kind: "all" };
|
|
1099
|
+
if (view.kind === "rail-chain") return { kind: "chain", blockchain: view.blockchain };
|
|
1100
|
+
return { kind: "networks" };
|
|
1101
|
+
}
|
|
1102
|
+
function chainByCode(chains, blockchain) {
|
|
1103
|
+
return chains.find((chain) => chain.blockchain === blockchain);
|
|
1104
|
+
}
|
|
1218
1105
|
function TokenSelectDialog(props) {
|
|
1219
1106
|
const {
|
|
1220
1107
|
open,
|
|
@@ -1231,6 +1118,7 @@ function TokenSelectDialog(props) {
|
|
|
1231
1118
|
disabledReason,
|
|
1232
1119
|
className,
|
|
1233
1120
|
titleClassName,
|
|
1121
|
+
headerClassName,
|
|
1234
1122
|
closeClassName,
|
|
1235
1123
|
contentClassName,
|
|
1236
1124
|
searchClassName,
|
|
@@ -1239,11 +1127,19 @@ function TokenSelectDialog(props) {
|
|
|
1239
1127
|
} = props;
|
|
1240
1128
|
const payer = role === "payer";
|
|
1241
1129
|
const showBalances = payer;
|
|
1242
|
-
const {
|
|
1243
|
-
|
|
1130
|
+
const {
|
|
1131
|
+
wallet,
|
|
1132
|
+
popularTokens: popularRefs,
|
|
1133
|
+
tokens,
|
|
1134
|
+
loading,
|
|
1135
|
+
stableSymbols,
|
|
1136
|
+
balanceMode,
|
|
1137
|
+
getBalance: syncGetBalance,
|
|
1138
|
+
isBalanceLoading: syncIsBalanceLoading
|
|
1139
|
+
} = usePayWidgets();
|
|
1244
1140
|
const prefs2 = useTokenSelectPrefs();
|
|
1245
|
-
const balanceEntries =
|
|
1246
|
-
const connectedOwners =
|
|
1141
|
+
const balanceEntries = useSyncExternalStore3(subscribePayTokenBalances, getPayTokenBalances, getPayTokenBalances);
|
|
1142
|
+
const connectedOwners = useMemo(() => {
|
|
1247
1143
|
const owners2 = {};
|
|
1248
1144
|
for (const [kind, account] of Object.entries(wallet.accounts)) {
|
|
1249
1145
|
const address = account?.address?.trim();
|
|
@@ -1251,53 +1147,57 @@ function TokenSelectDialog(props) {
|
|
|
1251
1147
|
}
|
|
1252
1148
|
return owners2;
|
|
1253
1149
|
}, [wallet.accounts]);
|
|
1254
|
-
const owners =
|
|
1150
|
+
const owners = useMemo(
|
|
1151
|
+
() => showBalances ? { ...connectedOwners, ...balanceOwners } : {},
|
|
1152
|
+
[showBalances, connectedOwners, balanceOwners]
|
|
1153
|
+
);
|
|
1255
1154
|
const walletConnected = hasAnyOwner(connectedOwners);
|
|
1256
|
-
const
|
|
1257
|
-
const [
|
|
1258
|
-
const [
|
|
1259
|
-
const
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1155
|
+
const [search, setSearch] = useState("");
|
|
1156
|
+
const [view, setView] = useState(payer ? { kind: "networks" } : { kind: "all" });
|
|
1157
|
+
const [slideDirection, setSlideDirection] = useState(1);
|
|
1158
|
+
const networksScrollRef = useRef(null);
|
|
1159
|
+
const savedNetworksScroll = useRef(0);
|
|
1160
|
+
const walletConnectedRef = useRef(walletConnected);
|
|
1161
|
+
const payerRef = useRef(payer);
|
|
1162
|
+
const lockRef = useRef(lockChainKind);
|
|
1163
|
+
const tokensRef = useRef(tokens);
|
|
1164
|
+
walletConnectedRef.current = walletConnected;
|
|
1165
|
+
payerRef.current = payer;
|
|
1166
|
+
lockRef.current = lockChainKind;
|
|
1167
|
+
tokensRef.current = tokens;
|
|
1168
|
+
useEffect2(() => {
|
|
1262
1169
|
if (!open) return;
|
|
1263
1170
|
setSearch("");
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
setChainFilter(initialChainFilter(
|
|
1276
|
-
lockChainKind,
|
|
1277
|
-
getTokenSelectPrefs().recentBlockchains,
|
|
1278
|
-
chainsFromTokens(tokens)
|
|
1279
|
-
));
|
|
1280
|
-
}, [open, lockChainKind, tokens]);
|
|
1281
|
-
useEffect3(() => {
|
|
1282
|
-
if (!open) {
|
|
1283
|
-
walletWasConnected.current = null;
|
|
1171
|
+
const lock = lockRef.current;
|
|
1172
|
+
if (lock) {
|
|
1173
|
+
const filter = initialChainFilter(
|
|
1174
|
+
lock,
|
|
1175
|
+
getTokenSelectPrefs().recentBlockchains,
|
|
1176
|
+
chainsFromTokens(tokensRef.current)
|
|
1177
|
+
);
|
|
1178
|
+
if (filter !== ALL_CHAIN_FILTER) {
|
|
1179
|
+
setView({ kind: "rail-chain", blockchain: filter });
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1284
1182
|
return;
|
|
1285
1183
|
}
|
|
1286
|
-
if (
|
|
1287
|
-
|
|
1184
|
+
if (!payerRef.current) {
|
|
1185
|
+
setView({ kind: "all" });
|
|
1288
1186
|
return;
|
|
1289
1187
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1188
|
+
setView(walletConnectedRef.current ? { kind: "wallet" } : { kind: "networks" });
|
|
1189
|
+
}, [open]);
|
|
1190
|
+
useEffect2(() => {
|
|
1191
|
+
if (!open || !lockChainKind) return;
|
|
1192
|
+
const filter = initialChainFilter(lockChainKind, getTokenSelectPrefs().recentBlockchains, chainsFromTokens(tokens));
|
|
1193
|
+
if (filter === ALL_CHAIN_FILTER) return;
|
|
1194
|
+
setView((current) => current.kind === "rail-chain" ? current : { kind: "rail-chain", blockchain: filter });
|
|
1195
|
+
}, [open, lockChainKind, tokens]);
|
|
1196
|
+
const allowed = useMemo(() => {
|
|
1297
1197
|
if (!allowedBlockchains || allowedBlockchains.length === 0) return null;
|
|
1298
1198
|
return new Set(allowedBlockchains.map((code) => code.toLowerCase()));
|
|
1299
1199
|
}, [allowedBlockchains]);
|
|
1300
|
-
const scopedTokens =
|
|
1200
|
+
const scopedTokens = useMemo(() => {
|
|
1301
1201
|
return tokens.filter((token) => {
|
|
1302
1202
|
if (allowed && !allowed.has(token.blockchain.toLowerCase())) return false;
|
|
1303
1203
|
if (excludeNative && isNativeToken(token)) return false;
|
|
@@ -1306,22 +1206,25 @@ function TokenSelectDialog(props) {
|
|
|
1306
1206
|
return true;
|
|
1307
1207
|
});
|
|
1308
1208
|
}, [tokens, allowed, excludeNative, payer]);
|
|
1309
|
-
const
|
|
1310
|
-
const tokenKey =
|
|
1209
|
+
const ownersKey = ownerKey2(owners);
|
|
1210
|
+
const tokenKey = useMemo(() => scopedTokens.map((token) => token.assetId).join("|"), [scopedTokens]);
|
|
1311
1211
|
const ownersRef = useRef(owners);
|
|
1312
|
-
const
|
|
1212
|
+
const scopedRef = useRef(scopedTokens);
|
|
1313
1213
|
ownersRef.current = owners;
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
if (!showBalances || !open || !hasAnyOwner(ownersRef.current) || !tokenKey) return;
|
|
1317
|
-
void ensurePayTokenBalances(ownersRef.current,
|
|
1214
|
+
scopedRef.current = scopedTokens;
|
|
1215
|
+
useEffect2(() => {
|
|
1216
|
+
if (balanceMode !== "async" || !showBalances || !open || !hasAnyOwner(ownersRef.current) || !tokenKey) return;
|
|
1217
|
+
void ensurePayTokenBalances(ownersRef.current, scopedRef.current);
|
|
1318
1218
|
const id = window.setInterval(() => {
|
|
1319
|
-
void ensurePayTokenBalances(ownersRef.current,
|
|
1219
|
+
void ensurePayTokenBalances(ownersRef.current, scopedRef.current, { force: true });
|
|
1320
1220
|
}, TOKEN_BALANCE_POLL_MS);
|
|
1321
1221
|
return () => window.clearInterval(id);
|
|
1322
|
-
}, [showBalances, open,
|
|
1222
|
+
}, [balanceMode, showBalances, open, ownersKey, tokenKey]);
|
|
1323
1223
|
function balanceOf(token) {
|
|
1324
|
-
|
|
1224
|
+
const owner = ownerForToken(owners, token);
|
|
1225
|
+
if (!showBalances || !owner) return void 0;
|
|
1226
|
+
if (balanceMode === "sync" && syncGetBalance) return syncGetBalance(token, owner) ?? void 0;
|
|
1227
|
+
return getPayTokenBalance(owner, token.assetId)?.formatted;
|
|
1325
1228
|
}
|
|
1326
1229
|
function usdOf(token) {
|
|
1327
1230
|
if (!showBalances) return -1;
|
|
@@ -1331,52 +1234,48 @@ function TokenSelectDialog(props) {
|
|
|
1331
1234
|
if (!showBalances) return false;
|
|
1332
1235
|
const owner = ownerForToken(owners, token);
|
|
1333
1236
|
if (!owner) return false;
|
|
1237
|
+
if (balanceMode === "sync") return syncIsBalanceLoading?.(token, owner) ?? false;
|
|
1334
1238
|
const entry = getPayTokenBalance(owner, token.assetId);
|
|
1335
1239
|
return entry?.formatted == null && (!entry || entry.status === "loading");
|
|
1336
1240
|
}
|
|
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(() => {
|
|
1241
|
+
const availableChains = useMemo(() => chainsFromTokens(scopedTokens), [scopedTokens]);
|
|
1242
|
+
const fundedBlockchains = useMemo(() => {
|
|
1354
1243
|
const funded = /* @__PURE__ */ new Set();
|
|
1355
1244
|
if (!showBalances) return funded;
|
|
1356
1245
|
for (const chain of availableChains) {
|
|
1357
|
-
if (chainHasBalance(chain.blockchain, scopedTokens, (token) =>
|
|
1246
|
+
if (chainHasBalance(chain.blockchain, scopedTokens, (token) => balanceOf(token))) {
|
|
1358
1247
|
funded.add(chain.blockchain);
|
|
1359
1248
|
}
|
|
1360
1249
|
}
|
|
1361
1250
|
return funded;
|
|
1362
|
-
}, [availableChains, owners, scopedTokens, showBalances, balanceEntries]);
|
|
1363
|
-
const
|
|
1251
|
+
}, [availableChains, owners, scopedTokens, showBalances, balanceEntries, balanceMode, syncGetBalance, syncIsBalanceLoading]);
|
|
1252
|
+
const railChains = useMemo(() => {
|
|
1253
|
+
if (!payer) return sortChainsAlpha(availableChains);
|
|
1254
|
+
const connected = payerRailChains(availableChains, owners, fundedBlockchains);
|
|
1255
|
+
return sortChainsForSidebar(connected, scopedTokens, {
|
|
1256
|
+
showBalances,
|
|
1257
|
+
getBalanceUsd: (token) => tokenBalanceUsd(token, balanceOf(token))
|
|
1258
|
+
});
|
|
1259
|
+
}, [payer, availableChains, owners, fundedBlockchains, scopedTokens, showBalances, balanceEntries, balanceMode, syncGetBalance]);
|
|
1260
|
+
const networkChains = useMemo(() => sortChainsAlpha(availableChains), [availableChains]);
|
|
1261
|
+
useEffect2(() => {
|
|
1262
|
+
if (!open || !payer || lockChainKind) return;
|
|
1263
|
+
setView((current) => {
|
|
1264
|
+
if (current.kind === "wallet" && !walletConnected) return { kind: "networks" };
|
|
1265
|
+
if (current.kind === "rail-chain" && !railChains.some((chain) => chain.blockchain === current.blockchain)) {
|
|
1266
|
+
return walletConnected ? { kind: "wallet" } : { kind: "networks" };
|
|
1267
|
+
}
|
|
1268
|
+
return current;
|
|
1269
|
+
});
|
|
1270
|
+
}, [open, payer, lockChainKind, walletConnected, railChains]);
|
|
1271
|
+
const popularTokens = useMemo(
|
|
1364
1272
|
() => popularTokensForWallets(scopedTokens, popularRefs, (kind) => Boolean(connectedOwners[kind])),
|
|
1365
1273
|
[scopedTokens, popularRefs, connectedOwners]
|
|
1366
1274
|
);
|
|
1367
1275
|
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) {
|
|
1276
|
+
const searching = search.trim().length > 0;
|
|
1277
|
+
const sections = useMemo(() => {
|
|
1278
|
+
if (view.kind === "wallet") {
|
|
1380
1279
|
const next = [];
|
|
1381
1280
|
const recent = recentTokensInOrder(scopedTokens, prefs2.recentAssetIds);
|
|
1382
1281
|
if (recent.length > 0) {
|
|
@@ -1396,42 +1295,87 @@ function TokenSelectDialog(props) {
|
|
|
1396
1295
|
}
|
|
1397
1296
|
return next;
|
|
1398
1297
|
}
|
|
1399
|
-
|
|
1400
|
-
if (!showBalances && chainFilter === ALL_CHAIN_FILTER && !search.trim()) {
|
|
1298
|
+
if (view.kind === "all") {
|
|
1401
1299
|
const next = [];
|
|
1402
1300
|
if (popularTokens.length > 0) {
|
|
1403
1301
|
next.push({ id: "popular", title: POPULAR_SECTION_TITLE, tokens: popularTokens });
|
|
1404
1302
|
}
|
|
1405
|
-
|
|
1303
|
+
const sorted = scopedTokens.slice().sort((left, right) => compareTokensByPriority(left, right, stableSymbols));
|
|
1304
|
+
next.push({ id: "all", tokens: sorted });
|
|
1406
1305
|
return next;
|
|
1407
1306
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1307
|
+
if (view.kind === "rail-chain" || view.kind === "network-chain") {
|
|
1308
|
+
const matched = scopedTokens.filter((token) => token.blockchain === view.blockchain);
|
|
1309
|
+
if (!payer) {
|
|
1310
|
+
const next = [];
|
|
1311
|
+
const popular = popularTokens.filter((token) => token.blockchain === view.blockchain);
|
|
1312
|
+
if (popular.length > 0) {
|
|
1313
|
+
next.push({ id: "popular", title: POPULAR_SECTION_TITLE, tokens: popular });
|
|
1314
|
+
}
|
|
1315
|
+
next.push({
|
|
1316
|
+
id: view.blockchain,
|
|
1317
|
+
tokens: matched.slice().sort((left, right) => compareTokensByPriority(left, right, stableSymbols))
|
|
1318
|
+
});
|
|
1319
|
+
return next;
|
|
1320
|
+
}
|
|
1321
|
+
const chain = chainByCode(availableChains, view.blockchain);
|
|
1322
|
+
return [{
|
|
1323
|
+
id: view.blockchain,
|
|
1324
|
+
tokens: sortTokensForChain(matched, {
|
|
1325
|
+
withBalances: Boolean(chain && owners[chain.chainKind]),
|
|
1326
|
+
usdOf,
|
|
1327
|
+
loadingOf,
|
|
1328
|
+
stableSymbols
|
|
1329
|
+
}),
|
|
1330
|
+
badgeAssetId: newestAssetId
|
|
1331
|
+
}];
|
|
1332
|
+
}
|
|
1333
|
+
return [];
|
|
1416
1334
|
}, [
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
searchingAll,
|
|
1420
|
-
search,
|
|
1335
|
+
view,
|
|
1336
|
+
payer,
|
|
1421
1337
|
scopedTokens,
|
|
1422
1338
|
prefs2.recentAssetIds,
|
|
1423
1339
|
newestAssetId,
|
|
1424
|
-
walletKind,
|
|
1425
1340
|
balanceEntries,
|
|
1426
1341
|
owners,
|
|
1427
1342
|
popularTokens,
|
|
1428
|
-
availableChains
|
|
1343
|
+
availableChains,
|
|
1344
|
+
stableSymbols,
|
|
1345
|
+
balanceMode,
|
|
1346
|
+
syncGetBalance,
|
|
1347
|
+
syncIsBalanceLoading
|
|
1429
1348
|
]);
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1349
|
+
const searchGroups = useMemo(() => {
|
|
1350
|
+
if (!searching) return [];
|
|
1351
|
+
return groupSearchResults(scopedTokens, search, {
|
|
1352
|
+
role: payer ? "payer" : "receiver",
|
|
1353
|
+
usdOf,
|
|
1354
|
+
loadingOf,
|
|
1355
|
+
hasOwner: (kind) => Boolean(owners[kind]),
|
|
1356
|
+
stableSymbols
|
|
1357
|
+
});
|
|
1358
|
+
}, [searching, scopedTokens, search, payer, owners, stableSymbols, balanceEntries, balanceMode, syncGetBalance, syncIsBalanceLoading]);
|
|
1359
|
+
function handleSelectChain(blockchain, fromNetworks) {
|
|
1360
|
+
setLastBlockchain(blockchain);
|
|
1361
|
+
if (fromNetworks) {
|
|
1362
|
+
savedNetworksScroll.current = networksScrollRef.current?.scrollTop ?? 0;
|
|
1363
|
+
setSlideDirection(1);
|
|
1364
|
+
setView({ kind: "network-chain", blockchain });
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
setView({ kind: "rail-chain", blockchain });
|
|
1434
1368
|
}
|
|
1369
|
+
function handleBackToNetworks() {
|
|
1370
|
+
setSlideDirection(-1);
|
|
1371
|
+
setView({ kind: "networks" });
|
|
1372
|
+
}
|
|
1373
|
+
useLayoutEffect(() => {
|
|
1374
|
+
if (view.kind !== "networks" || slideDirection !== -1) return;
|
|
1375
|
+
const node = networksScrollRef.current;
|
|
1376
|
+
if (!node) return;
|
|
1377
|
+
node.scrollTop = savedNetworksScroll.current;
|
|
1378
|
+
}, [view, slideDirection]);
|
|
1435
1379
|
function handleSelectToken(token) {
|
|
1436
1380
|
if (isBlockchainDisabled(token.blockchain, disabledBlockchains)) return;
|
|
1437
1381
|
if (isChainKindLocked(token.chain.chainKind, lockChainKind)) return;
|
|
@@ -1440,8 +1384,28 @@ function TokenSelectDialog(props) {
|
|
|
1440
1384
|
onSelect({ token });
|
|
1441
1385
|
onClose();
|
|
1442
1386
|
}
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1387
|
+
const activeChain = view.kind === "rail-chain" || view.kind === "network-chain" ? chainByCode(availableChains, view.blockchain) : void 0;
|
|
1388
|
+
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;
|
|
1389
|
+
const awaitingBalances = showBalances && sections.every((section) => section.tokens.length === 0) && scopedTokens.some((token) => loadingOf(token));
|
|
1390
|
+
const tokenDisabled = (token) => isBlockchainDisabled(token.blockchain, disabledBlockchains) || isChainKindLocked(token.chain.chainKind, lockChainKind);
|
|
1391
|
+
const sliding = view.kind === "networks" || view.kind === "network-chain";
|
|
1392
|
+
const tokenPane = /* @__PURE__ */ jsx8(
|
|
1393
|
+
TokenPane,
|
|
1394
|
+
{
|
|
1395
|
+
sections,
|
|
1396
|
+
header: chainHeader,
|
|
1397
|
+
selectedAssetId,
|
|
1398
|
+
loading: loading || awaitingBalances,
|
|
1399
|
+
showBalances,
|
|
1400
|
+
showWalletStatus: payer,
|
|
1401
|
+
getBalance: balanceOf,
|
|
1402
|
+
isBalanceLoading: loadingOf,
|
|
1403
|
+
onSelectToken: handleSelectToken,
|
|
1404
|
+
isTokenDisabled: tokenDisabled,
|
|
1405
|
+
rowClassName
|
|
1406
|
+
}
|
|
1407
|
+
);
|
|
1408
|
+
return /* @__PURE__ */ jsx8(
|
|
1445
1409
|
Dialog,
|
|
1446
1410
|
{
|
|
1447
1411
|
open,
|
|
@@ -1452,10 +1416,11 @@ function TokenSelectDialog(props) {
|
|
|
1452
1416
|
fontSans,
|
|
1453
1417
|
titleClassName
|
|
1454
1418
|
),
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1419
|
+
headerClassName: cx("h-14 border-b border-[#e3e3e3] px-[22px] pr-4", headerClassName),
|
|
1420
|
+
closeClassName: cx("md:ml-0", closeClassName),
|
|
1421
|
+
contentClassName: cx("overflow-hidden", contentClassName),
|
|
1422
|
+
headerAction: /* @__PURE__ */ jsx8(
|
|
1423
|
+
SearchInput,
|
|
1459
1424
|
{
|
|
1460
1425
|
value: search,
|
|
1461
1426
|
onChange: setSearch,
|
|
@@ -1463,80 +1428,261 @@ function TokenSelectDialog(props) {
|
|
|
1463
1428
|
className: "ml-auto hidden w-[215px] shrink-0 md:block",
|
|
1464
1429
|
inputClassName: cx(SEARCH_INPUT_CLASS, searchClassName)
|
|
1465
1430
|
}
|
|
1466
|
-
)
|
|
1467
|
-
cardClassName: cx(
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1431
|
+
),
|
|
1432
|
+
cardClassName: cx(
|
|
1433
|
+
"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",
|
|
1434
|
+
className
|
|
1435
|
+
),
|
|
1436
|
+
children: /* @__PURE__ */ jsxs6("div", { className: "flex h-[min(520px,62vh)] min-h-0 flex-col overflow-hidden md:h-[min(555px,70vh)]", children: [
|
|
1437
|
+
/* @__PURE__ */ jsx8(
|
|
1438
|
+
SearchInput,
|
|
1471
1439
|
{
|
|
1472
1440
|
value: search,
|
|
1473
1441
|
onChange: setSearch,
|
|
1474
1442
|
placeholder: "search or paste address",
|
|
1475
|
-
className: "mb-3 shrink-0 md:hidden",
|
|
1443
|
+
className: "mb-3 shrink-0 px-[22px] md:hidden",
|
|
1476
1444
|
inputClassName: cx(SEARCH_INPUT_CLASS, searchClassName)
|
|
1477
1445
|
}
|
|
1478
|
-
)
|
|
1479
|
-
/* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
1446
|
+
),
|
|
1447
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 flex-1", children: [
|
|
1448
|
+
/* @__PURE__ */ jsx8(
|
|
1481
1449
|
NetworkRail,
|
|
1482
1450
|
{
|
|
1483
|
-
|
|
1484
|
-
chainFilter,
|
|
1485
|
-
chips: sortedChains,
|
|
1486
|
-
fundedBlockchains,
|
|
1451
|
+
role: payer ? "payer" : "receiver",
|
|
1487
1452
|
walletConnected,
|
|
1488
|
-
|
|
1453
|
+
selection: railSelection(view),
|
|
1454
|
+
chains: railChains,
|
|
1455
|
+
fundedBlockchains,
|
|
1489
1456
|
lockChainKind,
|
|
1490
1457
|
disabledBlockchains,
|
|
1491
1458
|
disabledReason,
|
|
1492
|
-
|
|
1493
|
-
|
|
1459
|
+
onSelectWallet: () => setView({ kind: "wallet" }),
|
|
1460
|
+
onSelectAll: () => setView({ kind: "all" }),
|
|
1461
|
+
onSelectNetworks: () => setView({ kind: "networks" }),
|
|
1462
|
+
onSelectChain: (blockchain) => handleSelectChain(blockchain, false),
|
|
1494
1463
|
className: railClassName
|
|
1495
1464
|
}
|
|
1496
1465
|
),
|
|
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,
|
|
1466
|
+
searching ? /* @__PURE__ */ jsx8(
|
|
1467
|
+
SearchResults,
|
|
1510
1468
|
{
|
|
1511
|
-
|
|
1469
|
+
groups: searchGroups,
|
|
1512
1470
|
selectedAssetId,
|
|
1513
|
-
loading
|
|
1471
|
+
loading,
|
|
1514
1472
|
showBalances,
|
|
1515
1473
|
showWalletStatus: payer,
|
|
1516
1474
|
getBalance: balanceOf,
|
|
1517
1475
|
isBalanceLoading: loadingOf,
|
|
1518
1476
|
onSelectToken: handleSelectToken,
|
|
1519
|
-
isTokenDisabled:
|
|
1520
|
-
searchClassName,
|
|
1477
|
+
isTokenDisabled: tokenDisabled,
|
|
1521
1478
|
rowClassName
|
|
1522
1479
|
}
|
|
1523
|
-
)
|
|
1480
|
+
) : sliding ? /* @__PURE__ */ jsx8(
|
|
1481
|
+
PaneTransition,
|
|
1482
|
+
{
|
|
1483
|
+
paneKey: view.kind === "network-chain" ? `network-chain:${view.blockchain}` : "networks",
|
|
1484
|
+
direction: slideDirection,
|
|
1485
|
+
children: view.kind === "networks" ? /* @__PURE__ */ jsx8(
|
|
1486
|
+
ChainPane,
|
|
1487
|
+
{
|
|
1488
|
+
chains: networkChains,
|
|
1489
|
+
showWalletStatus: payer,
|
|
1490
|
+
onSelectChain: (blockchain) => handleSelectChain(blockchain, true),
|
|
1491
|
+
lockChainKind,
|
|
1492
|
+
disabledBlockchains,
|
|
1493
|
+
disabledReason,
|
|
1494
|
+
scrollRef: networksScrollRef
|
|
1495
|
+
}
|
|
1496
|
+
) : tokenPane
|
|
1497
|
+
}
|
|
1498
|
+
) : /* @__PURE__ */ jsx8("div", { className: "relative min-h-0 flex-1", children: tokenPane })
|
|
1524
1499
|
] })
|
|
1525
1500
|
] })
|
|
1526
1501
|
}
|
|
1527
1502
|
);
|
|
1528
1503
|
}
|
|
1504
|
+
|
|
1505
|
+
// src/token-select/logos.ts
|
|
1506
|
+
var LOGO_HOST = "https://assets.dapdap.net";
|
|
1507
|
+
var CHAIN_LOGO_ALIAS = {
|
|
1508
|
+
eth: "ethereum",
|
|
1509
|
+
ethereum: "ethereum",
|
|
1510
|
+
base: "base",
|
|
1511
|
+
arb: "arbitrum",
|
|
1512
|
+
arbitrum: "arbitrum",
|
|
1513
|
+
op: "optimism",
|
|
1514
|
+
optimism: "optimism",
|
|
1515
|
+
pol: "polygon",
|
|
1516
|
+
polygon: "polygon",
|
|
1517
|
+
bsc: "bsc",
|
|
1518
|
+
avax: "avalanche",
|
|
1519
|
+
avalanche: "avalanche",
|
|
1520
|
+
gnosis: "gnosis",
|
|
1521
|
+
scroll: "scroll",
|
|
1522
|
+
xlayer: "xlayer",
|
|
1523
|
+
bera: "berachain",
|
|
1524
|
+
berachain: "berachain",
|
|
1525
|
+
near: "near",
|
|
1526
|
+
sol: "solana",
|
|
1527
|
+
solana: "solana",
|
|
1528
|
+
tron: "tron",
|
|
1529
|
+
zec: "zcash",
|
|
1530
|
+
zcash: "zcash"
|
|
1531
|
+
};
|
|
1532
|
+
function logoPath(path) {
|
|
1533
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
1534
|
+
return `${LOGO_HOST}${normalized}`;
|
|
1535
|
+
}
|
|
1536
|
+
function chainLogoUrl(blockchainOrNetwork) {
|
|
1537
|
+
const key = String(blockchainOrNetwork || "").toLowerCase();
|
|
1538
|
+
const file = CHAIN_LOGO_ALIAS[key] || key.replace(/\s+/g, "");
|
|
1539
|
+
return logoPath(`/stableflow/networks/${file}.png`);
|
|
1540
|
+
}
|
|
1541
|
+
function tokenLogoUrl(symbol) {
|
|
1542
|
+
const upper = String(symbol || "").toUpperCase();
|
|
1543
|
+
const file = upper === "USDT0" ? "usdt" : upper.toLowerCase();
|
|
1544
|
+
return logoPath(`/stableflow/tokens/${file}.png`);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// src/token-select/pay-config.ts
|
|
1548
|
+
var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
|
|
1549
|
+
var WRAP_NEAR_CONTRACT = "wrap.near";
|
|
1550
|
+
function asRecord(value) {
|
|
1551
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1552
|
+
return value;
|
|
1553
|
+
}
|
|
1554
|
+
function apiText(value) {
|
|
1555
|
+
if (typeof value === "string") return value.trim();
|
|
1556
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
1557
|
+
return "";
|
|
1558
|
+
}
|
|
1559
|
+
function apiNumber(value) {
|
|
1560
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1561
|
+
if (typeof value === "string" && value.trim()) {
|
|
1562
|
+
const parsed = Number(value);
|
|
1563
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
1564
|
+
}
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
function asBoolean(value, fallback = true) {
|
|
1568
|
+
if (typeof value === "boolean") return value;
|
|
1569
|
+
if (typeof value === "string") {
|
|
1570
|
+
const lower = value.trim().toLowerCase();
|
|
1571
|
+
if (lower === "true") return true;
|
|
1572
|
+
if (lower === "false") return false;
|
|
1573
|
+
}
|
|
1574
|
+
return fallback;
|
|
1575
|
+
}
|
|
1576
|
+
function mapSourceChain(raw) {
|
|
1577
|
+
const row = asRecord(raw) ?? {};
|
|
1578
|
+
return {
|
|
1579
|
+
network: apiText(row.network),
|
|
1580
|
+
chainId: apiText(row.chain_id ?? row.chainId),
|
|
1581
|
+
chainName: apiText(row.chain_name ?? row.chainName),
|
|
1582
|
+
logo: apiText(row.logo),
|
|
1583
|
+
explorer: apiText(row.explorer)
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
function mapSourceToken(raw) {
|
|
1587
|
+
const row = asRecord(raw) ?? {};
|
|
1588
|
+
return {
|
|
1589
|
+
symbol: apiText(row.symbol),
|
|
1590
|
+
network: apiText(row.network),
|
|
1591
|
+
decimals: apiNumber(row.decimals) ?? 0,
|
|
1592
|
+
contractAddress: apiText(row.contract_address ?? row.contractAddress),
|
|
1593
|
+
price: apiText(row.price),
|
|
1594
|
+
supportPayment: asBoolean(row.support_payment ?? row.supportPayment),
|
|
1595
|
+
supportReceive: asBoolean(row.support_receive ?? row.supportReceive)
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
function configBody(raw) {
|
|
1599
|
+
const row = asRecord(raw) ?? {};
|
|
1600
|
+
const data = asRecord(row.data);
|
|
1601
|
+
if (typeof row.code === "number" && data) return data;
|
|
1602
|
+
return row;
|
|
1603
|
+
}
|
|
1604
|
+
function mapPayConfigSource(raw) {
|
|
1605
|
+
const row = configBody(raw);
|
|
1606
|
+
const chains = Array.isArray(row.chains) ? row.chains.map(mapSourceChain) : [];
|
|
1607
|
+
const tokens = Array.isArray(row.tokens) ? row.tokens.map(mapSourceToken) : [];
|
|
1608
|
+
return { chains, tokens };
|
|
1609
|
+
}
|
|
1610
|
+
function chainKindFor(network, overrides = {}) {
|
|
1611
|
+
const key = network.trim().toLowerCase();
|
|
1612
|
+
if (!key) return null;
|
|
1613
|
+
return overrides[key] ?? DEFAULT_CHAIN_KINDS[key] ?? null;
|
|
1614
|
+
}
|
|
1615
|
+
function normalizeSymbol(symbol) {
|
|
1616
|
+
const upper = symbol.trim().toUpperCase();
|
|
1617
|
+
if (!upper) return null;
|
|
1618
|
+
if (upper === "USDT0") return "USDT";
|
|
1619
|
+
if (upper === "WNEAR") return "NEAR";
|
|
1620
|
+
return upper;
|
|
1621
|
+
}
|
|
1622
|
+
function tokenAssetId(network, symbol, contractAddress) {
|
|
1623
|
+
const addr = String(contractAddress || "").trim();
|
|
1624
|
+
return `${network}:${symbol}:${addr || "native"}`;
|
|
1625
|
+
}
|
|
1626
|
+
function isNativeAddress(contractAddress) {
|
|
1627
|
+
const addr = String(contractAddress || "").trim();
|
|
1628
|
+
if (!addr) return true;
|
|
1629
|
+
const lower = addr.toLowerCase();
|
|
1630
|
+
return lower === "native" || lower === ZERO_ADDRESS2;
|
|
1631
|
+
}
|
|
1632
|
+
function normalizePayConfig(source, options) {
|
|
1633
|
+
const overrides = options?.chainKinds ?? {};
|
|
1634
|
+
const chains = [];
|
|
1635
|
+
for (const chain of source.chains) {
|
|
1636
|
+
const chainKind = chainKindFor(chain.network, overrides);
|
|
1637
|
+
if (!chainKind || !chain.network) continue;
|
|
1638
|
+
chains.push({
|
|
1639
|
+
blockchain: chain.network,
|
|
1640
|
+
chainName: chain.chainName || chain.network,
|
|
1641
|
+
chainKind,
|
|
1642
|
+
chainId: chain.chainId || void 0,
|
|
1643
|
+
logo: chain.logo || chainLogoUrl(chain.network),
|
|
1644
|
+
explorer: chain.explorer
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
const chainByCode2 = new Map(chains.map((chain) => [chain.blockchain, chain]));
|
|
1648
|
+
const tokens = [];
|
|
1649
|
+
for (const token of source.tokens) {
|
|
1650
|
+
const chain = chainByCode2.get(token.network);
|
|
1651
|
+
if (!chain) continue;
|
|
1652
|
+
const symbol = normalizeSymbol(token.symbol);
|
|
1653
|
+
if (!symbol) continue;
|
|
1654
|
+
if (!Number.isInteger(token.decimals) || token.decimals < 0) continue;
|
|
1655
|
+
const contractAddress = token.contractAddress.trim() || null;
|
|
1656
|
+
tokens.push({
|
|
1657
|
+
assetId: tokenAssetId(token.network, symbol, contractAddress),
|
|
1658
|
+
symbol,
|
|
1659
|
+
providerSymbol: token.symbol,
|
|
1660
|
+
blockchain: token.network,
|
|
1661
|
+
decimals: token.decimals,
|
|
1662
|
+
contractAddress,
|
|
1663
|
+
price: Number(token.price || 1),
|
|
1664
|
+
logo: tokenLogoUrl(symbol),
|
|
1665
|
+
supportPayment: token.supportPayment,
|
|
1666
|
+
supportReceive: token.supportReceive,
|
|
1667
|
+
chain
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
const hasNearWrap = tokens.some((token) => token.symbol === "NEAR" && token.blockchain === "near" && token.contractAddress?.toLowerCase() === WRAP_NEAR_CONTRACT);
|
|
1671
|
+
if (!hasNearWrap) return tokens;
|
|
1672
|
+
return tokens.filter((token) => !(token.symbol === "NEAR" && token.blockchain === "near" && isNativeAddress(token.contractAddress)));
|
|
1673
|
+
}
|
|
1529
1674
|
export {
|
|
1530
|
-
|
|
1675
|
+
KNOWN_CHAIN_KINDS,
|
|
1531
1676
|
PayWidgetsProvider,
|
|
1532
1677
|
TokenSelectDialog,
|
|
1678
|
+
chainsFromTokens,
|
|
1533
1679
|
clearPayTokenBalances,
|
|
1534
1680
|
mapPayConfigSource,
|
|
1535
1681
|
normalizePayConfig,
|
|
1682
|
+
ownerKey2 as ownerKey,
|
|
1536
1683
|
parsePopularTokens,
|
|
1537
1684
|
refreshPayTokenBalance,
|
|
1538
|
-
|
|
1539
|
-
usePayConfigSnapshot,
|
|
1685
|
+
tokenAssetId,
|
|
1540
1686
|
usePayTokenBalance
|
|
1541
1687
|
};
|
|
1542
1688
|
//# sourceMappingURL=index.js.map
|