@lydianpay/lydianconnect 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -66,6 +66,14 @@ var ConnectManager = class {
66
66
  get(walletId) {
67
67
  return this.connections.get(walletId);
68
68
  }
69
+ /**
70
+ * Fire a wallet's deep link. Goes straight to the fallback: it is the only
71
+ * connector that owns a link table, and the only one that emits the
72
+ * `display_uri` a host is responding to when it calls this.
73
+ */
74
+ openWallet(input) {
75
+ return this.fallback.openWallet?.(input) ?? false;
76
+ }
69
77
  async disconnect(walletId) {
70
78
  if (walletId) {
71
79
  await this.connections.get(walletId)?.disconnect().catch(() => {
@@ -238,10 +246,15 @@ var WALLET_CATALOG = WALLETS.map((w) => ({
238
246
  }));
239
247
 
240
248
  // src/core/connector.ts
249
+ function requestedChain(req) {
250
+ return req.namespace.value.chains?.[0];
251
+ }
241
252
  var BaseConnector = class {
242
253
  constructor() {
243
254
  this.walletIds = [];
244
255
  this.handlers = /* @__PURE__ */ new Set();
256
+ /** Depth of in-flight connects; see {@link duringConnect}. */
257
+ this.connecting = 0;
245
258
  }
246
259
  servesNamespace(_namespaceName) {
247
260
  return true;
@@ -255,7 +268,28 @@ var BaseConnector = class {
255
268
  this.handlers.add(handler);
256
269
  return () => this.handlers.delete(handler);
257
270
  }
271
+ /**
272
+ * Run a connect with its handshake events suppressed.
273
+ *
274
+ * A connector that binds provider listeners before requesting accounts sees the
275
+ * wallet's own setup traffic — an initial `chainChanged`, and another when
276
+ * `ensureChain` switches. Forwarding those makes the host believe the user moved
277
+ * networks mid-connect. Nothing is lost by dropping them: connect() reads the
278
+ * authoritative account and chain when it settles, and only changes made after
279
+ * that are the user's.
280
+ */
281
+ async duringConnect(run) {
282
+ this.connecting++;
283
+ try {
284
+ return await run();
285
+ } finally {
286
+ this.connecting--;
287
+ }
288
+ }
258
289
  emit(event) {
290
+ if (this.connecting > 0 && (event.type === "chainChanged" || event.type === "accountsChanged")) {
291
+ return;
292
+ }
259
293
  for (const handler of this.handlers) handler(event);
260
294
  }
261
295
  };
@@ -264,6 +298,84 @@ var BaseConnector = class {
264
298
  function deriveUrl() {
265
299
  return typeof window !== "undefined" ? window.location.origin : "";
266
300
  }
301
+ function deriveRedirectUrl() {
302
+ if (typeof window === "undefined") return "";
303
+ return window.location.origin + window.location.pathname;
304
+ }
305
+
306
+ // src/connectors/eip155/chain.ts
307
+ var CHAIN_SETTLE = { timeoutMs: 4e3, intervalMs: 100 };
308
+ function toCaip(chainId) {
309
+ if (!chainId) return "eip155:1";
310
+ const num = chainId.startsWith("0x") ? parseInt(chainId, 16) : Number(chainId);
311
+ return `eip155:${Number.isFinite(num) ? num : 1}`;
312
+ }
313
+ async function currentChain(rpc2) {
314
+ const raw = await rpc2({ method: "eth_chainId" }).catch(() => null);
315
+ return toCaip(typeof raw === "string" ? raw : null);
316
+ }
317
+ async function settleChain({
318
+ rpc: rpc2,
319
+ chainId,
320
+ timeoutMs,
321
+ intervalMs
322
+ }) {
323
+ const deadline = Date.now() + timeoutMs;
324
+ for (; ; ) {
325
+ const actual = await currentChain(rpc2);
326
+ if (actual === chainId || Date.now() >= deadline) return;
327
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
328
+ }
329
+ }
330
+ async function ensureChain({
331
+ rpc: rpc2,
332
+ chainId,
333
+ timeoutMs = CHAIN_SETTLE.timeoutMs,
334
+ intervalMs = CHAIN_SETTLE.intervalMs
335
+ }) {
336
+ const chainNumber = chainId ? Number(chainId.split(":")[1]) : NaN;
337
+ if (!Number.isFinite(chainNumber) || chainNumber <= 0) return;
338
+ const requested = `eip155:${chainNumber}`;
339
+ if (await currentChain(rpc2) === requested) return;
340
+ const switched = await rpc2({
341
+ method: "wallet_switchEthereumChain",
342
+ params: [{ chainId: `0x${chainNumber.toString(16)}` }]
343
+ }).then(
344
+ () => true,
345
+ // Chain not added to the wallet, or the user declined — the caller's
346
+ // chainChanged will reflect reality. No settle: the chain isn't moving.
347
+ () => false
348
+ );
349
+ if (switched) {
350
+ await settleChain({ rpc: rpc2, chainId: requested, timeoutMs, intervalMs });
351
+ }
352
+ }
353
+
354
+ // src/core/abort.ts
355
+ function withSignal(promise, signal, onAbort) {
356
+ if (!signal) return promise;
357
+ if (signal.aborted) {
358
+ onAbort?.();
359
+ return Promise.reject(new DOMException("Aborted", "AbortError"));
360
+ }
361
+ return new Promise((resolve, reject) => {
362
+ const abortHandler = () => {
363
+ onAbort?.();
364
+ reject(new DOMException("Aborted", "AbortError"));
365
+ };
366
+ signal.addEventListener("abort", abortHandler, { once: true });
367
+ promise.then(
368
+ (value) => {
369
+ signal.removeEventListener("abort", abortHandler);
370
+ resolve(value);
371
+ },
372
+ (error) => {
373
+ signal.removeEventListener("abort", abortHandler);
374
+ reject(error);
375
+ }
376
+ );
377
+ });
378
+ }
267
379
 
268
380
  // src/connectors/metamask/connector.ts
269
381
  var WALLET_ID = "metamask";
@@ -322,17 +434,23 @@ var MetaMaskConnector = class extends BaseConnector {
322
434
  );
323
435
  }
324
436
  async connect(req) {
325
- const provider = await this.getProvider();
326
- const accounts = await provider.request({
327
- method: "eth_requestAccounts"
437
+ return this.duringConnect(async () => {
438
+ const provider = await this.getProvider();
439
+ const accounts = await withSignal(
440
+ provider.request({ method: "eth_requestAccounts" }),
441
+ req.signal
442
+ );
443
+ const account = accounts?.[0];
444
+ if (!account) throw new Error("MetaMask returned no account");
445
+ await ensureChain({
446
+ rpc: rpcFor(provider),
447
+ chainId: requestedChain(req)
448
+ });
449
+ const chainId = toCaip(
450
+ await provider.request({ method: "eth_chainId" })
451
+ );
452
+ return this.toConnection(provider, account, chainId);
328
453
  });
329
- const account = accounts?.[0];
330
- if (!account) throw new Error("MetaMask returned no account");
331
- await this.ensureChain(provider, req);
332
- const chainId = toCaip(
333
- await provider.request({ method: "eth_chainId" })
334
- );
335
- return this.toConnection(provider, account, chainId);
336
454
  }
337
455
  async restore() {
338
456
  const provider = await this.getProvider().catch(() => null);
@@ -350,22 +468,6 @@ var MetaMaskConnector = class extends BaseConnector {
350
468
  this.provider = null;
351
469
  this.listenersBound = false;
352
470
  }
353
- /** Best-effort switch to the requested chain; leaves the wallet as-is if it's unknown to it. */
354
- async ensureChain(provider, req) {
355
- const wanted = req.namespace.value.chains?.[0];
356
- const wantedNum = wanted ? Number(wanted.split(":")[1]) : NaN;
357
- if (!Number.isFinite(wantedNum) || wantedNum <= 0) return;
358
- const current = parseInt(
359
- await provider.request({ method: "eth_chainId" }).catch(() => "0x0") ?? "0x0",
360
- 16
361
- );
362
- if (current === wantedNum) return;
363
- await provider.request({
364
- method: "wallet_switchEthereumChain",
365
- params: [{ chainId: `0x${wantedNum.toString(16)}` }]
366
- }).catch(() => {
367
- });
368
- }
369
471
  toConnection(provider, account, chainId) {
370
472
  return {
371
473
  walletId: WALLET_ID,
@@ -387,20 +489,18 @@ var MetaMaskConnector = class extends BaseConnector {
387
489
  };
388
490
  }
389
491
  };
390
- function toCaip(hexChainId) {
391
- if (!hexChainId) return "eip155:1";
392
- const num = hexChainId.startsWith("0x") ? parseInt(hexChainId, 16) : Number(hexChainId);
393
- return `eip155:${Number.isFinite(num) ? num : 1}`;
492
+ function rpcFor(provider) {
493
+ return (args) => provider.request(args);
394
494
  }
395
495
 
396
496
  // src/connectors/coinbase/connector.ts
497
+ var WALLET_ID2 = "coinbase";
397
498
  function rpc(provider, method, params) {
398
499
  return provider.request({
399
500
  method,
400
501
  params
401
502
  });
402
503
  }
403
- var WALLET_ID2 = "coinbase";
404
504
  var CoinbaseConnector = class extends BaseConnector {
405
505
  constructor(app) {
406
506
  super();
@@ -437,7 +537,7 @@ var CoinbaseConnector = class extends BaseConnector {
437
537
  this.emit({
438
538
  type: "chainChanged",
439
539
  walletId: WALLET_ID2,
440
- chainId: toCaip2(String(hex))
540
+ chainId: toCaip(String(hex))
441
541
  });
442
542
  });
443
543
  provider.on("accountsChanged", (accounts) => {
@@ -452,13 +552,18 @@ var CoinbaseConnector = class extends BaseConnector {
452
552
  );
453
553
  }
454
554
  async connect(req) {
455
- const provider = await this.getProvider(evmChainIds(req));
456
- const accounts = await rpc(provider, "eth_requestAccounts");
457
- const account = accounts?.[0];
458
- if (!account) throw new Error("Coinbase Wallet returned no account");
459
- await this.ensureChain(provider, req);
460
- const chainId = toCaip2(await rpc(provider, "eth_chainId"));
461
- return this.toConnection(provider, account, chainId);
555
+ return this.duringConnect(async () => {
556
+ const provider = await this.getProvider(evmChainIds(req));
557
+ const accounts = await rpc(provider, "eth_requestAccounts");
558
+ const account = accounts?.[0];
559
+ if (!account) throw new Error("Coinbase Wallet returned no account");
560
+ await ensureChain({
561
+ rpc: rpcFor2(provider),
562
+ chainId: requestedChain(req)
563
+ });
564
+ const chainId = toCaip(await rpc(provider, "eth_chainId"));
565
+ return this.toConnection(provider, account, chainId);
566
+ });
462
567
  }
463
568
  async restore() {
464
569
  const provider = await this.getProvider().catch(() => null);
@@ -468,7 +573,7 @@ var CoinbaseConnector = class extends BaseConnector {
468
573
  );
469
574
  const account = accounts?.[0];
470
575
  if (!account) return [];
471
- const chainId = toCaip2(
576
+ const chainId = toCaip(
472
577
  await rpc(provider, "eth_chainId").catch(() => "0x1")
473
578
  );
474
579
  return [this.toConnection(provider, account, chainId)];
@@ -479,20 +584,6 @@ var CoinbaseConnector = class extends BaseConnector {
479
584
  this.provider = null;
480
585
  this.listenersBound = false;
481
586
  }
482
- async ensureChain(provider, req) {
483
- const wanted = req.namespace.value.chains?.[0];
484
- const wantedNum = wanted ? Number(wanted.split(":")[1]) : NaN;
485
- if (!Number.isFinite(wantedNum) || wantedNum <= 0) return;
486
- const current = parseInt(
487
- await rpc(provider, "eth_chainId").catch(() => "0x0") ?? "0x0",
488
- 16
489
- );
490
- if (current === wantedNum) return;
491
- await rpc(provider, "wallet_switchEthereumChain", [
492
- { chainId: `0x${wantedNum.toString(16)}` }
493
- ]).catch(() => {
494
- });
495
- }
496
587
  toConnection(provider, account, chainId) {
497
588
  return {
498
589
  walletId: WALLET_ID2,
@@ -512,10 +603,8 @@ var CoinbaseConnector = class extends BaseConnector {
512
603
  };
513
604
  }
514
605
  };
515
- function toCaip2(hexChainId) {
516
- if (!hexChainId) return "eip155:1";
517
- const num = hexChainId.startsWith("0x") ? parseInt(hexChainId, 16) : Number(hexChainId);
518
- return `eip155:${Number.isFinite(num) ? num : 1}`;
606
+ function rpcFor2(provider) {
607
+ return ({ method, params }) => rpc(provider, method, params);
519
608
  }
520
609
  function evmChainIds(req) {
521
610
  const wanted = req.namespace.value.chains?.[0];
@@ -579,8 +668,8 @@ var InjectedConnector = class extends BaseConnector {
579
668
  const account = accounts?.[0];
580
669
  if (!account)
581
670
  throw new Error(`${req.walletId} extension returned no account`);
582
- await this.ensureChain(provider, req);
583
- const chainId = toCaip3(
671
+ await ensureChain({ rpc: rpcFor3(provider), chainId: requestedChain(req) });
672
+ const chainId = toCaip(
584
673
  await provider.request({ method: "eth_chainId" })
585
674
  );
586
675
  this.bindEvents(provider, req.walletId);
@@ -599,7 +688,7 @@ var InjectedConnector = class extends BaseConnector {
599
688
  const accounts = await provider.request({ method: "eth_accounts" }).catch(() => []);
600
689
  const account = accounts?.[0];
601
690
  if (!account) continue;
602
- const chainId = toCaip3(
691
+ const chainId = toCaip(
603
692
  await provider.request({ method: "eth_chainId" }).catch(() => "0x1")
604
693
  );
605
694
  this.bindEvents(provider, walletId);
@@ -618,7 +707,7 @@ var InjectedConnector = class extends BaseConnector {
618
707
  this.emit({
619
708
  type: "chainChanged",
620
709
  walletId,
621
- chainId: toCaip3(String(hex))
710
+ chainId: toCaip(String(hex))
622
711
  });
623
712
  });
624
713
  provider.on("accountsChanged", (accounts) => {
@@ -631,22 +720,6 @@ var InjectedConnector = class extends BaseConnector {
631
720
  () => this.emit({ type: "disconnect", walletId })
632
721
  );
633
722
  }
634
- /** Best-effort switch to the requested chain; leaves the wallet as-is if it's unknown to it. */
635
- async ensureChain(provider, req) {
636
- const wanted = req.namespace.value.chains?.[0];
637
- const wantedNum = wanted ? Number(wanted.split(":")[1]) : NaN;
638
- if (!Number.isFinite(wantedNum) || wantedNum <= 0) return;
639
- const current = parseInt(
640
- await provider.request({ method: "eth_chainId" }).catch(() => "0x0") ?? "0x0",
641
- 16
642
- );
643
- if (current === wantedNum) return;
644
- await provider.request({
645
- method: "wallet_switchEthereumChain",
646
- params: [{ chainId: `0x${wantedNum.toString(16)}` }]
647
- }).catch(() => {
648
- });
649
- }
650
723
  toConnection(provider, walletId, account, chainId) {
651
724
  return {
652
725
  walletId,
@@ -670,29 +743,8 @@ var InjectedConnector = class extends BaseConnector {
670
743
  };
671
744
  }
672
745
  };
673
- function toCaip3(hexChainId) {
674
- if (!hexChainId) return "eip155:1";
675
- const num = hexChainId.startsWith("0x") ? parseInt(hexChainId, 16) : Number(hexChainId);
676
- return `eip155:${Number.isFinite(num) ? num : 1}`;
677
- }
678
- function withSignal(promise, signal) {
679
- if (!signal) return promise;
680
- if (signal.aborted)
681
- return Promise.reject(new DOMException("Aborted", "AbortError"));
682
- return new Promise((resolve, reject) => {
683
- const onAbort = () => reject(new DOMException("Aborted", "AbortError"));
684
- signal.addEventListener("abort", onAbort, { once: true });
685
- promise.then(
686
- (value) => {
687
- signal.removeEventListener("abort", onAbort);
688
- resolve(value);
689
- },
690
- (error) => {
691
- signal.removeEventListener("abort", onAbort);
692
- reject(error);
693
- }
694
- );
695
- });
746
+ function rpcFor3(provider) {
747
+ return (args) => provider.request(args);
696
748
  }
697
749
 
698
750
  // src/connectors/walletconnect/wallet-links.ts
@@ -1142,13 +1194,15 @@ function buildLink(base, uri) {
1142
1194
  const sep = base.includes("?") ? "&" : "?";
1143
1195
  return `${base}${sep}uri=${encodeURIComponent(uri)}`;
1144
1196
  }
1145
- function openWallet(link, uri) {
1146
- if (typeof document === "undefined") return;
1147
- if (link.scheme) {
1148
- openHref(buildLink(link.scheme, uri));
1149
- } else if (link.universal) {
1150
- openHref(buildLink(link.universal, uri));
1151
- }
1197
+ function canOpen(link) {
1198
+ return !!(link?.scheme || link?.universal);
1199
+ }
1200
+ function openWallet(link, uri, via = "scheme") {
1201
+ if (typeof document === "undefined") return false;
1202
+ const base = via === "universal" ? link.universal : link.scheme ?? link.universal;
1203
+ if (!base) return false;
1204
+ openHref(buildLink(base, uri));
1205
+ return true;
1152
1206
  }
1153
1207
  function openWalletApp(link) {
1154
1208
  if (typeof document === "undefined") return;
@@ -1187,6 +1241,7 @@ function detectAppOpen(timeoutMs) {
1187
1241
 
1188
1242
  // src/connectors/walletconnect/connector.ts
1189
1243
  var STORAGE_KEY = "lydianconnect.wc.topics";
1244
+ var PING_TIMEOUT_MS = 3e3;
1190
1245
  var WalletConnectConnector = class extends BaseConnector {
1191
1246
  constructor(app, options) {
1192
1247
  super();
@@ -1197,6 +1252,9 @@ var WalletConnectConnector = class extends BaseConnector {
1197
1252
  // claims any wallet not served by an SDK connector
1198
1253
  this.client = null;
1199
1254
  this.topics = /* @__PURE__ */ new Map();
1255
+ /** walletId -> pairing URI of the connect currently awaiting approval, so a
1256
+ * host-driven open button can fire without the host tracking the URI itself. */
1257
+ this.pending = /* @__PURE__ */ new Map();
1200
1258
  this.links = { ...DEFAULT_WALLET_LINKS, ...options.walletLinks ?? {} };
1201
1259
  }
1202
1260
  isAvailable() {
@@ -1204,13 +1262,16 @@ var WalletConnectConnector = class extends BaseConnector {
1204
1262
  }
1205
1263
  async getClient() {
1206
1264
  if (this.client) return this.client;
1265
+ const redirectUrl = deriveRedirectUrl();
1207
1266
  const client = await SignClient.init({
1208
1267
  projectId: this.options.projectId,
1209
1268
  metadata: {
1210
1269
  name: this.app.appName,
1211
1270
  description: this.app.description ?? this.app.appName,
1212
1271
  url: deriveUrl(),
1213
- icons: this.app.icon ? [this.app.icon] : []
1272
+ icons: this.app.icon ? [this.app.icon] : [],
1273
+ // Wallets may use this to return the user after deep-link approvals.
1274
+ ...redirectUrl ? { redirect: { universal: redirectUrl } } : {}
1214
1275
  },
1215
1276
  relayUrl: this.options.relayUrl
1216
1277
  });
@@ -1240,35 +1301,37 @@ var WalletConnectConnector = class extends BaseConnector {
1240
1301
  }
1241
1302
  async connect(req) {
1242
1303
  const client = await this.getClient();
1243
- const reused = this.tryReuse(client, req);
1304
+ const reused = await this.tryReuse(client, req);
1244
1305
  if (reused) return reused;
1245
1306
  const { uri, approval } = await client.connect({
1246
1307
  requiredNamespaces: { [req.namespace.name]: req.namespace.value }
1247
1308
  });
1248
1309
  if (!uri) throw new Error("WalletConnect did not return a pairing URI");
1249
- this.emit({ type: "display_uri", walletId: req.walletId, uri });
1250
- if (isMobile()) {
1251
- const link = this.links[req.walletId];
1252
- if (link) {
1253
- openWallet(link, uri);
1254
- void detectAppOpen(this.options.openTimeoutMs ?? 2e3).then(
1255
- (opened) => {
1256
- if (!opened)
1257
- this.emit({
1258
- type: "wallet_open_failed",
1259
- walletId: req.walletId,
1260
- uri,
1261
- store: link.store
1262
- });
1263
- }
1264
- );
1265
- }
1310
+ const link = isMobile() ? this.links[req.walletId] : void 0;
1311
+ this.emit({
1312
+ type: "display_uri",
1313
+ walletId: req.walletId,
1314
+ uri,
1315
+ deepLink: canOpen(link)
1316
+ });
1317
+ if (link && canOpen(link)) {
1318
+ this.pending.set(req.walletId, uri);
1319
+ openWallet(link, uri);
1320
+ void detectAppOpen(this.options.openTimeoutMs ?? 2e3).then((opened) => {
1321
+ if (!opened)
1322
+ this.emit({
1323
+ type: "wallet_open_failed",
1324
+ walletId: req.walletId,
1325
+ uri,
1326
+ store: link.store
1327
+ });
1328
+ });
1266
1329
  }
1267
1330
  const pairingTopic = parseUri(uri).topic;
1268
- const session = await withSignal2(approval(), req.signal, () => {
1331
+ const session = await withSignal(approval(), req.signal, () => {
1269
1332
  void client.core.pairing.disconnect({ topic: pairingTopic }).catch(() => {
1270
1333
  });
1271
- });
1334
+ }).finally(() => this.pending.delete(req.walletId));
1272
1335
  const connection = this.toConnection(
1273
1336
  client,
1274
1337
  req.walletId,
@@ -1279,11 +1342,30 @@ var WalletConnectConnector = class extends BaseConnector {
1279
1342
  this.persist();
1280
1343
  return connection;
1281
1344
  }
1345
+ /**
1346
+ * Fire a wallet's deep link on demand. The host calls this from a real click,
1347
+ * which is the one context a mobile browser reliably allows a scheme
1348
+ * navigation from — see the note on {@link openWallet}.
1349
+ */
1350
+ openWallet(input) {
1351
+ const link = this.links[input.walletId];
1352
+ if (!link) return false;
1353
+ const uri = input.uri ?? this.pending.get(input.walletId);
1354
+ if (!uri) {
1355
+ openWalletApp(link);
1356
+ return !!link.native;
1357
+ }
1358
+ return openWallet(link, uri, input.via ?? "scheme");
1359
+ }
1282
1360
  async restore() {
1283
1361
  const client = await this.getClient();
1362
+ const checked = await Promise.all(
1363
+ [...this.topics].map(
1364
+ async ([walletId, topic]) => [walletId, await this.liveSession(client, topic)]
1365
+ )
1366
+ );
1284
1367
  const out = [];
1285
- for (const [walletId, topic] of [...this.topics]) {
1286
- const session = this.liveSession(client, topic);
1368
+ for (const [walletId, session] of checked) {
1287
1369
  if (!session) {
1288
1370
  this.topics.delete(walletId);
1289
1371
  continue;
@@ -1307,15 +1389,17 @@ var WalletConnectConnector = class extends BaseConnector {
1307
1389
  );
1308
1390
  }
1309
1391
  this.topics.clear();
1392
+ this.pending.clear();
1310
1393
  this.persist();
1311
1394
  }
1312
1395
  // --- helpers ---
1313
- tryReuse(client, req) {
1396
+ async tryReuse(client, req) {
1314
1397
  const topic = this.topics.get(req.walletId);
1315
1398
  if (!topic) return null;
1316
- const session = this.liveSession(client, topic);
1399
+ const session = await this.liveSession(client, topic);
1317
1400
  if (!session) {
1318
1401
  this.topics.delete(req.walletId);
1402
+ this.persist();
1319
1403
  return null;
1320
1404
  }
1321
1405
  const ns = session.namespaces[req.namespace.name];
@@ -1326,7 +1410,9 @@ var WalletConnectConnector = class extends BaseConnector {
1326
1410
  toConnection(client, walletId, session, namespace) {
1327
1411
  const ns = session.namespaces[namespace.name];
1328
1412
  const account = (ns?.accounts ?? []).map((a) => a.split(":")[2]).find((a) => !!a) ?? "";
1329
- const chainId = ns?.chains?.[0] ?? (ns?.accounts?.[0] ? ns.accounts[0].split(":").slice(0, 2).join(":") : `${namespace.name}:1`);
1413
+ const approved = ns?.chains?.length ? ns.chains : (ns?.accounts ?? []).map((a) => a.split(":").slice(0, 2).join(":"));
1414
+ const wanted = namespace.value.chains?.[0];
1415
+ const chainId = wanted && approved.includes(wanted) ? wanted : approved[0] ?? `${namespace.name}:1`;
1330
1416
  const topic = session.topic;
1331
1417
  return {
1332
1418
  walletId,
@@ -1351,12 +1437,27 @@ var WalletConnectConnector = class extends BaseConnector {
1351
1437
  }
1352
1438
  };
1353
1439
  }
1354
- liveSession(client, topic) {
1440
+ /**
1441
+ * A stored session the wallet is still on the other end of. The local record
1442
+ * is not evidence — it survives the user clearing the dApp inside their
1443
+ * wallet — and reusing a dead one returns a Connection that looks healthy
1444
+ * while every request hangs. So the peer has to answer for it.
1445
+ */
1446
+ async liveSession(client, topic) {
1447
+ let session;
1355
1448
  try {
1356
- return client.session.get(topic);
1449
+ session = client.session.get(topic);
1357
1450
  } catch {
1358
1451
  return void 0;
1359
1452
  }
1453
+ let timer;
1454
+ const alive = await Promise.race([
1455
+ client.ping({ topic }).then(() => true).catch(() => false),
1456
+ new Promise((resolve) => {
1457
+ timer = setTimeout(() => resolve(false), PING_TIMEOUT_MS);
1458
+ })
1459
+ ]).finally(() => clearTimeout(timer));
1460
+ return alive ? session : void 0;
1360
1461
  }
1361
1462
  walletForTopic(topic) {
1362
1463
  for (const [walletId, t] of this.topics) if (t === topic) return walletId;
@@ -1387,30 +1488,6 @@ function firstNamespace(session) {
1387
1488
  value: { chains: v.chains, methods: v.methods, events: v.events }
1388
1489
  };
1389
1490
  }
1390
- function withSignal2(promise, signal, onAbort) {
1391
- if (!signal) return promise;
1392
- if (signal.aborted) {
1393
- onAbort();
1394
- return Promise.reject(new DOMException("Aborted", "AbortError"));
1395
- }
1396
- return new Promise((resolve, reject) => {
1397
- const abortHandler = () => {
1398
- onAbort();
1399
- reject(new DOMException("Aborted", "AbortError"));
1400
- };
1401
- signal.addEventListener("abort", abortHandler, { once: true });
1402
- promise.then(
1403
- (v) => {
1404
- signal.removeEventListener("abort", abortHandler);
1405
- resolve(v);
1406
- },
1407
- (e) => {
1408
- signal.removeEventListener("abort", abortHandler);
1409
- reject(e);
1410
- }
1411
- );
1412
- });
1413
- }
1414
1491
 
1415
1492
  // src/core/lydian-connect.ts
1416
1493
  function injectedRdnsMap(sdkServed) {
@@ -1456,6 +1533,20 @@ var LydianConnect = class {
1456
1533
  signal: input.signal
1457
1534
  });
1458
1535
  }
1536
+ /**
1537
+ * Open a wallet app, optionally handing it the pairing URI from `display_uri`.
1538
+ *
1539
+ * Call this from a real click handler. A mobile browser will suppress the same
1540
+ * navigation issued automatically after an async step, which is why the
1541
+ * `display_uri` event reports `deepLink` — that is the library asking the host
1542
+ * for a button to put this behind.
1543
+ *
1544
+ * Returns whether a link was fired. `false` means nothing is known for this
1545
+ * wallet on this device; show the QR code instead.
1546
+ */
1547
+ openWallet(input) {
1548
+ return this.manager.openWallet(input);
1549
+ }
1459
1550
  restore() {
1460
1551
  return this.manager.restore();
1461
1552
  }