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

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 (83) hide show
  1. package/.mocharc.yml +2 -0
  2. package/.nycrc.yml +13 -0
  3. package/bws.example.config.js +4 -3
  4. package/package.json +6 -25
  5. package/src/config.ts +4 -3
  6. package/src/externalservices/moonpay.ts +10 -0
  7. package/src/externalservices/transak.ts +15 -0
  8. package/src/lib/blockchainexplorers/v8.ts +19 -0
  9. package/src/lib/chain/xrp/index.ts +13 -8
  10. package/src/lib/common/defaults.ts +3 -0
  11. package/src/lib/common/utils.ts +3 -3
  12. package/src/lib/emailservice.ts +2 -1
  13. package/src/lib/model/txproposal.ts +4 -0
  14. package/src/lib/pushnotificationsservice.ts +2 -1
  15. package/src/lib/routes/moralis.ts +12 -0
  16. package/src/lib/routes/services.ts +22 -6
  17. package/src/lib/routes/tss.ts +1 -1
  18. package/src/lib/routes/wallets.ts +16 -1
  19. package/src/lib/server.ts +96 -2
  20. package/test/chain/xrp.test.ts +21 -0
  21. package/test/integration/externalservices/moonpay.test.ts +14 -2
  22. package/test/integration/externalservices/moralis.test.ts +476 -0
  23. package/test/integration/server.test.ts +138 -0
  24. package/ts_build/package.json +5 -24
  25. package/ts_build/src/config.d.ts.map +1 -1
  26. package/ts_build/src/config.js.map +1 -1
  27. package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
  28. package/ts_build/src/externalservices/moonpay.js +6 -0
  29. package/ts_build/src/externalservices/moonpay.js.map +1 -1
  30. package/ts_build/src/externalservices/transak.d.ts.map +1 -1
  31. package/ts_build/src/externalservices/transak.js +15 -0
  32. package/ts_build/src/externalservices/transak.js.map +1 -1
  33. package/ts_build/src/lib/blockchainexplorers/v8.d.ts +3 -0
  34. package/ts_build/src/lib/blockchainexplorers/v8.d.ts.map +1 -1
  35. package/ts_build/src/lib/blockchainexplorers/v8.js +18 -0
  36. package/ts_build/src/lib/blockchainexplorers/v8.js.map +1 -1
  37. package/ts_build/src/lib/chain/xrp/index.d.ts.map +1 -1
  38. package/ts_build/src/lib/chain/xrp/index.js +12 -8
  39. package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
  40. package/ts_build/src/lib/common/defaults.d.ts +1 -0
  41. package/ts_build/src/lib/common/defaults.d.ts.map +1 -1
  42. package/ts_build/src/lib/common/defaults.js +1 -0
  43. package/ts_build/src/lib/common/defaults.js.map +1 -1
  44. package/ts_build/src/lib/common/index.d.ts +2 -1
  45. package/ts_build/src/lib/common/index.d.ts.map +1 -1
  46. package/ts_build/src/lib/common/utils.d.ts +1 -1
  47. package/ts_build/src/lib/common/utils.d.ts.map +1 -1
  48. package/ts_build/src/lib/common/utils.js.map +1 -1
  49. package/ts_build/src/lib/emailservice.d.ts.map +1 -1
  50. package/ts_build/src/lib/emailservice.js +1 -1
  51. package/ts_build/src/lib/emailservice.js.map +1 -1
  52. package/ts_build/src/lib/model/txproposal.d.ts +2 -0
  53. package/ts_build/src/lib/model/txproposal.d.ts.map +1 -1
  54. package/ts_build/src/lib/model/txproposal.js +3 -0
  55. package/ts_build/src/lib/model/txproposal.js.map +1 -1
  56. package/ts_build/src/lib/pushnotificationsservice.d.ts.map +1 -1
  57. package/ts_build/src/lib/pushnotificationsservice.js +1 -1
  58. package/ts_build/src/lib/pushnotificationsservice.js.map +1 -1
  59. package/ts_build/src/lib/routes/moralis.d.ts.map +1 -1
  60. package/ts_build/src/lib/routes/moralis.js +10 -0
  61. package/ts_build/src/lib/routes/moralis.js.map +1 -1
  62. package/ts_build/src/lib/routes/services.d.ts.map +1 -1
  63. package/ts_build/src/lib/routes/services.js +22 -6
  64. package/ts_build/src/lib/routes/services.js.map +1 -1
  65. package/ts_build/src/lib/routes/tss.js +0 -1
  66. package/ts_build/src/lib/routes/tss.js.map +1 -1
  67. package/ts_build/src/lib/routes/wallets.d.ts.map +1 -1
  68. package/ts_build/src/lib/routes/wallets.js +15 -0
  69. package/ts_build/src/lib/routes/wallets.js.map +1 -1
  70. package/ts_build/src/lib/server.d.ts +5 -0
  71. package/ts_build/src/lib/server.d.ts.map +1 -1
  72. package/ts_build/src/lib/server.js +83 -2
  73. package/ts_build/src/lib/server.js.map +1 -1
  74. package/ts_build/test/chain/xrp.test.js +19 -0
  75. package/ts_build/test/chain/xrp.test.js.map +1 -1
  76. package/ts_build/test/integration/externalservices/moonpay.test.js +14 -2
  77. package/ts_build/test/integration/externalservices/moonpay.test.js.map +1 -1
  78. package/ts_build/test/integration/externalservices/moralis.test.d.ts +2 -0
  79. package/ts_build/test/integration/externalservices/moralis.test.d.ts.map +1 -0
  80. package/ts_build/test/integration/externalservices/moralis.test.js +455 -0
  81. package/ts_build/test/integration/externalservices/moralis.test.js.map +1 -0
  82. package/ts_build/test/integration/server.test.js +115 -0
  83. package/ts_build/test/integration/server.test.js.map +1 -1
package/.mocharc.yml ADDED
@@ -0,0 +1,2 @@
1
+ enable-source-maps: true
2
+ exit: true
package/.nycrc.yml ADDED
@@ -0,0 +1,13 @@
1
+ include: [
2
+ ts_build/src,
3
+ src
4
+ ]
5
+ reporter: [
6
+ html,
7
+ text-summary
8
+ ]
9
+ check-coverage: true
10
+ statements: 60
11
+ branches: 50
12
+ functions: 60
13
+ lines: 70
@@ -325,7 +325,7 @@ module.exports = {
325
325
  // publicKey: 'simplex_web_public_key_here',
326
326
  // }
327
327
  // },
328
- // thorswap : {
328
+ // thorswap: {
329
329
  // sandbox: {
330
330
  // api: 'https://dev-api.thorswap.net',
331
331
  // apiKey: 'thorswap_sandbox_api_key_here',
@@ -339,7 +339,7 @@ module.exports = {
339
339
  // referer: 'thorswap_production_referer_here'
340
340
  // },
341
341
  // },
342
- // transak : {
342
+ // transak: {
343
343
  // sandbox: {
344
344
  // api: 'https://api-stg.transak.com',
345
345
  // widgetApi: 'https://api-gateway-stg.transak.com',
@@ -363,7 +363,8 @@ module.exports = {
363
363
  // widgetApi: 'https://api-gateway.transak.com',
364
364
  // apiKey: 'transak_production_web_api_key_here',
365
365
  // secretKey: 'transak_production_web_secret_key_here',
366
- // }
366
+ // },
367
+ // whitelist: []
367
368
  // },
368
369
  // wyre: {
369
370
  // sandbox: {
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.2",
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",
@@ -24,9 +24,7 @@
24
24
  "scripts": {
25
25
  "start": "npm run clean && npm run build && ./start.sh",
26
26
  "stop": "./stop.sh",
27
- "coverage": "npm run compile && nyc mocha 'ts_build/test/**/*.test.js'",
28
- "test": "npm run compile && mocha --exit 'ts_build/test/**/*.test.js'",
29
- "test:ci": "BWS_LOG_LEVEL=none npm run test",
27
+ "test": "npm run compile && nyc mocha 'ts_build/test/**/*.test.js'",
30
28
  "build": "tsc && npm run cp:templates",
31
29
  "build:prod": "tsc -p tsconfig.prod.json && npm run cp:templates",
32
30
  "build:docker": "docker build -t bitpay/bitcore-wallet-service:latest -f ./Dockerfile ../..",
@@ -43,9 +41,9 @@
43
41
  "cp:templates": "cp -r ./templates ./ts_build/templates"
44
42
  },
45
43
  "dependencies": {
46
- "@bitpay-labs/bitcore-logging": "^11.8.1",
47
- "@bitpay-labs/bitcore-tss": "^11.10.0",
48
- "@bitpay-labs/crypto-wallet-core": "^11.10.0",
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",
49
47
  "@sendgrid/mail": "6.5.4",
50
48
  "abi-decoder": "2.4.0",
51
49
  "async": "0.9.2",
@@ -86,27 +84,10 @@
86
84
  "@types/request": "^2.48.1",
87
85
  "chai": "^5.2.0",
88
86
  "memdown": "^1.0.0",
89
- "mocha": "^5.2.0",
90
- "nyc": "^17.1.0",
91
87
  "sinon": "^7.1.1",
92
88
  "supertest": "*",
93
89
  "typescript": "^5.7.3"
94
90
  },
95
- "nyc": {
96
- "include": [
97
- "ts_build/src",
98
- "src"
99
- ],
100
- "reporter": [
101
- "html",
102
- "text-summary"
103
- ],
104
- "check-coverage": true,
105
- "lines": 60,
106
- "functions": 60,
107
- "branches": 60,
108
- "statements": 60
109
- },
110
91
  "bitcoreNode": "./bitcorenode",
111
92
  "contributors": [
112
93
  {
@@ -126,5 +107,5 @@
126
107
  "email": "kjoseph@bitpay.com"
127
108
  }
128
109
  ],
129
- "gitHead": "bcf06b7f572a535f778a6060e1843fccf33ee91b"
110
+ "gitHead": "3cdd407df1a56d452b243083c884735757c10a2a"
130
111
  }
package/src/config.ts CHANGED
@@ -400,7 +400,7 @@ const Config = (): any => {
400
400
  // publicKey: 'simplex_web_public_key_here',
401
401
  // }
402
402
  // },
403
- // thorswap : {
403
+ // thorswap: {
404
404
  // sandbox: {
405
405
  // api: 'https://dev-api.thorswap.net',
406
406
  // apiKey: 'thorswap_sandbox_api_key_here',
@@ -414,7 +414,7 @@ const Config = (): any => {
414
414
  // referer: 'thorswap_production_referer_here'
415
415
  // },
416
416
  // },
417
- // transak : {
417
+ // transak: {
418
418
  // sandbox: {
419
419
  // api: 'https://api-stg.transak.com',
420
420
  // widgetApi: 'https://global-stg.transak.com',
@@ -438,7 +438,8 @@ const Config = (): any => {
438
438
  // widgetApi: 'https://global.transak.com',
439
439
  // apiKey: 'transak_production_web_api_key_here',
440
440
  // secretKey: 'transak_production_web_secret_key_here',
441
- // }
441
+ // },
442
+ // whitelist: []
442
443
  // },
443
444
  // wyre: {
444
445
  // sandbox: {
@@ -225,6 +225,16 @@ export class MoonpayService {
225
225
  if (req.body.paymentMethod) qs.push('paymentMethod=' + encodeURIComponent(req.body.paymentMethod));
226
226
  if (req.body.areFeesIncluded) qs.push('areFeesIncluded=' + encodeURIComponent(req.body.areFeesIncluded));
227
227
 
228
+ const deviceIp = Utils.getIpFromReq(req);
229
+ if (!deviceIp) {
230
+ throw new ClientError('Could not determine device IP address');
231
+ }
232
+ const allowedIpAddress: string = Bitcore.crypto.Hash.sha256hmac(
233
+ Buffer.from(deviceIp),
234
+ Buffer.from(SECRET_KEY)
235
+ ).toString('base64');
236
+ qs.push('allowedIpAddress=' + encodeURIComponent(allowedIpAddress));
237
+
228
238
  const URL_SEARCH: string = `?${qs.join('&')}`;
229
239
 
230
240
  const URLSignatureHash: string = Bitcore.crypto.Hash.sha256hmac(
@@ -1,5 +1,6 @@
1
1
  import * as request from 'request';
2
2
  import config from '../config';
3
+ import { Utils } from '../lib/common/utils';
3
4
  import { ClientError } from '../lib/errors/clienterror';
4
5
  import { checkRequired } from '../lib/server';
5
6
 
@@ -50,6 +51,8 @@ export class TransakService {
50
51
  const headers = {
51
52
  'Content-Type': 'application/json',
52
53
  'api-secret': SECRET_KEY,
54
+ 'x-api-key': API_KEY,
55
+ 'x-user-ip': Utils.getIpFromReq(req),
53
56
  };
54
57
 
55
58
  const body = {
@@ -85,10 +88,13 @@ export class TransakService {
85
88
  return reject(err);
86
89
  }
87
90
  const API = keys.API;
91
+ const API_KEY = keys.API_KEY;
88
92
 
89
93
  const headers = {
90
94
  'Accept': 'application/json',
91
95
  'Content-Type': 'application/json',
96
+ 'x-api-key': API_KEY,
97
+ 'x-user-ip': Utils.getIpFromReq(req),
92
98
  };
93
99
 
94
100
  const URL: string = API + '/api/v2/currencies/crypto-currencies';
@@ -124,6 +130,8 @@ export class TransakService {
124
130
  const headers = {
125
131
  'Accept': 'application/json',
126
132
  'Content-Type': 'application/json',
133
+ 'x-api-key': API_KEY,
134
+ 'x-user-ip': Utils.getIpFromReq(req),
127
135
  };
128
136
 
129
137
  const URL: string = API + `/api/v2/currencies/fiat-currencies?apiKey=${API_KEY}`;
@@ -162,6 +170,8 @@ export class TransakService {
162
170
 
163
171
  const headers = {
164
172
  Accept: 'application/json',
173
+ 'x-api-key': API_KEY,
174
+ 'x-user-ip': Utils.getIpFromReq(req),
165
175
  };
166
176
 
167
177
  const qs: string[] = [];
@@ -227,6 +237,8 @@ export class TransakService {
227
237
  Accept: 'application/json',
228
238
  'Content-Type': 'application/json',
229
239
  'access-token': req.body.accessToken,
240
+ 'x-api-key': API_KEY,
241
+ 'x-user-ip': Utils.getIpFromReq(req),
230
242
  };
231
243
 
232
244
  const body = {
@@ -266,6 +278,7 @@ export class TransakService {
266
278
  return reject(err);
267
279
  }
268
280
  const API = keys.API;
281
+ const API_KEY = keys.API_KEY;
269
282
 
270
283
  if (!checkRequired(req.body, ['orderId', 'accessToken'])) {
271
284
  return reject(new ClientError("Transak's request missing arguments"));
@@ -274,6 +287,8 @@ export class TransakService {
274
287
  const headers = {
275
288
  Accept: 'application/json',
276
289
  'access-token': req.body.accessToken,
290
+ 'x-api-key': API_KEY,
291
+ 'x-user-ip': Utils.getIpFromReq(req),
277
292
  };
278
293
 
279
294
  const URL: string = API + `/partners/api/v2/order/${req.body.orderId}`;
@@ -43,6 +43,13 @@ function v8network(bwsNetwork, chain = 'btc') {
43
43
  return bwsNetwork;
44
44
  }
45
45
 
46
+ function scrubResponseError(err) {
47
+ if (err.response) {
48
+ return new Error(`HTTP ${err.response.statusCode} - ${err.response.statusMessage}: ${err.response.body}\n${err.response.request.method} ${err.response.request.href}`);
49
+ }
50
+ return new Error(err);
51
+ }
52
+
46
53
  export type WalletWithOpts = IWallet & { tokenAddress?: string; multisigContractAddress?: string };
47
54
 
48
55
  export class V8 {
@@ -765,6 +772,18 @@ export class V8 {
765
772
  return callbacks.onIncomingPayments(notification);
766
773
  });
767
774
  }
775
+
776
+ async getFlags(opts: { address: string }) {
777
+ try {
778
+ const url = `${this.baseUrl}/address/${opts.address}/flags`;
779
+ const ret = await this.request.get(url);
780
+ return JSON.parse(ret).flags;
781
+ } catch (err) {
782
+ const e = scrubResponseError(err);
783
+ logger.error(`Error getting flags for address ${opts.address}: %o`, e);
784
+ throw e;
785
+ }
786
+ }
768
787
  }
769
788
 
770
789
  const getPerformanceKey = (name: string) => {
@@ -90,14 +90,19 @@ export class XrpChain implements IChain {
90
90
  getWalletSendMaxInfo(server, wallet, opts, cb) {
91
91
  server.getBalance({}, (err, balance) => {
92
92
  if (err) return cb(err);
93
- const { availableAmount } = balance;
94
- const fee = opts.feePerKb;
95
- return cb(null, {
96
- utxosBelowFee: 0,
97
- amountBelowFee: 0,
98
- amount: availableAmount - fee,
99
- feePerKb: opts.feePerKb,
100
- fee
93
+
94
+ server._getFeePerKb(wallet, opts, (err, feePerKb) => {
95
+ if (err) return cb(err);
96
+
97
+ const { availableAmount } = balance;
98
+ const fee = feePerKb;
99
+ return cb(null, {
100
+ utxosBelowFee: 0,
101
+ amountBelowFee: 0,
102
+ amount: availableAmount - fee,
103
+ feePerKb,
104
+ fee
105
+ });
101
106
  });
102
107
  });
103
108
  }
@@ -19,6 +19,9 @@ export const Defaults = {
19
19
  // TODO: should allow different gap sizes for external/internal chains
20
20
  SCAN_ADDRESS_GAP: 30,
21
21
 
22
+ // Lookback window (in seconds) for scanning broadcasted txps during nonce assignment
23
+ BROADCASTED_NONCE_SCAN_WINDOW: 24 * 3600,
24
+
22
25
  FEE_LEVELS: {
23
26
  btc: [
24
27
  {
@@ -81,11 +81,11 @@ export const Utils = {
81
81
 
82
82
  /**
83
83
  *
84
- * @desc rounds a JAvascript number
85
- * @param number
84
+ * @desc rounds a Javascript number
85
+ * @param {number} number
86
86
  * @return {number}
87
87
  */
88
- strip(number) {
88
+ strip(number: number): number {
89
89
  return parseFloat(number.toPrecision(12));
90
90
  },
91
91
 
@@ -561,7 +561,8 @@ export class EmailService {
561
561
  _checkShouldSendEmail(notification: Notification, cb) {
562
562
  if (notification.type != 'NewTxProposal') return cb(null, true);
563
563
  this.storage.fetchWallet(notification.walletId, (err, wallet) => {
564
- return cb(err, wallet.m > 1);
564
+ // TSS wallets are stored as m=1 but still need other signers
565
+ return cb(err, wallet && (wallet.m > 1 || !!wallet.tssKeyId));
565
566
  });
566
567
  }
567
568
 
@@ -81,6 +81,7 @@ export interface ITxProposal<NumberType = number> {
81
81
  multisigTxId?: string;
82
82
  destinationTag?: string;
83
83
  invoiceID?: string;
84
+ flags?: string; // Comma-delimited list of XRP account flags to set (e.g. "tfRequireDestTag,tfDisallowXRP")
84
85
  lockUntilBlockHeight?: NumberType;
85
86
  instantAcceptanceEscrow?: NumberType;
86
87
  isTokenSwap?: boolean;
@@ -175,6 +176,7 @@ export class TxProposal<NumberType = number> implements ITxProposal<NumberType>
175
176
  multisigTxId?: string;
176
177
  destinationTag?: string;
177
178
  invoiceID?: string;
179
+ flags?: string; // Comma-delimited list of XRP account flags to set (e.g. "tfRequireDestTag,tfDisallowXRP")
178
180
  lockUntilBlockHeight?: NumberType;
179
181
  instantAcceptanceEscrow?: NumberType;
180
182
  isTokenSwap?: boolean;
@@ -305,6 +307,7 @@ export class TxProposal<NumberType = number> implements ITxProposal<NumberType>
305
307
  x.destinationTag = opts.destinationTag;
306
308
  x.invoiceID = opts.invoiceID;
307
309
  x.multiTx = opts.multiTx; // proposal contains multiple transactions
310
+ x.flags = opts.flags; // XRP account flags to set
308
311
 
309
312
  // SOL
310
313
  x.space = opts.space; // space to allocate for account creation
@@ -400,6 +403,7 @@ export class TxProposal<NumberType = number> implements ITxProposal<NumberType>
400
403
  x.destinationTag = obj.destinationTag;
401
404
  x.invoiceID = obj.invoiceID;
402
405
  x.multiTx = obj.multiTx;
406
+ x.flags = obj.flags; // XRP account flags to set
403
407
 
404
408
  // SOL
405
409
  x.space = obj.space; // space to allocate for account creation
@@ -358,7 +358,8 @@ export class PushNotificationsService {
358
358
  _checkShouldSendNotif(notification, cb) {
359
359
  if (notification.type != 'NewTxProposal') return cb(null, true);
360
360
  this.storage.fetchWallet(notification.walletId, (err, wallet) => {
361
- return cb(err, wallet && wallet.m > 1);
361
+ // TSS wallets are stored as m=1 but still need other signers
362
+ return cb(err, wallet && (wallet.m > 1 || !!wallet.tssKeyId));
362
363
  });
363
364
  }
364
365
 
@@ -86,4 +86,16 @@ export function registerMoralisRoutes(router: express.Router, context: RouteCont
86
86
  return server.moralisGetSolWalletPortfolio(req);
87
87
  });
88
88
  });
89
+
90
+ router.post('/v1/moralis/getTransactionVerbose', cors(moralisCorsOptions), (req, res) => {
91
+ respondWithPublicServer(req, res, context, server => {
92
+ return server.moralisGetTransactionVerbose(req);
93
+ });
94
+ });
95
+
96
+ router.post('/v1/moralis/getMultipleSolTokenPrices', cors(moralisCorsOptions), (req, res) => {
97
+ respondWithPublicServer(req, res, context, server => {
98
+ return server.moralisGetMultipleSolTokenPrices(req);
99
+ });
100
+ });
89
101
  }
@@ -1,5 +1,7 @@
1
+ import cors from 'cors';
1
2
  import express from 'express';
2
3
  import rp from 'request-promise-native';
4
+ import config from '../../config';
3
5
  import { Common } from '../common';
4
6
  import type * as Types from '../../types/expressapp';
5
7
  import type { WalletService } from '../server';
@@ -65,6 +67,20 @@ function respondWithAuthServer(req, res, context: RouteContext, handler: RouteHa
65
67
  export function registerServiceRoutes(router: express.Router, context: RouteContext) {
66
68
  const { getServerWithAuth, setPublicCache, returnError } = context;
67
69
 
70
+ const transakCorsOptions = {
71
+ origin: (origin, cb) => {
72
+ if (!origin) {
73
+ return cb(null, true);
74
+ }
75
+ const transakWhiteList = config.transak?.whitelist ?? [];
76
+ if (transakWhiteList.indexOf(origin) !== -1) {
77
+ cb(null, true);
78
+ } else {
79
+ cb(new Error('Not allowed by CORS'));
80
+ }
81
+ }
82
+ };
83
+
68
84
  router.get('/latest-version', async (req, res) => {
69
85
  setPublicCache(res, 10 * ONE_MINUTE);
70
86
  try {
@@ -420,37 +436,37 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
420
436
  });
421
437
  });
422
438
 
423
- router.post('/v1/service/transak/getAccessToken', (req, res) => {
439
+ router.post('/v1/service/transak/getAccessToken', cors(transakCorsOptions), (req, res) => {
424
440
  respondWithAuthServer(req, res, context, server => {
425
441
  return server.externalServices.transak.transakGetAccessToken(req);
426
442
  });
427
443
  });
428
444
 
429
- router.post('/v1/service/transak/cryptoCurrencies', (req, res) => {
445
+ router.post('/v1/service/transak/cryptoCurrencies', cors(transakCorsOptions), (req, res) => {
430
446
  respondWithPublicServer(req, res, context, server => {
431
447
  return server.externalServices.transak.transakGetCryptoCurrencies(req);
432
448
  });
433
449
  });
434
450
 
435
- router.post('/v1/service/transak/fiatCurrencies', (req, res) => {
451
+ router.post('/v1/service/transak/fiatCurrencies', cors(transakCorsOptions), (req, res) => {
436
452
  respondWithPublicServer(req, res, context, server => {
437
453
  return server.externalServices.transak.transakGetFiatCurrencies(req);
438
454
  });
439
455
  });
440
456
 
441
- router.post('/v1/service/transak/quote', (req, res) => {
457
+ router.post('/v1/service/transak/quote', cors(transakCorsOptions), (req, res) => {
442
458
  respondWithAuthServer(req, res, context, server => {
443
459
  return server.externalServices.transak.transakGetQuote(req);
444
460
  });
445
461
  });
446
462
 
447
- router.post('/v1/service/transak/signedPaymentUrl', (req, res) => {
463
+ router.post('/v1/service/transak/signedPaymentUrl', cors(transakCorsOptions), (req, res) => {
448
464
  respondWithAuthServer(req, res, context, server => {
449
465
  return server.externalServices.transak.transakGetSignedPaymentUrl(req);
450
466
  });
451
467
  });
452
468
 
453
- router.post('/v1/service/transak/orderDetails', (req, res) => {
469
+ router.post('/v1/service/transak/orderDetails', cors(transakCorsOptions), (req, res) => {
454
470
  respondWithPublicServer(req, res, context, server => {
455
471
  return server.externalServices.transak.transakGetOrderDetails(req);
456
472
  });
@@ -120,4 +120,4 @@ export class TssRouter {
120
120
 
121
121
  this.router = router;
122
122
  }
123
- };
123
+ }
@@ -291,4 +291,19 @@ export function registerWalletRoutes(router: express.Router, context: RouteConte
291
291
  });
292
292
  });
293
293
  });
294
- }
294
+
295
+ router.get('/v1/flags', (req, res) => {
296
+ getServerWithAuth(req, res, async server => {
297
+ try {
298
+ const opts = { account: 0 };
299
+ if (req.query.account) {
300
+ opts.account = parseInt(req.query.account);
301
+ }
302
+ const flags = await server.getFlags(opts);
303
+ res.json(flags);
304
+ } catch (err) {
305
+ returnError(err, res, req);
306
+ }
307
+ });
308
+ });
309
+ }
package/src/lib/server.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import util from 'util';
1
2
  import {
2
3
  BitcoreLib as Bitcore,
3
4
  BitcoreLibCash as BitcoreCash,
@@ -2924,7 +2925,8 @@ export class WalletService implements IWalletService {
2924
2925
  fromAta: opts.fromAta,
2925
2926
  decimals: opts.decimals,
2926
2927
  refreshOnPublish: opts.refreshOnPublish,
2927
- deferNonce: opts.deferNonce
2928
+ deferNonce: opts.deferNonce,
2929
+ flags: opts.flags
2928
2930
  };
2929
2931
  txp = TxProposal.create(txOpts);
2930
2932
  next();
@@ -3358,6 +3360,19 @@ export class WalletService implements IWalletService {
3358
3360
  return cb(Errors.TX_NONCE_CONFLICT);
3359
3361
  }
3360
3362
  }
3363
+ // A deferred txp matching a broadcasted txp's nonce means its JIT-assigned
3364
+ // value went stale. Non-deferred txps may reuse an in-flight nonce
3365
+ // intentionally (e.g. speed-up).
3366
+ if (txp.nonce != null && txp.deferNonce) {
3367
+ const broadcastedTxps = await util.promisify(this.storage.fetchBroadcastedTxs).call(this.storage, this.walletId, {
3368
+ minTs: Math.floor(Date.now() / 1000) - Defaults.BROADCASTED_NONCE_SCAN_WINDOW
3369
+ });
3370
+ for (const t of broadcastedTxps || []) {
3371
+ if (t.id !== txp.id && t.nonce != null && Number(t.nonce) === Number(txp.nonce)) {
3372
+ return cb(Errors.TX_NONCE_CONFLICT);
3373
+ }
3374
+ }
3375
+ }
3361
3376
  } catch (err) {
3362
3377
  return cb(err);
3363
3378
  }
@@ -3486,9 +3501,18 @@ export class WalletService implements IWalletService {
3486
3501
  .filter(t => t.id !== txp.id && t.nonce != null && t.status !== 'rejected')
3487
3502
  .map(t => Number(t.nonce));
3488
3503
 
3504
+ // 2b. Broadcasted-but-unmined txps still occupy their nonces but appear in
3505
+ // neither the confirmed count nor the pending set
3506
+ const broadcastedTxps = await util.promisify(this.storage.fetchBroadcastedTxs).call(this.storage, this.walletId, {
3507
+ minTs: Math.floor(Date.now() / 1000) - Defaults.BROADCASTED_NONCE_SCAN_WINDOW
3508
+ });
3509
+ const inFlightNonces = (broadcastedTxps || [])
3510
+ .filter(t => t.nonce != null && Number(t.nonce) >= Number(confirmedNonce))
3511
+ .map(t => Number(t.nonce));
3512
+
3489
3513
  // 3. Calculate gap-free nonce
3490
3514
  let suggestedNonce = Number(confirmedNonce);
3491
- const allNonces = pendingNonces.sort((a, b) => a - b);
3515
+ const allNonces = [...pendingNonces, ...inFlightNonces].sort((a, b) => a - b);
3492
3516
  for (const n of allNonces) {
3493
3517
  if (n === suggestedNonce) {
3494
3518
  suggestedNonce++;
@@ -5303,6 +5327,27 @@ export class WalletService implements IWalletService {
5303
5327
  });
5304
5328
  }
5305
5329
 
5330
+ async getFlags(opts: { account: number }) {
5331
+ try {
5332
+ const wallet = await util.promisify(this.getWallet).call(this, {});
5333
+ if (wallet.chain !== 'xrp') throw new Error('Flags are only supported for XRP wallets');
5334
+
5335
+ const addresses = await util.promisify(this.storage.fetchAddresses).call(this.storage, this.walletId);
5336
+ const addressObj = addresses.find(a => a.path === `m/0/${opts.account || 0}`);
5337
+ if (!addressObj) throw new Error('Could not find address for account ' + opts.account);
5338
+
5339
+ const bc = this._getBlockchainExplorer(wallet.chain, wallet.network);
5340
+ if (!bc) throw new Error('Could not get blockchain explorer instance');
5341
+
5342
+ const address = addressObj.address.split(':')[0]; // Remove testnet suffix
5343
+ const flags = await bc.getFlags({ address });
5344
+ return flags;
5345
+ } catch (err) {
5346
+ this.logw('Error getting flags: %o', err);
5347
+ throw err;
5348
+ }
5349
+ }
5350
+
5306
5351
  static upgradeNeeded(
5307
5352
  paths: Upgrade | Upgrade[],
5308
5353
  opts: UpgradeCheckOpts & { clientVersion: string; userAgent: string }
@@ -5567,6 +5612,55 @@ export class WalletService implements IWalletService {
5567
5612
  });
5568
5613
  }
5569
5614
 
5615
+ moralisGetTransactionVerbose(req): Promise<any> {
5616
+ return new Promise(async (resolve, reject) => {
5617
+ try {
5618
+ const response = await Moralis.EvmApi.transaction.getTransactionVerbose({
5619
+ transactionHash: req.body.transactionHash,
5620
+ chain: req.body.chain,
5621
+ });
5622
+
5623
+ return resolve(response?.raw ?? response);
5624
+ } catch (err) {
5625
+ reject(err);
5626
+ }
5627
+ });
5628
+ }
5629
+
5630
+ moralisGetMultipleSolTokenPrices(req): Promise<any> {
5631
+ return new Promise((resolve, reject) => {
5632
+ if (!config.moralis) return reject(new Error('Moralis missing credentials'));
5633
+ if (!checkRequired(req.body, ['addresses']) || !Array.isArray(req.body.addresses)) {
5634
+ return reject(new ClientError('moralisGetMultipleSolTokenPrices request missing arguments'));
5635
+ }
5636
+
5637
+ const network = req.body.network === 'devnet' ? 'devnet' : 'mainnet';
5638
+ const headers = {
5639
+ 'Accept': 'application/json',
5640
+ 'Content-Type': 'application/json',
5641
+ 'X-Api-Key': config.moralis.apiKey,
5642
+ };
5643
+
5644
+ const URL: string = `https://solana-gateway.moralis.io/token/${network}/prices`;
5645
+
5646
+ this.request.post(
5647
+ URL,
5648
+ {
5649
+ headers,
5650
+ body: { addresses: req.body.addresses },
5651
+ json: true
5652
+ },
5653
+ (err, data) => {
5654
+ if (err) {
5655
+ return reject(err.body ?? err);
5656
+ } else {
5657
+ return resolve(data.body ?? data);
5658
+ }
5659
+ }
5660
+ );
5661
+ });
5662
+ }
5663
+
5570
5664
  moralisGetSolWalletPortfolio(req): Promise<any> {
5571
5665
  return new Promise(async (resolve, reject) => {
5572
5666
  let network;
@@ -10,6 +10,27 @@ const should = chai.should();
10
10
  const xpub = 'tpubDD7tYYerLNNm65Ez7pRjxQ2NpRHDoyRWoLWudnQ8agXjr7qs9BsPsRXk8Z6spPPJodnaY158YqeCKT5oXuZvbuNLfm1R4kXGJ2vPd9pUxDT';
11
11
 
12
12
  describe('Chain XRP', function() {
13
+ describe('#getWalletSendMaxInfo', function() {
14
+ it('should resolve fee level before calculating sendMax amount', function(done) {
15
+ const server = {
16
+ getBalance: (_opts, cb) => cb(null, { availableAmount: 1000000 }),
17
+ _getFeePerKb: (_wallet, opts, cb) => {
18
+ opts.feeLevel.should.equal('normal');
19
+ return cb(null, 12);
20
+ }
21
+ };
22
+ const wallet = { chain: 'xrp' };
23
+
24
+ ChainService.get('xrp').getWalletSendMaxInfo(server as any, wallet as any, { feeLevel: 'normal' }, (err, info) => {
25
+ should.not.exist(err);
26
+ info.amount.should.equal(999988);
27
+ info.fee.should.equal(12);
28
+ info.feePerKb.should.equal(12);
29
+ done();
30
+ });
31
+ });
32
+ });
33
+
13
34
  describe('#getBitcoreTx', function() {
14
35
  it('should create a valid bitcore TX', function() {
15
36
  const txp = TxProposal.fromObj(aTXP()) as TxProposal;