@etherplay/connect 0.0.9 → 0.0.10

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
@@ -1,7 +1,7 @@
1
1
  import type {AlchemyMechanism, OriginAccount} from '@etherplay/alchemy';
2
2
  import {writable} from 'svelte/store';
3
3
  import {createPopupLauncher, type PopupPromise} from './popup.js';
4
- import type {EIP1193WindowWalletProvider} from 'eip-1193';
4
+ import type {EIP1193ChainId, EIP1193WindowWalletProvider} from 'eip-1193';
5
5
  import {
6
6
  fromEntropyKeyToMnemonic,
7
7
  fromMnemonicToFirstAccount,
@@ -10,6 +10,7 @@ import {
10
10
  originPublicKeyPublicationMessage,
11
11
  } from '@etherplay/alchemy';
12
12
  import {hashMessage} from './utils.js';
13
+ import {createProvider} from './provider.js';
13
14
 
14
15
  export {fromEntropyKeyToMnemonic, originPublicKeyPublicationMessage, originKeyMessage};
15
16
  export type {OriginAccount};
@@ -84,6 +85,8 @@ export type Connection = {
84
85
  provider: EIP1193WindowWalletProvider;
85
86
  accountChanged?: `0x${string}`;
86
87
  chainId: string;
88
+ invalidChainId: boolean;
89
+ switchingChain: 'addingChain' | 'switchingChain' | false;
87
90
  };
88
91
  }
89
92
  );
@@ -106,7 +109,18 @@ export interface EIP6963AnnounceProviderEvent extends CustomEvent {
106
109
  }
107
110
 
108
111
  const storageAccountKey = '__origin_account';
109
- export function createConnection(settings: {walletHost: string; autoConnect?: boolean}) {
112
+ export function createConnection(settings: {
113
+ walletHost: string;
114
+ autoConnect?: boolean;
115
+ node: {url: string; chainId: string; prioritizeWalletProvider?: boolean; requestsPerSecond?: number};
116
+ }) {
117
+ const alwaysOnChainId = settings.node.chainId;
118
+ const alwaysOnProvider = createProvider({
119
+ endpoint: settings.node.url,
120
+ chainId: settings.node.chainId,
121
+ prioritizeWalletProvider: settings.node.prioritizeWalletProvider,
122
+ requestsPerSecond: settings.node.requestsPerSecond,
123
+ });
110
124
  let autoConnect = true;
111
125
  if (typeof settings.autoConnect !== 'undefined') {
112
126
  autoConnect = settings.autoConnect;
@@ -190,6 +204,7 @@ export function createConnection(settings: {walletHost: string; autoConnect?: bo
190
204
  const chainIdAsHex = await walletProvider.request({method: 'eth_chainId'});
191
205
  const chainId = Number(chainIdAsHex).toString();
192
206
  _wallet = {provider: walletProvider, chainId};
207
+ alwaysOnProvider.setWalletProvider(walletProvider);
193
208
  watchForChainIdChange(_wallet.provider);
194
209
  set({
195
210
  step: 'SignedIn',
@@ -200,6 +215,8 @@ export function createConnection(settings: {walletHost: string; autoConnect?: bo
200
215
  provider: walletProvider,
201
216
  accountChanged: undefined,
202
217
  chainId,
218
+ invalidChainId: alwaysOnChainId != chainId,
219
+ switchingChain: false,
203
220
  },
204
221
  });
205
222
  walletProvider.request({method: 'eth_accounts'}).then(onAccountChanged);
@@ -319,6 +336,8 @@ export function createConnection(settings: {walletHost: string; autoConnect?: bo
319
336
  chainId,
320
337
  provider: provider,
321
338
  accountChanged: undefined, // TODO check account list
339
+ invalidChainId: alwaysOnChainId != chainId,
340
+ switchingChain: false,
322
341
  },
323
342
  });
324
343
  if (remember) {
@@ -349,6 +368,7 @@ export function createConnection(settings: {walletHost: string; autoConnect?: bo
349
368
  wallet: {
350
369
  ...$connection.wallet,
351
370
  chainId,
371
+ invalidChainId: alwaysOnChainId != chainId,
352
372
  },
353
373
  });
354
374
  }
@@ -420,6 +440,7 @@ export function createConnection(settings: {walletHost: string; autoConnect?: bo
420
440
  const wallet = $connection.wallets.find((v) => v.info.name == walletName || v.info.uuid == walletName);
421
441
  if (wallet) {
422
442
  if (_wallet) {
443
+ alwaysOnProvider.setWalletProvider(undefined);
423
444
  stopatchingForAccountChange(_wallet.provider);
424
445
  stopatchingForChainIdChange(_wallet.provider);
425
446
  }
@@ -441,6 +462,7 @@ export function createConnection(settings: {walletHost: string; autoConnect?: bo
441
462
  chainId,
442
463
  provider,
443
464
  };
465
+ alwaysOnProvider.setWalletProvider(_wallet.provider);
444
466
  watchForChainIdChange(_wallet.provider);
445
467
  let accounts = await provider.request({method: 'eth_accounts'});
446
468
  accounts = accounts.map((v) => v.toLowerCase()) as `0x${string}`[];
@@ -580,6 +602,7 @@ export function createConnection(settings: {walletHost: string; autoConnect?: bo
580
602
  function disconnect() {
581
603
  deleteOriginAccount();
582
604
  if (_wallet) {
605
+ alwaysOnProvider.setWalletProvider(undefined);
583
606
  stopatchingForAccountChange(_wallet.provider);
584
607
  stopatchingForChainIdChange(_wallet.provider);
585
608
  }
@@ -693,6 +716,171 @@ export function createConnection(settings: {walletHost: string; autoConnect?: bo
693
716
  throw new Error(`no saved public key publication signature for ${account.address}`);
694
717
  }
695
718
 
719
+ async function switchWalletChain(
720
+ chainId: string,
721
+ config?: {
722
+ readonly rpcUrls?: readonly string[];
723
+ readonly blockExplorerUrls?: readonly string[];
724
+ readonly chainName?: string;
725
+ readonly iconUrls?: readonly string[];
726
+ readonly nativeCurrency?: {
727
+ name: string;
728
+ symbol: string;
729
+ decimals: number;
730
+ };
731
+ },
732
+ ) {
733
+ if ($connection.step !== 'SignedIn' || !$connection.wallet) {
734
+ throw new Error(`invali state`);
735
+ }
736
+
737
+ const wallet = $connection.wallet;
738
+ // if (!wallet) {
739
+ // throw new Error(`no wallet`);
740
+ // }
741
+
742
+ try {
743
+ // attempt to switch...
744
+ set({
745
+ ...$connection,
746
+ wallet: {...$connection.wallet, switchingChain: 'switchingChain'},
747
+ });
748
+ const result = await wallet.provider.request({
749
+ method: 'wallet_switchEthereumChain',
750
+ params: [
751
+ {
752
+ chainId: ('0x' + parseInt(chainId).toString(16)) as EIP1193ChainId,
753
+ },
754
+ ],
755
+ });
756
+ if (!result) {
757
+ if ($connection.wallet) {
758
+ set({
759
+ ...$connection,
760
+ wallet: {...$connection.wallet, switchingChain: false},
761
+ });
762
+ }
763
+
764
+ // logger.info(`wallet_switchEthereumChain: complete`);
765
+ // this will be taken care with `chainChanged` (but maybe it should be done there ?)
766
+ // handleNetwork(chainId);
767
+ } else {
768
+ if ($connection.wallet) {
769
+ set({
770
+ ...$connection,
771
+ wallet: {...$connection.wallet, switchingChain: false},
772
+ error: {
773
+ message: `Failed to switch to ${config?.chainName || `chain with id = ${chainId}`}`,
774
+ cause: result,
775
+ },
776
+ });
777
+ }
778
+ throw result;
779
+ }
780
+ } catch (err) {
781
+ if ((err as any).code === 4001) {
782
+ // logger.info(`wallet_addEthereumChain: failed but error code === 4001, we ignore as user rejected it`, err);
783
+ if ($connection.wallet) {
784
+ set({
785
+ ...$connection,
786
+ wallet: {...$connection.wallet, switchingChain: false},
787
+ });
788
+ }
789
+ return;
790
+ }
791
+ // if ((err as any).code === 4902) {
792
+ else if (config && config.rpcUrls && config.rpcUrls.length > 0) {
793
+ if ($connection.wallet) {
794
+ set({
795
+ ...$connection,
796
+ wallet: {...$connection.wallet, switchingChain: 'addingChain'},
797
+ });
798
+ }
799
+ // logger.info(`wallet_switchEthereumChain: could not switch, try adding the chain via "wallet_addEthereumChain"`);
800
+ try {
801
+ const result = await wallet.provider.request({
802
+ method: 'wallet_addEthereumChain',
803
+ params: [
804
+ {
805
+ chainId: ('0x' + parseInt(chainId).toString(16)) as EIP1193ChainId,
806
+ rpcUrls: config.rpcUrls,
807
+ chainName: config.chainName,
808
+ blockExplorerUrls: config.blockExplorerUrls,
809
+ iconUrls: config.iconUrls,
810
+ nativeCurrency: config.nativeCurrency,
811
+ },
812
+ ],
813
+ });
814
+ if (!result) {
815
+ if ($connection.wallet) {
816
+ set({
817
+ ...$connection,
818
+ wallet: {...$connection.wallet, switchingChain: false},
819
+ });
820
+ }
821
+ // this will be taken care with `chainChanged` (but maybe it should be done there ?)
822
+ // handleNetwork(chainId);
823
+ } else {
824
+ if ($connection.wallet) {
825
+ set({
826
+ ...$connection,
827
+ wallet: {...$connection.wallet, switchingChain: false},
828
+ error: {
829
+ message: `Failed to add new chain: ${config?.chainName || `chain with id = ${chainId}`}`,
830
+ cause: result,
831
+ },
832
+ });
833
+ }
834
+ // logger.info(`wallet_addEthereumChain: a non-undefinded result means an error`, result);
835
+ throw result;
836
+ }
837
+ } catch (err) {
838
+ if ((err as any).code !== 4001) {
839
+ if ($connection.wallet) {
840
+ set({
841
+ ...$connection,
842
+ wallet: {...$connection.wallet, switchingChain: false},
843
+ error: {
844
+ message: `Failed to add new chain: ${config?.chainName || `chain with id = ${chainId}`}`,
845
+ cause: err,
846
+ },
847
+ });
848
+ }
849
+ // logger.info(`wallet_addEthereumChain: failed`, err);
850
+ // TODO ?
851
+ // set({
852
+ // error: {message: `Failed to add new chain`, cause: err},
853
+ // });
854
+ // for now:
855
+ throw err;
856
+ } else {
857
+ if ($connection.wallet) {
858
+ set({
859
+ ...$connection,
860
+ wallet: {...$connection.wallet, switchingChain: false},
861
+ });
862
+ }
863
+ // logger.info(`wallet_addEthereumChain: failed but error code === 4001, we ignore as user rejected it`, err);
864
+ return;
865
+ }
866
+ }
867
+ } else {
868
+ const errorMessage = `Chain "${config?.chainName || `with chainId = ${chainId}`} " is not available on your wallet`;
869
+ if ($connection.wallet) {
870
+ set({
871
+ ...$connection,
872
+ wallet: {...$connection.wallet, switchingChain: false},
873
+ error: {
874
+ message: errorMessage,
875
+ },
876
+ });
877
+ }
878
+
879
+ throw new Error(errorMessage);
880
+ }
881
+ }
882
+ }
883
+
696
884
  return {
697
885
  subscribe: _store.subscribe,
698
886
  connect,
@@ -702,5 +890,9 @@ export function createConnection(settings: {walletHost: string; autoConnect?: bo
702
890
  connectOnCurrentWalletAccount,
703
891
  disconnect,
704
892
  getSignatureForPublicKeyPublication,
893
+ switchWalletChain,
894
+ provider: alwaysOnProvider,
705
895
  };
706
896
  }
897
+
898
+ export type ConnectionStore = ReturnType<typeof createConnection>;
@@ -0,0 +1,68 @@
1
+ import type {EIP1193WalletProvider, EIP1193WindowWalletProvider, Methods} from 'eip-1193';
2
+ import {createCurriedJSONRPC, CurriedRPC} from 'remote-procedure-call';
3
+
4
+ const signerMethods = [
5
+ 'eth_accounts',
6
+ 'eth_sign',
7
+ 'eth_signTransaction',
8
+ 'personal_sign',
9
+ 'eth_signTypedData_v4',
10
+ 'eth_signTypedData',
11
+ ];
12
+
13
+ const connectedAccountMethods = ['eth_sendTransaction'];
14
+
15
+ const walletOnlyMethods = ['eth_requestAccounts', 'wallet_switchEthereumChain', 'wallet_addEthereumChain'];
16
+
17
+ export function createProvider(params: {
18
+ endpoint: string;
19
+ chainId: string;
20
+ prioritizeWalletProvider?: boolean;
21
+ requestsPerSecond?: number;
22
+ }): CurriedRPC<Methods> & {setWalletProvider: (walletProvider: EIP1193WindowWalletProvider | undefined) => void} & {
23
+ chainId: string;
24
+ } {
25
+ const {endpoint, chainId, prioritizeWalletProvider, requestsPerSecond} = params;
26
+ const jsonRPC = createCurriedJSONRPC(endpoint);
27
+
28
+ let walletProvider: EIP1193WindowWalletProvider | undefined;
29
+
30
+ function setWalletProvider(walletProvider: EIP1193WindowWalletProvider | undefined) {
31
+ walletProvider = walletProvider;
32
+ }
33
+
34
+ const provider = {
35
+ 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;
42
+ if (prioritizeWalletProvider || signingMethod) {
43
+ const currentChainIdAsHex = walletProvider.request({
44
+ method: 'eth_chainId',
45
+ });
46
+ const currentChainId = Number(currentChainIdAsHex).toString();
47
+ if (chainId !== currentChainId) {
48
+ if (signingMethod) {
49
+ return Promise.reject(
50
+ new Error(
51
+ `wallet provider is connected to a different chain, expected ${chainId} but got ${currentChainId}`,
52
+ ),
53
+ );
54
+ } else {
55
+ // we fallback on jsonRPc if invalid chain and not a signing method
56
+ return jsonRPC.request(req);
57
+ }
58
+ }
59
+ return walletProvider.request(req as any);
60
+ }
61
+ }
62
+
63
+ return jsonRPC.request(req);
64
+ },
65
+ } as unknown as EIP1193WalletProvider;
66
+
67
+ return {...createCurriedJSONRPC<Methods>(provider as any, {requestsPerSecond}), setWalletProvider, chainId};
68
+ }