@bitpay-labs/bitcore-wallet-service 11.10.4 → 11.10.5

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@bitpay-labs/bitcore-wallet-service",
3
3
  "description": "A service for Mutisig HD Bitcoin Wallets",
4
4
  "author": "BitPay Inc",
5
- "version": "11.10.4",
5
+ "version": "11.10.5",
6
6
  "license": "MIT",
7
7
  "main": "ts_build/src/index.js",
8
8
  "types": "./ts_build/src/index.d.ts",
@@ -107,5 +107,5 @@
107
107
  "email": "kjoseph@bitpay.com"
108
108
  }
109
109
  ],
110
- "gitHead": "3cdd407df1a56d452b243083c884735757c10a2a"
110
+ "gitHead": "78b95920daa1de2feaa6744d009ad033f520fc3e"
111
111
  }
@@ -112,14 +112,13 @@ export class RampService {
112
112
  const webRequiredParams = [
113
113
  'selectedCountryCode',
114
114
  ];
115
- const appRequiredParams = [
115
+ let appRequiredParams = [
116
116
  'enabledFlows',
117
117
  'defaultFlow',
118
118
  'selectedCountryCode',
119
- 'defaultAsset',
120
119
  ];
121
- const extraRequiredParams = req.body.flow && req.body.flow === 'sell' ? ['offrampAsset'] : ['finalUrl', 'userAddress', 'swapAmount', 'swapAsset'];
122
- appRequiredParams.concat(extraRequiredParams);
120
+ const extraRequiredParams = req.body.flow && req.body.flow === 'sell' ? [] : ['finalUrl', 'userAddress'];
121
+ appRequiredParams = appRequiredParams.concat(extraRequiredParams);
123
122
 
124
123
  const requiredParams = req.body.context === 'web' ? webRequiredParams : appRequiredParams;
125
124
  const keys = this.rampGetKeys(req);
@@ -138,21 +137,42 @@ export class RampService {
138
137
  qs.push('selectedCountryCode=' + encodeURIComponent(req.body.selectedCountryCode));
139
138
  if (req.body.finalUrl) qs.push('finalUrl=' + encodeURIComponent(req.body.finalUrl));
140
139
  if (req.body.userAddress) qs.push('userAddress=' + encodeURIComponent(req.body.userAddress));
141
- if (req.body.swapAsset) qs.push('swapAsset=' + encodeURIComponent(req.body.swapAsset));
142
- if (req.body.offrampAsset) qs.push('offrampAsset=' + encodeURIComponent(req.body.offrampAsset));
143
140
  if (req.body.enabledFlows) qs.push('enabledFlows=' + encodeURIComponent(req.body.enabledFlows));
144
141
  if (req.body.defaultFlow) qs.push('defaultFlow=' + encodeURIComponent(req.body.defaultFlow));
145
142
  if (req.body.hostLogoUrl) qs.push('hostLogoUrl=' + encodeURIComponent(req.body.hostLogoUrl));
146
143
  if (req.body.hostAppName) qs.push('hostAppName=' + encodeURIComponent(req.body.hostAppName));
147
- if (req.body.swapAmount) qs.push('swapAmount=' + encodeURIComponent(req.body.swapAmount));
148
- if (req.body.fiatValue) qs.push('fiatValue=' + encodeURIComponent(req.body.fiatValue));
149
- if (req.body.fiatCurrency) qs.push('fiatCurrency=' + encodeURIComponent(req.body.fiatCurrency));
150
- if (req.body.defaultAsset) qs.push('defaultAsset=' + encodeURIComponent(req.body.defaultAsset));
151
144
  if (req.body.userEmailAddress) qs.push('userEmailAddress=' + encodeURIComponent(req.body.userEmailAddress));
152
145
  if (req.body.useSendCryptoCallback) qs.push('useSendCryptoCallback=' + encodeURIComponent(req.body.useSendCryptoCallback));
153
146
  if (req.body.paymentMethodType) qs.push('paymentMethodType=' + encodeURIComponent(req.body.paymentMethodType));
154
147
  if (req.body.hideExitButton) qs.push('hideExitButton=' + encodeURIComponent(req.body.hideExitButton));
155
- if (req.body.useSendCryptoCallbackVersion) qs.push('useSendCryptoCallbackVersion=' + encodeURIComponent(req.body.useSendCryptoCallbackVersion));
148
+
149
+ // Ramp deprecated the legacy per-flow search params in favor of a unified format.
150
+ // Ref: https://docs.rampnetwork.com/search-params-migration
151
+ // Older app versions still send the legacy fields, so we translate them into the new
152
+ // unified params here to keep those requests working going forward.
153
+ // `flow === 'sell'` => OFFRAMP; otherwise => ONRAMP.
154
+ // If a newer client already sends the new params, those take precedence.
155
+ const isOfframp = req.body.flow === 'sell';
156
+
157
+ // enabledCryptoAssets <- swapAsset (onramp) / offrampAsset (offramp)
158
+ const enabledCryptoAssets = req.body.enabledCryptoAssets ?? (isOfframp ? req.body.offrampAsset : req.body.swapAsset);
159
+ if (enabledCryptoAssets) qs.push('enabledCryptoAssets=' + encodeURIComponent(enabledCryptoAssets));
160
+
161
+ // inAsset (incoming, paid by the user) <- fiatCurrency (onramp) / defaultAsset (offramp)
162
+ const inAsset = req.body.inAsset ?? (isOfframp ? req.body.defaultAsset : req.body.fiatCurrency);
163
+ if (inAsset) qs.push('inAsset=' + encodeURIComponent(inAsset));
164
+
165
+ // outAsset (outgoing, received by the user) <- defaultAsset (onramp) / fiatCurrency (offramp)
166
+ const outAsset = req.body.outAsset ?? (isOfframp ? req.body.fiatCurrency : req.body.defaultAsset);
167
+ if (outAsset) qs.push('outAsset=' + encodeURIComponent(outAsset));
168
+
169
+ // inAssetValue (units, no decimals) <- fiatValue (onramp) / swapAmount (offramp)
170
+ const inAssetValue = req.body.inAssetValue ?? (isOfframp ? req.body.swapAmount : req.body.fiatValue);
171
+ if (inAssetValue) qs.push('inAssetValue=' + encodeURIComponent(inAssetValue));
172
+
173
+ // outAssetValue (units, no decimals) <- swapAmount (onramp) / fiatValue (offramp)
174
+ const outAssetValue = req.body.outAssetValue ?? (isOfframp ? req.body.fiatValue : req.body.swapAmount);
175
+ if (outAssetValue) qs.push('outAssetValue=' + encodeURIComponent(outAssetValue));
156
176
 
157
177
  const queryString = qs.join('&');
158
178
 
@@ -126,6 +126,7 @@ export class ExpressApp {
126
126
 
127
127
  registerMoralisRoutes(router, {
128
128
  getServer,
129
+ getServerWithAuth,
129
130
  returnError
130
131
  });
131
132
 
@@ -6,6 +6,7 @@ import type { WalletService } from '../server';
6
6
 
7
7
  interface RouteContext {
8
8
  getServer: Types.GetServerFn;
9
+ getServerWithAuth: Types.GetServerWithAuthFn;
9
10
  returnError: Types.ReturnErrorFn;
10
11
  }
11
12
 
@@ -33,6 +34,21 @@ function respondWithPublicServer(req, res, context: RouteContext, handler: Route
33
34
  });
34
35
  }
35
36
 
37
+ function walletAuthOrPublic(context: RouteContext) {
38
+ return function(req, res, next) {
39
+ if (!req.header('x-identity')) {
40
+ // Not from wallet
41
+ return next();
42
+ }
43
+ return context.getServerWithAuth(req, res, _server => {
44
+ if (!req.header('origin')) {
45
+ req.headers['origin'] = config.moralis?.whitelist?.[0];
46
+ }
47
+ return next();
48
+ });
49
+ };
50
+ }
51
+
36
52
  export function registerMoralisRoutes(router: express.Router, context: RouteContext) {
37
53
  const moralisCorsOptions = {
38
54
  origin: (origin, cb) => {
@@ -45,55 +61,55 @@ export function registerMoralisRoutes(router: express.Router, context: RouteCont
45
61
  }
46
62
  };
47
63
 
48
- router.post('/v1/moralis/getWalletTokenBalances', cors(moralisCorsOptions), (req, res) => {
64
+ router.post('/v1/moralis/getWalletTokenBalances', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
49
65
  respondWithPublicServer(req, res, context, server => {
50
66
  return server.moralisGetWalletTokenBalances(req);
51
67
  });
52
68
  });
53
69
 
54
- router.post('/v1/moralis/moralisGetTokenAllowance', cors(moralisCorsOptions), (req, res) => {
70
+ router.post('/v1/moralis/moralisGetTokenAllowance', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
55
71
  respondWithPublicServer(req, res, context, server => {
56
72
  return server.moralisGetTokenAllowance(req);
57
73
  });
58
74
  });
59
75
 
60
- router.post('/v1/moralis/moralisGetNativeBalance', cors(moralisCorsOptions), (req, res) => {
76
+ router.post('/v1/moralis/moralisGetNativeBalance', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
61
77
  respondWithPublicServer(req, res, context, server => {
62
78
  return server.moralisGetNativeBalance(req);
63
79
  });
64
80
  });
65
81
 
66
- router.post('/v1/moralis/GetTokenPrice', cors(moralisCorsOptions), (req, res) => {
82
+ router.post('/v1/moralis/GetTokenPrice', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
67
83
  respondWithPublicServer(req, res, context, server => {
68
84
  return server.moralisGetTokenPrice(req);
69
85
  });
70
86
  });
71
87
 
72
- router.post('/v1/moralis/getMultipleERC20TokenPrices', cors(moralisCorsOptions), (req, res) => {
88
+ router.post('/v1/moralis/getMultipleERC20TokenPrices', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
73
89
  respondWithPublicServer(req, res, context, server => {
74
90
  return server.moralisGetMultipleERC20TokenPrices(req);
75
91
  });
76
92
  });
77
93
 
78
- router.post('/v1/moralis/getERC20TokenBalancesWithPricesByWallet', cors(moralisCorsOptions), (req, res) => {
94
+ router.post('/v1/moralis/getERC20TokenBalancesWithPricesByWallet', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
79
95
  respondWithPublicServer(req, res, context, server => {
80
96
  return server.moralisGetERC20TokenBalancesWithPricesByWallet(req);
81
97
  });
82
98
  });
83
99
 
84
- router.post('/v1/moralis/getSolWalletPortfolio', cors(moralisCorsOptions), (req, res) => {
100
+ router.post('/v1/moralis/getSolWalletPortfolio', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
85
101
  respondWithPublicServer(req, res, context, server => {
86
102
  return server.moralisGetSolWalletPortfolio(req);
87
103
  });
88
104
  });
89
105
 
90
- router.post('/v1/moralis/getTransactionVerbose', cors(moralisCorsOptions), (req, res) => {
106
+ router.post('/v1/moralis/getTransactionVerbose', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
91
107
  respondWithPublicServer(req, res, context, server => {
92
108
  return server.moralisGetTransactionVerbose(req);
93
109
  });
94
110
  });
95
111
 
96
- router.post('/v1/moralis/getMultipleSolTokenPrices', cors(moralisCorsOptions), (req, res) => {
112
+ router.post('/v1/moralis/getMultipleSolTokenPrices', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
97
113
  respondWithPublicServer(req, res, context, server => {
98
114
  return server.moralisGetMultipleSolTokenPrices(req);
99
115
  });
@@ -232,7 +232,7 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
232
232
  });
233
233
 
234
234
  router.post('/v1/service/banxa/getOrder', (req, res) => {
235
- respondWithPublicServer(req, res, context, server => {
235
+ respondWithAuthServer(req, res, context, server => {
236
236
  return server.externalServices.banxa.banxaGetOrder(req);
237
237
  });
238
238
  });
@@ -274,7 +274,7 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
274
274
  });
275
275
 
276
276
  router.post('/v1/service/moonpay/transactionDetails', (req, res) => {
277
- respondWithPublicServer(req, res, context, server => {
277
+ respondWithAuthServer(req, res, context, server => {
278
278
  return server.externalServices.moonpay.moonpayGetTransactionDetails(req);
279
279
  });
280
280
  });
@@ -292,7 +292,7 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
292
292
  });
293
293
 
294
294
  router.post('/v1/service/moonpay/accountDetails', (req, res) => {
295
- respondWithPublicServer(req, res, context, server => {
295
+ respondWithAuthServer(req, res, context, server => {
296
296
  return server.externalServices.moonpay.moonpayGetAccountDetails(req);
297
297
  });
298
298
  });
@@ -364,7 +364,7 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
364
364
  });
365
365
 
366
366
  router.post('/v1/service/sardine/ordersDetails', (req, res) => {
367
- respondWithPublicServer(req, res, context, server => {
367
+ respondWithAuthServer(req, res, context, server => {
368
368
  return server.externalServices.sardine.sardineGetOrdersDetails(req);
369
369
  });
370
370
  });
@@ -431,7 +431,7 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
431
431
  });
432
432
 
433
433
  router.post('/v1/service/thorswap/getSwapTx', (req, res) => {
434
- respondWithPublicServer(req, res, context, server => {
434
+ respondWithAuthServer(req, res, context, server => {
435
435
  return server.externalServices.thorswap.thorswapGetSwapTx(req);
436
436
  });
437
437
  });
@@ -467,7 +467,7 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
467
467
  });
468
468
 
469
469
  router.post('/v1/service/transak/orderDetails', cors(transakCorsOptions), (req, res) => {
470
- respondWithPublicServer(req, res, context, server => {
470
+ respondWithAuthServer(req, res, context, server => {
471
471
  return server.externalServices.transak.transakGetOrderDetails(req);
472
472
  });
473
473
  });
@@ -509,13 +509,13 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
509
509
  });
510
510
 
511
511
  router.post('/v1/service/changelly/getTransactions', (req, res) => {
512
- respondWithPublicServer(req, res, context, server => {
512
+ respondWithAuthServer(req, res, context, server => {
513
513
  return server.externalServices.changelly.changellyGetTransactions(req);
514
514
  });
515
515
  });
516
516
 
517
517
  router.post('/v1/service/changelly/getStatus', (req, res) => {
518
- respondWithPublicServer(req, res, context, server => {
518
+ respondWithAuthServer(req, res, context, server => {
519
519
  return server.externalServices.changelly.changellyGetStatus(req);
520
520
  });
521
521
  });
@@ -610,6 +610,89 @@ describe('ExpressApp', function() {
610
610
  });
611
611
  });
612
612
 
613
+ describe('Moralis routes (wallet-authenticated or public)', function() {
614
+ const whitelistedOrigin = 'https://whitelisted-dapp.example';
615
+ let originalMoralisConfig;
616
+ beforeEach(function() {
617
+ originalMoralisConfig = config.moralis;
618
+ config.moralis = { whitelist: [whitelistedOrigin] };
619
+ });
620
+ afterEach(function() {
621
+ config.moralis = originalMoralisConfig;
622
+ });
623
+
624
+ it('/v1/moralis/getMultipleERC20TokenPrices should work without auth headers (public/dApp)', function(done) {
625
+ const server = {
626
+ moralisGetMultipleERC20TokenPrices: sinon.stub().resolves([{ tokenAddress: '0xaaa', usdPrice: 1 }]),
627
+ };
628
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
629
+ sandbox.stub(WalletService, 'getInstance').returns(server);
630
+ start(ExpressApp, function() {
631
+ const requestOptions = {
632
+ url: testHost + ':' + testPort + config.basePath + '/v1/moralis/getMultipleERC20TokenPrices',
633
+ method: 'post',
634
+ json: { chain: '0x1', tokens: [{ tokenAddress: '0xaaa' }] },
635
+ headers: {
636
+ origin: whitelistedOrigin
637
+ }
638
+ };
639
+ request(requestOptions, function(err, res, body) {
640
+ should.not.exist(err);
641
+ res.statusCode.should.equal(200);
642
+ body[0].tokenAddress.should.equal('0xaaa');
643
+ done();
644
+ });
645
+ });
646
+ });
647
+
648
+ it('/v1/moralis/getMultipleERC20TokenPrices should work with valid wallet auth headers', function(done) {
649
+ const server = {
650
+ moralisGetMultipleERC20TokenPrices: sinon.stub().resolves([{ tokenAddress: '0xaaa', usdPrice: 1 }]),
651
+ };
652
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
653
+ sandbox.stub(WalletService, 'getInstance').returns(server);
654
+ sandbox.stub(WalletService, 'getInstanceWithAuth').callsArgWith(1, null, server);
655
+ start(ExpressApp, function() {
656
+ const requestOptions = {
657
+ url: testHost + ':' + testPort + config.basePath + '/v1/moralis/getMultipleERC20TokenPrices',
658
+ method: 'post',
659
+ json: { chain: '0x1', tokens: [{ tokenAddress: '0xaaa' }] },
660
+ headers: {
661
+ 'x-identity': 'identity',
662
+ 'x-signature': 'signature'
663
+ }
664
+ };
665
+ request(requestOptions, function(err, res, body) {
666
+ should.not.exist(err);
667
+ res.statusCode.should.equal(200);
668
+ body[0].tokenAddress.should.equal('0xaaa');
669
+ done();
670
+ });
671
+ });
672
+ });
673
+
674
+ it('/v1/moralis/getMultipleERC20TokenPrices should fail with invalid wallet auth headers', function(done) {
675
+ sandbox.stub(WalletService, 'initialize').callsArg(1);
676
+ sandbox.stub(WalletService, 'getInstanceWithAuth').callsArgWith(1, new ClientError('NOT_AUTHORIZED', 'Not authorized'));
677
+ start(ExpressApp, function() {
678
+ const requestOptions = {
679
+ url: testHost + ':' + testPort + config.basePath + '/v1/moralis/getMultipleERC20TokenPrices',
680
+ method: 'post',
681
+ json: { chain: '0x1', tokens: [{ tokenAddress: '0xaaa' }] },
682
+ headers: {
683
+ 'x-identity': 'identity',
684
+ 'x-signature': 'bad-signature'
685
+ }
686
+ };
687
+ request(requestOptions, function(err, res) {
688
+ should.not.exist(err);
689
+ res.statusCode.should.equal(401);
690
+ done();
691
+ });
692
+ });
693
+ });
694
+ });
695
+
613
696
  describe('Token allowance', function() {
614
697
  it('/v1/token/allowance', function(done) {
615
698
  const server = {
@@ -223,7 +223,80 @@ describe('Ramp integration', () => {
223
223
  server.externalServices.ramp.request = fakeRequest;
224
224
  });
225
225
 
226
- it('should get the paymentUrl properly if req is OK', () => {
226
+ // ONRAMP (buy) - legacy params sent by older app versions.
227
+ // The service must translate them into Ramp's new unified search params:
228
+ // swapAsset -> enabledCryptoAssets
229
+ // defaultAsset -> outAsset
230
+ // swapAmount -> outAssetValue
231
+ // fiatCurrency -> inAsset
232
+ // fiatValue -> inAssetValue
233
+ it('should get the paymentUrl properly with legacy onramp params', () => {
234
+ req.body = {
235
+ env: 'production',
236
+ flow: 'buy',
237
+ swapAsset: 'BTC_BTC',
238
+ swapAmount: '1000000',
239
+ defaultAsset: 'BTC_BTC',
240
+ fiatCurrency: 'USD',
241
+ fiatValue: 50,
242
+ enabledFlows: 'ONRAMP',
243
+ defaultFlow: 'ONRAMP',
244
+ userAddress: 'bitcoin:123123',
245
+ selectedCountryCode: 'US',
246
+ finalUrl: 'bitpay://ramp',
247
+ };
248
+ const data = server.externalServices.ramp.rampGetSignedPaymentUrl(req);
249
+ should.exist(data.urlWithSignature);
250
+ const [base, qs] = data.urlWithSignature.split('?');
251
+
252
+ base.should.equal('widgetApi2');
253
+
254
+ const params = Object.fromEntries(new URLSearchParams(qs));
255
+ params.hostApiKey.should.equal('apiKey2');
256
+ params.selectedCountryCode.should.equal('US');
257
+ params.finalUrl.should.equal('bitpay://ramp');
258
+ params.userAddress.should.equal('bitcoin:123123');
259
+ params.enabledFlows.should.equal('ONRAMP');
260
+ params.defaultFlow.should.equal('ONRAMP');
261
+
262
+ // legacy fields must be translated into the new unified params
263
+ params.enabledCryptoAssets.should.equal('BTC_BTC');
264
+ params.outAsset.should.equal('BTC_BTC');
265
+ params.outAssetValue.should.equal('1000000');
266
+ params.inAsset.should.equal('USD');
267
+ params.inAssetValue.should.equal('50');
268
+
269
+ // legacy fields must NOT be forwarded to Ramp
270
+ should.not.exist(params.swapAsset);
271
+ should.not.exist(params.swapAmount);
272
+ should.not.exist(params.defaultAsset);
273
+ should.not.exist(params.fiatCurrency);
274
+ should.not.exist(params.fiatValue);
275
+
276
+ // timestamp must exist and be numeric
277
+ params.timestamp.should.match(/^\d+$/);
278
+
279
+ // signature must exist and not be empty
280
+ params.signature.should.be.a('string').and.not.equal('');
281
+ });
282
+
283
+ // ONRAMP (buy) - new unified params sent by newer app versions.
284
+ // They must be forwarded as-is.
285
+ it('should get the paymentUrl properly with new onramp params', () => {
286
+ req.body = {
287
+ env: 'production',
288
+ flow: 'buy',
289
+ enabledCryptoAssets: 'BTC_BTC',
290
+ outAsset: 'BTC_BTC',
291
+ outAssetValue: '1000000',
292
+ inAsset: 'USD',
293
+ inAssetValue: 50,
294
+ enabledFlows: 'ONRAMP',
295
+ defaultFlow: 'ONRAMP',
296
+ userAddress: 'bitcoin:123123',
297
+ selectedCountryCode: 'US',
298
+ finalUrl: 'bitpay://ramp',
299
+ };
227
300
  const data = server.externalServices.ramp.rampGetSignedPaymentUrl(req);
228
301
  should.exist(data.urlWithSignature);
229
302
  const [base, qs] = data.urlWithSignature.split('?');
@@ -235,11 +308,13 @@ describe('Ramp integration', () => {
235
308
  params.selectedCountryCode.should.equal('US');
236
309
  params.finalUrl.should.equal('bitpay://ramp');
237
310
  params.userAddress.should.equal('bitcoin:123123');
238
- params.swapAsset.should.equal('BTC_BTC');
239
311
  params.enabledFlows.should.equal('ONRAMP');
240
312
  params.defaultFlow.should.equal('ONRAMP');
241
- params.swapAmount.should.equal('1000000');
242
- params.defaultAsset.should.equal('BTC_BTC');
313
+ params.enabledCryptoAssets.should.equal('BTC_BTC');
314
+ params.outAsset.should.equal('BTC_BTC');
315
+ params.outAssetValue.should.equal('1000000');
316
+ params.inAsset.should.equal('USD');
317
+ params.inAssetValue.should.equal('50');
243
318
 
244
319
  // timestamp must exist and be numeric
245
320
  params.timestamp.should.match(/^\d+$/);
@@ -269,8 +344,8 @@ describe('Ramp integration', () => {
269
344
  params.selectedCountryCode.should.equal('US');
270
345
  params.finalUrl.should.equal('bitpay://ramp');
271
346
  params.userAddress.should.equal('bitcoin:123123');
272
- params.swapAsset.should.equal('BTC_BTC');
273
- params.defaultAsset.should.equal('BTC_BTC');
347
+ params.enabledCryptoAssets.should.equal('BTC_BTC');
348
+ params.outAsset.should.equal('BTC_BTC');
274
349
 
275
350
  // timestamp must exist and be numeric
276
351
  params.timestamp.should.match(/^\d+$/);
@@ -280,7 +355,7 @@ describe('Ramp integration', () => {
280
355
  });
281
356
 
282
357
  it('should return error if there is some missing arguments', () => {
283
- delete req.body.defaultAsset;
358
+ delete req.body.selectedCountryCode;
284
359
  try {
285
360
  server.externalServices.ramp.rampGetSignedPaymentUrl(req);
286
361
  should.fail('should have thrown');
@@ -300,18 +375,75 @@ describe('Ramp integration', () => {
300
375
  }
301
376
  });
302
377
 
303
- it('should get the sell paymentUrl properly if req is OK', () => {
378
+ // OFFRAMP (sell) - legacy params sent by older app versions.
379
+ // The service must translate them into Ramp's new unified search params:
380
+ // offrampAsset -> enabledCryptoAssets
381
+ // defaultAsset -> inAsset
382
+ // swapAmount -> inAssetValue
383
+ // fiatCurrency -> outAsset
384
+ // fiatValue -> outAssetValue
385
+ it('should get the sell paymentUrl properly with legacy offramp params', () => {
304
386
  req.body = {
305
387
  env: 'production',
306
388
  flow: 'sell',
307
389
  offrampAsset: 'BTC_BTC',
308
390
  swapAmount: '1000000',
391
+ defaultAsset: 'BTC_BTC',
392
+ fiatCurrency: 'USD',
393
+ fiatValue: 50,
394
+ enabledFlows: 'OFFRAMP',
395
+ defaultFlow: 'OFFRAMP',
396
+ selectedCountryCode: 'US',
397
+ useSendCryptoCallback: true,
398
+ hideExitButton: false,
399
+ };
400
+ const data = server.externalServices.ramp.rampGetSignedPaymentUrl(req);
401
+ should.exist(data.urlWithSignature);
402
+ const [base, qs] = data.urlWithSignature.split('?');
403
+ base.should.equal('widgetApi2');
404
+
405
+ const params = Object.fromEntries(new URLSearchParams(qs));
406
+ params.hostApiKey.should.equal('apiKey2');
407
+ params.selectedCountryCode.should.equal('US');
408
+ params.enabledFlows.should.equal('OFFRAMP');
409
+ params.defaultFlow.should.equal('OFFRAMP');
410
+ params.useSendCryptoCallback.should.equal('true');
411
+
412
+ // legacy fields must be translated into the new unified params
413
+ params.enabledCryptoAssets.should.equal('BTC_BTC');
414
+ params.inAsset.should.equal('BTC_BTC');
415
+ params.inAssetValue.should.equal('1000000');
416
+ params.outAsset.should.equal('USD');
417
+ params.outAssetValue.should.equal('50');
418
+
419
+ // legacy fields must NOT be forwarded to Ramp
420
+ should.not.exist(params.offrampAsset);
421
+ should.not.exist(params.swapAmount);
422
+ should.not.exist(params.defaultAsset);
423
+ should.not.exist(params.fiatCurrency);
424
+ should.not.exist(params.fiatValue);
425
+
426
+ // timestamp must exist and be numeric
427
+ params.timestamp.should.match(/^\d+$/);
428
+
429
+ // signature must exist and not be empty
430
+ params.signature.should.be.a('string').and.not.equal('');
431
+ });
432
+
433
+ // OFFRAMP (sell) - new unified params sent by newer app versions.
434
+ it('should get the sell paymentUrl properly with new offramp params', () => {
435
+ req.body = {
436
+ env: 'production',
437
+ flow: 'sell',
438
+ enabledCryptoAssets: 'BTC_BTC',
439
+ inAsset: 'BTC_BTC',
440
+ inAssetValue: '1000000',
441
+ outAsset: 'USD',
442
+ outAssetValue: 50,
309
443
  enabledFlows: 'OFFRAMP',
310
444
  defaultFlow: 'OFFRAMP',
311
445
  selectedCountryCode: 'US',
312
- defaultAsset: 'BTC_BTC',
313
446
  useSendCryptoCallback: true,
314
- useSendCryptoCallbackVersion: 1,
315
447
  hideExitButton: false,
316
448
  };
317
449
  const data = server.externalServices.ramp.rampGetSignedPaymentUrl(req);
@@ -322,13 +454,14 @@ describe('Ramp integration', () => {
322
454
  const params = Object.fromEntries(new URLSearchParams(qs));
323
455
  params.hostApiKey.should.equal('apiKey2');
324
456
  params.selectedCountryCode.should.equal('US');
325
- params.offrampAsset.should.equal('BTC_BTC');
326
457
  params.enabledFlows.should.equal('OFFRAMP');
327
458
  params.defaultFlow.should.equal('OFFRAMP');
328
- params.swapAmount.should.equal('1000000');
329
- params.defaultAsset.should.equal('BTC_BTC');
330
459
  params.useSendCryptoCallback.should.equal('true');
331
- params.useSendCryptoCallbackVersion.should.equal('1');
460
+ params.enabledCryptoAssets.should.equal('BTC_BTC');
461
+ params.inAsset.should.equal('BTC_BTC');
462
+ params.inAssetValue.should.equal('1000000');
463
+ params.outAsset.should.equal('USD');
464
+ params.outAssetValue.should.equal('50');
332
465
 
333
466
  // timestamp must exist and be numeric
334
467
  params.timestamp.should.match(/^\d+$/);
@@ -344,12 +477,11 @@ describe('Ramp integration', () => {
344
477
  context: 'web',
345
478
  offrampAsset: 'BTC_BTC',
346
479
  swapAmount: '1000000',
480
+ defaultAsset: 'BTC_BTC',
347
481
  enabledFlows: 'OFFRAMP',
348
482
  defaultFlow: 'OFFRAMP',
349
483
  selectedCountryCode: 'US',
350
- defaultAsset: 'BTC_BTC',
351
484
  useSendCryptoCallback: true,
352
- useSendCryptoCallbackVersion: 1,
353
485
  hideExitButton: false,
354
486
  };
355
487
  const data = server.externalServices.ramp.rampGetSignedPaymentUrl(req);
@@ -360,13 +492,12 @@ describe('Ramp integration', () => {
360
492
  const params = Object.fromEntries(new URLSearchParams(qs));
361
493
  params.hostApiKey.should.equal('apiKey4');
362
494
  params.selectedCountryCode.should.equal('US');
363
- params.offrampAsset.should.equal('BTC_BTC');
364
495
  params.enabledFlows.should.equal('OFFRAMP');
365
496
  params.defaultFlow.should.equal('OFFRAMP');
366
- params.swapAmount.should.equal('1000000');
367
- params.defaultAsset.should.equal('BTC_BTC');
368
497
  params.useSendCryptoCallback.should.equal('true');
369
- params.useSendCryptoCallbackVersion.should.equal('1');
498
+ params.enabledCryptoAssets.should.equal('BTC_BTC');
499
+ params.inAsset.should.equal('BTC_BTC');
500
+ params.inAssetValue.should.equal('1000000');
370
501
 
371
502
  // timestamp must exist and be numeric
372
503
  params.timestamp.should.match(/^\d+$/);
@@ -2,7 +2,7 @@
2
2
  "name": "@bitpay-labs/bitcore-wallet-service",
3
3
  "description": "A service for Mutisig HD Bitcoin Wallets",
4
4
  "author": "BitPay Inc",
5
- "version": "11.10.3",
5
+ "version": "11.10.4",
6
6
  "license": "MIT",
7
7
  "main": "ts_build/src/index.js",
8
8
  "types": "./ts_build/src/index.d.ts",
@@ -41,9 +41,9 @@
41
41
  "cp:templates": "cp -r ./templates ./ts_build/templates"
42
42
  },
43
43
  "dependencies": {
44
- "@bitpay-labs/bitcore-logging": "^11.10.3",
45
- "@bitpay-labs/bitcore-tss": "^11.10.3",
46
- "@bitpay-labs/crypto-wallet-core": "^11.10.3",
44
+ "@bitpay-labs/bitcore-logging": "^11.10.4",
45
+ "@bitpay-labs/bitcore-tss": "^11.10.4",
46
+ "@bitpay-labs/crypto-wallet-core": "^11.10.4",
47
47
  "@sendgrid/mail": "6.5.4",
48
48
  "abi-decoder": "2.4.0",
49
49
  "async": "0.9.2",
@@ -1 +1 @@
1
- {"version":3,"file":"ramp.d.ts","sourceRoot":"","sources":["../../../src/externalservices/ramp.ts"],"names":[],"mappings":"AAOA,qBAAa,WAAW;IACtB,OAAO,EAAE,GAAG,CAAW;IAEvB,OAAO,CAAC,WAAW;IA0BnB,YAAY,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAkC/B,gBAAgB,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAwCnC,uBAAuB,CAAC,GAAG,KAAA,GAAG;QAAE,gBAAgB,EAAE,MAAM,CAAA;KAAE;IAyE1D,aAAa,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAwChC,6BAA6B,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;CAkCjD"}
1
+ {"version":3,"file":"ramp.d.ts","sourceRoot":"","sources":["../../../src/externalservices/ramp.ts"],"names":[],"mappings":"AAOA,qBAAa,WAAW;IACtB,OAAO,EAAE,GAAG,CAAW;IAEvB,OAAO,CAAC,WAAW;IA0BnB,YAAY,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAkC/B,gBAAgB,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAwCnC,uBAAuB,CAAC,GAAG,KAAA,GAAG;QAAE,gBAAgB,EAAE,MAAM,CAAA;KAAE;IA6F1D,aAAa,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAwChC,6BAA6B,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;CAkCjD"}