@portal-hq/web 3.17.0 → 3.18.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 +62 -20
- package/lib/commonjs/index.test.js +51 -10
- package/lib/commonjs/integrations/ramps/index.js +2 -0
- package/lib/commonjs/integrations/ramps/meld/index.js +121 -0
- package/lib/commonjs/integrations/ramps/meld/index.test.js +162 -0
- package/lib/commonjs/integrations/ramps/noah/index.js +2 -2
- package/lib/commonjs/integrations/ramps/noah/index.test.js +11 -2
- package/lib/commonjs/mpc/index.js +199 -3
- package/lib/commonjs/mpc/index.test.js +634 -0
- package/lib/commonjs/provider/index.js +44 -2
- package/lib/commonjs/provider/index.test.js +186 -0
- package/lib/commonjs/rpc.test.js +162 -0
- package/lib/commonjs/shared/rpc/auth.js +29 -0
- package/lib/commonjs/shared/rpc/defaults.js +40 -0
- package/lib/commonjs/shared/types/index.js +1 -0
- package/lib/commonjs/shared/types/meld.js +2 -0
- package/lib/esm/index.js +59 -19
- package/lib/esm/index.test.js +51 -10
- package/lib/esm/integrations/ramps/index.js +2 -0
- package/lib/esm/integrations/ramps/meld/index.js +118 -0
- package/lib/esm/integrations/ramps/meld/index.test.js +157 -0
- package/lib/esm/integrations/ramps/noah/index.js +2 -2
- package/lib/esm/integrations/ramps/noah/index.test.js +11 -2
- package/lib/esm/mpc/index.js +199 -3
- package/lib/esm/mpc/index.test.js +635 -1
- package/lib/esm/provider/index.js +44 -2
- package/lib/esm/provider/index.test.js +186 -0
- package/lib/esm/rpc.test.js +157 -0
- package/lib/esm/shared/rpc/auth.js +25 -0
- package/lib/esm/shared/rpc/defaults.js +36 -0
- package/lib/esm/shared/types/index.js +1 -0
- package/lib/esm/shared/types/meld.js +1 -0
- package/noah-types.d.ts +17 -1
- package/package.json +3 -2
- package/src/__mocks/constants.ts +68 -0
- package/src/__mocks/portal/portal.ts +0 -1
- package/src/index.test.ts +90 -0
- package/src/index.ts +159 -7
- package/src/integrations/ramps/index.ts +3 -0
- package/src/integrations/ramps/meld/index.test.ts +189 -0
- package/src/integrations/ramps/meld/index.ts +149 -0
- package/src/integrations/ramps/noah/index.test.ts +11 -2
- package/src/integrations/ramps/noah/index.ts +5 -2
- package/src/mpc/index.test.ts +804 -7
- package/src/mpc/index.ts +247 -3
- package/src/provider/index.test.ts +233 -0
- package/src/provider/index.ts +64 -1
- package/src/rpc.test.ts +190 -0
- package/src/shared/rpc/auth.ts +27 -0
- package/src/shared/rpc/defaults.ts +41 -0
- package/src/shared/types/common.ts +24 -0
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/meld.ts +302 -0
- package/src/shared/types/noah.ts +191 -29
- package/types.d.ts +65 -3
package/lib/esm/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import PasskeyService from './passkeys';
|
|
|
19
19
|
import { EvmAccountType } from './namespaces/evmAccountType';
|
|
20
20
|
import { sdkLogger } from './logger';
|
|
21
21
|
import { generateTraceId } from './shared/trace';
|
|
22
|
+
import { buildDefaultRpcConfig, DEFAULT_RPC_HOST } from './shared/rpc/defaults';
|
|
22
23
|
import { waitForEvmOrUserOpConfirmation } from './internal/waitForEvmOrUserOpConfirmation';
|
|
23
24
|
import { waitForSolanaTxConfirmation } from './internal/waitForSolanaTxConfirmation';
|
|
24
25
|
class Portal {
|
|
@@ -32,12 +33,14 @@ class Portal {
|
|
|
32
33
|
return this._iframeRpcConfig;
|
|
33
34
|
}
|
|
34
35
|
constructor({
|
|
35
|
-
//
|
|
36
|
-
rpcConfig,
|
|
36
|
+
// Optional — defaults to Portal-managed RPC gateway when omitted or empty
|
|
37
|
+
rpcConfig, gatewayHost,
|
|
37
38
|
// Optional
|
|
38
|
-
iframeRpcConfig, apiKey, authToken, authUrl, autoApprove = false, gdrive, passkey, host =
|
|
39
|
+
iframeRpcConfig, apiKey, authToken, authUrl, autoApprove = false, gdrive, passkey, host = DEFAULT_RPC_HOST, mpcVersion = 'v6', mpcHost = 'mpc-client.portalhq.io', featureFlags = {}, chainId, logLevel = 'none', logger = console, }) {
|
|
39
40
|
this.errorCallbacks = [];
|
|
40
41
|
this.readyCallbacks = [];
|
|
42
|
+
this.walletNotOnDeviceCallbacks = [];
|
|
43
|
+
this.lastWalletNotOnDevicePayload = null;
|
|
41
44
|
this.logger = console;
|
|
42
45
|
this.sendEth = ({ chainId, to, value, }) => __awaiter(this, void 0, void 0, function* () {
|
|
43
46
|
return this.provider.request({
|
|
@@ -52,11 +55,27 @@ class Portal {
|
|
|
52
55
|
],
|
|
53
56
|
});
|
|
54
57
|
});
|
|
58
|
+
// Gateway host resolution — three-level priority:
|
|
59
|
+
// 1. gatewayHost (explicit override)
|
|
60
|
+
// 2. host (environment identity: 'web.portalhq.io' prod / 'web.portalhq.dev' staging)
|
|
61
|
+
// 3. DEFAULT_RPC_HOST (compile-time production fallback)
|
|
62
|
+
//
|
|
63
|
+
// `host` doubles as the RPC gateway domain in Portal deployments — the two are
|
|
64
|
+
// always on the same hostname per environment (confirmed in deploy configs).
|
|
65
|
+
// Deriving from `host` ensures staging callers don't silently RPC to production
|
|
66
|
+
// when only `host` is overridden. Explicit `gatewayHost` takes full precedence
|
|
67
|
+
// for custom domains or non-standard deployments.
|
|
68
|
+
const effectiveGatewayHost = gatewayHost !== null && gatewayHost !== void 0 ? gatewayHost : host;
|
|
69
|
+
// Resolve rpcConfig: undefined or {} → auto-generate from effectiveGatewayHost.
|
|
70
|
+
// An explicit non-empty map is used verbatim — no merging with defaults.
|
|
71
|
+
const resolvedRpcConfig = !rpcConfig || Object.keys(rpcConfig).length === 0
|
|
72
|
+
? buildDefaultRpcConfig(effectiveGatewayHost)
|
|
73
|
+
: rpcConfig;
|
|
55
74
|
this.apiKey = apiKey;
|
|
56
75
|
this.authToken = authToken;
|
|
57
76
|
this.authUrl = authUrl;
|
|
58
77
|
this.autoApprove = autoApprove;
|
|
59
|
-
this._rpcConfig =
|
|
78
|
+
this._rpcConfig = resolvedRpcConfig;
|
|
60
79
|
this._iframeRpcConfig = iframeRpcConfig;
|
|
61
80
|
this.host = host;
|
|
62
81
|
this.mpcHost = mpcHost;
|
|
@@ -172,6 +191,27 @@ class Portal {
|
|
|
172
191
|
this.readyCallbacks = [];
|
|
173
192
|
}
|
|
174
193
|
}
|
|
194
|
+
onWalletNotOnDevice(callback) {
|
|
195
|
+
// Replay to late subscribers: if the event already fired, deliver immediately.
|
|
196
|
+
if (this.lastWalletNotOnDevicePayload !== null) {
|
|
197
|
+
const cached = this.lastWalletNotOnDevicePayload;
|
|
198
|
+
Promise.resolve()
|
|
199
|
+
.then(() => callback(cached))
|
|
200
|
+
.catch((err) => sdkLogger.warn('[Portal] onWalletNotOnDevice callback failed:', err));
|
|
201
|
+
}
|
|
202
|
+
this.walletNotOnDeviceCallbacks.push(callback);
|
|
203
|
+
return () => {
|
|
204
|
+
this.walletNotOnDeviceCallbacks = this.walletNotOnDeviceCallbacks.filter((cb) => cb !== callback);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
triggerWalletNotOnDevice(payload) {
|
|
208
|
+
this.lastWalletNotOnDevicePayload = payload;
|
|
209
|
+
this.walletNotOnDeviceCallbacks.forEach((callback) => {
|
|
210
|
+
Promise.resolve()
|
|
211
|
+
.then(() => callback(payload))
|
|
212
|
+
.catch((err) => sdkLogger.warn('[Portal] onWalletNotOnDevice callback failed:', err));
|
|
213
|
+
});
|
|
214
|
+
}
|
|
175
215
|
/*****************************
|
|
176
216
|
* Wallet Methods
|
|
177
217
|
*****************************/
|
|
@@ -182,20 +222,20 @@ class Portal {
|
|
|
182
222
|
}
|
|
183
223
|
createWallet(progress = () => {
|
|
184
224
|
// Noop
|
|
185
|
-
}) {
|
|
225
|
+
}, traceId) {
|
|
186
226
|
return __awaiter(this, void 0, void 0, function* () {
|
|
187
227
|
const address = yield this.mpc.generate({
|
|
188
228
|
host: this.host,
|
|
189
229
|
mpcVersion: this.mpcVersion,
|
|
190
230
|
featureFlags: this.featureFlags,
|
|
191
|
-
}, progress);
|
|
231
|
+
}, progress, traceId);
|
|
192
232
|
this.address = address;
|
|
193
233
|
return address;
|
|
194
234
|
});
|
|
195
235
|
}
|
|
196
236
|
generateBackupShare(progress = () => {
|
|
197
237
|
// Noop
|
|
198
|
-
}) {
|
|
238
|
+
}, traceId) {
|
|
199
239
|
return __awaiter(this, void 0, void 0, function* () {
|
|
200
240
|
const response = yield this.mpc.backup({
|
|
201
241
|
backupMethod: BackupMethods.passkey,
|
|
@@ -205,7 +245,7 @@ class Portal {
|
|
|
205
245
|
host: this.host,
|
|
206
246
|
mpcVersion: this.mpcVersion,
|
|
207
247
|
featureFlags: this.featureFlags,
|
|
208
|
-
}, progress);
|
|
248
|
+
}, progress, traceId);
|
|
209
249
|
if (!response.encryptionKey) {
|
|
210
250
|
throw new Error('[Portal] Passkey backup did not return an encryption key. Please ensure you are using the latest iframe bundle.');
|
|
211
251
|
}
|
|
@@ -268,17 +308,17 @@ class Portal {
|
|
|
268
308
|
}
|
|
269
309
|
backupWithPasskey(options = {}, progress = () => {
|
|
270
310
|
// Noop
|
|
271
|
-
}) {
|
|
311
|
+
}, traceId) {
|
|
272
312
|
return __awaiter(this, void 0, void 0, function* () {
|
|
273
313
|
const { usePopup = true, customDomain, relyingPartyName, backupMethod = BackupMethods.passkey, } = options;
|
|
274
314
|
if (usePopup) {
|
|
275
|
-
yield this.backupWallet(backupMethod, progress, {});
|
|
315
|
+
yield this.backupWallet(backupMethod, progress, {}, traceId);
|
|
276
316
|
return;
|
|
277
317
|
}
|
|
278
318
|
if (backupMethod !== BackupMethods.passkey) {
|
|
279
319
|
throw new Error(`[Portal] Direct passkey backup currently supports only BackupMethods.passkey (received ${backupMethod}).`);
|
|
280
320
|
}
|
|
281
|
-
const { cipherText, encryptionKey } = yield this.generateBackupShare(progress);
|
|
321
|
+
const { cipherText, encryptionKey } = yield this.generateBackupShare(progress, traceId);
|
|
282
322
|
yield this.registerPasskeyAndStoreEncryptionKey(cipherText, encryptionKey, {
|
|
283
323
|
customDomain,
|
|
284
324
|
relyingPartyName,
|
|
@@ -292,7 +332,7 @@ class Portal {
|
|
|
292
332
|
}
|
|
293
333
|
backupWallet(backupMethod, progress = () => {
|
|
294
334
|
// Noop
|
|
295
|
-
}, backupConfigs = {}) {
|
|
335
|
+
}, backupConfigs = {}, traceId) {
|
|
296
336
|
return __awaiter(this, void 0, void 0, function* () {
|
|
297
337
|
const response = yield this.mpc.backup({
|
|
298
338
|
backupMethod,
|
|
@@ -300,13 +340,13 @@ class Portal {
|
|
|
300
340
|
host: this.host,
|
|
301
341
|
mpcVersion: this.mpcVersion,
|
|
302
342
|
featureFlags: this.featureFlags,
|
|
303
|
-
}, progress);
|
|
343
|
+
}, progress, traceId);
|
|
304
344
|
return response;
|
|
305
345
|
});
|
|
306
346
|
}
|
|
307
347
|
recoverWallet(cipherText, backupMethod, backupConfigs = {}, progress = () => {
|
|
308
348
|
// Noop
|
|
309
|
-
}) {
|
|
349
|
+
}, traceId) {
|
|
310
350
|
return __awaiter(this, void 0, void 0, function* () {
|
|
311
351
|
const address = yield this.mpc.recover({
|
|
312
352
|
cipherText,
|
|
@@ -315,16 +355,16 @@ class Portal {
|
|
|
315
355
|
host: this.host,
|
|
316
356
|
mpcVersion: this.mpcVersion,
|
|
317
357
|
featureFlags: this.featureFlags,
|
|
318
|
-
}, progress);
|
|
358
|
+
}, progress, traceId);
|
|
319
359
|
this.address = address;
|
|
320
360
|
return address;
|
|
321
361
|
});
|
|
322
362
|
}
|
|
323
363
|
provisionWallet(cipherText, backupMethod, backupConfigs, progress = () => {
|
|
324
364
|
// Noop
|
|
325
|
-
}) {
|
|
365
|
+
}, traceId) {
|
|
326
366
|
return __awaiter(this, void 0, void 0, function* () {
|
|
327
|
-
return this.recoverWallet(cipherText, backupMethod, backupConfigs, progress);
|
|
367
|
+
return this.recoverWallet(cipherText, backupMethod, backupConfigs, progress, traceId);
|
|
328
368
|
});
|
|
329
369
|
}
|
|
330
370
|
eject(backupMethod, backupConfigs, orgBackupShare = '', clientBackupCipherText = '') {
|
|
@@ -1345,9 +1385,8 @@ class Portal {
|
|
|
1345
1385
|
ethereum: 'eip155:1',
|
|
1346
1386
|
sepolia: 'eip155:11155111',
|
|
1347
1387
|
base: 'eip155:8453',
|
|
1348
|
-
'base-sepolia': 'eip155:
|
|
1388
|
+
'base-sepolia': 'eip155:84532',
|
|
1349
1389
|
polygon: 'eip155:137',
|
|
1350
|
-
'polygon-mumbai': 'eip155:80001',
|
|
1351
1390
|
'polygon-amoy': 'eip155:80002',
|
|
1352
1391
|
solana: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
1353
1392
|
'solana-devnet': 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
|
|
@@ -1412,6 +1451,7 @@ class Portal {
|
|
|
1412
1451
|
}
|
|
1413
1452
|
export { MpcError, MpcErrorCodes } from './mpc';
|
|
1414
1453
|
export { PortalMpcError } from './mpc/errors';
|
|
1454
|
+
export { buildDefaultRpcConfig, DEFAULT_RPC_HOST } from './shared/rpc/defaults';
|
|
1415
1455
|
export { RequestMethod } from './provider';
|
|
1416
1456
|
export var MpcStatuses;
|
|
1417
1457
|
(function (MpcStatuses) {
|
package/lib/esm/index.test.js
CHANGED
|
@@ -60,7 +60,7 @@ describe('Portal', () => {
|
|
|
60
60
|
host: 'web.portalhq.io',
|
|
61
61
|
mpcVersion: 'v6',
|
|
62
62
|
featureFlags: {},
|
|
63
|
-
}, mockProgressFn);
|
|
63
|
+
}, mockProgressFn, undefined);
|
|
64
64
|
}));
|
|
65
65
|
it('should successfully generate a wallet and call mpc.generate correctly with custom constructor args', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
66
|
portal = new Portal({
|
|
@@ -78,7 +78,13 @@ describe('Portal', () => {
|
|
|
78
78
|
host: 'test-host',
|
|
79
79
|
mpcVersion: 'v6',
|
|
80
80
|
featureFlags: { isMultiBackupEnabled: true },
|
|
81
|
-
}, mockProgressFn);
|
|
81
|
+
}, mockProgressFn, undefined);
|
|
82
|
+
}));
|
|
83
|
+
it('should pass caller-supplied traceId to mpc.generate', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
|
+
const mockProgressFn = jest.fn();
|
|
85
|
+
const callerTraceId = 'test-trace-id-create';
|
|
86
|
+
yield portal.createWallet(mockProgressFn, callerTraceId);
|
|
87
|
+
expect(portal.mpc.generate).toHaveBeenCalledWith(expect.any(Object), mockProgressFn, callerTraceId);
|
|
82
88
|
}));
|
|
83
89
|
});
|
|
84
90
|
describe('backupWallet', () => {
|
|
@@ -93,7 +99,7 @@ describe('Portal', () => {
|
|
|
93
99
|
host: 'web.portalhq.io',
|
|
94
100
|
mpcVersion: 'v6',
|
|
95
101
|
featureFlags: {},
|
|
96
|
-
}, mockProgressFn);
|
|
102
|
+
}, mockProgressFn, undefined);
|
|
97
103
|
}));
|
|
98
104
|
it('should successfully backup a wallet and call mpc.backup correctly with custom constructor args', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
99
105
|
portal = new Portal({
|
|
@@ -113,7 +119,13 @@ describe('Portal', () => {
|
|
|
113
119
|
host: 'test-host',
|
|
114
120
|
mpcVersion: 'v6',
|
|
115
121
|
featureFlags: { isMultiBackupEnabled: true },
|
|
116
|
-
}, mockProgressFn);
|
|
122
|
+
}, mockProgressFn, undefined);
|
|
123
|
+
}));
|
|
124
|
+
it('should pass caller-supplied traceId to mpc.backup', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
125
|
+
const mockProgressFn = jest.fn();
|
|
126
|
+
const callerTraceId = 'test-trace-id-backup';
|
|
127
|
+
yield portal.backupWallet(BackupMethods.gdrive, mockProgressFn, mockBackupConfig, callerTraceId);
|
|
128
|
+
expect(portal.mpc.backup).toHaveBeenCalledWith(expect.any(Object), mockProgressFn, callerTraceId);
|
|
117
129
|
}));
|
|
118
130
|
});
|
|
119
131
|
describe('generateBackupShare', () => {
|
|
@@ -137,7 +149,7 @@ describe('Portal', () => {
|
|
|
137
149
|
host: 'web.portalhq.io',
|
|
138
150
|
mpcVersion: 'v6',
|
|
139
151
|
featureFlags: {},
|
|
140
|
-
}, expect.any(Function));
|
|
152
|
+
}, expect.any(Function), undefined);
|
|
141
153
|
}));
|
|
142
154
|
it('should throw if the iframe response does not contain an encryption key', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
143
155
|
;
|
|
@@ -147,6 +159,16 @@ describe('Portal', () => {
|
|
|
147
159
|
});
|
|
148
160
|
yield expect(portal.generateBackupShare()).rejects.toThrow('Passkey backup did not return an encryption key');
|
|
149
161
|
}));
|
|
162
|
+
it('should pass caller-supplied traceId to mpc.backup', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
163
|
+
const callerTraceId = 'test-trace-id-gen-backup';
|
|
164
|
+
portal.mpc.backup.mockResolvedValueOnce({
|
|
165
|
+
cipherText: mockCipherText,
|
|
166
|
+
encryptionKey: 'manual-key',
|
|
167
|
+
storageCallback: jest.fn(),
|
|
168
|
+
});
|
|
169
|
+
yield portal.generateBackupShare(undefined, callerTraceId);
|
|
170
|
+
expect(portal.mpc.backup).toHaveBeenCalledWith(expect.any(Object), expect.any(Function), callerTraceId);
|
|
171
|
+
}));
|
|
150
172
|
});
|
|
151
173
|
describe('registerPasskeyAndStoreEncryptionKey', () => {
|
|
152
174
|
it('should delegate to the passkey service with computed relying party data', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -232,7 +254,7 @@ describe('Portal', () => {
|
|
|
232
254
|
host: 'web.portalhq.io',
|
|
233
255
|
mpcVersion: 'v6',
|
|
234
256
|
featureFlags: {},
|
|
235
|
-
}, progress);
|
|
257
|
+
}, progress, undefined);
|
|
236
258
|
}));
|
|
237
259
|
});
|
|
238
260
|
describe('recoverWallet', () => {
|
|
@@ -248,7 +270,7 @@ describe('Portal', () => {
|
|
|
248
270
|
host: 'web.portalhq.io',
|
|
249
271
|
mpcVersion: 'v6',
|
|
250
272
|
featureFlags: {},
|
|
251
|
-
}, mockProgressFn);
|
|
273
|
+
}, mockProgressFn, undefined);
|
|
252
274
|
}));
|
|
253
275
|
it('should successfully recover a wallet and call mpc.recover correctly with custom constructor args', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
254
276
|
portal = new Portal({
|
|
@@ -269,7 +291,13 @@ describe('Portal', () => {
|
|
|
269
291
|
host: 'test-host',
|
|
270
292
|
mpcVersion: 'v6',
|
|
271
293
|
featureFlags: { isMultiBackupEnabled: true },
|
|
272
|
-
}, mockProgressFn);
|
|
294
|
+
}, mockProgressFn, undefined);
|
|
295
|
+
}));
|
|
296
|
+
it('should pass caller-supplied traceId to mpc.recover', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
297
|
+
const mockProgressFn = jest.fn();
|
|
298
|
+
const callerTraceId = 'test-trace-id-recover';
|
|
299
|
+
yield portal.recoverWallet(mockCipherText, BackupMethods.password, mockBackupConfig, mockProgressFn, callerTraceId);
|
|
300
|
+
expect(portal.mpc.recover).toHaveBeenCalledWith(expect.any(Object), mockProgressFn, callerTraceId);
|
|
273
301
|
}));
|
|
274
302
|
});
|
|
275
303
|
describe('provisionWallet', () => {
|
|
@@ -285,7 +313,7 @@ describe('Portal', () => {
|
|
|
285
313
|
host: 'web.portalhq.io',
|
|
286
314
|
mpcVersion: 'v6',
|
|
287
315
|
featureFlags: {},
|
|
288
|
-
}, mockProgressFn);
|
|
316
|
+
}, mockProgressFn, undefined);
|
|
289
317
|
}));
|
|
290
318
|
it('should successfully generate a wallet and call mpc.recover correctly with custom constructor args', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
291
319
|
portal = new Portal({
|
|
@@ -306,7 +334,7 @@ describe('Portal', () => {
|
|
|
306
334
|
host: 'test-host',
|
|
307
335
|
mpcVersion: 'v6',
|
|
308
336
|
featureFlags: { isMultiBackupEnabled: true },
|
|
309
|
-
}, mockProgressFn);
|
|
337
|
+
}, mockProgressFn, undefined);
|
|
310
338
|
}));
|
|
311
339
|
});
|
|
312
340
|
describe('eject', () => {
|
|
@@ -1001,4 +1029,17 @@ describe('Portal', () => {
|
|
|
1001
1029
|
expect(portal.iframeRpcConfig).toEqual(iframeRpcConfig);
|
|
1002
1030
|
});
|
|
1003
1031
|
});
|
|
1032
|
+
describe('onWalletNotOnDevice', () => {
|
|
1033
|
+
it('returns an unsubscribe function that stops the callback from firing', () => {
|
|
1034
|
+
const callback = jest.fn();
|
|
1035
|
+
const unsubscribe = portal.onWalletNotOnDevice(callback);
|
|
1036
|
+
unsubscribe();
|
|
1037
|
+
portal.triggerWalletNotOnDevice({
|
|
1038
|
+
clientId: 'test-client',
|
|
1039
|
+
isBackedUp: false,
|
|
1040
|
+
reason: 'storage_cleared',
|
|
1041
|
+
});
|
|
1042
|
+
expect(callback).not.toHaveBeenCalled();
|
|
1043
|
+
});
|
|
1044
|
+
});
|
|
1004
1045
|
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Meld fiat on-ramp API on `portal.ramps.meld`.
|
|
12
|
+
* Each method forwards to the embedded Portal iframe, which calls connect-api.
|
|
13
|
+
*/
|
|
14
|
+
export default class Meld {
|
|
15
|
+
constructor({ mpc }) {
|
|
16
|
+
this.mpc = mpc;
|
|
17
|
+
}
|
|
18
|
+
/** Register a new Meld customer for the authenticated client. */
|
|
19
|
+
createCustomer(data) {
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
return this.mpc.meldCreateCustomer(data);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** Retrieve the Meld customer record for the authenticated client. */
|
|
25
|
+
searchCustomer() {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
return this.mpc.meldSearchCustomer();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Get fiat-to-crypto purchase quotes from Meld service providers. */
|
|
31
|
+
getRetailQuote(data) {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
return this.mpc.meldGetRetailQuote(data);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create a Meld-hosted retail widget session.
|
|
38
|
+
* The response contains `data.widgetUrl` — open this URL in a new browser tab
|
|
39
|
+
* to let the user complete the purchase or KYC flow on the Meld-hosted page.
|
|
40
|
+
*/
|
|
41
|
+
createRetailWidget(data) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
return this.mpc.meldCreateRetailWidget(data);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Search retail transactions for the authenticated client. */
|
|
47
|
+
searchRetailTransactions(data) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
return this.mpc.meldSearchRetailTransactions(data);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Get a single retail transaction by session ID. */
|
|
53
|
+
getRetailTransactionBySession(sessionId) {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
return this.mpc.meldGetRetailTransactionBySession(sessionId);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Get a single retail transaction by transaction ID. */
|
|
59
|
+
getRetailTransaction(id) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
return this.mpc.meldGetRetailTransaction(id);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Discover available Meld service providers. */
|
|
65
|
+
getServiceProviders(params) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
return this.mpc.meldGetServiceProviders(params);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** Discover supported countries. */
|
|
71
|
+
getCountries(params) {
|
|
72
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
+
return this.mpc.meldGetCountries(params);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Discover supported fiat currencies. */
|
|
77
|
+
getFiatCurrencies(params) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
return this.mpc.meldGetFiatCurrencies(params);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Discover supported crypto currencies. */
|
|
83
|
+
getCryptoCurrencies(params) {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
return this.mpc.meldGetCryptoCurrencies(params);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** Discover supported payment methods. */
|
|
89
|
+
getPaymentMethods(params) {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
return this.mpc.meldGetPaymentMethods(params);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** Discover default currency and payment method settings by country. */
|
|
95
|
+
getDefaults(params) {
|
|
96
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
return this.mpc.meldGetDefaults(params);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** Discover fiat currency purchase limits. */
|
|
101
|
+
getBuyLimits(params) {
|
|
102
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
103
|
+
return this.mpc.meldGetBuyLimits(params);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Discover crypto currency sell limits. */
|
|
107
|
+
getSellLimits(params) {
|
|
108
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
109
|
+
return this.mpc.meldGetSellLimits(params);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** Discover KYC fiat level limits. */
|
|
113
|
+
getKycLimits(params) {
|
|
114
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
return this.mpc.meldGetKycLimits(params);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import Meld from '.';
|
|
11
|
+
const mockMeldCreateCustomerResponse = {
|
|
12
|
+
data: { id: 'cust-1', accountId: 'acc-1', externalId: 'ext-1' },
|
|
13
|
+
};
|
|
14
|
+
const mockMeldSearchCustomerResponse = {
|
|
15
|
+
data: { customers: [], count: 0, remaining: 0 },
|
|
16
|
+
};
|
|
17
|
+
const mockMeldGetRetailQuoteResponse = {
|
|
18
|
+
data: { quotes: [] },
|
|
19
|
+
};
|
|
20
|
+
const mockMeldCreateRetailWidgetResponse = {
|
|
21
|
+
data: { id: 'sess-1', token: 'tok-1', customerId: 'cust-1', externalCustomerId: 'ext-1', externalSessionId: 'ses-1', widgetUrl: 'https://widget.meld.io' },
|
|
22
|
+
};
|
|
23
|
+
const mockMeldSearchRetailTransactionsResponse = {
|
|
24
|
+
data: { transactions: [], count: 0, remaining: 0, totalCount: 0 },
|
|
25
|
+
};
|
|
26
|
+
const mockMeldGetRetailTransactionResponse = {
|
|
27
|
+
data: { transaction: { id: 'tx-1', sessionId: 'ses-1', status: 'COMPLETED', transactionType: 'BUY', serviceProvider: 'STRIPE', createdAt: '2024-01-01', updatedAt: '2024-01-01' } },
|
|
28
|
+
};
|
|
29
|
+
const mockMeldDiscoveryResponse = { data: [] };
|
|
30
|
+
const buildMpcMock = () => ({
|
|
31
|
+
meldCreateCustomer: jest.fn().mockResolvedValue(mockMeldCreateCustomerResponse),
|
|
32
|
+
meldSearchCustomer: jest.fn().mockResolvedValue(mockMeldSearchCustomerResponse),
|
|
33
|
+
meldGetRetailQuote: jest.fn().mockResolvedValue(mockMeldGetRetailQuoteResponse),
|
|
34
|
+
meldCreateRetailWidget: jest.fn().mockResolvedValue(mockMeldCreateRetailWidgetResponse),
|
|
35
|
+
meldSearchRetailTransactions: jest.fn().mockResolvedValue(mockMeldSearchRetailTransactionsResponse),
|
|
36
|
+
meldGetRetailTransactionBySession: jest.fn().mockResolvedValue(mockMeldGetRetailTransactionResponse),
|
|
37
|
+
meldGetRetailTransaction: jest.fn().mockResolvedValue(mockMeldGetRetailTransactionResponse),
|
|
38
|
+
meldGetServiceProviders: jest.fn().mockResolvedValue(mockMeldDiscoveryResponse),
|
|
39
|
+
meldGetCountries: jest.fn().mockResolvedValue(mockMeldDiscoveryResponse),
|
|
40
|
+
meldGetFiatCurrencies: jest.fn().mockResolvedValue(mockMeldDiscoveryResponse),
|
|
41
|
+
meldGetCryptoCurrencies: jest.fn().mockResolvedValue(mockMeldDiscoveryResponse),
|
|
42
|
+
meldGetPaymentMethods: jest.fn().mockResolvedValue(mockMeldDiscoveryResponse),
|
|
43
|
+
meldGetDefaults: jest.fn().mockResolvedValue(mockMeldDiscoveryResponse),
|
|
44
|
+
meldGetBuyLimits: jest.fn().mockResolvedValue(mockMeldDiscoveryResponse),
|
|
45
|
+
meldGetSellLimits: jest.fn().mockResolvedValue(mockMeldDiscoveryResponse),
|
|
46
|
+
meldGetKycLimits: jest.fn().mockResolvedValue(mockMeldDiscoveryResponse),
|
|
47
|
+
});
|
|
48
|
+
describe('Meld', () => {
|
|
49
|
+
let mpcMock;
|
|
50
|
+
let meld;
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
mpcMock = buildMpcMock();
|
|
53
|
+
meld = new Meld({ mpc: mpcMock });
|
|
54
|
+
});
|
|
55
|
+
it('createCustomer delegates to mpc.meldCreateCustomer', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
56
|
+
const req = { email: 'user@example.com', type: 'INDIVIDUAL' };
|
|
57
|
+
const result = yield meld.createCustomer(req);
|
|
58
|
+
expect(mpcMock.meldCreateCustomer).toHaveBeenCalledWith(req);
|
|
59
|
+
expect(result).toEqual(mockMeldCreateCustomerResponse);
|
|
60
|
+
}));
|
|
61
|
+
it('searchCustomer delegates to mpc.meldSearchCustomer', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
62
|
+
const result = yield meld.searchCustomer();
|
|
63
|
+
expect(mpcMock.meldSearchCustomer).toHaveBeenCalled();
|
|
64
|
+
expect(result).toEqual(mockMeldSearchCustomerResponse);
|
|
65
|
+
}));
|
|
66
|
+
it('getRetailQuote delegates to mpc.meldGetRetailQuote', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
const req = { countryCode: 'US', sourceCurrencyCode: 'USD', destinationCurrencyCode: 'ETH', sourceAmount: 100 };
|
|
68
|
+
const result = yield meld.getRetailQuote(req);
|
|
69
|
+
expect(mpcMock.meldGetRetailQuote).toHaveBeenCalledWith(req);
|
|
70
|
+
expect(result).toEqual(mockMeldGetRetailQuoteResponse);
|
|
71
|
+
}));
|
|
72
|
+
it('createRetailWidget delegates to mpc.meldCreateRetailWidget', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
+
const req = {
|
|
74
|
+
sessionType: 'BUY',
|
|
75
|
+
sessionData: {
|
|
76
|
+
countryCode: 'US',
|
|
77
|
+
serviceProvider: 'STRIPE',
|
|
78
|
+
sourceCurrencyCode: 'USD',
|
|
79
|
+
sourceAmount: '100',
|
|
80
|
+
destinationCurrencyCode: 'ETH',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
const result = yield meld.createRetailWidget(req);
|
|
84
|
+
expect(mpcMock.meldCreateRetailWidget).toHaveBeenCalledWith(req);
|
|
85
|
+
expect(result).toEqual(mockMeldCreateRetailWidgetResponse);
|
|
86
|
+
}));
|
|
87
|
+
it('searchRetailTransactions delegates to mpc.meldSearchRetailTransactions without params', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
+
const result = yield meld.searchRetailTransactions();
|
|
89
|
+
expect(mpcMock.meldSearchRetailTransactions).toHaveBeenCalledWith(undefined);
|
|
90
|
+
expect(result).toEqual(mockMeldSearchRetailTransactionsResponse);
|
|
91
|
+
}));
|
|
92
|
+
it('searchRetailTransactions delegates with params when provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
const params = { status: 'COMPLETED' };
|
|
94
|
+
yield meld.searchRetailTransactions(params);
|
|
95
|
+
expect(mpcMock.meldSearchRetailTransactions).toHaveBeenCalledWith(params);
|
|
96
|
+
}));
|
|
97
|
+
it('getRetailTransactionBySession delegates to mpc.meldGetRetailTransactionBySession', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
98
|
+
const result = yield meld.getRetailTransactionBySession('ses-1');
|
|
99
|
+
expect(mpcMock.meldGetRetailTransactionBySession).toHaveBeenCalledWith('ses-1');
|
|
100
|
+
expect(result).toEqual(mockMeldGetRetailTransactionResponse);
|
|
101
|
+
}));
|
|
102
|
+
it('getRetailTransaction delegates to mpc.meldGetRetailTransaction', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
|
+
const result = yield meld.getRetailTransaction('tx-1');
|
|
104
|
+
expect(mpcMock.meldGetRetailTransaction).toHaveBeenCalledWith('tx-1');
|
|
105
|
+
expect(result).toEqual(mockMeldGetRetailTransactionResponse);
|
|
106
|
+
}));
|
|
107
|
+
it('getServiceProviders delegates to mpc.meldGetServiceProviders', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
108
|
+
const params = { countries: 'US' };
|
|
109
|
+
const result = yield meld.getServiceProviders(params);
|
|
110
|
+
expect(mpcMock.meldGetServiceProviders).toHaveBeenCalledWith(params);
|
|
111
|
+
expect(result).toEqual(mockMeldDiscoveryResponse);
|
|
112
|
+
}));
|
|
113
|
+
it('getServiceProviders without params delegates undefined', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
114
|
+
yield meld.getServiceProviders();
|
|
115
|
+
expect(mpcMock.meldGetServiceProviders).toHaveBeenCalledWith(undefined);
|
|
116
|
+
}));
|
|
117
|
+
it('getCountries delegates to mpc.meldGetCountries', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
118
|
+
const result = yield meld.getCountries();
|
|
119
|
+
expect(mpcMock.meldGetCountries).toHaveBeenCalledWith(undefined);
|
|
120
|
+
expect(result).toEqual(mockMeldDiscoveryResponse);
|
|
121
|
+
}));
|
|
122
|
+
it('getFiatCurrencies delegates to mpc.meldGetFiatCurrencies', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
|
+
const result = yield meld.getFiatCurrencies();
|
|
124
|
+
expect(mpcMock.meldGetFiatCurrencies).toHaveBeenCalledWith(undefined);
|
|
125
|
+
expect(result).toEqual(mockMeldDiscoveryResponse);
|
|
126
|
+
}));
|
|
127
|
+
it('getCryptoCurrencies delegates to mpc.meldGetCryptoCurrencies', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
128
|
+
const result = yield meld.getCryptoCurrencies();
|
|
129
|
+
expect(mpcMock.meldGetCryptoCurrencies).toHaveBeenCalledWith(undefined);
|
|
130
|
+
expect(result).toEqual(mockMeldDiscoveryResponse);
|
|
131
|
+
}));
|
|
132
|
+
it('getPaymentMethods delegates to mpc.meldGetPaymentMethods', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
133
|
+
const result = yield meld.getPaymentMethods();
|
|
134
|
+
expect(mpcMock.meldGetPaymentMethods).toHaveBeenCalledWith(undefined);
|
|
135
|
+
expect(result).toEqual(mockMeldDiscoveryResponse);
|
|
136
|
+
}));
|
|
137
|
+
it('getDefaults delegates to mpc.meldGetDefaults', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
138
|
+
const result = yield meld.getDefaults();
|
|
139
|
+
expect(mpcMock.meldGetDefaults).toHaveBeenCalledWith(undefined);
|
|
140
|
+
expect(result).toEqual(mockMeldDiscoveryResponse);
|
|
141
|
+
}));
|
|
142
|
+
it('getBuyLimits delegates to mpc.meldGetBuyLimits', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
143
|
+
const result = yield meld.getBuyLimits();
|
|
144
|
+
expect(mpcMock.meldGetBuyLimits).toHaveBeenCalledWith(undefined);
|
|
145
|
+
expect(result).toEqual(mockMeldDiscoveryResponse);
|
|
146
|
+
}));
|
|
147
|
+
it('getSellLimits delegates to mpc.meldGetSellLimits', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
148
|
+
const result = yield meld.getSellLimits();
|
|
149
|
+
expect(mpcMock.meldGetSellLimits).toHaveBeenCalledWith(undefined);
|
|
150
|
+
expect(result).toEqual(mockMeldDiscoveryResponse);
|
|
151
|
+
}));
|
|
152
|
+
it('getKycLimits delegates to mpc.meldGetKycLimits', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
153
|
+
const result = yield meld.getKycLimits();
|
|
154
|
+
expect(mpcMock.meldGetKycLimits).toHaveBeenCalledWith(undefined);
|
|
155
|
+
expect(result).toEqual(mockMeldDiscoveryResponse);
|
|
156
|
+
}));
|
|
157
|
+
});
|
|
@@ -64,9 +64,9 @@ export default class Noah {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
/** Pay-in payment methods available to the customer. */
|
|
67
|
-
getPaymentMethods() {
|
|
67
|
+
getPaymentMethods(data) {
|
|
68
68
|
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
-
return this.mpc.getPaymentMethods();
|
|
69
|
+
return this.mpc.getPaymentMethods(data);
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -64,11 +64,15 @@ describe('Noah', () => {
|
|
|
64
64
|
const spy = jest.spyOn(mpc, 'initiatePayin').mockResolvedValue({
|
|
65
65
|
data: {
|
|
66
66
|
payinId: 'p1',
|
|
67
|
+
cryptoCurrency: 'USDC_TEST',
|
|
68
|
+
fee: { fiatCurrencyCode: 'USD', totalFeePct: '0', totalFeeBase: '0', totalFeeMin: '0' },
|
|
67
69
|
bankDetails: {
|
|
68
70
|
paymentMethodId: 'pm-123',
|
|
69
|
-
paymentMethodType: '
|
|
71
|
+
paymentMethodType: 'BankLocal',
|
|
70
72
|
accountNumber: '1234567890',
|
|
73
|
+
cryptoCurrency: 'USDC_TEST',
|
|
71
74
|
network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
|
|
75
|
+
fee: { fiatCurrencyCode: 'USD', totalFeePct: '0', totalFeeBase: '0', totalFeeMin: '0' },
|
|
72
76
|
},
|
|
73
77
|
},
|
|
74
78
|
});
|
|
@@ -130,6 +134,7 @@ describe('Noah', () => {
|
|
|
130
134
|
payoutId: 'out-1',
|
|
131
135
|
formSessionId: 'fs-1',
|
|
132
136
|
cryptoAmountEstimate: '10',
|
|
137
|
+
cryptoAuthorizedAmount: '10',
|
|
133
138
|
totalFee: '0.01',
|
|
134
139
|
},
|
|
135
140
|
});
|
|
@@ -158,7 +163,11 @@ describe('Noah', () => {
|
|
|
158
163
|
const spy = jest.spyOn(mpc, 'getPaymentMethods').mockResolvedValue({
|
|
159
164
|
data: { paymentMethods: [] },
|
|
160
165
|
});
|
|
161
|
-
yield noah.getPaymentMethods(
|
|
166
|
+
yield noah.getPaymentMethods({
|
|
167
|
+
pageSize: 50,
|
|
168
|
+
pageToken: 'token-123',
|
|
169
|
+
capability: 'PayoutFrom',
|
|
170
|
+
});
|
|
162
171
|
expect(spy).toHaveBeenCalled();
|
|
163
172
|
}));
|
|
164
173
|
});
|