@rango-dev/wallets-react 0.0.0-experimental-99f1781b-20260920 → 0.0.0-experimental-a84f27a7-20260921

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.
@@ -1,193 +0,0 @@
1
- import type { EventHandler } from '../legacy/types.js';
2
- import type { Namespace } from '@hub3js/namespaces';
3
-
4
- import {
5
- createStore,
6
- Hub,
7
- NamespaceBuilder,
8
- ProviderBuilder,
9
- } from '@hub3js/core';
10
- import { WalletConnectionError } from '@hub3js/std/utils';
11
- import { beforeEach, describe, expect, it, vi } from 'vitest';
12
-
13
- import { AutoConnectionAttemptError } from '../errors.js';
14
- import { Events } from '../legacy/types.js';
15
-
16
- import { autoConnect } from './autoConnect.js';
17
- import { HUB_LAST_CONNECTED_WALLETS } from './constants.js';
18
- import { LastConnectedWalletsFromStorage } from './lastConnectedWallets.js';
19
-
20
- const WALLET = 'test-wallet';
21
-
22
- type TestActions = {
23
- connect: (...args: unknown[]) => Promise<string[]>;
24
- canEagerConnect: () => Promise<boolean>;
25
- };
26
-
27
- type NamespaceBehaviour = {
28
- connect?: () => Promise<string[]>;
29
- canEagerConnect?: () => Promise<boolean>;
30
- };
31
-
32
- const storage = new LastConnectedWalletsFromStorage(HUB_LAST_CONNECTED_WALLETS);
33
-
34
- function buildHub(
35
- namespaces: Partial<Record<Namespace, NamespaceBehaviour>>
36
- ): Hub {
37
- const providerBuilder = new ProviderBuilder(WALLET).config('metadata', {
38
- name: 'Test Wallet',
39
- icon: 'https://example.com/icon.svg',
40
- extensions: { homepage: 'https://example.com' },
41
- });
42
-
43
- Object.entries(namespaces).forEach(([namespaceId, behaviour = {}]) => {
44
- const namespace = new NamespaceBuilder<TestActions>(namespaceId, WALLET)
45
- .action('connect', async () =>
46
- behaviour.connect ? behaviour.connect() : []
47
- )
48
- .action('canEagerConnect', async () =>
49
- behaviour.canEagerConnect ? behaviour.canEagerConnect() : true
50
- )
51
- .build();
52
- providerBuilder.add(namespaceId, namespace);
53
- });
54
-
55
- return new Hub({ store: createStore() }).add(WALLET, providerBuilder.build());
56
- }
57
-
58
- async function runAutoConnect(hub: Hub) {
59
- const onUpdateState = vi.fn<EventHandler>();
60
-
61
- await autoConnect({ getHub: () => hub, allBlockChains: [], onUpdateState });
62
- // `autoConnect` resolves before the auto-connect attempts it starts have settled.
63
- await new Promise((resolve) => setTimeout(resolve, 0));
64
-
65
- return onUpdateState.mock.calls.filter(
66
- ([, event]) => event === Events.AUTO_CONNECT_FAILED
67
- );
68
- }
69
-
70
- describe('autoConnect', () => {
71
- beforeEach(() => {
72
- localStorage.clear();
73
- storage.addWallet(WALLET, [
74
- { namespace: 'EVM', network: 'ETH' },
75
- { namespace: 'Solana', network: 'SOLANA' },
76
- ]);
77
- });
78
-
79
- it('send no event when every attempted namespace connected', async () => {
80
- const connect = vi.fn(async () => []);
81
-
82
- const events = await runAutoConnect(
83
- buildHub({ EVM: { connect }, Solana: { connect } })
84
- );
85
-
86
- expect(connect).toHaveBeenCalledTimes(2);
87
- expect(events).toEqual([]);
88
- });
89
-
90
- it('send no event when no namespace was attempted', async () => {
91
- const connect = vi.fn(async () => []);
92
-
93
- const events = await runAutoConnect(
94
- buildHub({
95
- EVM: {
96
- connect,
97
- canEagerConnect: async () => Promise.reject(new Error('locked')),
98
- },
99
- Solana: { connect, canEagerConnect: async () => false },
100
- })
101
- );
102
-
103
- expect(connect).not.toHaveBeenCalled();
104
- expect(events).toEqual([]);
105
- });
106
-
107
- it('send no event for a saved wallet that is not registered', async () => {
108
- localStorage.clear();
109
- storage.addWallet('unregistered-wallet', [
110
- { namespace: 'EVM', network: 'ETH' },
111
- ]);
112
-
113
- const events = await runAutoConnect(buildHub({ EVM: {} }));
114
-
115
- expect(events).toEqual([]);
116
- });
117
-
118
- it('send an auto connection attempt error listing the tried namespaces with their saved networks', async () => {
119
- const solanaError = new Error('Solana failed');
120
-
121
- const events = await runAutoConnect(
122
- buildHub({
123
- EVM: {},
124
- Solana: { connect: async () => Promise.reject(solanaError) },
125
- })
126
- );
127
-
128
- expect(events).toHaveLength(1);
129
- const [type, , value] = events[0];
130
- expect(type).toBe(WALLET);
131
- expect(value).toBeInstanceOf(AutoConnectionAttemptError);
132
- expect(value.name).toBe('AutoConnectionAttemptError');
133
- expect(value.message).toBe('Solana failed');
134
- expect(value.requestedNamespaces).toStrictEqual([
135
- { namespace: 'EVM', network: 'ETH' },
136
- { namespace: 'Solana', network: 'SOLANA' },
137
- ]);
138
- expect(value.errors).toHaveLength(1);
139
- expect(value.errors[0]).toBeInstanceOf(WalletConnectionError);
140
- expect(value.errors[0]).toMatchObject({
141
- namespace: 'Solana',
142
- cause: solanaError,
143
- });
144
- });
145
-
146
- it('wrap a thrown value that is not an error object in a wallet connection error for its namespace', async () => {
147
- const events = await runAutoConnect(
148
- buildHub({
149
- EVM: { connect: async () => Promise.reject('User rejected') },
150
- Solana: {},
151
- })
152
- );
153
-
154
- const [, , value] = events[0];
155
- expect(value.errors[0]).toBeInstanceOf(WalletConnectionError);
156
- expect(value.errors[0]).toMatchObject({
157
- namespace: 'EVM',
158
- message: 'User rejected',
159
- cause: 'User rejected',
160
- });
161
- });
162
-
163
- it('leave saved namespaces the wallet no longer has out of the event', async () => {
164
- const evmError = new Error('EVM failed');
165
-
166
- const events = await runAutoConnect(
167
- buildHub({ EVM: { connect: async () => Promise.reject(evmError) } })
168
- );
169
-
170
- expect(events).toHaveLength(1);
171
- const [, , value] = events[0];
172
- expect(value.requestedNamespaces).toEqual([
173
- { namespace: 'EVM', network: 'ETH' },
174
- ]);
175
- expect(value.errors).toHaveLength(1);
176
- expect(value.errors[0]).toMatchObject({ namespace: 'EVM' });
177
- });
178
-
179
- it('remove the namespaces that failed to connect from storage', async () => {
180
- await runAutoConnect(
181
- buildHub({
182
- EVM: {},
183
- Solana: {
184
- connect: async () => Promise.reject(new Error('Solana failed')),
185
- },
186
- })
187
- );
188
-
189
- expect(storage.list()[WALLET]).toEqual([
190
- { namespace: 'EVM', network: 'ETH' },
191
- ]);
192
- });
193
- });
@@ -1,138 +0,0 @@
1
- import type { NamespaceInputForConnect } from '../legacy/types.js';
2
-
3
- import { USER_REJECTION_ERROR_CODE } from '@hub3js/std/utils';
4
- import { Err, Ok } from 'ts-results';
5
- import { describe, expect, it } from 'vitest';
6
-
7
- import { WalletConnectionAttemptError } from '../errors.js';
8
-
9
- import { runConnectionAttempt } from './helpers.js';
10
-
11
- const connected = new Ok({ accounts: [], network: null });
12
-
13
- const SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'";
14
- const evm: NamespaceInputForConnect = { namespace: 'EVM', network: 'ETH' };
15
- const solana: NamespaceInputForConnect = {
16
- namespace: 'Solana',
17
- network: undefined,
18
- derivationPath: SOLANA_DERIVATION_PATH,
19
- };
20
- const sui: NamespaceInputForConnect = { namespace: 'Sui', network: undefined };
21
- const requestedNamespaces = [evm, solana, sui];
22
-
23
- function rejection() {
24
- return Object.assign(new Error('User rejected the request.'), {
25
- code: USER_REJECTION_ERROR_CODE,
26
- });
27
- }
28
-
29
- async function catchAttemptError(
30
- ...args: Parameters<typeof runConnectionAttempt>
31
- ): Promise<WalletConnectionAttemptError> {
32
- const thrown: unknown = await runConnectionAttempt(...args).then(
33
- () => undefined,
34
- (error: unknown) => error
35
- );
36
- expect(thrown).toBeInstanceOf(WalletConnectionAttemptError);
37
- return thrown as WalletConnectionAttemptError;
38
- }
39
-
40
- describe('runConnectionAttempt', () => {
41
- it('list each requested namespace with the network and derivation path passed for it including cancelled ones', async () => {
42
- const error = await catchAttemptError(
43
- requestedNamespaces,
44
- async (reportAttempts) => {
45
- reportAttempts([
46
- { input: evm, result: new Err(rejection()) },
47
- { input: solana, result: new Err(rejection()), cancelled: true },
48
- { input: sui, result: new Err(rejection()), cancelled: true },
49
- ]);
50
- }
51
- );
52
-
53
- expect(error.requestedNamespaces).toEqual([
54
- { namespace: 'EVM', network: 'ETH' },
55
- {
56
- namespace: 'Solana',
57
- network: undefined,
58
- derivationPath: SOLANA_DERIVATION_PATH,
59
- },
60
- { namespace: 'Sui', network: undefined },
61
- ]);
62
- expect(error.errors).toHaveLength(1);
63
- expect(error.errors[0]).toMatchObject({ namespace: 'EVM' });
64
- });
65
-
66
- it('add a config error thrown before any namespace is tried as the only entry without wrapping it', async () => {
67
- const configError = new Error(
68
- "You should add phantom to provider first then call 'connect'."
69
- );
70
-
71
- const error = await catchAttemptError(requestedNamespaces, async () => {
72
- throw configError;
73
- });
74
-
75
- expect(error.errors).toHaveLength(1);
76
- expect(error.errors[0]).toBe(configError);
77
- expect(error.message).toBe(configError.message);
78
- expect(error.requestedNamespaces.map(({ namespace }) => namespace)).toEqual(
79
- ['EVM', 'Solana', 'Sui']
80
- );
81
- });
82
-
83
- it('list no requested namespaces when none were passed', async () => {
84
- const configError = new Error(
85
- 'Passing namespace to `connect` is required.'
86
- );
87
-
88
- const error = await catchAttemptError(undefined, async () => {
89
- throw configError;
90
- });
91
-
92
- expect(error.errors).toEqual([configError]);
93
- expect(error.requestedNamespaces).toEqual([]);
94
- });
95
-
96
- it('add a value thrown once the namespaces have settled after their failures without wrapping it', async () => {
97
- const storageError = new Error('QuotaExceededError');
98
-
99
- const error = await catchAttemptError(
100
- requestedNamespaces,
101
- async (reportAttempts) => {
102
- reportAttempts([
103
- { input: evm, result: new Err(new Error('EVM failed')) },
104
- { input: solana, result: connected },
105
- { input: sui, result: connected },
106
- ]);
107
- throw storageError;
108
- }
109
- );
110
-
111
- expect(error.errors).toHaveLength(2);
112
- expect(error.errors[0]).toMatchObject({ namespace: 'EVM' });
113
- expect(error.errors[1]).toBe(storageError);
114
- });
115
-
116
- it('add a thrown undefined as the only entry', async () => {
117
- const error = await catchAttemptError(requestedNamespaces, async () => {
118
- // eslint-disable-next-line @typescript-eslint/only-throw-error
119
- throw undefined;
120
- });
121
-
122
- expect(error.errors).toEqual([undefined]);
123
- });
124
-
125
- it('return each namespace result in request order when every namespace connected', async () => {
126
- const results = await runConnectionAttempt(
127
- [evm, solana],
128
- async (reportAttempts) => {
129
- reportAttempts([
130
- { input: evm, result: new Ok('evm accounts') },
131
- { input: solana, result: new Ok('solana accounts') },
132
- ]);
133
- }
134
- );
135
-
136
- expect(results).toEqual(['evm accounts', 'solana accounts']);
137
- });
138
- });
@@ -1,186 +0,0 @@
1
- import type { NamespaceInputForConnect } from '../legacy/types.js';
2
- import type { ProviderContext } from '../types.js';
3
- import type { Provider } from '@hub3js/core';
4
-
5
- import { NamespaceBuilder, ProviderBuilder } from '@hub3js/core';
6
- import {
7
- USER_REJECTION_ERROR_CODE,
8
- WALLET_LOCKED_ERROR_CODE,
9
- } from '@hub3js/std/utils';
10
- import { act, renderHook } from '@testing-library/react-hooks';
11
- import { beforeEach, describe, expect, it, vi } from 'vitest';
12
-
13
- import { WalletConnectionAttemptError } from '../errors.js';
14
-
15
- import { useHubAdapter } from './useHubAdapter.js';
16
-
17
- type TestActions = {
18
- connect: (...args: unknown[]) => Promise<string[]>;
19
- };
20
-
21
- type NamespaceConnect = () => Promise<string[]>;
22
-
23
- const evm: NamespaceInputForConnect = { namespace: 'EVM', network: 'ETH' };
24
- const solana: NamespaceInputForConnect = {
25
- namespace: 'Solana',
26
- network: undefined,
27
- };
28
- const sui: NamespaceInputForConnect = { namespace: 'Sui', network: undefined };
29
-
30
- function rejection() {
31
- return Object.assign(new Error('User rejected the request.'), {
32
- code: USER_REJECTION_ERROR_CODE,
33
- });
34
- }
35
-
36
- function lockedWallet() {
37
- return Object.assign(new Error('The device is locked'), {
38
- code: WALLET_LOCKED_ERROR_CODE,
39
- });
40
- }
41
-
42
- function buildProvider(
43
- walletType: string,
44
- connects: Record<string, NamespaceConnect>
45
- ): Provider {
46
- const providerBuilder = new ProviderBuilder<unknown>(walletType).config(
47
- 'metadata',
48
- {
49
- name: walletType,
50
- icon: 'https://example.com/icon.svg',
51
- extensions: { homepage: 'https://example.com' },
52
- }
53
- );
54
-
55
- Object.entries(connects).forEach(([namespaceId, connect]) => {
56
- const namespace = new NamespaceBuilder<TestActions>(namespaceId, walletType)
57
- .action('connect', async () => connect())
58
- .build();
59
- providerBuilder.add(namespaceId, namespace);
60
- });
61
-
62
- return providerBuilder.build();
63
- }
64
-
65
- function renderAdapter(providers: Provider[]): ProviderContext {
66
- return renderHook(() => useHubAdapter({ providers })).result.current;
67
- }
68
-
69
- async function connectAndCatch(
70
- api: ProviderContext,
71
- walletType: string,
72
- namespaces: NamespaceInputForConnect[]
73
- ): Promise<WalletConnectionAttemptError> {
74
- let thrown: unknown;
75
- await act(async () => {
76
- thrown = await api.connect(walletType, namespaces).then(
77
- () => undefined,
78
- (error: unknown) => error
79
- );
80
- });
81
- expect(thrown).toBeInstanceOf(WalletConnectionAttemptError);
82
- return thrown as WalletConnectionAttemptError;
83
- }
84
-
85
- describe('useHubAdapter', () => {
86
- beforeEach(() => {
87
- localStorage.clear();
88
- });
89
-
90
- describe('connect', () => {
91
- it('abandon the namespaces still waiting after a rejection', async () => {
92
- const solanaConnect = vi.fn(async () => []);
93
- const suiConnect = vi.fn(async () => []);
94
- const api = renderAdapter([
95
- buildProvider('phantom', {
96
- EVM: async () => Promise.reject(rejection()),
97
- Solana: solanaConnect,
98
- Sui: suiConnect,
99
- }),
100
- ]);
101
-
102
- await connectAndCatch(api, 'phantom', [evm, solana, sui]);
103
-
104
- expect(solanaConnect).not.toHaveBeenCalled();
105
- expect(suiConnect).not.toHaveBeenCalled();
106
- });
107
-
108
- it('leave the namespaces abandoned after a rejection out of errors', async () => {
109
- const api = renderAdapter([
110
- buildProvider('phantom', {
111
- EVM: async () => Promise.reject(rejection()),
112
- Solana: async () => Promise.reject(new Error('Solana failed')),
113
- Sui: async () => Promise.reject(new Error('Sui failed')),
114
- }),
115
- ]);
116
-
117
- const error = await connectAndCatch(api, 'phantom', [evm, solana, sui]);
118
-
119
- expect(error.errors).toHaveLength(1);
120
- expect(error.errors[0]).toMatchObject({
121
- namespace: 'EVM',
122
- code: USER_REJECTION_ERROR_CODE,
123
- });
124
- });
125
-
126
- it('keep the namespaces abandoned after a rejection in the requested namespaces', async () => {
127
- const api = renderAdapter([
128
- buildProvider('phantom', {
129
- EVM: async () => Promise.reject(rejection()),
130
- Solana: async () => [],
131
- Sui: async () => [],
132
- }),
133
- ]);
134
-
135
- const error = await connectAndCatch(api, 'phantom', [evm, solana, sui]);
136
-
137
- expect(error.requestedNamespaces).toEqual([evm, solana, sui]);
138
- });
139
-
140
- it('keep connecting the waiting namespaces after a locked wallet', async () => {
141
- const solanaConnect = vi.fn(async () => []);
142
- const api = renderAdapter([
143
- buildProvider('ledger', {
144
- EVM: async () => Promise.reject(lockedWallet()),
145
- Solana: solanaConnect,
146
- }),
147
- ]);
148
-
149
- const error = await connectAndCatch(api, 'ledger', [evm, solana]);
150
-
151
- expect(solanaConnect).toHaveBeenCalledTimes(1);
152
- expect(error.errors).toHaveLength(1);
153
- expect(error.errors[0]).toMatchObject({
154
- namespace: 'EVM',
155
- code: WALLET_LOCKED_ERROR_CODE,
156
- });
157
- });
158
-
159
- it('keep connecting the waiting namespaces of other wallets after a rejection', async () => {
160
- let finishMetamaskEvm: (accounts: string[]) => void = () => undefined;
161
- const metamaskEvmFinished = new Promise<string[]>((resolve) => {
162
- finishMetamaskEvm = resolve;
163
- });
164
- const metamaskSolanaConnect = vi.fn(async () => []);
165
- const api = renderAdapter([
166
- buildProvider('phantom', {
167
- EVM: async () => Promise.reject(rejection()),
168
- Solana: async () => [],
169
- }),
170
- buildProvider('metamask', {
171
- EVM: async () => metamaskEvmFinished,
172
- Solana: metamaskSolanaConnect,
173
- }),
174
- ]);
175
-
176
- await act(async () => {
177
- const metamask = api.connect('metamask', [evm, solana]);
178
- await api.connect('phantom', [evm, solana]).catch(() => undefined);
179
- finishMetamaskEvm([]);
180
- await metamask;
181
- });
182
-
183
- expect(metamaskSolanaConnect).toHaveBeenCalledTimes(1);
184
- });
185
- });
186
- });