@portal-hq/web 3.2.2 → 3.3.0

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.
@@ -0,0 +1,1570 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import portal from '../__mocks/portal/portal'
6
+ import Provider from '.'
7
+ import { mockRpcUrl, mockSignedHash } from '../__mocks/constants'
8
+ import { ProviderRpcError, RpcErrorCodes } from './errors'
9
+ import { RequestMethod } from '../'
10
+ import mpcMock from '../__mocks/portal/mpc'
11
+
12
+ describe('Provider', () => {
13
+ beforeEach(() => {
14
+ portal.autoApprove = true
15
+ portal.mpc = mpcMock
16
+ jest.clearAllMocks()
17
+
18
+ global.fetch = jest.fn().mockResolvedValue({
19
+ json: jest.fn().mockResolvedValue({ result: 'test', error: null }),
20
+ })
21
+ })
22
+
23
+ describe('request', () => {
24
+ let provider = new Provider({ portal })
25
+
26
+ describe('Signer methods with auto-approval', () => {
27
+ beforeAll(() => {
28
+ portal.autoApprove = true
29
+ provider.emit = jest.fn()
30
+ })
31
+
32
+ describe('eth_chainId', () => {
33
+ it('should throw an error if no chainId is provided alongside eth_chainId', async () => {
34
+ expect(
35
+ provider.request({
36
+ method: RequestMethod.eth_chainId,
37
+ params: ['test'],
38
+ }),
39
+ ).rejects.toThrow(
40
+ new Error(
41
+ `[PortalProvider] Chain ID is required for the operation`,
42
+ ),
43
+ )
44
+ })
45
+
46
+ it('should throw an error if malformed chainId is provided alongside eth_chainId', async () => {
47
+ const chainId = 'unsupported:chain'
48
+ expect(
49
+ provider.request({
50
+ chainId,
51
+ method: RequestMethod.eth_chainId,
52
+ params: ['test'],
53
+ }),
54
+ ).rejects.toThrow(
55
+ new Error(
56
+ `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`,
57
+ ),
58
+ )
59
+ })
60
+
61
+ it('should successfully handle an eth_chainId request', async () => {
62
+ const result = await provider.request({
63
+ chainId: 'eip155:1',
64
+ method: RequestMethod.eth_chainId,
65
+ params: ['test'],
66
+ })
67
+
68
+ expect(result).toEqual('0x1')
69
+ expect(provider.emit).toHaveBeenCalledWith(
70
+ 'portal_signatureReceived',
71
+ {
72
+ chainId: 'eip155:1',
73
+ method: RequestMethod.eth_chainId,
74
+ params: ['test'],
75
+ signature: result,
76
+ },
77
+ )
78
+ })
79
+ })
80
+
81
+ describe('eth_accounts', () => {
82
+ it('should throw an error if no chainId is provided alongside eth_accounts', async () => {
83
+ expect(
84
+ provider.request({
85
+ method: RequestMethod.eth_accounts,
86
+ params: ['test'],
87
+ }),
88
+ ).rejects.toThrow(
89
+ new Error(
90
+ `[PortalProvider] Chain ID is required for the operation`,
91
+ ),
92
+ )
93
+ })
94
+
95
+ it('should throw an error if malformed chainId is provided alongside eth_accounts', async () => {
96
+ const chainId = 'unsupported:chain'
97
+ expect(
98
+ provider.request({
99
+ chainId,
100
+ method: RequestMethod.eth_accounts,
101
+ params: ['test'],
102
+ }),
103
+ ).rejects.toThrow(
104
+ new Error(
105
+ `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`,
106
+ ),
107
+ )
108
+ })
109
+
110
+ it('should successfully handle an eth_accounts request', async () => {
111
+ portal.address = 'test'
112
+ const result = await provider.request({
113
+ chainId: 'eip155:1',
114
+ method: RequestMethod.eth_accounts,
115
+ params: ['test'],
116
+ })
117
+
118
+ expect(result).toEqual([mockSignedHash])
119
+ expect(provider.emit).toHaveBeenCalledWith(
120
+ 'portal_signatureReceived',
121
+ {
122
+ chainId: 'eip155:1',
123
+ method: RequestMethod.eth_accounts,
124
+ params: ['test'],
125
+ signature: result,
126
+ },
127
+ )
128
+ })
129
+ })
130
+
131
+ describe('eth_requestAccounts', () => {
132
+ it('should throw an error if no chainId is provided alongside eth_requestAccounts', async () => {
133
+ expect(
134
+ provider.request({
135
+ method: RequestMethod.eth_requestAccounts,
136
+ params: ['test'],
137
+ }),
138
+ ).rejects.toThrow(
139
+ new Error(
140
+ `[PortalProvider] Chain ID is required for the operation`,
141
+ ),
142
+ )
143
+ })
144
+
145
+ it('should throw an error if malformed chainId is provided alongside eth_requestAccounts', async () => {
146
+ const chainId = 'unsupported:chain'
147
+ expect(
148
+ provider.request({
149
+ chainId,
150
+ method: RequestMethod.eth_requestAccounts,
151
+ params: ['test'],
152
+ }),
153
+ ).rejects.toThrow(
154
+ new Error(
155
+ `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`,
156
+ ),
157
+ )
158
+ })
159
+
160
+ it('should successfully handle an eth_requestAccounts request', async () => {
161
+ portal.address = 'test'
162
+ const result = await provider.request({
163
+ chainId: 'eip155:1',
164
+ method: RequestMethod.eth_requestAccounts,
165
+ params: ['test'],
166
+ })
167
+
168
+ expect(result).toEqual([mockSignedHash])
169
+ expect(provider.emit).toHaveBeenCalledWith(
170
+ 'portal_signatureReceived',
171
+ {
172
+ chainId: 'eip155:1',
173
+ method: RequestMethod.eth_requestAccounts,
174
+ params: ['test'],
175
+ signature: result,
176
+ },
177
+ )
178
+ })
179
+ })
180
+
181
+ describe('eth_sendTransaction', () => {
182
+ it('should throw an error if no chainId is provided alongside eth_sendTransaction', async () => {
183
+ expect(
184
+ provider.request({
185
+ method: RequestMethod.eth_sendTransaction,
186
+ params: ['test'],
187
+ }),
188
+ ).rejects.toThrow(
189
+ new Error(
190
+ `[PortalProvider] Chain ID is required for the operation`,
191
+ ),
192
+ )
193
+ })
194
+
195
+ it('should throw an error if malformed chainId is provided alongside eth_sendTransaction', async () => {
196
+ const chainId = 'unsupported:chain'
197
+ expect(
198
+ provider.request({
199
+ chainId,
200
+ method: RequestMethod.eth_sendTransaction,
201
+ params: ['test'],
202
+ }),
203
+ ).rejects.toThrow(
204
+ new Error(
205
+ `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`,
206
+ ),
207
+ )
208
+ })
209
+
210
+ it('should successfully handle an eth_sendTransaction request', async () => {
211
+ const result = await provider.request({
212
+ chainId: 'eip155:1',
213
+ method: RequestMethod.eth_sendTransaction,
214
+ params: ['test'],
215
+ })
216
+
217
+ expect(result).toEqual(mockSignedHash)
218
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
219
+ chainId: 'eip155:1',
220
+ method: RequestMethod.eth_sendTransaction,
221
+ params: 'test',
222
+ rpcUrl: mockRpcUrl,
223
+ })
224
+ expect(provider.emit).toHaveBeenCalledWith(
225
+ 'portal_signatureReceived',
226
+ {
227
+ chainId: 'eip155:1',
228
+ method: RequestMethod.eth_sendTransaction,
229
+ params: ['test'],
230
+ signature: result,
231
+ },
232
+ )
233
+ })
234
+ })
235
+
236
+ describe('eth_signTransaction', () => {
237
+ it('should throw an error if no chainId is provided alongside eth_signTransaction', async () => {
238
+ expect(
239
+ provider.request({
240
+ method: RequestMethod.eth_signTransaction,
241
+ params: ['test'],
242
+ }),
243
+ ).rejects.toThrow(
244
+ new Error(
245
+ `[PortalProvider] Chain ID is required for the operation`,
246
+ ),
247
+ )
248
+ })
249
+
250
+ it('should throw an error if malformed chainId is provided alongside eth_signTransaction', async () => {
251
+ const chainId = 'unsupported:chain'
252
+ expect(
253
+ provider.request({
254
+ chainId,
255
+ method: RequestMethod.eth_signTransaction,
256
+ params: ['test'],
257
+ }),
258
+ ).rejects.toThrow(
259
+ new Error(
260
+ `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`,
261
+ ),
262
+ )
263
+ })
264
+
265
+ it('should successfully handle an eth_signTransaction request', async () => {
266
+ const result = await provider.request({
267
+ chainId: 'eip155:1',
268
+ method: RequestMethod.eth_signTransaction,
269
+ params: ['test'],
270
+ })
271
+
272
+ expect(result).toEqual(mockSignedHash)
273
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
274
+ chainId: 'eip155:1',
275
+ method: RequestMethod.eth_signTransaction,
276
+ params: 'test',
277
+ rpcUrl: mockRpcUrl,
278
+ })
279
+ expect(provider.emit).toHaveBeenCalledWith(
280
+ 'portal_signatureReceived',
281
+ {
282
+ chainId: 'eip155:1',
283
+ method: RequestMethod.eth_signTransaction,
284
+ params: ['test'],
285
+ signature: result,
286
+ },
287
+ )
288
+ })
289
+ })
290
+
291
+ describe('eth_sign', () => {
292
+ it('should throw an error if no chainId is provided alongside eth_sign', async () => {
293
+ expect(
294
+ provider.request({
295
+ method: RequestMethod.eth_sign,
296
+ params: ['test'],
297
+ }),
298
+ ).rejects.toThrow(
299
+ new Error(
300
+ `[PortalProvider] Chain ID is required for the operation`,
301
+ ),
302
+ )
303
+ })
304
+
305
+ it('should throw an error if malformed chainId is provided alongside eth_sign', async () => {
306
+ const chainId = 'unsupported:chain'
307
+ expect(
308
+ provider.request({
309
+ chainId,
310
+ method: RequestMethod.eth_sign,
311
+ params: ['test'],
312
+ }),
313
+ ).rejects.toThrow(
314
+ new Error(
315
+ `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`,
316
+ ),
317
+ )
318
+ })
319
+
320
+ it('should successfully handle an eth_sign request', async () => {
321
+ const result = await provider.request({
322
+ chainId: 'eip155:1',
323
+ method: RequestMethod.eth_sign,
324
+ params: ['test'],
325
+ })
326
+
327
+ expect(result).toEqual(mockSignedHash)
328
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
329
+ chainId: 'eip155:1',
330
+ method: RequestMethod.eth_sign,
331
+ params: ['test'],
332
+ rpcUrl: mockRpcUrl,
333
+ })
334
+ expect(provider.emit).toHaveBeenCalledWith(
335
+ 'portal_signatureReceived',
336
+ {
337
+ chainId: 'eip155:1',
338
+ method: RequestMethod.eth_sign,
339
+ params: ['test'],
340
+ signature: result,
341
+ },
342
+ )
343
+ })
344
+ })
345
+
346
+ describe('eth_signTypedData_v3', () => {
347
+ it('should throw an error if no chainId is provided alongside eth_signTypedData_v3', async () => {
348
+ expect(
349
+ provider.request({
350
+ method: RequestMethod.eth_signTypedData_v3,
351
+ params: ['test'],
352
+ }),
353
+ ).rejects.toThrow(
354
+ new Error(
355
+ `[PortalProvider] Chain ID is required for the operation`,
356
+ ),
357
+ )
358
+ })
359
+
360
+ it('should throw an error if malformed chainId is provided alongside eth_signTypedData_v3', async () => {
361
+ const chainId = 'unsupported:chain'
362
+ expect(
363
+ provider.request({
364
+ chainId,
365
+ method: RequestMethod.eth_signTypedData_v3,
366
+ params: ['test'],
367
+ }),
368
+ ).rejects.toThrow(
369
+ new Error(
370
+ `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`,
371
+ ),
372
+ )
373
+ })
374
+
375
+ it('should successfully handle an eth_signTypedData_v3 request', async () => {
376
+ const result = await provider.request({
377
+ chainId: 'eip155:1',
378
+ method: RequestMethod.eth_signTypedData_v3,
379
+ params: ['test'],
380
+ })
381
+
382
+ expect(result).toEqual(mockSignedHash)
383
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
384
+ chainId: 'eip155:1',
385
+ method: RequestMethod.eth_signTypedData_v3,
386
+ params: ['test'],
387
+ rpcUrl: mockRpcUrl,
388
+ })
389
+ expect(provider.emit).toHaveBeenCalledWith(
390
+ 'portal_signatureReceived',
391
+ {
392
+ chainId: 'eip155:1',
393
+ method: RequestMethod.eth_signTypedData_v3,
394
+ params: ['test'],
395
+ signature: result,
396
+ },
397
+ )
398
+ })
399
+ })
400
+
401
+ describe('eth_signTypedData_v4', () => {
402
+ it('should throw an error if no chainId is provided alongside eth_signTypedData_v4', async () => {
403
+ expect(
404
+ provider.request({
405
+ method: RequestMethod.eth_signTypedData_v4,
406
+ params: ['test'],
407
+ }),
408
+ ).rejects.toThrow(
409
+ new Error(
410
+ `[PortalProvider] Chain ID is required for the operation`,
411
+ ),
412
+ )
413
+ })
414
+
415
+ it('should throw an error if malformed chainId is provided alongside eth_signTypedData_v4', async () => {
416
+ const chainId = 'unsupported:chain'
417
+ expect(
418
+ provider.request({
419
+ chainId,
420
+ method: RequestMethod.eth_signTypedData_v4,
421
+ params: ['test'],
422
+ }),
423
+ ).rejects.toThrow(
424
+ new Error(
425
+ `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`,
426
+ ),
427
+ )
428
+ })
429
+
430
+ it('should successfully handle an eth_signTypedData_v4 request', async () => {
431
+ const result = await provider.request({
432
+ chainId: 'eip155:1',
433
+ method: RequestMethod.eth_signTypedData_v4,
434
+ params: ['test'],
435
+ })
436
+
437
+ expect(result).toEqual(mockSignedHash)
438
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
439
+ chainId: 'eip155:1',
440
+ method: RequestMethod.eth_signTypedData_v4,
441
+ params: ['test'],
442
+ rpcUrl: mockRpcUrl,
443
+ })
444
+ expect(provider.emit).toHaveBeenCalledWith(
445
+ 'portal_signatureReceived',
446
+ {
447
+ chainId: 'eip155:1',
448
+ method: RequestMethod.eth_signTypedData_v4,
449
+ params: ['test'],
450
+ signature: result,
451
+ },
452
+ )
453
+ })
454
+ })
455
+
456
+ describe('personal_sign', () => {
457
+ it('should throw an error if no chainId is provided alongside personal_sign', async () => {
458
+ expect(
459
+ provider.request({
460
+ method: RequestMethod.personal_sign,
461
+ params: ['test'],
462
+ }),
463
+ ).rejects.toThrow(
464
+ new Error(
465
+ `[PortalProvider] Chain ID is required for the operation`,
466
+ ),
467
+ )
468
+ })
469
+
470
+ it('should throw an error if malformed chainId is provided alongside personal_sign', async () => {
471
+ const chainId = 'unsupported:chain'
472
+ expect(
473
+ provider.request({
474
+ chainId,
475
+ method: RequestMethod.personal_sign,
476
+ params: ['test'],
477
+ }),
478
+ ).rejects.toThrow(
479
+ new Error(
480
+ `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`,
481
+ ),
482
+ )
483
+ })
484
+
485
+ it('should successfully handle a personal_sign request', async () => {
486
+ const result = await provider.request({
487
+ chainId: 'eip155:1',
488
+ method: RequestMethod.personal_sign,
489
+ params: ['test'],
490
+ })
491
+
492
+ expect(result).toEqual(mockSignedHash)
493
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
494
+ chainId: 'eip155:1',
495
+ method: RequestMethod.personal_sign,
496
+ params: ['test'],
497
+ rpcUrl: mockRpcUrl,
498
+ })
499
+ expect(provider.emit).toHaveBeenCalledWith(
500
+ 'portal_signatureReceived',
501
+ {
502
+ chainId: 'eip155:1',
503
+ method: RequestMethod.personal_sign,
504
+ params: ['test'],
505
+ signature: result,
506
+ },
507
+ )
508
+ })
509
+ })
510
+
511
+ describe('sol_signAndConfirmTransaction', () => {
512
+ it('should throw an error if no chainId is provided alongside sol_signAndConfirmTransaction', async () => {
513
+ expect(
514
+ provider.request({
515
+ method: RequestMethod.sol_signAndConfirmTransaction,
516
+ params: ['test'],
517
+ }),
518
+ ).rejects.toThrow(
519
+ new Error(
520
+ `[PortalProvider] Chain ID is required for the operation`,
521
+ ),
522
+ )
523
+ })
524
+
525
+ it('should throw an error if malformed chainId is provided alongside sol_signAndConfirmTransaction', async () => {
526
+ const chainId = 'unsupported:chain'
527
+ expect(
528
+ provider.request({
529
+ chainId,
530
+ method: RequestMethod.sol_signAndConfirmTransaction,
531
+ params: ['test'],
532
+ }),
533
+ ).rejects.toThrow(
534
+ new Error(
535
+ `[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`,
536
+ ),
537
+ )
538
+ })
539
+
540
+ it('should successfully handle a sol_signAndConfirmTransaction request', async () => {
541
+ const result = await provider.request({
542
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
543
+ method: RequestMethod.sol_signAndConfirmTransaction,
544
+ params: ['test'],
545
+ })
546
+
547
+ expect(result).toEqual(mockSignedHash)
548
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
549
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
550
+ method: RequestMethod.sol_signAndConfirmTransaction,
551
+ params: ['test'],
552
+ rpcUrl: mockRpcUrl,
553
+ })
554
+ expect(provider.emit).toHaveBeenCalledWith(
555
+ 'portal_signatureReceived',
556
+ {
557
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
558
+ method: RequestMethod.sol_signAndConfirmTransaction,
559
+ params: ['test'],
560
+ signature: result,
561
+ },
562
+ )
563
+ })
564
+ })
565
+
566
+ describe('sol_signAndSendTransaction', () => {
567
+ it('should throw an error if no chainId is provided alongside sol_signAndSendTransaction', async () => {
568
+ expect(
569
+ provider.request({
570
+ method: RequestMethod.sol_signAndSendTransaction,
571
+ params: ['test'],
572
+ }),
573
+ ).rejects.toThrow(
574
+ new Error(
575
+ `[PortalProvider] Chain ID is required for the operation`,
576
+ ),
577
+ )
578
+ })
579
+
580
+ it('should throw an error if malformed chainId is provided alongside sol_signAndSendTransaction', async () => {
581
+ const chainId = 'unsupported:chain'
582
+ expect(
583
+ provider.request({
584
+ chainId,
585
+ method: RequestMethod.sol_signAndSendTransaction,
586
+ params: ['test'],
587
+ }),
588
+ ).rejects.toThrow(
589
+ new Error(
590
+ `[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`,
591
+ ),
592
+ )
593
+ })
594
+
595
+ it('should successfully handle a sol_signAndSendTransaction request', async () => {
596
+ const result = await provider.request({
597
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
598
+ method: RequestMethod.sol_signAndSendTransaction,
599
+ params: ['test'],
600
+ })
601
+
602
+ expect(result).toEqual(mockSignedHash)
603
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
604
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
605
+ method: RequestMethod.sol_signAndSendTransaction,
606
+ params: ['test'],
607
+ rpcUrl: mockRpcUrl,
608
+ })
609
+ expect(provider.emit).toHaveBeenCalledWith(
610
+ 'portal_signatureReceived',
611
+ {
612
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
613
+ method: RequestMethod.sol_signAndSendTransaction,
614
+ params: ['test'],
615
+ signature: result,
616
+ },
617
+ )
618
+ })
619
+ })
620
+
621
+ describe('sol_signMessage', () => {
622
+ it('should throw an error if no chainId is provided alongside sol_signMessage', async () => {
623
+ expect(
624
+ provider.request({
625
+ method: RequestMethod.sol_signMessage,
626
+ params: ['test'],
627
+ }),
628
+ ).rejects.toThrow(
629
+ new Error(
630
+ `[PortalProvider] Chain ID is required for the operation`,
631
+ ),
632
+ )
633
+ })
634
+
635
+ it('should throw an error if malformed chainId is provided alongside sol_signMessage', async () => {
636
+ const chainId = 'unsupported:chain'
637
+ expect(
638
+ provider.request({
639
+ chainId,
640
+ method: RequestMethod.sol_signMessage,
641
+ params: ['test'],
642
+ }),
643
+ ).rejects.toThrow(
644
+ new Error(
645
+ `[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`,
646
+ ),
647
+ )
648
+ })
649
+
650
+ it('should successfully handle a sol_signMessage request', async () => {
651
+ const result = await provider.request({
652
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
653
+ method: RequestMethod.sol_signMessage,
654
+ params: ['test'],
655
+ })
656
+
657
+ expect(result).toEqual(mockSignedHash)
658
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
659
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
660
+ method: RequestMethod.sol_signMessage,
661
+ params: ['test'],
662
+ rpcUrl: mockRpcUrl,
663
+ })
664
+ expect(provider.emit).toHaveBeenCalledWith(
665
+ 'portal_signatureReceived',
666
+ {
667
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
668
+ method: RequestMethod.sol_signMessage,
669
+ params: ['test'],
670
+ signature: result,
671
+ },
672
+ )
673
+ })
674
+ })
675
+
676
+ describe('sol_signTransaction', () => {
677
+ it('should throw an error if no chainId is provided alongside sol_signTransaction', async () => {
678
+ expect(
679
+ provider.request({
680
+ method: RequestMethod.sol_signTransaction,
681
+ params: ['test'],
682
+ }),
683
+ ).rejects.toThrow(
684
+ new Error(
685
+ `[PortalProvider] Chain ID is required for the operation`,
686
+ ),
687
+ )
688
+ })
689
+
690
+ it('should throw an error if malformed chainId is provided alongside sol_signTransaction', async () => {
691
+ const chainId = 'unsupported:chain'
692
+ expect(
693
+ provider.request({
694
+ chainId,
695
+ method: RequestMethod.sol_signTransaction,
696
+ params: ['test'],
697
+ }),
698
+ ).rejects.toThrow(
699
+ new Error(
700
+ `[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`,
701
+ ),
702
+ )
703
+ })
704
+
705
+ it('should successfully handle a sol_signTransaction request', async () => {
706
+ const result = await provider.request({
707
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
708
+ method: RequestMethod.sol_signTransaction,
709
+ params: ['test'],
710
+ })
711
+
712
+ expect(result).toEqual(mockSignedHash)
713
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
714
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
715
+ method: RequestMethod.sol_signTransaction,
716
+ params: ['test'],
717
+ rpcUrl: mockRpcUrl,
718
+ })
719
+ expect(provider.emit).toHaveBeenCalledWith(
720
+ 'portal_signatureReceived',
721
+ {
722
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
723
+ method: RequestMethod.sol_signTransaction,
724
+ params: ['test'],
725
+ signature: result,
726
+ },
727
+ )
728
+ })
729
+ })
730
+ })
731
+
732
+ describe('Signer methods without auto-approval', () => {
733
+ const mockSigningRequestedHandler = jest.fn()
734
+ const mockSignatureReceivedHandler = jest.fn()
735
+ const mockConsoleWarn = jest.spyOn(global.console, 'warn')
736
+
737
+ beforeEach(() => {
738
+ provider = new Provider({ portal })
739
+ portal.autoApprove = false
740
+ provider.on('portal_signingRequested', mockSigningRequestedHandler)
741
+ provider.on('portal_signatureReceived', mockSignatureReceivedHandler)
742
+ })
743
+
744
+ describe('eth_chainId', () => {
745
+ it('should successfully handle an eth_chainId request', async () => {
746
+ const result = await provider.request({
747
+ chainId: 'eip155:1',
748
+ method: RequestMethod.eth_chainId,
749
+ params: ['test'],
750
+ })
751
+
752
+ expect(mockSigningRequestedHandler).toHaveBeenCalledTimes(0)
753
+ expect(result).toEqual('0x1')
754
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
755
+ chainId: 'eip155:1',
756
+ method: RequestMethod.eth_chainId,
757
+ params: ['test'],
758
+ signature: result,
759
+ })
760
+ })
761
+ })
762
+
763
+ describe('eth_accounts', () => {
764
+ it('should successfully handle an eth_accounts request', async () => {
765
+ portal.address = 'test'
766
+ const result = await provider.request({
767
+ chainId: 'eip155:1',
768
+ method: RequestMethod.eth_accounts,
769
+ params: ['test'],
770
+ })
771
+
772
+ expect(mockSigningRequestedHandler).toHaveBeenCalledTimes(0)
773
+ expect(result).toEqual([mockSignedHash])
774
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
775
+ chainId: 'eip155:1',
776
+ method: RequestMethod.eth_accounts,
777
+ params: ['test'],
778
+ signature: result,
779
+ })
780
+ })
781
+ })
782
+
783
+ describe('eth_requestAccounts', () => {
784
+ it('should successfully handle an approved eth_requestAccounts request', async () => {
785
+ portal.address = 'test'
786
+ const result = await provider.request({
787
+ chainId: 'eip155:1',
788
+ method: RequestMethod.eth_requestAccounts,
789
+ params: ['test'],
790
+ })
791
+
792
+ expect(mockSigningRequestedHandler).toHaveBeenCalledTimes(0)
793
+ expect(result).toEqual([mockSignedHash])
794
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
795
+ chainId: 'eip155:1',
796
+ method: RequestMethod.eth_requestAccounts,
797
+ params: ['test'],
798
+ signature: result,
799
+ })
800
+ })
801
+ })
802
+
803
+ describe('eth_sendTransaction', () => {
804
+ it('should successfully handle an approved eth_sendTransaction request', async () => {
805
+ provider.on('portal_signingRequested', () => {
806
+ provider.emit('portal_signingApproved', {
807
+ method: RequestMethod.eth_sendTransaction,
808
+ params: ['test'],
809
+ })
810
+ })
811
+ const result = await provider.request({
812
+ chainId: 'eip155:1',
813
+ method: RequestMethod.eth_sendTransaction,
814
+ params: ['test'],
815
+ })
816
+
817
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
818
+ method: RequestMethod.eth_sendTransaction,
819
+ params: ['test'],
820
+ })
821
+ expect(result).toEqual(mockSignedHash)
822
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
823
+ chainId: 'eip155:1',
824
+ method: RequestMethod.eth_sendTransaction,
825
+ params: 'test',
826
+ rpcUrl: mockRpcUrl,
827
+ })
828
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
829
+ chainId: 'eip155:1',
830
+ method: RequestMethod.eth_sendTransaction,
831
+ params: ['test'],
832
+ signature: result,
833
+ })
834
+ })
835
+
836
+ it('should successfully handle a rejected eth_sendTransaction request', async () => {
837
+ provider.on('portal_signingRequested', () => {
838
+ provider.emit('portal_signingRejected', {
839
+ method: RequestMethod.eth_sendTransaction,
840
+ params: ['test'],
841
+ })
842
+ })
843
+
844
+ const result = await provider.request({
845
+ chainId: 'eip155:1',
846
+ method: RequestMethod.eth_sendTransaction,
847
+ params: ['test'],
848
+ })
849
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
850
+ method: RequestMethod.eth_sendTransaction,
851
+ params: ['test'],
852
+ })
853
+ expect(result).toEqual(undefined)
854
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
855
+ "[PortalProvider] Request for signing method 'eth_sendTransaction' could not be completed because it was not approved by the user.",
856
+ )
857
+ })
858
+ })
859
+
860
+ describe('eth_signTransaction', () => {
861
+ it('should successfully handle an approved eth_signTransaction request', async () => {
862
+ provider.on('portal_signingRequested', () => {
863
+ provider.emit('portal_signingApproved', {
864
+ method: RequestMethod.eth_signTransaction,
865
+ params: ['test'],
866
+ })
867
+ })
868
+ const result = await provider.request({
869
+ chainId: 'eip155:1',
870
+ method: RequestMethod.eth_signTransaction,
871
+ params: ['test'],
872
+ })
873
+
874
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
875
+ method: RequestMethod.eth_signTransaction,
876
+ params: ['test'],
877
+ })
878
+ expect(result).toEqual(mockSignedHash)
879
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
880
+ chainId: 'eip155:1',
881
+ method: RequestMethod.eth_signTransaction,
882
+ params: 'test',
883
+ rpcUrl: mockRpcUrl,
884
+ })
885
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
886
+ chainId: 'eip155:1',
887
+ method: RequestMethod.eth_signTransaction,
888
+ params: ['test'],
889
+ signature: result,
890
+ })
891
+ })
892
+
893
+ it('should successfully handle a rejected eth_signTransaction request', async () => {
894
+ provider.on('portal_signingRequested', () => {
895
+ provider.emit('portal_signingRejected', {
896
+ method: RequestMethod.eth_signTransaction,
897
+ params: ['test'],
898
+ })
899
+ })
900
+
901
+ const result = await provider.request({
902
+ chainId: 'eip155:1',
903
+ method: RequestMethod.eth_signTransaction,
904
+ params: ['test'],
905
+ })
906
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
907
+ method: RequestMethod.eth_signTransaction,
908
+ params: ['test'],
909
+ })
910
+ expect(result).toEqual(undefined)
911
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
912
+ "[PortalProvider] Request for signing method 'eth_signTransaction' could not be completed because it was not approved by the user.",
913
+ )
914
+ })
915
+ })
916
+
917
+ describe('eth_sign', () => {
918
+ it('should successfully handle an approved eth_sign request', async () => {
919
+ provider.on('portal_signingRequested', () => {
920
+ provider.emit('portal_signingApproved', {
921
+ method: RequestMethod.eth_sign,
922
+ params: ['test'],
923
+ })
924
+ })
925
+ const result = await provider.request({
926
+ chainId: 'eip155:1',
927
+ method: RequestMethod.eth_sign,
928
+ params: ['test'],
929
+ })
930
+
931
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
932
+ method: RequestMethod.eth_sign,
933
+ params: ['test'],
934
+ })
935
+ expect(result).toEqual(mockSignedHash)
936
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
937
+ chainId: 'eip155:1',
938
+ method: RequestMethod.eth_sign,
939
+ params: ['test'],
940
+ rpcUrl: mockRpcUrl,
941
+ })
942
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
943
+ chainId: 'eip155:1',
944
+ method: RequestMethod.eth_sign,
945
+ params: ['test'],
946
+ signature: result,
947
+ })
948
+ })
949
+
950
+ it('should successfully handle a rejected eth_sign request', async () => {
951
+ provider.on('portal_signingRequested', () => {
952
+ provider.emit('portal_signingRejected', {
953
+ method: RequestMethod.eth_sign,
954
+ params: ['test'],
955
+ })
956
+ })
957
+
958
+ const result = await provider.request({
959
+ chainId: 'eip155:1',
960
+ method: RequestMethod.eth_sign,
961
+ params: ['test'],
962
+ })
963
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
964
+ method: RequestMethod.eth_sign,
965
+ params: ['test'],
966
+ })
967
+ expect(result).toEqual(undefined)
968
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
969
+ "[PortalProvider] Request for signing method 'eth_sign' could not be completed because it was not approved by the user.",
970
+ )
971
+ })
972
+ })
973
+
974
+ describe('eth_signTypedData_v3', () => {
975
+ it('should successfully handle an approved eth_signTypedData_v3 request', async () => {
976
+ provider.on('portal_signingRequested', () => {
977
+ provider.emit('portal_signingApproved', {
978
+ method: RequestMethod.eth_signTypedData_v3,
979
+ params: ['test'],
980
+ })
981
+ })
982
+ const result = await provider.request({
983
+ chainId: 'eip155:1',
984
+ method: RequestMethod.eth_signTypedData_v3,
985
+ params: ['test'],
986
+ })
987
+
988
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
989
+ method: RequestMethod.eth_signTypedData_v3,
990
+ params: ['test'],
991
+ })
992
+ expect(result).toEqual(mockSignedHash)
993
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
994
+ chainId: 'eip155:1',
995
+ method: RequestMethod.eth_signTypedData_v3,
996
+ params: ['test'],
997
+ rpcUrl: mockRpcUrl,
998
+ })
999
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1000
+ chainId: 'eip155:1',
1001
+ method: RequestMethod.eth_signTypedData_v3,
1002
+ params: ['test'],
1003
+ signature: result,
1004
+ })
1005
+ })
1006
+
1007
+ it('should successfully handle a rejected eth_signTypedData_v3 request', async () => {
1008
+ provider.on('portal_signingRequested', () => {
1009
+ provider.emit('portal_signingRejected', {
1010
+ method: RequestMethod.eth_signTypedData_v3,
1011
+ params: ['test'],
1012
+ })
1013
+ })
1014
+
1015
+ const result = await provider.request({
1016
+ chainId: 'eip155:1',
1017
+ method: RequestMethod.eth_signTypedData_v3,
1018
+ params: ['test'],
1019
+ })
1020
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1021
+ method: RequestMethod.eth_signTypedData_v3,
1022
+ params: ['test'],
1023
+ })
1024
+ expect(result).toEqual(undefined)
1025
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
1026
+ "[PortalProvider] Request for signing method 'eth_signTypedData_v3' could not be completed because it was not approved by the user.",
1027
+ )
1028
+ })
1029
+ })
1030
+
1031
+ describe('eth_signTypedData_v4', () => {
1032
+ it('should successfully handle an approved eth_signTypedData_v4 request', async () => {
1033
+ provider.on('portal_signingRequested', () => {
1034
+ provider.emit('portal_signingApproved', {
1035
+ method: RequestMethod.eth_signTypedData_v4,
1036
+ params: ['test'],
1037
+ })
1038
+ })
1039
+ const result = await provider.request({
1040
+ chainId: 'eip155:1',
1041
+ method: RequestMethod.eth_signTypedData_v4,
1042
+ params: ['test'],
1043
+ })
1044
+
1045
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1046
+ method: RequestMethod.eth_signTypedData_v4,
1047
+ params: ['test'],
1048
+ })
1049
+ expect(result).toEqual(mockSignedHash)
1050
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
1051
+ chainId: 'eip155:1',
1052
+ method: RequestMethod.eth_signTypedData_v4,
1053
+ params: ['test'],
1054
+ rpcUrl: mockRpcUrl,
1055
+ })
1056
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1057
+ chainId: 'eip155:1',
1058
+ method: RequestMethod.eth_signTypedData_v4,
1059
+ params: ['test'],
1060
+ signature: result,
1061
+ })
1062
+ })
1063
+
1064
+ it('should successfully handle a rejected eth_signTypedData_v4 request', async () => {
1065
+ provider.on('portal_signingRequested', () => {
1066
+ provider.emit('portal_signingRejected', {
1067
+ method: RequestMethod.eth_signTypedData_v4,
1068
+ params: ['test'],
1069
+ })
1070
+ })
1071
+
1072
+ const result = await provider.request({
1073
+ chainId: 'eip155:1',
1074
+ method: RequestMethod.eth_signTypedData_v4,
1075
+ params: ['test'],
1076
+ })
1077
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1078
+ method: RequestMethod.eth_signTypedData_v4,
1079
+ params: ['test'],
1080
+ })
1081
+ expect(result).toEqual(undefined)
1082
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
1083
+ "[PortalProvider] Request for signing method 'eth_signTypedData_v4' could not be completed because it was not approved by the user.",
1084
+ )
1085
+ })
1086
+ })
1087
+
1088
+ describe('personal_sign', () => {
1089
+ it('should successfully handle an approved personal_sign request', async () => {
1090
+ provider.on('portal_signingRequested', () => {
1091
+ provider.emit('portal_signingApproved', {
1092
+ method: RequestMethod.personal_sign,
1093
+ params: ['test'],
1094
+ })
1095
+ })
1096
+ const result = await provider.request({
1097
+ chainId: 'eip155:1',
1098
+ method: RequestMethod.personal_sign,
1099
+ params: ['test'],
1100
+ })
1101
+
1102
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1103
+ method: RequestMethod.personal_sign,
1104
+ params: ['test'],
1105
+ })
1106
+ expect(result).toEqual(mockSignedHash)
1107
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
1108
+ chainId: 'eip155:1',
1109
+ method: RequestMethod.personal_sign,
1110
+ params: ['test'],
1111
+ rpcUrl: mockRpcUrl,
1112
+ })
1113
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1114
+ chainId: 'eip155:1',
1115
+ method: RequestMethod.personal_sign,
1116
+ params: ['test'],
1117
+ signature: result,
1118
+ })
1119
+ })
1120
+
1121
+ it('should successfully handle a rejected personal_sign request', async () => {
1122
+ provider.on('portal_signingRequested', () => {
1123
+ provider.emit('portal_signingRejected', {
1124
+ method: RequestMethod.personal_sign,
1125
+ params: ['test'],
1126
+ })
1127
+ })
1128
+
1129
+ const result = await provider.request({
1130
+ chainId: 'eip155:1',
1131
+ method: RequestMethod.personal_sign,
1132
+ params: ['test'],
1133
+ })
1134
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1135
+ method: RequestMethod.personal_sign,
1136
+ params: ['test'],
1137
+ })
1138
+ expect(result).toEqual(undefined)
1139
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
1140
+ "[PortalProvider] Request for signing method 'personal_sign' could not be completed because it was not approved by the user.",
1141
+ )
1142
+ })
1143
+ })
1144
+
1145
+ describe('sol_signAndConfirmTransaction', () => {
1146
+ it('should successfully handle an approved sol_signAndConfirmTransaction request', async () => {
1147
+ provider.on('portal_signingRequested', () => {
1148
+ provider.emit('portal_signingApproved', {
1149
+ method: RequestMethod.sol_signAndConfirmTransaction,
1150
+ params: ['test'],
1151
+ })
1152
+ })
1153
+ const result = await provider.request({
1154
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1155
+ method: RequestMethod.sol_signAndConfirmTransaction,
1156
+ params: ['test'],
1157
+ })
1158
+
1159
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1160
+ method: RequestMethod.sol_signAndConfirmTransaction,
1161
+ params: ['test'],
1162
+ })
1163
+ expect(result).toEqual(mockSignedHash)
1164
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
1165
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1166
+ method: RequestMethod.sol_signAndConfirmTransaction,
1167
+ params: ['test'],
1168
+ rpcUrl: mockRpcUrl,
1169
+ })
1170
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1171
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1172
+ method: RequestMethod.sol_signAndConfirmTransaction,
1173
+ params: ['test'],
1174
+ signature: result,
1175
+ })
1176
+ })
1177
+
1178
+ it('should successfully handle a rejected sol_signAndConfirmTransaction request', async () => {
1179
+ provider.on('portal_signingRequested', () => {
1180
+ provider.emit('portal_signingRejected', {
1181
+ method: RequestMethod.sol_signAndConfirmTransaction,
1182
+ params: ['test'],
1183
+ })
1184
+ })
1185
+
1186
+ const result = await provider.request({
1187
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1188
+ method: RequestMethod.sol_signAndConfirmTransaction,
1189
+ params: ['test'],
1190
+ })
1191
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1192
+ method: RequestMethod.sol_signAndConfirmTransaction,
1193
+ params: ['test'],
1194
+ })
1195
+ expect(result).toEqual(undefined)
1196
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
1197
+ "[PortalProvider] Request for signing method 'sol_signAndConfirmTransaction' could not be completed because it was not approved by the user.",
1198
+ )
1199
+ })
1200
+ })
1201
+
1202
+ describe('sol_signAndSendTransaction', () => {
1203
+ it('should successfully handle an approved sol_signAndSendTransaction request', async () => {
1204
+ provider.on('portal_signingRequested', () => {
1205
+ provider.emit('portal_signingApproved', {
1206
+ method: RequestMethod.sol_signAndSendTransaction,
1207
+ params: ['test'],
1208
+ })
1209
+ })
1210
+ const result = await provider.request({
1211
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1212
+ method: RequestMethod.sol_signAndSendTransaction,
1213
+ params: ['test'],
1214
+ })
1215
+
1216
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1217
+ method: RequestMethod.sol_signAndSendTransaction,
1218
+ params: ['test'],
1219
+ })
1220
+ expect(result).toEqual(mockSignedHash)
1221
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
1222
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1223
+ method: RequestMethod.sol_signAndSendTransaction,
1224
+ params: ['test'],
1225
+ rpcUrl: mockRpcUrl,
1226
+ })
1227
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1228
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1229
+ method: RequestMethod.sol_signAndSendTransaction,
1230
+ params: ['test'],
1231
+ signature: result,
1232
+ })
1233
+ })
1234
+
1235
+ it('should successfully handle a rejected sol_signAndSendTransaction request', async () => {
1236
+ provider.on('portal_signingRequested', () => {
1237
+ provider.emit('portal_signingRejected', {
1238
+ method: RequestMethod.sol_signAndSendTransaction,
1239
+ params: ['test'],
1240
+ })
1241
+ })
1242
+
1243
+ const result = await provider.request({
1244
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1245
+ method: RequestMethod.sol_signAndSendTransaction,
1246
+ params: ['test'],
1247
+ })
1248
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1249
+ method: RequestMethod.sol_signAndSendTransaction,
1250
+ params: ['test'],
1251
+ })
1252
+ expect(result).toEqual(undefined)
1253
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
1254
+ "[PortalProvider] Request for signing method 'sol_signAndSendTransaction' could not be completed because it was not approved by the user.",
1255
+ )
1256
+ })
1257
+ })
1258
+
1259
+ describe('sol_signMessage', () => {
1260
+ it('should successfully handle an approved sol_signMessage request', async () => {
1261
+ provider.on('portal_signingRequested', () => {
1262
+ provider.emit('portal_signingApproved', {
1263
+ method: RequestMethod.sol_signMessage,
1264
+ params: ['test'],
1265
+ })
1266
+ })
1267
+ const result = await provider.request({
1268
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1269
+ method: RequestMethod.sol_signMessage,
1270
+ params: ['test'],
1271
+ })
1272
+
1273
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1274
+ method: RequestMethod.sol_signMessage,
1275
+ params: ['test'],
1276
+ })
1277
+ expect(result).toEqual(mockSignedHash)
1278
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
1279
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1280
+ method: RequestMethod.sol_signMessage,
1281
+ params: ['test'],
1282
+ rpcUrl: mockRpcUrl,
1283
+ })
1284
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1285
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1286
+ method: RequestMethod.sol_signMessage,
1287
+ params: ['test'],
1288
+ signature: result,
1289
+ })
1290
+ })
1291
+
1292
+ it('should successfully handle a rejected sol_signMessage request', async () => {
1293
+ provider.on('portal_signingRequested', () => {
1294
+ provider.emit('portal_signingRejected', {
1295
+ method: RequestMethod.sol_signMessage,
1296
+ params: ['test'],
1297
+ })
1298
+ })
1299
+
1300
+ const result = await provider.request({
1301
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1302
+ method: RequestMethod.sol_signMessage,
1303
+ params: ['test'],
1304
+ })
1305
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1306
+ method: RequestMethod.sol_signMessage,
1307
+ params: ['test'],
1308
+ })
1309
+ expect(result).toEqual(undefined)
1310
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
1311
+ "[PortalProvider] Request for signing method 'sol_signMessage' could not be completed because it was not approved by the user.",
1312
+ )
1313
+ })
1314
+ })
1315
+
1316
+ describe('sol_signTransaction', () => {
1317
+ it('should successfully handle an approved sol_signTransaction request', async () => {
1318
+ provider.on('portal_signingRequested', () => {
1319
+ provider.emit('portal_signingApproved', {
1320
+ method: RequestMethod.sol_signTransaction,
1321
+ params: ['test'],
1322
+ })
1323
+ })
1324
+ const result = await provider.request({
1325
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1326
+ method: RequestMethod.sol_signTransaction,
1327
+ params: ['test'],
1328
+ })
1329
+
1330
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1331
+ method: RequestMethod.sol_signTransaction,
1332
+ params: ['test'],
1333
+ })
1334
+ expect(result).toEqual(mockSignedHash)
1335
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
1336
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1337
+ method: RequestMethod.sol_signTransaction,
1338
+ params: ['test'],
1339
+ rpcUrl: mockRpcUrl,
1340
+ })
1341
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1342
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1343
+ method: RequestMethod.sol_signTransaction,
1344
+ params: ['test'],
1345
+ signature: result,
1346
+ })
1347
+ })
1348
+
1349
+ it('should successfully handle a rejected sol_signTransaction request', async () => {
1350
+ provider.on('portal_signingRequested', () => {
1351
+ provider.emit('portal_signingRejected', {
1352
+ method: RequestMethod.sol_signTransaction,
1353
+ params: ['test'],
1354
+ })
1355
+ })
1356
+
1357
+ const result = await provider.request({
1358
+ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
1359
+ method: RequestMethod.sol_signTransaction,
1360
+ params: ['test'],
1361
+ })
1362
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1363
+ method: RequestMethod.sol_signTransaction,
1364
+ params: ['test'],
1365
+ })
1366
+ expect(result).toEqual(undefined)
1367
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
1368
+ "[PortalProvider] Request for signing method 'sol_signTransaction' could not be completed because it was not approved by the user.",
1369
+ )
1370
+ })
1371
+ })
1372
+ })
1373
+
1374
+ describe('Non-signer methods', () => {
1375
+ beforeAll(() => {
1376
+ portal.autoApprove = true
1377
+ provider.emit = jest.fn()
1378
+ })
1379
+
1380
+ it('should successfully handle non-signer method requests', async () => {
1381
+ const result = await provider.request({
1382
+ chainId: 'eip155:1',
1383
+ method: RequestMethod.eth_sendRawTransaction,
1384
+ params: ['test'],
1385
+ })
1386
+
1387
+ expect(result).toEqual('test')
1388
+ expect(global.fetch).toHaveBeenCalledWith(mockRpcUrl, {
1389
+ method: 'POST',
1390
+ headers: {
1391
+ 'Content-Type': 'application/json',
1392
+ },
1393
+ body: JSON.stringify({
1394
+ jsonrpc: '2.0',
1395
+ id: '0',
1396
+ method: RequestMethod.eth_sendRawTransaction,
1397
+ params: ['test'],
1398
+ }),
1399
+ })
1400
+ expect(provider.emit).toHaveBeenCalledWith('portal_signatureReceived', {
1401
+ chainId: 'eip155:1',
1402
+ method: RequestMethod.eth_sendRawTransaction,
1403
+ params: ['test'],
1404
+ signature: { result: 'test', error: null },
1405
+ })
1406
+ })
1407
+
1408
+ it('should throw an error if gateway request results in an error', async () => {
1409
+ const errRes = {
1410
+ result: null,
1411
+ error: { code: 4901, data: { key: 'value' } },
1412
+ }
1413
+ global.fetch = jest.fn().mockResolvedValue({
1414
+ json: jest.fn().mockResolvedValue(errRes),
1415
+ })
1416
+
1417
+ await expect(
1418
+ provider.request({
1419
+ chainId: 'eip155:1',
1420
+ method: RequestMethod.eth_sendRawTransaction,
1421
+ params: ['test'],
1422
+ }),
1423
+ ).rejects.toThrow(new ProviderRpcError(errRes.error))
1424
+
1425
+ expect(provider.emit).toHaveBeenCalledWith('portal_signatureReceived', {
1426
+ chainId: 'eip155:1',
1427
+ method: RequestMethod.eth_sendRawTransaction,
1428
+ params: ['test'],
1429
+ signature: errRes,
1430
+ })
1431
+ })
1432
+
1433
+ it('should throw an error for an unsupported method', async () => {
1434
+ await expect(
1435
+ provider.request({
1436
+ chainId: 'eip155:1',
1437
+ method: 'wallet_switchEthereumChain' as RequestMethod,
1438
+ params: ['test'],
1439
+ }),
1440
+ ).rejects.toThrow(
1441
+ new ProviderRpcError({
1442
+ code: RpcErrorCodes.UnsupportedMethod,
1443
+ data: {
1444
+ method: 'wallet_switchEthereumChain' as RequestMethod,
1445
+ params: ['test'],
1446
+ },
1447
+ }),
1448
+ )
1449
+
1450
+ expect(provider.emit).toHaveBeenCalledTimes(0)
1451
+ })
1452
+ })
1453
+ })
1454
+
1455
+ describe('emit', () => {
1456
+ const provider = new Provider({ portal })
1457
+
1458
+ const mockHandler1 = jest.fn()
1459
+ const mockHandler2 = jest.fn()
1460
+
1461
+ beforeEach(() => {
1462
+ provider.on('test', mockHandler1)
1463
+ provider.on('test', mockHandler2)
1464
+ })
1465
+
1466
+ it('should successfully execute all handlers of an event', () => {
1467
+ provider.emit('test', { data: 'test' })
1468
+
1469
+ expect(mockHandler1).toHaveBeenCalledWith({ data: 'test' })
1470
+ expect(mockHandler1).toHaveBeenCalledTimes(1)
1471
+ expect(mockHandler2).toHaveBeenCalledWith({ data: 'test' })
1472
+ expect(mockHandler2).toHaveBeenCalledTimes(1)
1473
+ })
1474
+
1475
+ it('should not execute handlers for a different event', () => {
1476
+ provider.emit('test1', { data: 'test' })
1477
+
1478
+ expect(mockHandler1).toHaveBeenCalledTimes(0)
1479
+ expect(mockHandler2).toHaveBeenCalledTimes(0)
1480
+ })
1481
+ })
1482
+
1483
+ describe('on', () => {
1484
+ let provider = new Provider({ portal })
1485
+
1486
+ const mockHandler1 = jest.fn()
1487
+ const mockHandler2 = jest.fn()
1488
+
1489
+ beforeEach(() => {
1490
+ provider = new Provider({ portal })
1491
+ })
1492
+
1493
+ it('should successfully register handlers for an event', () => {
1494
+ const event = 'test'
1495
+ provider.on(event, mockHandler1)
1496
+ provider.on(event, mockHandler2)
1497
+
1498
+ const eventHandlers = provider.events[event]
1499
+ expect(eventHandlers.length).toEqual(2)
1500
+ expect(eventHandlers).toEqual([
1501
+ { handler: mockHandler1, once: false },
1502
+ { handler: mockHandler2, once: false },
1503
+ ])
1504
+ })
1505
+
1506
+ it('should not register undefined handlers', () => {
1507
+ provider.on('test', undefined as any)
1508
+
1509
+ const eventHandlers = provider.events['test']
1510
+ expect(eventHandlers.length).toEqual(0)
1511
+ })
1512
+ })
1513
+
1514
+ describe('removeEventListener', () => {
1515
+ let provider = new Provider({ portal })
1516
+
1517
+ const mockHandler1 = jest.fn()
1518
+ const mockHandler2 = jest.fn()
1519
+
1520
+ beforeEach(() => {
1521
+ provider = new Provider({ portal })
1522
+ })
1523
+
1524
+ it('should successfully remove specified event listener of an event', () => {
1525
+ const event = 'test'
1526
+ provider.on(event, mockHandler1)
1527
+ provider.on(event, mockHandler2)
1528
+
1529
+ provider.removeEventListener(event, mockHandler1)
1530
+
1531
+ const eventHandlers = provider.events[event]
1532
+ expect(eventHandlers.length).toEqual(1)
1533
+ expect(eventHandlers).toEqual([{ handler: mockHandler2, once: false }])
1534
+ })
1535
+
1536
+ it('should successfully remove all event listeners of an event if no specific handler is specified to be removed', () => {
1537
+ const event = 'test'
1538
+ provider.on(event, mockHandler1)
1539
+ provider.on(event, mockHandler2)
1540
+
1541
+ provider.removeEventListener(event)
1542
+
1543
+ const eventHandlers = provider.events[event]
1544
+ expect(eventHandlers.length).toEqual(0)
1545
+ expect(eventHandlers).toEqual([])
1546
+ })
1547
+
1548
+ it('should skip handler removal for events with no handlers', () => {
1549
+ provider.removeEventListener('non-existent', mockHandler1)
1550
+
1551
+ const eventHandlers = provider.events['non-existent']
1552
+ expect(eventHandlers).toEqual(undefined)
1553
+ })
1554
+
1555
+ it('should skip handler removal for a handler that does not exist for an event', () => {
1556
+ const event = 'test'
1557
+ provider.on(event, mockHandler1)
1558
+ provider.on(event, mockHandler2)
1559
+
1560
+ provider.removeEventListener(event, jest.fn())
1561
+
1562
+ const eventHandlers = provider.events[event]
1563
+ expect(eventHandlers.length).toEqual(2)
1564
+ expect(eventHandlers).toEqual([
1565
+ { handler: mockHandler1, once: false },
1566
+ { handler: mockHandler2, once: false },
1567
+ ])
1568
+ })
1569
+ })
1570
+ })