@portal-hq/web 3.12.0 → 3.13.0-alpha.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,151 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
+ return new (P || (P = Promise))(function (resolve, reject) {
7
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
11
+ });
12
+ };
13
+ import Mpc from '../../../mpc';
14
+ import Noah from '.';
15
+ import portalMock from '../../../__mocks/portal/portal';
16
+ import { mockHost } from '../../../__mocks/constants';
17
+ const TRUSTED_KYC_HOST = 'kyc.example';
18
+ const openTrustedUrl = (url) => {
19
+ let parsed;
20
+ try {
21
+ parsed = new URL(url);
22
+ }
23
+ catch (_a) {
24
+ throw new Error('Invalid hostedUrl');
25
+ }
26
+ if (parsed.protocol !== 'https:' || parsed.hostname !== TRUSTED_KYC_HOST) {
27
+ throw new Error('Untrusted hostedUrl origin');
28
+ }
29
+ return window.open(parsed.toString(), '_blank', 'noopener,noreferrer');
30
+ };
31
+ describe('Noah', () => {
32
+ let mpc;
33
+ let noah;
34
+ beforeEach(() => {
35
+ jest.clearAllMocks();
36
+ portalMock.host = mockHost;
37
+ mpc = new Mpc({
38
+ portal: portalMock,
39
+ });
40
+ noah = new Noah({ mpc });
41
+ });
42
+ it('should call mpc.initiateKyc with the request', () => __awaiter(void 0, void 0, void 0, function* () {
43
+ const spy = jest.spyOn(mpc, 'initiateKyc').mockResolvedValue({
44
+ data: {
45
+ hostedUrl: 'https://kyc.example',
46
+ },
47
+ });
48
+ const openSpy = jest.spyOn(window, 'open').mockImplementation(() => null);
49
+ const body = { returnUrl: 'https://app.example/cb' };
50
+ const result = yield noah.initiateKyc(body);
51
+ expect(spy).toHaveBeenCalledWith(body);
52
+ expect(result.data.hostedUrl).toBe('https://kyc.example');
53
+ openTrustedUrl(result.data.hostedUrl);
54
+ expect(openSpy).toHaveBeenCalledWith('https://kyc.example/', '_blank', 'noopener,noreferrer');
55
+ openSpy.mockRestore();
56
+ }));
57
+ it('should call mpc.initiatePayin', () => __awaiter(void 0, void 0, void 0, function* () {
58
+ const req = {
59
+ fiatCurrency: 'USD',
60
+ cryptoCurrency: 'USDC_TEST',
61
+ network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
62
+ destinationAddress: 'SoLAddr1111111111111111111111111111111111111',
63
+ };
64
+ const spy = jest.spyOn(mpc, 'initiatePayin').mockResolvedValue({
65
+ data: { payinId: 'p1', bankDetails: {} },
66
+ });
67
+ yield noah.initiatePayin(req);
68
+ expect(spy).toHaveBeenCalledWith(req);
69
+ }));
70
+ it('should call mpc.simulatePayin with sandbox body', () => __awaiter(void 0, void 0, void 0, function* () {
71
+ const sim = {
72
+ paymentMethodId: 'pm-1',
73
+ fiatAmount: '10',
74
+ fiatCurrency: 'USD',
75
+ };
76
+ const spy = jest.spyOn(mpc, 'simulatePayin').mockResolvedValue({
77
+ data: {},
78
+ });
79
+ yield noah.simulatePayin(sim);
80
+ expect(spy).toHaveBeenCalledWith(sim);
81
+ }));
82
+ it('should call mpc.getPayoutCountries', () => __awaiter(void 0, void 0, void 0, function* () {
83
+ const spy = jest.spyOn(mpc, 'getPayoutCountries').mockResolvedValue({
84
+ data: { countries: [] },
85
+ });
86
+ yield noah.getPayoutCountries();
87
+ expect(spy).toHaveBeenCalled();
88
+ }));
89
+ it('should call mpc.getPayoutChannels', () => __awaiter(void 0, void 0, void 0, function* () {
90
+ const req = {
91
+ country: 'US',
92
+ cryptoCurrency: 'USDC_TEST',
93
+ fiatCurrency: 'USD',
94
+ fiatAmount: '10',
95
+ };
96
+ const spy = jest.spyOn(mpc, 'getPayoutChannels').mockResolvedValue({
97
+ data: [],
98
+ });
99
+ yield noah.getPayoutChannels(req);
100
+ expect(spy).toHaveBeenCalledWith(req);
101
+ }));
102
+ it('should call mpc.getPayoutChannelForm', () => __awaiter(void 0, void 0, void 0, function* () {
103
+ const spy = jest
104
+ .spyOn(mpc, 'getPayoutChannelForm')
105
+ .mockResolvedValue({ data: {} });
106
+ yield noah.getPayoutChannelForm('ch-1');
107
+ expect(spy).toHaveBeenCalledWith('ch-1');
108
+ }));
109
+ it('should call mpc.getPayoutQuote', () => __awaiter(void 0, void 0, void 0, function* () {
110
+ const req = {
111
+ channelId: 'ch-1',
112
+ cryptoCurrency: 'USDC_TEST',
113
+ fiatAmount: '10',
114
+ };
115
+ const spy = jest.spyOn(mpc, 'getPayoutQuote').mockResolvedValue({
116
+ data: {
117
+ payoutId: 'out-1',
118
+ formSessionId: 'fs-1',
119
+ cryptoAmountEstimate: '10',
120
+ totalFee: '0.01',
121
+ },
122
+ });
123
+ yield noah.getPayoutQuote(req);
124
+ expect(spy).toHaveBeenCalledWith(req);
125
+ }));
126
+ it('should call mpc.initiatePayout', () => __awaiter(void 0, void 0, void 0, function* () {
127
+ const expiry = new Date(Date.now() + 24 * 60 * 60 * 1000)
128
+ .toISOString()
129
+ .replace(/\.\d{3}Z$/, 'Z');
130
+ const nonce = `nonce-${Date.now()}-${Math.floor(Math.random() * 1e9)}`;
131
+ const req = {
132
+ payoutId: 'p1',
133
+ sourceAddress: 'SoLAddr1111111111111111111111111111111111111',
134
+ expiry,
135
+ nonce,
136
+ network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
137
+ };
138
+ const spy = jest.spyOn(mpc, 'initiatePayout').mockResolvedValue({
139
+ data: { destinationAddress: 'dest', conditions: [] },
140
+ });
141
+ yield noah.initiatePayout(req);
142
+ expect(spy).toHaveBeenCalledWith(req);
143
+ }));
144
+ it('should call mpc.getPaymentMethods', () => __awaiter(void 0, void 0, void 0, function* () {
145
+ const spy = jest.spyOn(mpc, 'getPaymentMethods').mockResolvedValue({
146
+ data: { paymentMethods: [] },
147
+ });
148
+ yield noah.getPaymentMethods();
149
+ expect(spy).toHaveBeenCalled();
150
+ }));
151
+ });
@@ -11,7 +11,7 @@ import { PortalMpcError } from './errors';
11
11
  import { sdkLogger } from '../logger';
12
12
  import { BackupMethods, } from '../index';
13
13
  import { generateTraceId } from '../shared/trace';
14
- const WEB_SDK_VERSION = '3.12.0';
14
+ const WEB_SDK_VERSION = '3.13.0-alpha.0';
15
15
  class Mpc {
16
16
  get ready() {
17
17
  return this._ready;
@@ -515,6 +515,100 @@ class Mpc {
515
515
  });
516
516
  });
517
517
  }
518
+ /**
519
+ * Noah ramp: postMessage bridge to the Portal iframe (connect-api).
520
+ * Application code should call `portal.ramps.noah` instead of Mpc directly.
521
+ */
522
+ initiateKyc(data) {
523
+ return __awaiter(this, void 0, void 0, function* () {
524
+ return this.handleRequestToIframeAndPost({
525
+ methodMessage: 'portal:noah:initiateKyc',
526
+ errorMessage: 'portal:noah:initiateKycError',
527
+ resultMessage: 'portal:noah:initiateKycResult',
528
+ data,
529
+ });
530
+ });
531
+ }
532
+ initiatePayin(data) {
533
+ return __awaiter(this, void 0, void 0, function* () {
534
+ return this.handleRequestToIframeAndPost({
535
+ methodMessage: 'portal:noah:initiatePayin',
536
+ errorMessage: 'portal:noah:initiatePayinError',
537
+ resultMessage: 'portal:noah:initiatePayinResult',
538
+ data,
539
+ });
540
+ });
541
+ }
542
+ simulatePayin(data) {
543
+ return __awaiter(this, void 0, void 0, function* () {
544
+ return this.handleRequestToIframeAndPost({
545
+ methodMessage: 'portal:noah:simulatePayin',
546
+ errorMessage: 'portal:noah:simulatePayinError',
547
+ resultMessage: 'portal:noah:simulatePayinResult',
548
+ data,
549
+ });
550
+ });
551
+ }
552
+ getPayoutCountries() {
553
+ return __awaiter(this, void 0, void 0, function* () {
554
+ return this.handleRequestToIframeAndPost({
555
+ methodMessage: 'portal:noah:getPayoutCountries',
556
+ errorMessage: 'portal:noah:getPayoutCountriesError',
557
+ resultMessage: 'portal:noah:getPayoutCountriesResult',
558
+ data: {},
559
+ });
560
+ });
561
+ }
562
+ getPayoutChannels(data) {
563
+ return __awaiter(this, void 0, void 0, function* () {
564
+ return this.handleRequestToIframeAndPost({
565
+ methodMessage: 'portal:noah:getPayoutChannels',
566
+ errorMessage: 'portal:noah:getPayoutChannelsError',
567
+ resultMessage: 'portal:noah:getPayoutChannelsResult',
568
+ data,
569
+ });
570
+ });
571
+ }
572
+ getPayoutChannelForm(channelId) {
573
+ return __awaiter(this, void 0, void 0, function* () {
574
+ return this.handleRequestToIframeAndPost({
575
+ methodMessage: 'portal:noah:getPayoutChannelForm',
576
+ errorMessage: 'portal:noah:getPayoutChannelFormError',
577
+ resultMessage: 'portal:noah:getPayoutChannelFormResult',
578
+ data: channelId,
579
+ });
580
+ });
581
+ }
582
+ getPayoutQuote(data) {
583
+ return __awaiter(this, void 0, void 0, function* () {
584
+ return this.handleRequestToIframeAndPost({
585
+ methodMessage: 'portal:noah:getPayoutQuote',
586
+ errorMessage: 'portal:noah:getPayoutQuoteError',
587
+ resultMessage: 'portal:noah:getPayoutQuoteResult',
588
+ data,
589
+ });
590
+ });
591
+ }
592
+ initiatePayout(data) {
593
+ return __awaiter(this, void 0, void 0, function* () {
594
+ return this.handleRequestToIframeAndPost({
595
+ methodMessage: 'portal:noah:initiatePayout',
596
+ errorMessage: 'portal:noah:initiatePayoutError',
597
+ resultMessage: 'portal:noah:initiatePayoutResult',
598
+ data,
599
+ });
600
+ });
601
+ }
602
+ getPaymentMethods() {
603
+ return __awaiter(this, void 0, void 0, function* () {
604
+ return this.handleRequestToIframeAndPost({
605
+ methodMessage: 'portal:noah:getPaymentMethods',
606
+ errorMessage: 'portal:noah:getPaymentMethodsError',
607
+ resultMessage: 'portal:noah:getPaymentMethodsResult',
608
+ data: {},
609
+ });
610
+ });
611
+ }
518
612
  getSwapsQuoteV2(data, options) {
519
613
  return __awaiter(this, void 0, void 0, function* () {
520
614
  return this.handleRequestToIframeAndPost({
@@ -13,5 +13,6 @@ export * from './api';
13
13
  export * from './yieldxyz';
14
14
  export * from './zero-x';
15
15
  export * from './lifi';
16
+ export * from './noah';
16
17
  export * from './hypernative';
17
18
  export * from './blockaid';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ export type {
2
+ NoahAmountConditionRow,
3
+ NoahGetPaymentMethodsResponse,
4
+ NoahGetPaymentMethodsResponseData,
5
+ NoahGetPayoutChannelFormResponse,
6
+ NoahGetPayoutChannelsRequest,
7
+ NoahGetPayoutChannelsResponse,
8
+ NoahGetPayoutCountriesResponse,
9
+ NoahGetPayoutQuoteRequest,
10
+ NoahGetPayoutQuoteResponse,
11
+ NoahGetPayoutQuoteResponseData,
12
+ NoahInitiateKycRequest,
13
+ NoahInitiateKycResponse,
14
+ NoahInitiateKycResponseData,
15
+ NoahInitiatePayinRequest,
16
+ NoahInitiatePayinResponse,
17
+ NoahInitiatePayinResponseData,
18
+ NoahInitiatePayoutRequest,
19
+ NoahInitiatePayoutResponse,
20
+ NoahInitiatePayoutResponseData,
21
+ NoahPayoutConditionBlock,
22
+ NoahSingleOnchainDepositSourceTriggerInput,
23
+ NoahSimulatePayinRequest,
24
+ NoahSimulatePayinResponse,
25
+ NoahSimulatePayinResponseData,
26
+ NoahSolanaCaipId,
27
+ PortalApiSuccessEnvelope,
28
+ } from './src/shared/types/noah'
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Portal MPC Support for Web",
4
4
  "author": "Portal Labs, Inc.",
5
5
  "homepage": "https://portalhq.io/",
6
- "version": "3.12.0",
6
+ "version": "3.13.0-alpha.0",
7
7
  "license": "MIT",
8
8
  "main": "lib/commonjs/index",
9
9
  "module": "lib/esm/index",
@@ -15,6 +15,7 @@
15
15
  "tsconfig.json",
16
16
  "hypernative.d.ts",
17
17
  "lifi-types.d.ts",
18
+ "noah-types.d.ts",
18
19
  "yieldxyz-types.d.ts",
19
20
  "zero-x.d.ts",
20
21
  "types.d.ts",
@@ -57,5 +58,6 @@
57
58
  },
58
59
  "dependencies": {
59
60
  "@solana/web3.js": "^1.91.8"
60
- }
61
+ },
62
+ "stableVersion": "3.12.0"
61
63
  }
package/src/index.test.ts CHANGED
@@ -837,7 +837,7 @@ describe('Portal', () => {
837
837
  }),
838
838
  ).rejects.toThrow(
839
839
  new Error(
840
- '[Portal] Invalid "to" Solana address provided, must be 44 characters',
840
+ '[Portal] Invalid "to" Solana address provided (not a valid public key)',
841
841
  ),
842
842
  )
843
843
  })
package/src/index.ts CHANGED
@@ -51,6 +51,7 @@ import Mpc from './mpc'
51
51
  import Provider, { RequestMethod } from './provider'
52
52
  import Yield from './integrations/yield'
53
53
  import Trading from './integrations/trading'
54
+ import Ramps from './integrations/ramps'
54
55
  import Security from './integrations/security'
55
56
  import Delegations from './integrations/delegations'
56
57
  import PasskeyService from './passkeys'
@@ -70,6 +71,7 @@ class Portal {
70
71
  public host: string
71
72
  public mpc: Mpc
72
73
  public yield: Yield
74
+ public ramps: Ramps
73
75
  public trading: Trading
74
76
  public security: Security
75
77
  public evmAccountType: EvmAccountType
@@ -136,6 +138,8 @@ class Portal {
136
138
 
137
139
  this.yield = new Yield({ mpc: this.mpc })
138
140
 
141
+ this.ramps = new Ramps({ mpc: this.mpc })
142
+
139
143
  this.trading = new Trading({ mpc: this.mpc })
140
144
 
141
145
  this.security = new Security({ mpc: this.mpc })
@@ -895,6 +899,10 @@ class Portal {
895
899
  return response
896
900
  }
897
901
 
902
+ /**
903
+ * Build, sign, and broadcast a native SOL transfer on the given Solana CAIP-2 `chainId`.
904
+ * `to` must be a valid Solana public key (base58); `lamports` must be a positive integer.
905
+ */
898
906
  public async sendSol({
899
907
  chainId,
900
908
  to,
@@ -911,10 +919,16 @@ class Portal {
911
919
  )
912
920
  }
913
921
 
914
- // Ensure the to address is a valid Solana address.
915
- if (!to || typeof to !== 'string' || to.length !== 44) {
922
+ if (!to || typeof to !== 'string') {
923
+ throw new Error('[Portal] Invalid "to" Solana address provided')
924
+ }
925
+ const toTrimmed = to.trim()
926
+ let toPublicKey: PublicKey
927
+ try {
928
+ toPublicKey = new PublicKey(toTrimmed)
929
+ } catch {
916
930
  throw new Error(
917
- '[Portal] Invalid "to" Solana address provided, must be 44 characters',
931
+ '[Portal] Invalid "to" Solana address provided (not a valid public key)',
918
932
  )
919
933
  }
920
934
 
@@ -956,9 +970,6 @@ class Portal {
956
970
  // The sender's public key.
957
971
  const fromPublicKey = new PublicKey(solanaAddress)
958
972
 
959
- // The recipient's public key.
960
- const toPublicKey = new PublicKey(to)
961
-
962
973
  // Create a new transaction.
963
974
  const transaction = new SolanaTransaction().add(
964
975
  SystemProgram.transfer({
@@ -1332,6 +1343,35 @@ export {
1332
1343
  type GetAddressesResponse,
1333
1344
  } from './shared/types'
1334
1345
 
1346
+ export type {
1347
+ NoahAmountConditionRow,
1348
+ NoahGetPaymentMethodsResponse,
1349
+ NoahGetPaymentMethodsResponseData,
1350
+ NoahGetPayoutChannelFormResponse,
1351
+ NoahGetPayoutChannelsRequest,
1352
+ NoahGetPayoutChannelsResponse,
1353
+ NoahGetPayoutCountriesResponse,
1354
+ NoahGetPayoutQuoteRequest,
1355
+ NoahGetPayoutQuoteResponse,
1356
+ NoahGetPayoutQuoteResponseData,
1357
+ NoahInitiateKycRequest,
1358
+ NoahInitiateKycResponse,
1359
+ NoahInitiateKycResponseData,
1360
+ NoahInitiatePayinRequest,
1361
+ NoahInitiatePayinResponse,
1362
+ NoahInitiatePayinResponseData,
1363
+ NoahInitiatePayoutRequest,
1364
+ NoahInitiatePayoutResponse,
1365
+ NoahInitiatePayoutResponseData,
1366
+ NoahPayoutConditionBlock,
1367
+ NoahSingleOnchainDepositSourceTriggerInput,
1368
+ NoahSimulatePayinRequest,
1369
+ NoahSimulatePayinResponse,
1370
+ NoahSimulatePayinResponseData,
1371
+ NoahSolanaCaipId,
1372
+ PortalApiSuccessEnvelope,
1373
+ } from './shared/types'
1374
+
1335
1375
  export { MpcError, MpcErrorCodes } from './mpc'
1336
1376
 
1337
1377
  export { PortalMpcError } from './mpc/errors'
@@ -0,0 +1,10 @@
1
+ import Mpc from '../../mpc'
2
+ import Noah from './noah'
3
+
4
+ export default class Ramps {
5
+ public noah: Noah
6
+
7
+ constructor({ mpc }: { mpc: Mpc }) {
8
+ this.noah = new Noah({ mpc })
9
+ }
10
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import Mpc from '../../../mpc'
6
+ import Noah from '.'
7
+ import portalMock from '../../../__mocks/portal/portal'
8
+ import { mockHost } from '../../../__mocks/constants'
9
+
10
+ const TRUSTED_KYC_HOST = 'kyc.example'
11
+
12
+ const openTrustedUrl = (url: string) => {
13
+ let parsed: URL
14
+ try {
15
+ parsed = new URL(url)
16
+ } catch {
17
+ throw new Error('Invalid hostedUrl')
18
+ }
19
+ if (parsed.protocol !== 'https:' || parsed.hostname !== TRUSTED_KYC_HOST) {
20
+ throw new Error('Untrusted hostedUrl origin')
21
+ }
22
+ return window.open(parsed.toString(), '_blank', 'noopener,noreferrer')
23
+ }
24
+
25
+ describe('Noah', () => {
26
+ let mpc: Mpc
27
+ let noah: Noah
28
+
29
+ beforeEach(() => {
30
+ jest.clearAllMocks()
31
+ portalMock.host = mockHost
32
+ mpc = new Mpc({
33
+ portal: portalMock,
34
+ })
35
+ noah = new Noah({ mpc })
36
+ })
37
+
38
+ it('should call mpc.initiateKyc with the request', async () => {
39
+ const spy = jest.spyOn(mpc, 'initiateKyc').mockResolvedValue({
40
+ data: {
41
+ hostedUrl: 'https://kyc.example',
42
+ },
43
+ })
44
+ const openSpy = jest.spyOn(window, 'open').mockImplementation(() => null)
45
+ const body = { returnUrl: 'https://app.example/cb' }
46
+ const result = await noah.initiateKyc(body)
47
+ expect(spy).toHaveBeenCalledWith(body)
48
+ expect(result.data.hostedUrl).toBe('https://kyc.example')
49
+ openTrustedUrl(result.data.hostedUrl)
50
+ expect(openSpy).toHaveBeenCalledWith(
51
+ 'https://kyc.example/',
52
+ '_blank',
53
+ 'noopener,noreferrer',
54
+ )
55
+ openSpy.mockRestore()
56
+ })
57
+
58
+ it('should call mpc.initiatePayin', async () => {
59
+ const req = {
60
+ fiatCurrency: 'USD',
61
+ cryptoCurrency: 'USDC_TEST',
62
+ network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
63
+ destinationAddress: 'SoLAddr1111111111111111111111111111111111111',
64
+ }
65
+ const spy = jest.spyOn(mpc, 'initiatePayin').mockResolvedValue({
66
+ data: { payinId: 'p1', bankDetails: {} },
67
+ })
68
+ await noah.initiatePayin(req)
69
+ expect(spy).toHaveBeenCalledWith(req)
70
+ })
71
+
72
+ it('should call mpc.simulatePayin with sandbox body', async () => {
73
+ const sim = {
74
+ paymentMethodId: 'pm-1',
75
+ fiatAmount: '10',
76
+ fiatCurrency: 'USD',
77
+ }
78
+ const spy = jest.spyOn(mpc, 'simulatePayin').mockResolvedValue({
79
+ data: {},
80
+ })
81
+ await noah.simulatePayin(sim)
82
+ expect(spy).toHaveBeenCalledWith(sim)
83
+ })
84
+
85
+ it('should call mpc.getPayoutCountries', async () => {
86
+ const spy = jest.spyOn(mpc, 'getPayoutCountries').mockResolvedValue({
87
+ data: { countries: [] },
88
+ })
89
+ await noah.getPayoutCountries()
90
+ expect(spy).toHaveBeenCalled()
91
+ })
92
+
93
+ it('should call mpc.getPayoutChannels', async () => {
94
+ const req = {
95
+ country: 'US',
96
+ cryptoCurrency: 'USDC_TEST',
97
+ fiatCurrency: 'USD',
98
+ fiatAmount: '10',
99
+ }
100
+ const spy = jest.spyOn(mpc, 'getPayoutChannels').mockResolvedValue({
101
+ data: [],
102
+ } as never)
103
+ await noah.getPayoutChannels(req)
104
+ expect(spy).toHaveBeenCalledWith(req)
105
+ })
106
+
107
+ it('should call mpc.getPayoutChannelForm', async () => {
108
+ const spy = jest
109
+ .spyOn(mpc, 'getPayoutChannelForm')
110
+ .mockResolvedValue({ data: {} } as never)
111
+ await noah.getPayoutChannelForm('ch-1')
112
+ expect(spy).toHaveBeenCalledWith('ch-1')
113
+ })
114
+
115
+ it('should call mpc.getPayoutQuote', async () => {
116
+ const req = {
117
+ channelId: 'ch-1',
118
+ cryptoCurrency: 'USDC_TEST',
119
+ fiatAmount: '10',
120
+ }
121
+ const spy = jest.spyOn(mpc, 'getPayoutQuote').mockResolvedValue({
122
+ data: {
123
+ payoutId: 'out-1',
124
+ formSessionId: 'fs-1',
125
+ cryptoAmountEstimate: '10',
126
+ totalFee: '0.01',
127
+ },
128
+ })
129
+ await noah.getPayoutQuote(req)
130
+ expect(spy).toHaveBeenCalledWith(req)
131
+ })
132
+
133
+ it('should call mpc.initiatePayout', async () => {
134
+ const expiry = new Date(Date.now() + 24 * 60 * 60 * 1000)
135
+ .toISOString()
136
+ .replace(/\.\d{3}Z$/, 'Z')
137
+ const nonce = `nonce-${Date.now()}-${Math.floor(Math.random() * 1e9)}`
138
+ const req = {
139
+ payoutId: 'p1',
140
+ sourceAddress: 'SoLAddr1111111111111111111111111111111111111',
141
+ expiry,
142
+ nonce,
143
+ network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
144
+ }
145
+ const spy = jest.spyOn(mpc, 'initiatePayout').mockResolvedValue({
146
+ data: { destinationAddress: 'dest', conditions: [] },
147
+ })
148
+ await noah.initiatePayout(req)
149
+ expect(spy).toHaveBeenCalledWith(req)
150
+ })
151
+
152
+ it('should call mpc.getPaymentMethods', async () => {
153
+ const spy = jest.spyOn(mpc, 'getPaymentMethods').mockResolvedValue({
154
+ data: { paymentMethods: [] },
155
+ })
156
+ await noah.getPaymentMethods()
157
+ expect(spy).toHaveBeenCalled()
158
+ })
159
+ })