@imtbl/wallet 2.12.7-alpha.1 → 2.12.7-alpha.11
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/README.md +1 -31
- package/dist/browser/index.js +30 -204
- package/dist/node/index.cjs +55 -244
- package/dist/node/index.js +29 -203
- package/dist/types/confirmation/confirmation.d.ts +1 -1
- package/dist/types/connectWallet.d.ts +3 -2
- package/dist/types/constants.d.ts +0 -9
- package/dist/types/index.d.ts +2 -4
- package/dist/types/{network/presets.d.ts → presets.d.ts} +1 -55
- package/dist/types/types.d.ts +0 -12
- package/dist/types/zkEvm/types.d.ts +10 -3
- package/package.json +4 -9
- package/src/confirmation/confirmation.ts +4 -14
- package/src/connectWallet.test.ts +464 -58
- package/src/connectWallet.ts +40 -81
- package/src/constants.ts +0 -13
- package/src/guardian/index.ts +0 -2
- package/src/index.ts +2 -18
- package/src/presets.ts +92 -0
- package/src/types.ts +0 -16
- package/src/zkEvm/types.ts +10 -4
- package/dist/types/network/chainRegistry.d.ts +0 -13
- package/dist/types/sequence/sequenceProvider.d.ts +0 -21
- package/dist/types/sequence/signer/identityInstrumentSigner.d.ts +0 -15
- package/dist/types/sequence/signer/index.d.ts +0 -21
- package/dist/types/sequence/signer/privateKeySigner.d.ts +0 -15
- package/dist/types/sequence/signer/types.d.ts +0 -14
- package/dist/types/sequence/user/index.d.ts +0 -2
- package/dist/types/sequence/user/registerUser.d.ts +0 -18
- package/src/network/chainRegistry.test.ts +0 -64
- package/src/network/chainRegistry.ts +0 -74
- package/src/network/presets.ts +0 -185
- package/src/sequence/sequenceProvider.ts +0 -284
- package/src/sequence/signer/identityInstrumentSigner.ts +0 -195
- package/src/sequence/signer/index.ts +0 -41
- package/src/sequence/signer/privateKeySigner.ts +0 -112
- package/src/sequence/signer/types.ts +0 -24
- package/src/sequence/user/index.ts +0 -2
- package/src/sequence/user/registerUser.ts +0 -101
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
// Mock Auth with configurable behavior
|
|
2
|
+
const mockAuthInstance = {
|
|
3
|
+
getConfig: jest.fn().mockReturnValue({
|
|
4
|
+
authenticationDomain: 'https://auth.immutable.com',
|
|
5
|
+
passportDomain: 'https://passport.immutable.com',
|
|
6
|
+
oidcConfiguration: {
|
|
7
|
+
clientId: 'client',
|
|
8
|
+
redirectUri: 'https://redirect',
|
|
9
|
+
},
|
|
10
|
+
}),
|
|
11
|
+
getUser: jest.fn().mockResolvedValue({ profile: { sub: 'user' } }),
|
|
12
|
+
getUserOrLogin: jest.fn().mockResolvedValue({ profile: { sub: 'user' }, accessToken: 'token' }),
|
|
13
|
+
loginCallback: jest.fn().mockResolvedValue(undefined),
|
|
14
|
+
};
|
|
14
15
|
|
|
16
|
+
const Auth = jest.fn().mockImplementation(() => mockAuthInstance);
|
|
17
|
+
|
|
18
|
+
jest.mock('@imtbl/auth', () => {
|
|
15
19
|
const TypedEventEmitter = jest.fn().mockImplementation(() => ({
|
|
16
20
|
emit: jest.fn(),
|
|
17
21
|
on: jest.fn(),
|
|
@@ -43,18 +47,6 @@ jest.mock('./zkEvm/zkEvmProvider', () => ({
|
|
|
43
47
|
ZkEvmProvider: jest.fn(),
|
|
44
48
|
}));
|
|
45
49
|
|
|
46
|
-
jest.mock('./sequence/sequenceProvider', () => ({
|
|
47
|
-
SequenceProvider: jest.fn(),
|
|
48
|
-
}));
|
|
49
|
-
|
|
50
|
-
jest.mock('./sequence/signer', () => ({
|
|
51
|
-
createSequenceSigner: jest.fn().mockReturnValue({
|
|
52
|
-
getAddress: jest.fn().mockResolvedValue('0x1234'),
|
|
53
|
-
signPayload: jest.fn(),
|
|
54
|
-
signMessage: jest.fn(),
|
|
55
|
-
}),
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
50
|
jest.mock('./provider/eip6963', () => ({
|
|
59
51
|
announceProvider: jest.fn(),
|
|
60
52
|
passportProviderInfo: { name: 'passport', rdns: 'com.immutable.passport', icon: '' },
|
|
@@ -64,7 +56,6 @@ const { connectWallet } = require('./connectWallet');
|
|
|
64
56
|
|
|
65
57
|
const { announceProvider } = jest.requireMock('./provider/eip6963');
|
|
66
58
|
const { ZkEvmProvider } = jest.requireMock('./zkEvm/zkEvmProvider');
|
|
67
|
-
const { SequenceProvider } = jest.requireMock('./sequence/sequenceProvider');
|
|
68
59
|
|
|
69
60
|
const zkEvmChain = {
|
|
70
61
|
chainId: 13473,
|
|
@@ -74,15 +65,6 @@ const zkEvmChain = {
|
|
|
74
65
|
name: 'Immutable zkEVM Testnet',
|
|
75
66
|
};
|
|
76
67
|
|
|
77
|
-
const arbitrumChain = {
|
|
78
|
-
chainId: 42161,
|
|
79
|
-
rpcUrl: 'https://arb1.arbitrum.io/rpc',
|
|
80
|
-
relayerUrl: 'https://next-arbitrum-one-relayer.sequence.app',
|
|
81
|
-
apiUrl: 'https://api.immutable.com',
|
|
82
|
-
name: 'Arbitrum One',
|
|
83
|
-
sequenceIdentityInstrumentEndpoint: 'https://sequence.immutable.com',
|
|
84
|
-
};
|
|
85
|
-
|
|
86
68
|
// Create a mock getUser function for tests
|
|
87
69
|
const createGetUserMock = () => jest.fn().mockResolvedValue({
|
|
88
70
|
profile: { sub: 'user' },
|
|
@@ -92,26 +74,461 @@ const createGetUserMock = () => jest.fn().mockResolvedValue({
|
|
|
92
74
|
describe('connectWallet', () => {
|
|
93
75
|
beforeEach(() => {
|
|
94
76
|
jest.clearAllMocks();
|
|
77
|
+
Auth.mockClear();
|
|
78
|
+
mockAuthInstance.getUser.mockClear();
|
|
79
|
+
mockAuthInstance.getUserOrLogin.mockClear();
|
|
80
|
+
mockAuthInstance.loginCallback.mockClear();
|
|
95
81
|
});
|
|
96
82
|
|
|
97
|
-
|
|
98
|
-
|
|
83
|
+
describe('with external getUser (existing tests)', () => {
|
|
84
|
+
it('announces provider by default', async () => {
|
|
85
|
+
const getUser = createGetUserMock();
|
|
99
86
|
|
|
100
|
-
|
|
87
|
+
const provider = await connectWallet({ getUser, chains: [zkEvmChain] });
|
|
101
88
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
89
|
+
expect(ZkEvmProvider).toHaveBeenCalled();
|
|
90
|
+
expect(announceProvider).toHaveBeenCalledWith({
|
|
91
|
+
info: expect.any(Object),
|
|
92
|
+
provider,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('does not announce provider when disabled', async () => {
|
|
97
|
+
const getUser = createGetUserMock();
|
|
98
|
+
|
|
99
|
+
await connectWallet({ getUser, chains: [zkEvmChain], announceProvider: false });
|
|
100
|
+
|
|
101
|
+
expect(announceProvider).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('uses provided getUser when supplied', async () => {
|
|
105
|
+
const getUser = createGetUserMock();
|
|
106
|
+
|
|
107
|
+
await connectWallet({ getUser, chains: [zkEvmChain] });
|
|
108
|
+
|
|
109
|
+
// Should NOT create internal Auth instance
|
|
110
|
+
expect(Auth).not.toHaveBeenCalled();
|
|
111
|
+
// Should use the provided getUser
|
|
112
|
+
expect(getUser).toHaveBeenCalled();
|
|
106
113
|
});
|
|
107
114
|
});
|
|
108
115
|
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
describe('default auth (no getUser provided)', () => {
|
|
117
|
+
describe('Auth instance creation', () => {
|
|
118
|
+
it('creates internal Auth instance when getUser is not provided', async () => {
|
|
119
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
120
|
+
|
|
121
|
+
expect(Auth).toHaveBeenCalledTimes(1);
|
|
122
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
123
|
+
expect.objectContaining({
|
|
124
|
+
scope: 'openid profile email offline_access transact',
|
|
125
|
+
audience: 'platform_api',
|
|
126
|
+
authenticationDomain: 'https://auth.immutable.com',
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('uses getUserOrLogin from internal Auth', async () => {
|
|
132
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
133
|
+
|
|
134
|
+
// Internal Auth's getUserOrLogin should be called during setup
|
|
135
|
+
expect(mockAuthInstance.getUserOrLogin).toHaveBeenCalled();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('derives passportDomain from chain apiUrl', async () => {
|
|
139
|
+
const customChain = {
|
|
140
|
+
...zkEvmChain,
|
|
141
|
+
apiUrl: 'https://api.custom.immutable.com',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
await connectWallet({ chains: [customChain] });
|
|
145
|
+
|
|
146
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
147
|
+
expect.objectContaining({
|
|
148
|
+
passportDomain: 'https://passport.custom.immutable.com',
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('uses provided passportDomain if specified in chain config', async () => {
|
|
154
|
+
const customChain = {
|
|
155
|
+
...zkEvmChain,
|
|
156
|
+
passportDomain: 'https://custom-passport.immutable.com',
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
await connectWallet({ chains: [customChain] });
|
|
160
|
+
|
|
161
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
162
|
+
expect.objectContaining({
|
|
163
|
+
passportDomain: 'https://custom-passport.immutable.com',
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('uses default redirect URI fallback', async () => {
|
|
169
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
170
|
+
|
|
171
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
172
|
+
expect.objectContaining({
|
|
173
|
+
redirectUri: 'https://auth.immutable.com/im-logged-in',
|
|
174
|
+
popupRedirectUri: 'https://auth.immutable.com/im-logged-in',
|
|
175
|
+
logoutRedirectUri: 'https://auth.immutable.com/im-logged-in',
|
|
176
|
+
}),
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('passes popupOverlayOptions to Auth', async () => {
|
|
181
|
+
const popupOverlayOptions = {
|
|
182
|
+
disableGenericPopupOverlay: true,
|
|
183
|
+
disableBlockedPopupOverlay: false,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
await connectWallet({ chains: [zkEvmChain], popupOverlayOptions });
|
|
187
|
+
|
|
188
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
189
|
+
expect.objectContaining({
|
|
190
|
+
popupOverlayOptions,
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('passes crossSdkBridgeEnabled to Auth', async () => {
|
|
196
|
+
await connectWallet({ chains: [zkEvmChain], crossSdkBridgeEnabled: true });
|
|
197
|
+
|
|
198
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
199
|
+
expect.objectContaining({
|
|
200
|
+
crossSdkBridgeEnabled: true,
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('clientId auto-detection', () => {
|
|
207
|
+
it('uses sandbox client ID for testnet chain (chainId 13473)', async () => {
|
|
208
|
+
const testnetChain = {
|
|
209
|
+
...zkEvmChain,
|
|
210
|
+
chainId: 13473,
|
|
211
|
+
apiUrl: 'https://api.sandbox.immutable.com',
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
await connectWallet({ chains: [testnetChain] });
|
|
215
|
+
|
|
216
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
217
|
+
expect.objectContaining({
|
|
218
|
+
clientId: 'mjtCL8mt06BtbxSkp2vbrYStKWnXVZfo', // Sandbox client ID
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('uses production client ID for mainnet chain (chainId 13371)', async () => {
|
|
224
|
+
const mainnetChain = {
|
|
225
|
+
chainId: 13371,
|
|
226
|
+
rpcUrl: 'https://rpc.immutable.com',
|
|
227
|
+
relayerUrl: 'https://relayer.immutable.com',
|
|
228
|
+
apiUrl: 'https://api.immutable.com',
|
|
229
|
+
name: 'Immutable zkEVM Mainnet',
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
await connectWallet({ chains: [mainnetChain] });
|
|
233
|
+
|
|
234
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
235
|
+
expect.objectContaining({
|
|
236
|
+
clientId: 'PtQRK4iRJ8GkXjiz6xfImMAYhPhW0cYk', // Production client ID
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('detects sandbox from apiUrl containing "sandbox"', async () => {
|
|
242
|
+
const sandboxChain = {
|
|
243
|
+
chainId: 99999, // unknown chainId
|
|
244
|
+
rpcUrl: 'https://rpc.custom.com',
|
|
245
|
+
relayerUrl: 'https://relayer.custom.com',
|
|
246
|
+
apiUrl: 'https://api.sandbox.custom.com', // "sandbox" in URL
|
|
247
|
+
name: 'Custom Sandbox Chain',
|
|
248
|
+
magicPublishableApiKey: 'pk_test_123',
|
|
249
|
+
magicProviderId: 'provider-123',
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
await connectWallet({ chains: [sandboxChain] });
|
|
253
|
+
|
|
254
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
255
|
+
expect.objectContaining({
|
|
256
|
+
clientId: 'mjtCL8mt06BtbxSkp2vbrYStKWnXVZfo', // Sandbox client ID
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('detects sandbox from apiUrl containing "testnet"', async () => {
|
|
262
|
+
const testnetChain = {
|
|
263
|
+
chainId: 99999,
|
|
264
|
+
rpcUrl: 'https://rpc.testnet.custom.com',
|
|
265
|
+
relayerUrl: 'https://relayer.testnet.custom.com',
|
|
266
|
+
apiUrl: 'https://api.testnet.custom.com', // "testnet" in URL
|
|
267
|
+
name: 'Custom Testnet Chain',
|
|
268
|
+
magicPublishableApiKey: 'pk_test_123',
|
|
269
|
+
magicProviderId: 'provider-123',
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
await connectWallet({ chains: [testnetChain] });
|
|
273
|
+
|
|
274
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
275
|
+
expect.objectContaining({
|
|
276
|
+
clientId: 'mjtCL8mt06BtbxSkp2vbrYStKWnXVZfo', // Sandbox client ID
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('uses provided clientId when explicitly set', async () => {
|
|
282
|
+
const customClientId = 'custom-client-id-123';
|
|
283
|
+
|
|
284
|
+
await connectWallet({ chains: [zkEvmChain], clientId: customClientId });
|
|
285
|
+
|
|
286
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
287
|
+
expect.objectContaining({
|
|
288
|
+
clientId: customClientId,
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
});
|
|
111
292
|
|
|
112
|
-
|
|
293
|
+
it('prefers provided clientId over auto-detected one', async () => {
|
|
294
|
+
const customClientId = 'custom-client-id-456';
|
|
295
|
+
const mainnetChain = {
|
|
296
|
+
chainId: 13371,
|
|
297
|
+
rpcUrl: 'https://rpc.immutable.com',
|
|
298
|
+
relayerUrl: 'https://relayer.immutable.com',
|
|
299
|
+
apiUrl: 'https://api.immutable.com',
|
|
300
|
+
name: 'Immutable zkEVM Mainnet',
|
|
301
|
+
};
|
|
113
302
|
|
|
114
|
-
|
|
303
|
+
await connectWallet({ chains: [mainnetChain], clientId: customClientId });
|
|
304
|
+
|
|
305
|
+
// Should use custom, not production client ID
|
|
306
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
307
|
+
expect.objectContaining({
|
|
308
|
+
clientId: customClientId,
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('popup callback handling', () => {
|
|
315
|
+
it('sets up message listener for popup callback', async () => {
|
|
316
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
317
|
+
|
|
318
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
319
|
+
|
|
320
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function));
|
|
321
|
+
|
|
322
|
+
addEventListenerSpy.mockRestore();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('handles OAuth callback message with code and state', async () => {
|
|
326
|
+
let messageHandler: ((event: MessageEvent) => void) | null = null;
|
|
327
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener').mockImplementation((event, handler) => {
|
|
328
|
+
if (event === 'message') {
|
|
329
|
+
messageHandler = handler as (event: MessageEvent) => void;
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const replaceStateSpy = jest.spyOn(window.history, 'replaceState').mockImplementation(() => {});
|
|
334
|
+
|
|
335
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
336
|
+
|
|
337
|
+
expect(messageHandler).not.toBeNull();
|
|
338
|
+
|
|
339
|
+
// Simulate popup callback message
|
|
340
|
+
const callbackMessage = {
|
|
341
|
+
data: {
|
|
342
|
+
code: 'auth-code-123',
|
|
343
|
+
state: 'state-456',
|
|
344
|
+
},
|
|
345
|
+
} as MessageEvent;
|
|
346
|
+
|
|
347
|
+
// Trigger the message event
|
|
348
|
+
const promise = messageHandler!(callbackMessage);
|
|
349
|
+
|
|
350
|
+
// Wait for async operations
|
|
351
|
+
await promise;
|
|
352
|
+
await Promise.resolve();
|
|
353
|
+
|
|
354
|
+
// Should call Auth.loginCallback
|
|
355
|
+
expect(mockAuthInstance.loginCallback).toHaveBeenCalled();
|
|
356
|
+
|
|
357
|
+
// Should update browser history with code/state
|
|
358
|
+
expect(replaceStateSpy).toHaveBeenCalledTimes(2);
|
|
359
|
+
|
|
360
|
+
addEventListenerSpy.mockRestore();
|
|
361
|
+
replaceStateSpy.mockRestore();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('ignores messages without code and state', async () => {
|
|
365
|
+
let messageHandler: ((event: MessageEvent) => void) | null = null;
|
|
366
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener').mockImplementation((event, handler) => {
|
|
367
|
+
if (event === 'message') {
|
|
368
|
+
messageHandler = handler as (event: MessageEvent) => void;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
373
|
+
|
|
374
|
+
// Simulate non-callback message
|
|
375
|
+
const regularMessage = {
|
|
376
|
+
data: {
|
|
377
|
+
someOtherData: 'value',
|
|
378
|
+
},
|
|
379
|
+
} as MessageEvent;
|
|
380
|
+
|
|
381
|
+
messageHandler!(regularMessage);
|
|
382
|
+
|
|
383
|
+
await Promise.resolve();
|
|
384
|
+
|
|
385
|
+
// Should NOT call Auth.loginCallback
|
|
386
|
+
expect(mockAuthInstance.loginCallback).not.toHaveBeenCalled();
|
|
387
|
+
|
|
388
|
+
addEventListenerSpy.mockRestore();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('updates query string with code and state during callback', async () => {
|
|
392
|
+
let messageHandler: ((event: MessageEvent) => void) | null = null;
|
|
393
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener').mockImplementation((event, handler) => {
|
|
394
|
+
if (event === 'message') {
|
|
395
|
+
messageHandler = handler as (event: MessageEvent) => void;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const replaceStateSpy = jest.spyOn(window.history, 'replaceState').mockImplementation(() => {});
|
|
400
|
+
|
|
401
|
+
// Mock window.location.search
|
|
402
|
+
Object.defineProperty(window, 'location', {
|
|
403
|
+
value: { search: '?existing=param' },
|
|
404
|
+
writable: true,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
408
|
+
|
|
409
|
+
const callbackMessage = {
|
|
410
|
+
data: {
|
|
411
|
+
code: 'test-code',
|
|
412
|
+
state: 'test-state',
|
|
413
|
+
},
|
|
414
|
+
} as MessageEvent;
|
|
415
|
+
|
|
416
|
+
const promise = messageHandler!(callbackMessage);
|
|
417
|
+
await promise;
|
|
418
|
+
await Promise.resolve();
|
|
419
|
+
|
|
420
|
+
// First call: add code and state
|
|
421
|
+
expect(replaceStateSpy).toHaveBeenNthCalledWith(
|
|
422
|
+
1,
|
|
423
|
+
null,
|
|
424
|
+
'',
|
|
425
|
+
expect.stringContaining('code=test-code'),
|
|
426
|
+
);
|
|
427
|
+
expect(replaceStateSpy).toHaveBeenNthCalledWith(
|
|
428
|
+
1,
|
|
429
|
+
null,
|
|
430
|
+
'',
|
|
431
|
+
expect.stringContaining('state=test-state'),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
// Second call: remove code and state after callback
|
|
435
|
+
expect(replaceStateSpy).toHaveBeenNthCalledWith(
|
|
436
|
+
2,
|
|
437
|
+
null,
|
|
438
|
+
'',
|
|
439
|
+
expect.not.stringContaining('code='),
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
addEventListenerSpy.mockRestore();
|
|
443
|
+
replaceStateSpy.mockRestore();
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
describe('provider creation with default auth', () => {
|
|
448
|
+
it('creates provider successfully without getUser', async () => {
|
|
449
|
+
const provider = await connectWallet({ chains: [zkEvmChain] });
|
|
450
|
+
|
|
451
|
+
expect(provider).toBeDefined();
|
|
452
|
+
expect(ZkEvmProvider).toHaveBeenCalled();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('passes getUser function to ZkEvmProvider', async () => {
|
|
456
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
457
|
+
|
|
458
|
+
const zkEvmProviderCall = (ZkEvmProvider as jest.Mock).mock.calls[0][0];
|
|
459
|
+
expect(zkEvmProviderCall.getUser).toEqual(expect.any(Function));
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('passes clientId to ZkEvmProvider', async () => {
|
|
463
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
464
|
+
|
|
465
|
+
const zkEvmProviderCall = (ZkEvmProvider as jest.Mock).mock.calls[0][0];
|
|
466
|
+
expect(zkEvmProviderCall.clientId).toBe('mjtCL8mt06BtbxSkp2vbrYStKWnXVZfo');
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('works with custom chain configuration', async () => {
|
|
470
|
+
const customChain = {
|
|
471
|
+
chainId: 99999,
|
|
472
|
+
rpcUrl: 'https://rpc.custom.com',
|
|
473
|
+
relayerUrl: 'https://relayer.custom.com',
|
|
474
|
+
apiUrl: 'https://api.custom.com',
|
|
475
|
+
passportDomain: 'https://passport.custom.com',
|
|
476
|
+
name: 'Custom Chain',
|
|
477
|
+
magicPublishableApiKey: 'pk_test_custom',
|
|
478
|
+
magicProviderId: 'provider-custom',
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const provider = await connectWallet({ chains: [customChain] });
|
|
482
|
+
|
|
483
|
+
expect(provider).toBeDefined();
|
|
484
|
+
expect(Auth).toHaveBeenCalledWith(
|
|
485
|
+
expect.objectContaining({
|
|
486
|
+
passportDomain: 'https://passport.custom.com',
|
|
487
|
+
}),
|
|
488
|
+
);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe('error handling', () => {
|
|
493
|
+
it('handles auth failure gracefully', async () => {
|
|
494
|
+
mockAuthInstance.getUserOrLogin.mockRejectedValueOnce(new Error('Auth failed'));
|
|
495
|
+
|
|
496
|
+
const provider = await connectWallet({ chains: [zkEvmChain] });
|
|
497
|
+
|
|
498
|
+
// Should still create provider (user will be null)
|
|
499
|
+
expect(provider).toBeDefined();
|
|
500
|
+
expect(ZkEvmProvider).toHaveBeenCalledWith(
|
|
501
|
+
expect.objectContaining({
|
|
502
|
+
user: null,
|
|
503
|
+
}),
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('handles loginCallback failure gracefully', async () => {
|
|
508
|
+
let messageHandler: ((event: MessageEvent) => void) | null = null;
|
|
509
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener').mockImplementation((event, handler) => {
|
|
510
|
+
if (event === 'message') {
|
|
511
|
+
messageHandler = handler as (event: MessageEvent) => void;
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
mockAuthInstance.loginCallback.mockRejectedValueOnce(new Error('Callback failed'));
|
|
516
|
+
|
|
517
|
+
await connectWallet({ chains: [zkEvmChain] });
|
|
518
|
+
|
|
519
|
+
const callbackMessage = {
|
|
520
|
+
data: {
|
|
521
|
+
code: 'test-code',
|
|
522
|
+
state: 'test-state',
|
|
523
|
+
},
|
|
524
|
+
} as MessageEvent;
|
|
525
|
+
|
|
526
|
+
// Should not throw (error is handled internally)
|
|
527
|
+
await expect(messageHandler!(callbackMessage)).rejects.toThrow('Callback failed');
|
|
528
|
+
|
|
529
|
+
addEventListenerSpy.mockRestore();
|
|
530
|
+
});
|
|
531
|
+
});
|
|
115
532
|
});
|
|
116
533
|
|
|
117
534
|
describe('provider selection', () => {
|
|
@@ -121,13 +538,12 @@ describe('connectWallet', () => {
|
|
|
121
538
|
await connectWallet({ getUser, chains: [zkEvmChain] });
|
|
122
539
|
|
|
123
540
|
expect(ZkEvmProvider).toHaveBeenCalled();
|
|
124
|
-
expect(SequenceProvider).not.toHaveBeenCalled();
|
|
125
541
|
});
|
|
126
542
|
|
|
127
543
|
it('uses ZkEvmProvider for zkEVM devnet chain', async () => {
|
|
128
544
|
const getUser = createGetUserMock();
|
|
129
545
|
const devChain = {
|
|
130
|
-
chainId:
|
|
546
|
+
chainId: 99999, // unknown chainId
|
|
131
547
|
rpcUrl: 'https://rpc.dev.immutable.com',
|
|
132
548
|
relayerUrl: 'https://relayer.dev.immutable.com',
|
|
133
549
|
apiUrl: 'https://api.dev.immutable.com',
|
|
@@ -139,16 +555,6 @@ describe('connectWallet', () => {
|
|
|
139
555
|
await connectWallet({ getUser, chains: [devChain] });
|
|
140
556
|
|
|
141
557
|
expect(ZkEvmProvider).toHaveBeenCalled();
|
|
142
|
-
expect(SequenceProvider).not.toHaveBeenCalled();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('uses SequenceProvider for non-zkEVM chain (Arbitrum)', async () => {
|
|
146
|
-
const getUser = createGetUserMock();
|
|
147
|
-
|
|
148
|
-
await connectWallet({ getUser, chains: [arbitrumChain] });
|
|
149
|
-
|
|
150
|
-
expect(SequenceProvider).toHaveBeenCalled();
|
|
151
|
-
expect(ZkEvmProvider).not.toHaveBeenCalled();
|
|
152
558
|
});
|
|
153
559
|
});
|
|
154
560
|
});
|