@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.
- package/lib/commonjs/index.js +16 -5
- package/lib/commonjs/index.test.js +1 -1
- package/lib/commonjs/integrations/ramps/index.js +12 -0
- package/lib/commonjs/integrations/ramps/noah/index.js +75 -0
- package/lib/commonjs/integrations/ramps/noah/index.test.js +156 -0
- package/lib/commonjs/mpc/index.js +95 -1
- package/lib/commonjs/shared/types/index.js +1 -0
- package/lib/commonjs/shared/types/noah.js +2 -0
- package/lib/esm/index.js +16 -5
- package/lib/esm/index.test.js +1 -1
- package/lib/esm/integrations/ramps/index.js +6 -0
- package/lib/esm/integrations/ramps/noah/index.js +72 -0
- package/lib/esm/integrations/ramps/noah/index.test.js +151 -0
- package/lib/esm/mpc/index.js +95 -1
- package/lib/esm/shared/types/index.js +1 -0
- package/lib/esm/shared/types/noah.js +1 -0
- package/noah-types.d.ts +28 -0
- package/package.json +4 -2
- package/src/index.test.ts +1 -1
- package/src/index.ts +46 -6
- package/src/integrations/ramps/index.ts +10 -0
- package/src/integrations/ramps/noah/index.test.ts +159 -0
- package/src/integrations/ramps/noah/index.ts +89 -0
- package/src/mpc/index.ts +115 -1
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/noah.ts +150 -0
- package/tsconfig.json +1 -0
|
@@ -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
|
+
});
|
package/lib/esm/mpc/index.js
CHANGED
|
@@ -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.
|
|
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({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/noah-types.d.ts
ADDED
|
@@ -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.
|
|
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
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
|
-
|
|
915
|
-
|
|
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
|
|
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,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
|
+
})
|