@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.
Files changed (55) hide show
  1. package/lib/commonjs/index.js +62 -20
  2. package/lib/commonjs/index.test.js +51 -10
  3. package/lib/commonjs/integrations/ramps/index.js +2 -0
  4. package/lib/commonjs/integrations/ramps/meld/index.js +121 -0
  5. package/lib/commonjs/integrations/ramps/meld/index.test.js +162 -0
  6. package/lib/commonjs/integrations/ramps/noah/index.js +2 -2
  7. package/lib/commonjs/integrations/ramps/noah/index.test.js +11 -2
  8. package/lib/commonjs/mpc/index.js +199 -3
  9. package/lib/commonjs/mpc/index.test.js +634 -0
  10. package/lib/commonjs/provider/index.js +44 -2
  11. package/lib/commonjs/provider/index.test.js +186 -0
  12. package/lib/commonjs/rpc.test.js +162 -0
  13. package/lib/commonjs/shared/rpc/auth.js +29 -0
  14. package/lib/commonjs/shared/rpc/defaults.js +40 -0
  15. package/lib/commonjs/shared/types/index.js +1 -0
  16. package/lib/commonjs/shared/types/meld.js +2 -0
  17. package/lib/esm/index.js +59 -19
  18. package/lib/esm/index.test.js +51 -10
  19. package/lib/esm/integrations/ramps/index.js +2 -0
  20. package/lib/esm/integrations/ramps/meld/index.js +118 -0
  21. package/lib/esm/integrations/ramps/meld/index.test.js +157 -0
  22. package/lib/esm/integrations/ramps/noah/index.js +2 -2
  23. package/lib/esm/integrations/ramps/noah/index.test.js +11 -2
  24. package/lib/esm/mpc/index.js +199 -3
  25. package/lib/esm/mpc/index.test.js +635 -1
  26. package/lib/esm/provider/index.js +44 -2
  27. package/lib/esm/provider/index.test.js +186 -0
  28. package/lib/esm/rpc.test.js +157 -0
  29. package/lib/esm/shared/rpc/auth.js +25 -0
  30. package/lib/esm/shared/rpc/defaults.js +36 -0
  31. package/lib/esm/shared/types/index.js +1 -0
  32. package/lib/esm/shared/types/meld.js +1 -0
  33. package/noah-types.d.ts +17 -1
  34. package/package.json +3 -2
  35. package/src/__mocks/constants.ts +68 -0
  36. package/src/__mocks/portal/portal.ts +0 -1
  37. package/src/index.test.ts +90 -0
  38. package/src/index.ts +159 -7
  39. package/src/integrations/ramps/index.ts +3 -0
  40. package/src/integrations/ramps/meld/index.test.ts +189 -0
  41. package/src/integrations/ramps/meld/index.ts +149 -0
  42. package/src/integrations/ramps/noah/index.test.ts +11 -2
  43. package/src/integrations/ramps/noah/index.ts +5 -2
  44. package/src/mpc/index.test.ts +804 -7
  45. package/src/mpc/index.ts +247 -3
  46. package/src/provider/index.test.ts +233 -0
  47. package/src/provider/index.ts +64 -1
  48. package/src/rpc.test.ts +190 -0
  49. package/src/shared/rpc/auth.ts +27 -0
  50. package/src/shared/rpc/defaults.ts +41 -0
  51. package/src/shared/types/common.ts +24 -0
  52. package/src/shared/types/index.ts +1 -0
  53. package/src/shared/types/meld.ts +302 -0
  54. package/src/shared/types/noah.ts +191 -29
  55. 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
- // Required
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 = 'web.portalhq.io', mpcVersion = 'v6', mpcHost = 'mpc-client.portalhq.io', featureFlags = {}, chainId, logLevel = 'none', logger = console, }) {
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 = 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:84531',
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) {
@@ -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
  });
@@ -1,6 +1,8 @@
1
+ import Meld from './meld';
1
2
  import Noah from './noah';
2
3
  export default class Ramps {
3
4
  constructor({ mpc }) {
5
+ this.meld = new Meld({ mpc });
4
6
  this.noah = new Noah({ mpc });
5
7
  }
6
8
  }
@@ -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: 'bank_account',
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
  });