@rango-dev/wallets-core 0.1.11-next.69

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/types.ts ADDED
@@ -0,0 +1,131 @@
1
+ import { PropsWithChildren } from 'react';
2
+ import {
3
+ Network,
4
+ WalletType,
5
+ BlockchainMeta,
6
+ WalletInfo,
7
+ WalletSigners,
8
+ } from '@rango-dev/wallets-shared';
9
+ import {
10
+ EventHandler as WalletEventHandler,
11
+ State as WalletState,
12
+ } from './wallet';
13
+
14
+ export type State = {
15
+ [key in WalletType]?: WalletState;
16
+ };
17
+
18
+ export type ConnectResult = {
19
+ accounts: string[] | null;
20
+ network: Network | null;
21
+ provider: any;
22
+ };
23
+
24
+ export type Providers = { [type in WalletType]?: any };
25
+
26
+ export type ProviderContext = {
27
+ connect(type: WalletType, network?: Network): Promise<ConnectResult>;
28
+ disconnect(type: WalletType): Promise<void>;
29
+ disconnectAll(): Promise<PromiseSettledResult<any>[]>;
30
+ state(type: WalletType): WalletState;
31
+ canSwitchNetworkTo(type: WalletType, network: Network): boolean;
32
+ providers(): Providers;
33
+ getSigners(type: WalletType): WalletSigners;
34
+ getWalletInfo(type: WalletType): WalletInfo;
35
+ };
36
+
37
+ export type ProviderProps = PropsWithChildren<{
38
+ onUpdateState?: WalletEventHandler;
39
+ allBlockChains?: BlockchainMeta[];
40
+ providers: WalletProvider[];
41
+ }>;
42
+
43
+ export enum Events {
44
+ CONNECTED = 'connected',
45
+ CONNECTING = 'connecting',
46
+ REACHABLE = 'reachable',
47
+ INSTALLED = 'installed',
48
+ ACCOUNTS = 'accounts',
49
+ NETWORK = 'network',
50
+ }
51
+
52
+ export type ProviderConnectResult = {
53
+ accounts: string[];
54
+ chainId: string;
55
+ };
56
+
57
+ export type GetInstanceOptions = {
58
+ network?: Network;
59
+ currentProvider: any;
60
+ meta: BlockchainMeta[];
61
+ force?: boolean;
62
+ };
63
+ export type GetInstance =
64
+ | (() => any)
65
+ | ((options: GetInstanceOptions) => Promise<any>);
66
+ export type TryGetInstance =
67
+ | (() => any)
68
+ | ((options: Pick<GetInstanceOptions, 'force' | 'network'>) => Promise<any>);
69
+ export type Connect = (options: {
70
+ instance: any;
71
+ network?: Network;
72
+ meta: BlockchainMeta[];
73
+ }) => Promise<ProviderConnectResult | ProviderConnectResult[]>;
74
+
75
+ export type Disconnect = (options: {
76
+ instance: any;
77
+ destroyInstance: () => void;
78
+ }) => Promise<void>;
79
+ export type Subscribe = (options: {
80
+ instance: any;
81
+ state: WalletState;
82
+ meta: BlockchainMeta[];
83
+ updateChainId: (chainId: string) => void;
84
+ updateAccounts: (accounts: string[], chainId?: string) => void;
85
+ connect: (network?: Network) => void;
86
+ disconnect: () => void;
87
+ }) => void;
88
+
89
+ export type SwitchNetwork = (options: {
90
+ instance: any;
91
+ network: Network;
92
+ meta: BlockchainMeta[];
93
+ newInstance?: TryGetInstance;
94
+ }) => Promise<void>;
95
+
96
+ export type CanSwitchNetwork = (options: {
97
+ network: Network;
98
+ meta: BlockchainMeta[];
99
+ }) => boolean;
100
+
101
+ export interface WalletActions {
102
+ connect: Connect;
103
+ getInstance: any;
104
+ disconnect?: Disconnect;
105
+ subscribe?: Subscribe;
106
+ // eagerConnect, // optional?
107
+ // unsubscribe, // coupled to subscribe.
108
+
109
+ // Optional, but should be provided at the same time.
110
+ switchNetwork?: SwitchNetwork;
111
+ getSigners: (provider: any) => WalletSigners;
112
+ canSwitchNetworkTo?: CanSwitchNetwork;
113
+ getWalletInfo(allBlockChains: BlockchainMeta[]): WalletInfo;
114
+ }
115
+
116
+ export interface WalletConfig {
117
+ type: WalletType;
118
+ defaultNetwork?: Network;
119
+ checkInstallation?: boolean;
120
+ isAsyncInstance?: boolean;
121
+ }
122
+
123
+ export type WalletProviders = Map<
124
+ WalletType,
125
+ {
126
+ actions: WalletActions;
127
+ config: WalletConfig;
128
+ }
129
+ >;
130
+
131
+ export type WalletProvider = { config: WalletConfig } & WalletActions;
package/src/wallet.ts ADDED
@@ -0,0 +1,383 @@
1
+ import {
2
+ BlockchainMeta,
3
+ getBlockChainNameFromId,
4
+ Network,
5
+ WalletType,
6
+ } from '@rango-dev/wallets-shared';
7
+ import { accountAddressesWithNetwork, needsCheckInstallation } from './helpers';
8
+ import {
9
+ Events,
10
+ GetInstanceOptions,
11
+ WalletActions,
12
+ WalletConfig,
13
+ } from './types';
14
+
15
+ export type EventHandler = (
16
+ type: WalletType,
17
+ event: Events,
18
+ value: any,
19
+ coreState: State,
20
+ supportedChains: BlockchainMeta[]
21
+ ) => void;
22
+
23
+ export interface State {
24
+ connected: boolean;
25
+ connecting: boolean;
26
+ reachable: boolean;
27
+ installed: boolean;
28
+ accounts: string[] | null;
29
+ network: Network | null;
30
+ }
31
+
32
+ export interface Options {
33
+ config: WalletConfig;
34
+ handler: EventHandler;
35
+ }
36
+
37
+ class Wallet<InstanceType = any> {
38
+ private actions: WalletActions;
39
+ private state: State;
40
+ private options: Options;
41
+ private meta: BlockchainMeta[];
42
+ public provider: InstanceType | null;
43
+
44
+ constructor(options: Options, actions: WalletActions) {
45
+ this.actions = actions;
46
+ this.options = options;
47
+ this.provider = null;
48
+ this.meta = [];
49
+ this.state = {
50
+ connected: false,
51
+ connecting: false,
52
+ // TODO: Remove
53
+ reachable: false,
54
+ installed: false,
55
+ accounts: null,
56
+ network: null,
57
+ };
58
+
59
+ if (!needsCheckInstallation(options)) {
60
+ this.setInstalledAs(true);
61
+ }
62
+ }
63
+
64
+ async eagerConnection() {
65
+ // Already connected, so we return provider that we have in memory.
66
+
67
+ // For switching network on Trust Wallet (WalletConnect),
68
+ // We only kill the session (and not restting the whole state)
69
+ // So we are relying on this.provider for achieving this functionality.
70
+ if (this.state.connected && !!this.provider) {
71
+ return {
72
+ accounts: this.state.accounts,
73
+ network: this.state.network,
74
+ provider: this.provider,
75
+ };
76
+ }
77
+
78
+ // TODO: call actions.eagerConnection
79
+ return null;
80
+ }
81
+ async connect(network?: Network) {
82
+ // If it's connecting, nothing do.
83
+ if (this.state.connecting) {
84
+ throw new Error('Connecting...');
85
+ }
86
+
87
+ const eagerConnection = await this.eagerConnection();
88
+ const currentNetwork = this.state.network;
89
+ // If a network hasn't been provided and also we have `lastNetwork`
90
+ // We will use lastNetwork to make sure we will not
91
+ // Ask the user to switch his network wrongly.
92
+ const requestedNetwork =
93
+ network || currentNetwork || this.options.config.defaultNetwork;
94
+
95
+ if (!!eagerConnection) {
96
+ const networkChanged =
97
+ currentNetwork !== requestedNetwork && !!requestedNetwork;
98
+
99
+ // Reuse current connection if nothing has changed and we already have the connection in memory.
100
+ if (currentNetwork === requestedNetwork) {
101
+ return eagerConnection;
102
+ }
103
+ if (networkChanged && !!this.actions.switchNetwork) {
104
+ await this.actions.switchNetwork({
105
+ instance: this.provider,
106
+ meta: this.meta,
107
+ // TODO: Fix type error
108
+ // @ts-ignore
109
+ network: requestedNetwork,
110
+ newInstance: this.tryGetInstance.bind(this),
111
+ });
112
+
113
+ return {
114
+ // Only network has been changed, so we reuse accounts from what we have already.
115
+ accounts: eagerConnection.accounts,
116
+ network: requestedNetwork,
117
+ provider: this.provider,
118
+ };
119
+ }
120
+
121
+ // If none of the above conditions didn't match, continute to connect.
122
+ }
123
+
124
+ // We are connecting to wallet for the first time
125
+
126
+ // Trying to get wallet's instance, if it's not available, raise an error.
127
+ const instance = await this.tryGetInstance({ network });
128
+
129
+ // Instance exists, trying to connect
130
+ this.updateState({
131
+ connecting: true,
132
+ });
133
+ this.setInstalledAs(true);
134
+
135
+ try {
136
+ // eslint-disable-next-line no-var
137
+ var connectResult = await this.actions.connect({
138
+ instance,
139
+ network: requestedNetwork || undefined,
140
+ meta: this.meta || [],
141
+ });
142
+ } catch (e) {
143
+ this.resetState();
144
+ throw e;
145
+ }
146
+
147
+ this.updateState({
148
+ connected: true,
149
+ reachable: true,
150
+ connecting: false,
151
+ });
152
+
153
+ // TODO: Handle accounts.length > 0
154
+
155
+ // Inserting accounts into our state.
156
+ let nextAccounts: string[] = [];
157
+ let nextNetwork: Network | null | undefined = null;
158
+ if (Array.isArray(connectResult)) {
159
+ const accounts = connectResult.flatMap((blockchain) => {
160
+ const chainId = blockchain.chainId || Network.Unknown;
161
+ // Try to map chainId with a Network, if not found, we use chainId directly.
162
+ const network =
163
+ getBlockChainNameFromId(chainId, this.meta) || Network.Unknown;
164
+ // TODO: second parameter should be `string` when we decided to open source the package.
165
+ return accountAddressesWithNetwork(
166
+ blockchain.accounts,
167
+ network as Network
168
+ );
169
+ });
170
+ // Typescript can not detect we are filtering out null values:(
171
+ nextAccounts = accounts.filter(Boolean) as string[];
172
+ nextNetwork = requestedNetwork || this.options.config.defaultNetwork;
173
+ } else {
174
+ const chainId = connectResult.chainId || Network.Unknown;
175
+ const network =
176
+ getBlockChainNameFromId(chainId, this.meta) || Network.Unknown;
177
+ // We fallback to current active network if `chainId` not provided.
178
+ nextAccounts = accountAddressesWithNetwork(
179
+ connectResult.accounts,
180
+ network as Network
181
+ );
182
+ nextNetwork = network as Network;
183
+ }
184
+
185
+ if (nextAccounts.length > 0) {
186
+ this.updateState({
187
+ accounts: nextAccounts,
188
+ network: nextNetwork,
189
+ });
190
+ }
191
+
192
+ return {
193
+ accounts: this.state.accounts,
194
+ network: this.state.network,
195
+ provider: this.provider,
196
+ };
197
+ }
198
+
199
+ async disconnect() {
200
+ this.resetState();
201
+
202
+ if (this.actions.disconnect) {
203
+ this.actions.disconnect({
204
+ instance: this.provider,
205
+ // On wallet connect, we need to destory the instance and get a whole new instance when we are going to connect
206
+ destroyInstance: () => {
207
+ this.setProvider(null);
208
+ },
209
+ });
210
+ }
211
+ }
212
+
213
+ getSigners(provider: any) {
214
+ return this.actions.getSigners(provider);
215
+ }
216
+ getWalletInfo(allBlockChains: BlockchainMeta[]) {
217
+ return this.actions.getWalletInfo(allBlockChains);
218
+ }
219
+ canSwitchNetworkTo(network: Network) {
220
+ const switchTo = this.actions.canSwitchNetworkTo;
221
+ if (!switchTo) return false;
222
+
223
+ return switchTo({
224
+ network,
225
+ meta: this.meta,
226
+ });
227
+ }
228
+
229
+ onInit() {
230
+ if (!this.options.config.isAsyncInstance) {
231
+ const instance = this.actions.getInstance();
232
+ if (!!instance && !this.state.installed) {
233
+ this.setInstalledAs(true);
234
+ }
235
+ }
236
+ }
237
+
238
+ setProvider(value: any) {
239
+ this.provider = value;
240
+
241
+ if (!!value && !!this.actions.subscribe) {
242
+ this.actions.subscribe({
243
+ instance: value,
244
+ state: this.state,
245
+ meta: this.meta,
246
+ connect: this.connect.bind(this),
247
+ disconnect: this.disconnect.bind(this),
248
+ updateAccounts: (accounts, chainId) => {
249
+ let network = this.state.network;
250
+ if (chainId) {
251
+ network =
252
+ getBlockChainNameFromId(chainId, this.meta) || Network.Unknown;
253
+ }
254
+
255
+ const nextAccounts = accountAddressesWithNetwork(accounts, network);
256
+ if (nextAccounts.length > 0) {
257
+ this.updateState({
258
+ accounts: nextAccounts,
259
+ });
260
+ }
261
+ },
262
+ updateChainId: (chainId) => {
263
+ const network = !!chainId
264
+ ? getBlockChainNameFromId(chainId, this.meta)
265
+ : Network.Unknown;
266
+ this.updateState({
267
+ network,
268
+ });
269
+ },
270
+ });
271
+ }
272
+ }
273
+
274
+ setMeta(value: BlockchainMeta[]) {
275
+ this.meta = value;
276
+ }
277
+
278
+ setHandler(handler: EventHandler) {
279
+ this.options.handler = handler;
280
+ }
281
+
282
+ getState(): State {
283
+ return this.state;
284
+ }
285
+ updateState(states: Partial<State>) {
286
+ // We will notify handler after updating all the states.
287
+ // Because when we call `handler` it will has latest states.
288
+ const updates: [Events, any][] = [];
289
+
290
+ if (typeof states.connected !== 'undefined') {
291
+ this.state.connected = states.connected;
292
+ updates.push([Events.CONNECTED, states.connected]);
293
+ }
294
+ if (typeof states.reachable !== 'undefined') {
295
+ this.state.reachable = states.reachable;
296
+ updates.push([Events.REACHABLE, states.reachable]);
297
+ }
298
+ if (typeof states.installed !== 'undefined') {
299
+ this.state.installed = states.installed;
300
+ updates.push([Events.INSTALLED, states.installed]);
301
+ }
302
+ if (typeof states.accounts !== 'undefined') {
303
+ this.state.accounts = states.accounts;
304
+ updates.push([Events.ACCOUNTS, states.accounts]);
305
+ }
306
+ if (typeof states.network !== 'undefined') {
307
+ this.state.network = states.network;
308
+ updates.push([Events.NETWORK, states.network]);
309
+ }
310
+
311
+ const state = this.getState();
312
+ updates.forEach(([name, value]) => {
313
+ this.options.handler(
314
+ this.options.config.type,
315
+ name,
316
+ value,
317
+ state,
318
+ this.meta
319
+ );
320
+ });
321
+ }
322
+
323
+ resetState() {
324
+ this.updateState({
325
+ connected: false,
326
+ connecting: false,
327
+ reachable: false,
328
+ accounts: null,
329
+ network: null,
330
+ });
331
+ }
332
+
333
+ private setInstalledAs(value: boolean) {
334
+ if (!needsCheckInstallation(this.options) && value === false) return;
335
+
336
+ this.updateState({
337
+ installed: value,
338
+ });
339
+ }
340
+ private async tryGetInstance({
341
+ network,
342
+ force,
343
+ }: {
344
+ network?: Network;
345
+ force?: boolean;
346
+ }) {
347
+ let instance = null;
348
+ // For switching network on Trust Wallet (WalletConnect),
349
+ // We only kill the session (and not restting the whole state)
350
+ // So we are relying on this.provider for achieving this functionality.
351
+ this.setProvider(null);
352
+
353
+ if (this.options.config.isAsyncInstance) {
354
+ // Trying to connect
355
+ const instanceOptions: GetInstanceOptions = {
356
+ currentProvider: this.provider,
357
+ meta: this.meta,
358
+ force: force || false,
359
+ };
360
+
361
+ if (network) {
362
+ instanceOptions.network = network;
363
+ }
364
+
365
+ instance = await this.actions.getInstance(instanceOptions);
366
+ } else {
367
+ instance = this.actions.getInstance();
368
+ }
369
+
370
+ if (!instance) {
371
+ this.setInstalledAs(false);
372
+ this.resetState();
373
+
374
+ const error_message = `It seems your selected wallet (${this.options.config.type}) isn't installed.`;
375
+ throw new Error(error_message);
376
+ }
377
+
378
+ this.setProvider(instance);
379
+ return instance;
380
+ }
381
+ }
382
+
383
+ export default Wallet;