@etherplay/connect 0.0.14 → 0.0.16

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 CHANGED
@@ -30,6 +30,15 @@ export type Mechanism = AlchemyMechanism | WalletMechanism<string | undefined, `
30
30
 
31
31
  export type FullfilledMechanism = AlchemyMechanism | WalletMechanism<string, `0x${string}`>;
32
32
 
33
+ export type WalletState = {
34
+ provider: EIP1193WindowWalletProvider;
35
+ accounts: `0x${string}`[];
36
+ accountChanged?: `0x${string}`;
37
+ chainId: string;
38
+ invalidChainId: boolean;
39
+ switchingChain: 'addingChain' | 'switchingChain' | false;
40
+ } & ({status: 'connected'} | {status: 'locked'; unlocking: boolean} | {status: 'disconnected'; connecting: boolean});
41
+
33
42
  export type Connection = {
34
43
  // The connection can have an error in every state.
35
44
  // a banner or other mechanism to show error should be used.
@@ -68,34 +77,25 @@ export type Connection = {
68
77
  wallet: undefined;
69
78
  mechanism: WalletMechanism<string, undefined>;
70
79
  }
80
+ // Once the wallet is connected, if multiple account are connected to the site
81
+ // the user can choose which one to connect to
82
+ | {
83
+ step: 'ChooseWalletAccount';
84
+ mechanism: WalletMechanism<string, undefined>;
85
+ wallet: WalletState;
86
+ }
71
87
  // Once the wallet is connected, the system will need a signature
72
88
  // this state represent the fact and require another user interaction to request the signature
73
89
  | {
74
90
  step: 'WalletConnected';
75
91
  mechanism: WalletMechanism<string, `0x${string}`>;
76
- wallet: {
77
- provider: EIP1193WindowWalletProvider;
78
- locked: boolean;
79
- disconnected: boolean;
80
- accountChanged?: `0x${string}`;
81
- chainId: string;
82
- invalidChainId: boolean;
83
- switchingChain: 'addingChain' | 'switchingChain' | false;
84
- };
92
+ wallet: WalletState;
85
93
  }
86
94
  // This state is triggered once the signature is requested, the user will have to confirm with its wallet
87
95
  | {
88
96
  step: 'WaitingForSignature';
89
97
  mechanism: WalletMechanism<string, `0x${string}`>;
90
- wallet: {
91
- provider: EIP1193WindowWalletProvider;
92
- locked: boolean;
93
- disconnected: boolean;
94
- accountChanged?: `0x${string}`;
95
- chainId: string;
96
- invalidChainId: boolean;
97
- switchingChain: 'addingChain' | 'switchingChain' | false;
98
- };
98
+ wallet: WalletState;
99
99
  }
100
100
  // Finally the user is fully signed in
101
101
  // wallet?.accountChanged if set, represent the fact that the user has changed its web3-wallet accounnt.
@@ -112,15 +112,7 @@ export type Connection = {
112
112
  step: 'SignedIn';
113
113
  mechanism: WalletMechanism<string, `0x${string}`>;
114
114
  account: OriginAccount;
115
- wallet: {
116
- provider: EIP1193WindowWalletProvider;
117
- locked: boolean;
118
- disconnected: boolean;
119
- accountChanged?: `0x${string}`;
120
- chainId: string;
121
- invalidChainId: boolean;
122
- switchingChain: 'addingChain' | 'switchingChain' | false;
123
- };
115
+ wallet: WalletState;
124
116
  }
125
117
  );
126
118
 
@@ -250,6 +242,12 @@ export function createConnection(settings: {
250
242
  _wallet = {provider: walletProvider, chainId};
251
243
  alwaysOnProvider.setWalletProvider(walletProvider);
252
244
  watchForChainIdChange(_wallet.provider);
245
+ let accounts: `0x${string}`[] = [];
246
+ // try {
247
+ accounts = await walletProvider.request({method: 'eth_accounts'});
248
+ accounts = accounts.map((v) => v.toLowerCase() as `0x${string}`);
249
+ // } catch {}
250
+ // // TODO try catch ? and use logic of onAccountChanged
253
251
  set({
254
252
  step: 'SignedIn',
255
253
  account: existingAccount,
@@ -257,15 +255,17 @@ export function createConnection(settings: {
257
255
  wallets: $connection.wallets,
258
256
  wallet: {
259
257
  provider: walletProvider,
260
- locked: false, // TODO should fetch eth_account first
261
- disconnected: false, // TODO should fetch eth_account first
258
+ accounts,
259
+ status: 'connected',
262
260
  accountChanged: undefined,
263
261
  chainId,
264
262
  invalidChainId: alwaysOnChainId != chainId,
265
263
  switchingChain: false,
266
264
  },
267
265
  });
268
- walletProvider.request({method: 'eth_accounts'}).then(onAccountChanged);
266
+ alwaysOnProvider.setWalletStatus('connected');
267
+ // TODO use the same logic before hand
268
+ onAccountChanged(accounts);
269
269
  watchForAccountChange(walletProvider);
270
270
  })
271
271
  .catch((err) => {
@@ -295,21 +295,30 @@ export function createConnection(settings: {
295
295
  _wallet = {provider: walletProvider, chainId};
296
296
  alwaysOnProvider.setWalletProvider(walletProvider);
297
297
  watchForChainIdChange(_wallet.provider);
298
+
299
+ let accounts: `0x${string}`[] = [];
300
+ // try {
301
+ accounts = await walletProvider.request({method: 'eth_accounts'});
302
+ accounts = accounts.map((v) => v.toLowerCase() as `0x${string}`);
303
+ // } catch {}
304
+ // // TODO try catch ? and use logic of onAccountChanged
298
305
  set({
299
306
  step: 'WalletConnected',
300
307
  mechanism: lastWallet,
301
308
  wallets: $connection.wallets,
302
309
  wallet: {
303
310
  provider: walletProvider,
304
- locked: false, // TODO should fetch eth_account first
305
- disconnected: false, // TODO should fetch eth_account first
311
+ accounts,
312
+ status: 'connected',
306
313
  accountChanged: undefined,
307
314
  chainId,
308
315
  invalidChainId: alwaysOnChainId != chainId,
309
316
  switchingChain: false,
310
317
  },
311
318
  });
312
- walletProvider.request({method: 'eth_accounts'}).then(onAccountChanged);
319
+ alwaysOnProvider.setWalletStatus('connected');
320
+ // TODO use the same logic before hand
321
+ onAccountChanged(accounts);
313
322
  watchForAccountChange(walletProvider);
314
323
  })
315
324
  .catch((err) => {
@@ -368,12 +377,7 @@ export function createConnection(settings: {
368
377
  throw new Error(`invalid step: ${$connection.step}, needs to be WalletConnected`);
369
378
  }
370
379
 
371
- if (!_wallet) {
372
- // TODO error ?
373
- throw new Error(`no wallet provided initialised`);
374
- }
375
- const provider = _wallet.provider;
376
- const chainId = _wallet.chainId;
380
+ const provider = $connection.wallet.provider;
377
381
  const message = originKeyMessage(origin);
378
382
  const msg = hashMessage(message);
379
383
 
@@ -424,36 +428,28 @@ export function createConnection(settings: {
424
428
  set({
425
429
  ...$connection,
426
430
  step: 'SignedIn',
427
- mechanism: {
428
- type: 'wallet',
429
- name: $connection.mechanism.name,
430
- address: $connection.mechanism.address,
431
- },
432
431
  account,
433
- wallet: {
434
- chainId,
435
- provider: provider,
436
- locked: false,
437
- disconnected: false,
438
- accountChanged: undefined, // TODO check account list
439
- invalidChainId: alwaysOnChainId != chainId,
440
- switchingChain: false,
441
- },
432
+ wallet: $connection.wallet,
442
433
  });
443
434
  if (remember) {
444
435
  saveOriginAccount(account);
445
436
  }
446
437
  }
447
438
 
448
- function connectOnCurrentWalletAccount(address: `0x${string}`) {
449
- if ($connection.step === 'SignedIn' && $connection.mechanism.type === 'wallet') {
450
- connect({
451
- type: 'wallet',
452
- address,
453
- name: $connection.mechanism.name,
454
- });
439
+ function connecToAddress(address: `0x${string}`, options?: {requireUserConfirmationBeforeSignatureRequest: boolean}) {
440
+ if ($connection.wallet) {
441
+ connect(
442
+ {
443
+ type: 'wallet',
444
+ address,
445
+ name: $connection.mechanism.name,
446
+ },
447
+ {
448
+ requireUserConfirmationBeforeSignatureRequest: options?.requireUserConfirmationBeforeSignatureRequest,
449
+ },
450
+ );
455
451
  } else {
456
- throw new Error(`need to be using a mechanism of type wallet and be SignedIN`);
452
+ throw new Error(`need to be using a wallet`);
457
453
  }
458
454
  }
459
455
 
@@ -487,23 +483,34 @@ export function createConnection(settings: {
487
483
  ...$connection,
488
484
  wallet: {
489
485
  ...$connection.wallet,
490
- locked: true,
486
+ status: 'locked',
487
+ unlocking: false,
491
488
  },
492
489
  });
490
+ alwaysOnProvider.setWalletStatus('locked');
493
491
  } else {
494
492
  const disconnected = accountsFormated.find((v) => v == addressSignedIn) ? false : true;
495
493
 
496
- if ($connection.wallet.locked) {
494
+ if (disconnected) {
495
+ set({
496
+ ...$connection,
497
+ wallet: {
498
+ ...$connection.wallet,
499
+ status: 'disconnected',
500
+ connecting: false,
501
+ },
502
+ });
503
+ alwaysOnProvider.setWalletStatus('disconnected');
504
+ } else {
497
505
  set({
498
506
  ...$connection,
499
507
  wallet: {
500
508
  ...$connection.wallet,
501
- locked: false,
502
- disconnected,
509
+ status: 'connected',
503
510
  },
504
511
  });
512
+ alwaysOnProvider.setWalletStatus('connected');
505
513
  }
506
- // TODO disconnected ?
507
514
  }
508
515
 
509
516
  if (accountsFormated.length > 0 && accountsFormated[0] != $connection.mechanism.address) {
@@ -552,7 +559,9 @@ export function createConnection(settings: {
552
559
  remember = !(options?.doNotStoreLocally || false);
553
560
  if (mechanism) {
554
561
  if (mechanism.type === 'wallet') {
555
- const walletName = mechanism.name;
562
+ const specificAddress = mechanism.address;
563
+ const walletName =
564
+ mechanism.name || ($connection.wallets.length == 1 ? $connection.wallets[0].info.name : undefined);
556
565
  if (walletName) {
557
566
  const wallet = $connection.wallets.find((v) => v.info.name == walletName || v.info.uuid == walletName);
558
567
  if (wallet) {
@@ -562,14 +571,14 @@ export function createConnection(settings: {
562
571
  stopatchingForChainIdChange(_wallet.provider);
563
572
  }
564
573
 
565
- const mechanism: WalletMechanism<string, undefined> = {
574
+ const mechanismToSave: WalletMechanism<string, undefined> = {
566
575
  type: 'wallet',
567
576
  name: walletName,
568
577
  };
569
578
 
570
579
  set({
571
580
  step: 'WaitingForWalletConnection', // TODO FetchingAccounts
572
- mechanism,
581
+ mechanism: mechanismToSave,
573
582
  wallets: $connection.wallets,
574
583
  wallet: undefined,
575
584
  });
@@ -587,52 +596,72 @@ export function createConnection(settings: {
587
596
  if (accounts.length === 0) {
588
597
  set({
589
598
  step: 'WaitingForWalletConnection', // TODO add another step to unlock ?
590
- mechanism,
599
+ mechanism: mechanismToSave,
591
600
  wallets: $connection.wallets,
592
601
  wallet: undefined,
593
602
  });
594
603
  accounts = await provider.request({method: 'eth_requestAccounts'});
595
604
  accounts = accounts.map((v) => v.toLowerCase()) as `0x${string}`[];
596
605
  if (accounts.length > 0) {
597
- const walletMechanism = {
598
- ...mechanism,
599
- address: accounts[0],
600
- };
601
- if (options?.requestSignatureRightAway) {
606
+ const nextStep = !specificAddress && accounts.length > 1 ? 'ChooseWalletAccount' : 'WalletConnected';
607
+ let account = accounts[0];
608
+ if (specificAddress) {
609
+ if (accounts.find((v) => v === specificAddress)) {
610
+ account = specificAddress;
611
+ } else {
612
+ // TODO error
613
+ throw new Error(`could not find address ${specificAddress}`);
614
+ }
615
+ }
616
+
617
+ const newState: Connection =
618
+ nextStep === 'ChooseWalletAccount'
619
+ ? {
620
+ step: nextStep,
621
+ mechanism: mechanismToSave,
622
+ wallets: $connection.wallets,
623
+
624
+ wallet: {
625
+ provider: _wallet.provider,
626
+ accounts,
627
+ status: 'connected',
628
+ accountChanged: undefined,
629
+ chainId,
630
+ invalidChainId: alwaysOnChainId != chainId,
631
+ switchingChain: false,
632
+ },
633
+ }
634
+ : {
635
+ step: nextStep,
636
+ mechanism: {
637
+ ...mechanismToSave,
638
+ address: account,
639
+ },
640
+ wallets: $connection.wallets,
641
+ wallet: {
642
+ provider: _wallet.provider,
643
+ accounts,
644
+ status: 'connected',
645
+ accountChanged: undefined,
646
+ chainId,
647
+ invalidChainId: alwaysOnChainId != chainId,
648
+ switchingChain: false,
649
+ },
650
+ };
651
+ if (newState.step === 'WalletConnected' && options?.requestSignatureRightAway) {
602
652
  watchForAccountChange(_wallet.provider);
603
653
 
604
- set({
605
- step: 'WalletConnected',
606
- mechanism: walletMechanism,
607
- wallets: $connection.wallets,
608
- wallet: {
609
- provider: _wallet.provider,
610
- locked: false,
611
- disconnected: false,
612
- accountChanged: undefined,
613
- chainId,
614
- invalidChainId: alwaysOnChainId != chainId,
615
- switchingChain: false,
616
- },
617
- });
618
- saveLastWallet(walletMechanism);
654
+ set(newState);
655
+ alwaysOnProvider.setWalletStatus('connected');
656
+ saveLastWallet(newState.mechanism);
619
657
  await requestSignature();
620
658
  } else {
621
- set({
622
- step: 'WalletConnected',
623
- mechanism: walletMechanism,
624
- wallets: $connection.wallets,
625
- wallet: {
626
- provider: _wallet.provider,
627
- locked: false,
628
- disconnected: false,
629
- accountChanged: undefined,
630
- chainId,
631
- invalidChainId: alwaysOnChainId != chainId,
632
- switchingChain: false,
633
- },
634
- });
635
- saveLastWallet(walletMechanism);
659
+ set(newState);
660
+ alwaysOnProvider.setWalletStatus('connected');
661
+ if (newState.step === 'WalletConnected') {
662
+ saveLastWallet(newState.mechanism);
663
+ }
664
+
636
665
  watchForAccountChange(_wallet.provider);
637
666
  }
638
667
  } else {
@@ -644,45 +673,66 @@ export function createConnection(settings: {
644
673
  });
645
674
  }
646
675
  } else {
647
- const walletMechanism = {
648
- ...mechanism,
649
- address: accounts[0],
650
- };
651
- if (!requestSignatureAutomaticallyIfPossible || options?.requireUserConfirmationBeforeSignatureRequest) {
652
- set({
653
- step: 'WalletConnected',
654
- mechanism: walletMechanism,
655
- wallets: $connection.wallets,
656
- wallet: {
657
- provider: _wallet.provider,
658
- locked: false,
659
- disconnected: false,
660
- accountChanged: undefined,
661
- chainId,
662
- invalidChainId: alwaysOnChainId != chainId,
663
- switchingChain: false,
664
- },
665
- });
666
- saveLastWallet(walletMechanism);
676
+ let account = accounts[0];
677
+ if (specificAddress) {
678
+ if (accounts.find((v) => v === specificAddress)) {
679
+ account = specificAddress;
680
+ } else {
681
+ // TODO error
682
+ throw new Error(`could not find address ${specificAddress}`);
683
+ }
684
+ }
685
+ const nextStep = !specificAddress && accounts.length > 1 ? 'ChooseWalletAccount' : 'WalletConnected';
686
+ const newState: Connection =
687
+ nextStep === 'ChooseWalletAccount'
688
+ ? {
689
+ step: nextStep,
690
+ mechanism: mechanismToSave,
691
+ wallets: $connection.wallets,
692
+ wallet: {
693
+ provider: _wallet.provider,
694
+ accounts,
695
+ status: 'connected',
696
+ accountChanged: undefined,
697
+ chainId,
698
+ invalidChainId: alwaysOnChainId != chainId,
699
+ switchingChain: false,
700
+ },
701
+ }
702
+ : {
703
+ step: nextStep,
704
+ mechanism: {
705
+ ...mechanismToSave,
706
+ address: account,
707
+ },
708
+ wallets: $connection.wallets,
709
+ wallet: {
710
+ provider: _wallet.provider,
711
+ accounts,
712
+ status: 'connected',
713
+ accountChanged: undefined,
714
+ chainId,
715
+ invalidChainId: alwaysOnChainId != chainId,
716
+ switchingChain: false,
717
+ },
718
+ };
719
+ if (
720
+ newState.step === 'WalletConnected' &&
721
+ requestSignatureAutomaticallyIfPossible &&
722
+ !options?.requireUserConfirmationBeforeSignatureRequest
723
+ ) {
724
+ set(newState);
725
+ alwaysOnProvider.setWalletStatus('connected');
726
+ saveLastWallet(newState.mechanism);
667
727
  watchForAccountChange(_wallet.provider);
728
+ await requestSignature();
668
729
  } else {
669
730
  watchForAccountChange(_wallet.provider);
670
- set({
671
- step: 'WalletConnected',
672
- mechanism: walletMechanism,
673
- wallets: $connection.wallets,
674
- wallet: {
675
- provider: _wallet.provider,
676
- locked: false,
677
- disconnected: false,
678
- accountChanged: undefined,
679
- chainId,
680
- invalidChainId: alwaysOnChainId != chainId,
681
- switchingChain: false,
682
- },
683
- });
684
- saveLastWallet(walletMechanism);
685
- await requestSignature();
731
+ set(newState);
732
+ alwaysOnProvider.setWalletStatus('connected');
733
+ if (newState.step === 'WalletConnected') {
734
+ saveLastWallet(newState.mechanism);
735
+ }
686
736
  }
687
737
  }
688
738
  } else {
@@ -880,14 +930,30 @@ export function createConnection(settings: {
880
930
  }
881
931
 
882
932
  async function unlock() {
883
- if (!$connection.wallet) {
933
+ const wallet = $connection.wallet;
934
+ if (!wallet || wallet.status !== 'locked') {
884
935
  throw new Error(`invalid state`);
885
936
  }
886
937
 
887
- const wallet = $connection.wallet;
938
+ set({
939
+ ...$connection,
940
+ wallet: {
941
+ ...wallet,
942
+ unlocking: true,
943
+ },
944
+ });
888
945
 
889
- // TODO unlocking state
890
- await wallet.provider.request({method: 'eth_requestAccounts'}).then(onAccountChanged);
946
+ try {
947
+ await wallet.provider.request({method: 'eth_requestAccounts'}).then(onAccountChanged);
948
+ } catch {
949
+ set({
950
+ ...$connection,
951
+ wallet: {
952
+ ...wallet,
953
+ unlocking: false,
954
+ },
955
+ });
956
+ }
891
957
  }
892
958
 
893
959
  async function switchWalletChain(
@@ -1061,7 +1127,7 @@ export function createConnection(settings: {
1061
1127
  cancel,
1062
1128
  back,
1063
1129
  requestSignature,
1064
- connectOnCurrentWalletAccount,
1130
+ connecToAddress,
1065
1131
  disconnect,
1066
1132
  getSignatureForPublicKeyPublication,
1067
1133
  switchWalletChain,
package/src/provider.ts CHANGED
@@ -19,39 +19,51 @@ export function createProvider(params: {
19
19
  chainId: string;
20
20
  prioritizeWalletProvider?: boolean;
21
21
  requestsPerSecond?: number;
22
- }): CurriedRPC<Methods> & {setWalletProvider: (walletProvider: EIP1193WindowWalletProvider | undefined) => void} & {
22
+ }): CurriedRPC<Methods> & {
23
+ setWalletProvider: (walletProvider: EIP1193WindowWalletProvider | undefined) => void;
24
+ setWalletStatus: (newStatus: 'connected' | 'locked' | 'disconnected') => void;
25
+ } & {
23
26
  chainId: string;
24
27
  } {
25
28
  const {endpoint, chainId, prioritizeWalletProvider, requestsPerSecond} = params;
26
29
  const jsonRPC = createCurriedJSONRPC(endpoint);
27
30
 
28
31
  let walletProvider: EIP1193WindowWalletProvider | undefined;
32
+ let status: 'connected' | 'locked' | 'disconnected' = 'disconnected';
29
33
 
30
34
  function setWalletProvider(newWalletProvider: EIP1193WindowWalletProvider | undefined) {
31
35
  walletProvider = newWalletProvider;
32
36
  }
37
+ function setWalletStatus(newStatus: 'connected' | 'locked' | 'disconnected') {
38
+ status = newStatus;
39
+ }
33
40
 
34
41
  const provider = {
35
42
  async request(req: {method: string; params?: any[]}) {
36
- if (walletProvider) {
37
- const signingMethod =
38
- signerMethods.includes(req.method) ||
39
- connectedAccountMethods.includes(req.method) ||
40
- walletOnlyMethods.includes(req.method) ||
41
- req.method.indexOf('sign') != -1;
43
+ const signingMethod =
44
+ signerMethods.includes(req.method) ||
45
+ connectedAccountMethods.includes(req.method) ||
46
+ walletOnlyMethods.includes(req.method) ||
47
+ req.method.indexOf('sign') != -1;
42
48
 
49
+ if (walletProvider) {
43
50
  if (prioritizeWalletProvider || signingMethod) {
51
+ if (signingMethod) {
52
+ if (status !== 'connected') {
53
+ return Promise.reject({message: 'wallet provider is not connected', code: 4001});
54
+ }
55
+ }
56
+
44
57
  const currentChainIdAsHex = await walletProvider.request({
45
58
  method: 'eth_chainId',
46
59
  });
47
60
  const currentChainId = Number(currentChainIdAsHex).toString();
48
61
  if (chainId !== currentChainId) {
49
62
  if (signingMethod) {
50
- return Promise.reject(
51
- new Error(
52
- `wallet provider is connected to a different chain, expected ${chainId} but got ${currentChainId}`,
53
- ),
54
- );
63
+ return Promise.reject({
64
+ message: `wallet provider is connected to a different chain, expected ${chainId} but got ${currentChainId}`,
65
+ code: 4001,
66
+ });
55
67
  } else {
56
68
  // we fallback on jsonRPc if invalid chain and not a signing method
57
69
  return jsonRPC.request(req);
@@ -61,10 +73,14 @@ export function createProvider(params: {
61
73
  }
62
74
  }
63
75
 
76
+ // if (signingMethod) {
77
+ // return Promise.reject(new Error('wallet provider is not connected'));
78
+ // }
79
+
64
80
  return jsonRPC.request(req);
65
81
  },
66
82
  } as unknown as EIP1193WalletProvider;
67
83
 
68
84
  const curriedRPC = createCurriedJSONRPC<Methods>(provider as any, {requestsPerSecond});
69
- return {...curriedRPC, setWalletProvider, chainId};
85
+ return {...curriedRPC, setWalletProvider, setWalletStatus, chainId};
70
86
  }