@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/README.md +2 -0
- package/dist/index.cjs +251 -160
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -2
- package/dist/index.d.ts +40 -2
- package/dist/index.js +251 -160
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
391
|
-
|
|
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:
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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 =
|
|
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
|
|
516
|
-
|
|
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
|
|
583
|
-
const chainId =
|
|
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 =
|
|
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:
|
|
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
|
|
674
|
-
|
|
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
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|