@etherplay/connect 0.0.8 → 0.0.9

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/src/index.ts ADDED
@@ -0,0 +1,706 @@
1
+ import type {AlchemyMechanism, OriginAccount} from '@etherplay/alchemy';
2
+ import {writable} from 'svelte/store';
3
+ import {createPopupLauncher, type PopupPromise} from './popup.js';
4
+ import type {EIP1193WindowWalletProvider} from 'eip-1193';
5
+ import {
6
+ fromEntropyKeyToMnemonic,
7
+ fromMnemonicToFirstAccount,
8
+ fromSignatureToKey,
9
+ originKeyMessage,
10
+ originPublicKeyPublicationMessage,
11
+ } from '@etherplay/alchemy';
12
+ import {hashMessage} from './utils.js';
13
+
14
+ export {fromEntropyKeyToMnemonic, originPublicKeyPublicationMessage, originKeyMessage};
15
+ export type {OriginAccount};
16
+
17
+ export type PopupSettings = {
18
+ walletHost: string;
19
+ mechanism: AlchemyMechanism;
20
+ // extraParams?: Record<string, string>;
21
+ };
22
+
23
+ export type WalletMechanism<WalletName extends string | undefined, Address extends `0x${string}` | undefined> = {
24
+ type: 'wallet';
25
+ } & (WalletName extends undefined ? {name?: undefined} : {name: WalletName}) &
26
+ (Address extends undefined ? {address?: undefined} : {address: Address});
27
+
28
+ export type Mechanism = AlchemyMechanism | WalletMechanism<string | undefined, `0x${string}` | undefined>;
29
+
30
+ export type FullfilledMechanism = AlchemyMechanism | WalletMechanism<string, `0x${string}`>;
31
+
32
+ export type Connection = {
33
+ // The connection can have an error in every state.
34
+ // a banner or other mechanism to show error should be used.
35
+ // error should be dismissable
36
+ error?: {message: string; cause?: any};
37
+ // wallets represent the web3 wallet installed on the user browser
38
+ wallets: EIP6963ProviderDetail[];
39
+ } & ( // loading can be true initially as the system will try to auto-login and fetch installed web3 wallet // Start in Idle
40
+ | {
41
+ step: 'Idle';
42
+ loading: boolean;
43
+ }
44
+ // It can then end up in MechanismToChoose if no specific connection mechanism was chosen upon clicking "connect"
45
+ | {
46
+ step: 'MechanismToChoose';
47
+ }
48
+ // if a social/email login mechanism was chose, a popup will be launched
49
+ // popupClosed can be true and this means the popup has been closed and the user has to cancel the process to continue further
50
+ | {
51
+ step: 'PopupLaunched';
52
+ popupClosed: boolean;
53
+ mechanism: AlchemyMechanism;
54
+ }
55
+ // If the user has chosen to use web3-wallet there might be multi-choice for it
56
+ | {
57
+ step: 'WalletToChoose';
58
+ mechanism: WalletMechanism<undefined, undefined>;
59
+ }
60
+ // Once a user has chosen a wallet, the system will try to connect to it
61
+ | {
62
+ step: 'WaitingForWalletConnection';
63
+ mechanism: WalletMechanism<string, undefined>;
64
+ }
65
+ // Once the wallet is connected, the system will need a signature
66
+ // this state represent the fact and require another user interaction to request the signature
67
+ | {
68
+ step: 'NeedWalletSignature';
69
+ mechanism: WalletMechanism<string, `0x${string}`>;
70
+ }
71
+ // This state is triggered once the signature is requested, the user will have to confirm with its wallet
72
+ | {
73
+ step: 'WaitingForSignature';
74
+ mechanism: WalletMechanism<string, `0x${string}`>;
75
+ }
76
+ // Finally the user is fully signed in
77
+ // walletAccountChanged if set, represent the fact that the user has changed its web3-wallet accounnt.
78
+ // a notification could be shown to the user so that he can switch the app to use that other account.
79
+ | {
80
+ step: 'SignedIn';
81
+ mechanism: FullfilledMechanism;
82
+ account: OriginAccount;
83
+ wallet?: {
84
+ provider: EIP1193WindowWalletProvider;
85
+ accountChanged?: `0x${string}`;
86
+ chainId: string;
87
+ };
88
+ }
89
+ );
90
+
91
+ interface EIP6963ProviderInfo {
92
+ uuid: string;
93
+ name: string;
94
+ icon: string;
95
+ rdns: string;
96
+ }
97
+
98
+ interface EIP6963ProviderDetail {
99
+ info: EIP6963ProviderInfo;
100
+ provider: EIP1193WindowWalletProvider;
101
+ }
102
+
103
+ export interface EIP6963AnnounceProviderEvent extends CustomEvent {
104
+ type: 'eip6963:announceProvider';
105
+ detail: EIP6963ProviderDetail;
106
+ }
107
+
108
+ const storageAccountKey = '__origin_account';
109
+ export function createConnection(settings: {walletHost: string; autoConnect?: boolean}) {
110
+ let autoConnect = true;
111
+ if (typeof settings.autoConnect !== 'undefined') {
112
+ autoConnect = settings.autoConnect;
113
+ }
114
+
115
+ let $connection: Connection = {step: 'Idle', loading: true, wallets: []};
116
+ const _store = writable<Connection>($connection);
117
+ function set(connection: Connection) {
118
+ $connection = connection;
119
+ _store.set($connection);
120
+ return $connection;
121
+ }
122
+ function setError(error: {message: string; cause?: any}) {
123
+ if ($connection) {
124
+ set({
125
+ ...$connection,
126
+ error,
127
+ });
128
+ } else {
129
+ throw new Error(`no connection`);
130
+ }
131
+ }
132
+
133
+ let _wallet: {provider: EIP1193WindowWalletProvider; chainId: string} | undefined;
134
+
135
+ let popup: PopupPromise<OriginAccount> | undefined;
136
+
137
+ function fetchWallets() {
138
+ if (typeof window !== 'undefined') {
139
+ // const defaultProvider = (window as any).ethereum;
140
+ // console.log(defaultProvider);
141
+ // TODO ?
142
+ (window as any).addEventListener('eip6963:announceProvider', (event: EIP6963AnnounceProviderEvent) => {
143
+ const {detail} = event;
144
+ // const { info, provider } = detail;
145
+ // const { uuid, name, icon, rdns } = info;
146
+ // console.log('provider', provider);
147
+ // console.log(`isDefault: ${provider === defaultProvider}`);
148
+ // console.log('info', info);
149
+ const existingWallets = $connection.wallets;
150
+ existingWallets.push(detail);
151
+
152
+ set({
153
+ ...$connection,
154
+ wallets: existingWallets,
155
+ });
156
+ });
157
+ window.dispatchEvent(new Event('eip6963:requestProvider'));
158
+ }
159
+ }
160
+
161
+ function waitForWallet(name: string): Promise<EIP6963ProviderDetail> {
162
+ return new Promise((resolve, reject) => {
163
+ const timeout = setTimeout(() => {
164
+ clearInterval(interval);
165
+ reject('timeout');
166
+ }, 1000);
167
+ const interval = setInterval(() => {
168
+ const wallet = $connection.wallets.find((v) => v.info.name == name);
169
+ if (wallet) {
170
+ clearTimeout(timeout);
171
+ clearInterval(interval);
172
+ resolve(wallet);
173
+ }
174
+ }, 100);
175
+ });
176
+ }
177
+
178
+ if (autoConnect) {
179
+ if (typeof window !== 'undefined') {
180
+ // set({step: 'Idle', loading: true, wallets: $connection.wallets});
181
+ try {
182
+ const existingAccount = getOriginAccount();
183
+ if (existingAccount) {
184
+ if (existingAccount.signer) {
185
+ if (existingAccount.mechanismUsed.type == 'wallet') {
186
+ const walletMechanism = existingAccount.mechanismUsed as WalletMechanism<string, `0x${string}`>;
187
+ waitForWallet(walletMechanism.name)
188
+ .then(async (walletDetails: EIP6963ProviderDetail) => {
189
+ const walletProvider = walletDetails.provider;
190
+ const chainIdAsHex = await walletProvider.request({method: 'eth_chainId'});
191
+ const chainId = Number(chainIdAsHex).toString();
192
+ _wallet = {provider: walletProvider, chainId};
193
+ watchForChainIdChange(_wallet.provider);
194
+ set({
195
+ step: 'SignedIn',
196
+ account: existingAccount,
197
+ mechanism: existingAccount.mechanismUsed as FullfilledMechanism,
198
+ wallets: $connection.wallets,
199
+ wallet: {
200
+ provider: walletProvider,
201
+ accountChanged: undefined,
202
+ chainId,
203
+ },
204
+ });
205
+ walletProvider.request({method: 'eth_accounts'}).then(onAccountChanged);
206
+ watchForAccountChange(walletProvider);
207
+ })
208
+ .catch((err) => {
209
+ set({step: 'Idle', loading: false, wallets: $connection.wallets});
210
+ });
211
+ } else {
212
+ set({
213
+ step: 'SignedIn',
214
+ account: existingAccount,
215
+ mechanism: existingAccount.mechanismUsed as FullfilledMechanism,
216
+ wallets: $connection.wallets,
217
+ wallet: undefined,
218
+ });
219
+ }
220
+ } else {
221
+ set({step: 'Idle', loading: false, wallets: $connection.wallets});
222
+ }
223
+ } else {
224
+ set({step: 'Idle', loading: false, wallets: $connection.wallets});
225
+ }
226
+ } catch {
227
+ set({step: 'Idle', loading: false, wallets: $connection.wallets});
228
+ }
229
+ }
230
+ } else {
231
+ set({step: 'Idle', loading: false, wallets: $connection.wallets});
232
+ }
233
+ fetchWallets();
234
+
235
+ function getOriginAccount(): OriginAccount | undefined {
236
+ const fromStorage = localStorage.getItem(storageAccountKey);
237
+ if (fromStorage) {
238
+ return JSON.parse(fromStorage) as OriginAccount;
239
+ }
240
+ }
241
+ function saveOriginAccount(account: OriginAccount) {
242
+ const accountSTR = JSON.stringify(account);
243
+ sessionStorage.setItem(storageAccountKey, accountSTR);
244
+ localStorage.setItem(storageAccountKey, accountSTR);
245
+ }
246
+ function deleteOriginAccount() {
247
+ sessionStorage.removeItem(storageAccountKey);
248
+ localStorage.removeItem(storageAccountKey);
249
+ }
250
+
251
+ async function requestSignature() {
252
+ if ($connection.step !== 'NeedWalletSignature') {
253
+ throw new Error(`invalid step: ${$connection.step}, needs to be NeedWalletSignature`);
254
+ }
255
+
256
+ if (!_wallet) {
257
+ // TODO error ?
258
+ throw new Error(`no wallet provided initialised`);
259
+ }
260
+ const provider = _wallet.provider;
261
+ const chainId = _wallet.chainId;
262
+ const message = originKeyMessage(origin);
263
+ const msg = hashMessage(message);
264
+
265
+ set({
266
+ ...$connection,
267
+ step: 'WaitingForSignature',
268
+ });
269
+
270
+ let signature: `0x${string}`;
271
+ try {
272
+ // TODO timeout
273
+ signature = await provider.request({
274
+ method: 'personal_sign',
275
+ params: [msg, $connection.mechanism.address],
276
+ });
277
+ } catch (err) {
278
+ // TODO handle rejection (code: 4001 ?)
279
+ set({
280
+ ...$connection,
281
+ step: 'NeedWalletSignature',
282
+ mechanism: {
283
+ type: 'wallet',
284
+ name: $connection.mechanism.name,
285
+ address: $connection.mechanism.address,
286
+ },
287
+ error: {message: 'failed to sign message', cause: err},
288
+ });
289
+ return;
290
+ }
291
+
292
+ const originKey = fromSignatureToKey(signature);
293
+ const originMnemonic = fromEntropyKeyToMnemonic(originKey);
294
+ const originAccount = fromMnemonicToFirstAccount(originMnemonic);
295
+
296
+ const account = {
297
+ address: $connection.mechanism.address as `0x${string}`,
298
+ signer: {
299
+ origin,
300
+ address: originAccount.address,
301
+ publicKey: originAccount.publicKey,
302
+ privateKey: originAccount.privateKey,
303
+ mnemonicKey: originKey,
304
+ },
305
+ metadata: {},
306
+ mechanismUsed: $connection.mechanism,
307
+ savedPublicKeyPublicationSignature: undefined,
308
+ };
309
+ set({
310
+ ...$connection,
311
+ step: 'SignedIn',
312
+ mechanism: {
313
+ type: 'wallet',
314
+ name: $connection.mechanism.name,
315
+ address: $connection.mechanism.address,
316
+ },
317
+ account,
318
+ wallet: {
319
+ chainId,
320
+ provider: provider,
321
+ accountChanged: undefined, // TODO check account list
322
+ },
323
+ });
324
+ if (remember) {
325
+ saveOriginAccount(account);
326
+ }
327
+ }
328
+
329
+ function connectOnCurrentWalletAccount(address: `0x${string}`) {
330
+ if ($connection.step === 'SignedIn' && $connection.mechanism.type === 'wallet') {
331
+ connect({
332
+ type: 'wallet',
333
+ address,
334
+ name: $connection.mechanism.name,
335
+ });
336
+ } else {
337
+ throw new Error(`need to be using a mechanism of type wallet and be SignedIN`);
338
+ }
339
+ }
340
+
341
+ function onChainChanged(chainIdAsHex: `0x${string}`) {
342
+ const chainId = Number(chainIdAsHex).toString();
343
+ if (_wallet) {
344
+ _wallet.chainId = chainId;
345
+ }
346
+ if ($connection.step === 'SignedIn' && $connection.wallet && $connection.wallet.chainId != chainId) {
347
+ set({
348
+ ...$connection,
349
+ wallet: {
350
+ ...$connection.wallet,
351
+ chainId,
352
+ },
353
+ });
354
+ }
355
+ }
356
+
357
+ function onAccountChanged(accounts: `0x${string}`[]) {
358
+ const accountsFormated = accounts.map((a) => a.toLowerCase()) as `0x${string}`[];
359
+ if ($connection.step === 'SignedIn' && $connection.mechanism.type === 'wallet') {
360
+ // TODO if auto-connect and saved-signature ?
361
+ // connect(
362
+ // {
363
+ // type: 'wallet',
364
+ // address: accounts[0],
365
+ // name: $connection.mechanism.name
366
+ // },
367
+ // { requireUserConfirmationBeforeSIgnatureRequest: true }
368
+ // );
369
+
370
+ if ($connection.wallet && accountsFormated.length > 0 && accountsFormated[0] != $connection.account.address) {
371
+ set({
372
+ ...$connection,
373
+ wallet: {
374
+ ...$connection.wallet,
375
+ accountChanged: accountsFormated[0],
376
+ },
377
+ });
378
+ } else if ($connection.wallet) {
379
+ set({
380
+ ...$connection,
381
+ wallet: {
382
+ ...$connection.wallet,
383
+ accountChanged: undefined,
384
+ },
385
+ });
386
+ }
387
+ }
388
+
389
+ // if (accounts[0] !== $connection)
390
+ }
391
+
392
+ function watchForAccountChange(walletProvider: EIP1193WindowWalletProvider) {
393
+ walletProvider.on('accountsChanged', onAccountChanged);
394
+ }
395
+ function stopatchingForAccountChange(walletProvider: EIP1193WindowWalletProvider) {
396
+ walletProvider.removeListener('accountsChanged', onAccountChanged);
397
+ }
398
+
399
+ function watchForChainIdChange(walletProvider: EIP1193WindowWalletProvider) {
400
+ walletProvider.on('chainChanged', onChainChanged);
401
+ }
402
+ function stopatchingForChainIdChange(walletProvider: EIP1193WindowWalletProvider) {
403
+ walletProvider.removeListener('chainChanged', onChainChanged);
404
+ }
405
+
406
+ let remember: boolean = false;
407
+ async function connect(
408
+ mechanism?: Mechanism,
409
+ options?: {
410
+ requireUserConfirmationBeforeSIgnatureRequest?: boolean;
411
+ doNotStoreLocally?: boolean;
412
+ requestSignatureRightAway?: boolean;
413
+ },
414
+ ) {
415
+ remember = !(options?.doNotStoreLocally || false);
416
+ if (mechanism) {
417
+ if (mechanism.type === 'wallet') {
418
+ const walletName = mechanism.name;
419
+ if (walletName) {
420
+ const wallet = $connection.wallets.find((v) => v.info.name == walletName || v.info.uuid == walletName);
421
+ if (wallet) {
422
+ if (_wallet) {
423
+ stopatchingForAccountChange(_wallet.provider);
424
+ stopatchingForChainIdChange(_wallet.provider);
425
+ }
426
+
427
+ const mechanism: WalletMechanism<string, undefined> = {
428
+ type: 'wallet',
429
+ name: walletName,
430
+ };
431
+
432
+ set({
433
+ step: 'WaitingForWalletConnection', // TODO FetchingAccounts
434
+ mechanism,
435
+ wallets: $connection.wallets,
436
+ });
437
+ const provider = wallet.provider;
438
+ const chainIdAsHex = await provider.request({method: 'eth_chainId'});
439
+ const chainId = Number(chainIdAsHex).toString();
440
+ _wallet = {
441
+ chainId,
442
+ provider,
443
+ };
444
+ watchForChainIdChange(_wallet.provider);
445
+ let accounts = await provider.request({method: 'eth_accounts'});
446
+ accounts = accounts.map((v) => v.toLowerCase()) as `0x${string}`[];
447
+ if (accounts.length === 0) {
448
+ set({
449
+ step: 'WaitingForWalletConnection',
450
+ mechanism,
451
+ wallets: $connection.wallets,
452
+ });
453
+ accounts = await provider.request({method: 'eth_requestAccounts'});
454
+ accounts = accounts.map((v) => v.toLowerCase()) as `0x${string}`[];
455
+ if (accounts.length > 0) {
456
+ if (options?.requestSignatureRightAway) {
457
+ watchForAccountChange(_wallet.provider);
458
+ set({
459
+ step: 'NeedWalletSignature',
460
+ mechanism: {
461
+ ...mechanism,
462
+ address: accounts[0],
463
+ },
464
+ wallets: $connection.wallets,
465
+ });
466
+ await requestSignature();
467
+ } else {
468
+ set({
469
+ step: 'NeedWalletSignature',
470
+ mechanism: {
471
+ ...mechanism,
472
+ address: accounts[0],
473
+ },
474
+ wallets: $connection.wallets,
475
+ });
476
+ watchForAccountChange(_wallet.provider);
477
+ }
478
+ } else {
479
+ set({
480
+ step: 'MechanismToChoose',
481
+ wallets: $connection.wallets,
482
+ error: {message: 'could not get any accounts'},
483
+ });
484
+ }
485
+ } else {
486
+ if (options?.requireUserConfirmationBeforeSIgnatureRequest) {
487
+ set({
488
+ step: 'NeedWalletSignature',
489
+ mechanism: {
490
+ ...mechanism,
491
+ address: accounts[0],
492
+ },
493
+ wallets: $connection.wallets,
494
+ });
495
+ watchForAccountChange(_wallet.provider);
496
+ } else {
497
+ watchForAccountChange(_wallet.provider);
498
+ set({
499
+ step: 'NeedWalletSignature',
500
+ mechanism: {
501
+ ...mechanism,
502
+ address: accounts[0],
503
+ },
504
+ wallets: $connection.wallets,
505
+ });
506
+ await requestSignature();
507
+ }
508
+ }
509
+ } else {
510
+ console.error(`failed to get wallet ${walletName}`, $connection.wallets);
511
+ set({
512
+ step: 'MechanismToChoose',
513
+ wallets: $connection.wallets,
514
+ error: {message: `failed to get wallet ${walletName}`},
515
+ });
516
+ }
517
+ } else {
518
+ // TODO can also be done automatically before hand
519
+ // set({
520
+ // step: 'FetchingWallets',
521
+ // mechanism: { type: 'wallet', wallet: undefined }
522
+ // });
523
+
524
+ set({
525
+ step: 'WalletToChoose',
526
+ mechanism: {type: 'wallet'},
527
+ wallets: $connection.wallets,
528
+ });
529
+ }
530
+ } else {
531
+ popup = connectViaPopup({
532
+ mechanism,
533
+ walletHost: settings.walletHost,
534
+ });
535
+ set({
536
+ step: 'PopupLaunched',
537
+ popupClosed: false,
538
+ mechanism,
539
+ wallets: $connection.wallets,
540
+ });
541
+
542
+ const unsubscribe = popup.subscribe(($popup) => {
543
+ if ($connection?.step === 'PopupLaunched') {
544
+ if ($popup.closed) {
545
+ set({
546
+ ...$connection,
547
+ popupClosed: true,
548
+ });
549
+ }
550
+ }
551
+ });
552
+ try {
553
+ const result = await popup;
554
+ console.log({result});
555
+ set({
556
+ step: 'SignedIn',
557
+ account: result,
558
+ mechanism,
559
+ wallets: $connection.wallets,
560
+ wallet: undefined,
561
+ });
562
+ if (remember) {
563
+ saveOriginAccount(result);
564
+ }
565
+ } catch (err) {
566
+ console.log({error: err});
567
+ set({step: 'Idle', loading: false, wallets: $connection.wallets});
568
+ } finally {
569
+ unsubscribe();
570
+ }
571
+ }
572
+ } else {
573
+ set({
574
+ step: 'MechanismToChoose',
575
+ wallets: $connection.wallets,
576
+ });
577
+ }
578
+ }
579
+
580
+ function disconnect() {
581
+ deleteOriginAccount();
582
+ if (_wallet) {
583
+ stopatchingForAccountChange(_wallet.provider);
584
+ stopatchingForChainIdChange(_wallet.provider);
585
+ }
586
+ _wallet = undefined;
587
+ set({
588
+ step: 'Idle',
589
+ loading: false,
590
+ wallets: $connection.wallets,
591
+ });
592
+ }
593
+
594
+ function back(step: 'MechanismToChoose' | 'Idle' | 'WalletToChoose') {
595
+ popup?.cancel();
596
+ if (step === 'MechanismToChoose') {
597
+ set({step, wallets: $connection.wallets});
598
+ } else if (step === 'Idle') {
599
+ set({step, loading: false, wallets: $connection.wallets});
600
+ } else if (step === 'WalletToChoose') {
601
+ set({step, wallets: $connection.wallets, mechanism: {type: 'wallet'}});
602
+ }
603
+ }
604
+
605
+ const popupLauncher = createPopupLauncher<OriginAccount>();
606
+
607
+ function connectViaPopup(settings: PopupSettings) {
608
+ let popupURL = new URL(`${settings.walletHost}/login/`);
609
+ let fullWindow = false;
610
+ if (settings.mechanism.type === 'mnemonic') {
611
+ popupURL.searchParams.append('type', 'mnemonic');
612
+ } else if (settings.mechanism.type === 'email') {
613
+ popupURL.searchParams.append('type', 'email');
614
+ if (settings.mechanism.email) {
615
+ popupURL.searchParams.append('email', encodeURIComponent(settings.mechanism.email));
616
+ }
617
+ if (settings.mechanism.mode) {
618
+ popupURL.searchParams.append('emailMode', settings.mechanism.mode);
619
+ }
620
+ } else if (settings.mechanism.type === 'oauth') {
621
+ popupURL.searchParams.append('type', 'oauth');
622
+
623
+ if (settings.mechanism.provider.id === 'auth0') {
624
+ popupURL.searchParams.append('oauth-provider', settings.mechanism.provider.id);
625
+ popupURL.searchParams.append('oauth-connection', settings.mechanism.provider.connection);
626
+ } else {
627
+ popupURL.searchParams.append('oauth-provider', settings.mechanism.provider.id);
628
+ }
629
+
630
+ if (!settings.mechanism.usePopup) {
631
+ popupURL.searchParams.append('oauth-redirection', 'true');
632
+ }
633
+ } else {
634
+ throw new Error(`mechanism ${(settings.mechanism as any).type} not supported`);
635
+ }
636
+
637
+ // if (settings.extraParams) {
638
+ // for (const [key, value] of Object.entries(settings.extraParams)) {
639
+ // popupURL.searchParams.append(`${key}`, value);
640
+ // }
641
+ // }
642
+
643
+ const currentURL = new URL(location.href);
644
+
645
+ const entriesToAdd: [string, string][] = [];
646
+ currentURL.searchParams.forEach((value, key) => {
647
+ if (key.startsWith('renraku_')) {
648
+ entriesToAdd.push([key.slice(`renraku_`.length), value]);
649
+ }
650
+ });
651
+
652
+ if (currentURL.searchParams.has('eruda')) {
653
+ entriesToAdd.push(['eruda', currentURL.searchParams.get('eruda') || '']);
654
+ }
655
+ if (currentURL.searchParams.has('eruda')) {
656
+ entriesToAdd.push(['eruda', currentURL.searchParams.get('eruda') || '']);
657
+ }
658
+
659
+ for (const entryToAdd of entriesToAdd) {
660
+ popupURL.searchParams.append(entryToAdd[0], entryToAdd[1]);
661
+ }
662
+ return popupLauncher.launchPopup(popupURL.toString(), {fullWindow});
663
+ }
664
+
665
+ function cancel() {
666
+ popup?.cancel();
667
+ set({step: 'Idle', loading: false, wallets: $connection.wallets});
668
+ }
669
+
670
+ function getSignatureForPublicKeyPublication(): Promise<`0x${string}`> {
671
+ if ($connection.step !== 'SignedIn') {
672
+ throw new Error('Not signed in');
673
+ }
674
+ const account = $connection.account;
675
+ if ($connection.mechanism.type === 'wallet') {
676
+ if (!_wallet) {
677
+ throw new Error(`no provider`);
678
+ }
679
+ const message = originPublicKeyPublicationMessage(origin, account.signer.publicKey);
680
+ const msg = hashMessage(message);
681
+ return _wallet.provider.request({
682
+ method: 'personal_sign',
683
+ params: [msg, account.address],
684
+ });
685
+ }
686
+
687
+ if (account.savedPublicKeyPublicationSignature) {
688
+ return Promise.resolve(account.savedPublicKeyPublicationSignature);
689
+ }
690
+
691
+ // TODO offer a way to use iframe + popup to sign the message
692
+ // this would require saving mnemonic or privatekey on etherplay localstorage though
693
+ throw new Error(`no saved public key publication signature for ${account.address}`);
694
+ }
695
+
696
+ return {
697
+ subscribe: _store.subscribe,
698
+ connect,
699
+ cancel,
700
+ back,
701
+ requestSignature,
702
+ connectOnCurrentWalletAccount,
703
+ disconnect,
704
+ getSignatureForPublicKeyPublication,
705
+ };
706
+ }