@bitpay-labs/bitcore-wallet-service 11.6.5 → 11.7.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/package.json +22 -11
  2. package/src/externalservices/moonpay.ts +3 -0
  3. package/src/lib/blockchainexplorers/v8.ts +42 -0
  4. package/src/lib/chain/eth/index.ts +3 -2
  5. package/src/lib/expressapp.ts +25 -0
  6. package/src/lib/model/txproposal.ts +8 -0
  7. package/src/lib/routes/aave.ts +48 -0
  8. package/src/lib/routes/tss.ts +2 -2
  9. package/src/lib/server.ts +183 -6
  10. package/src/lib/tss.ts +9 -4
  11. package/test/expressapp.test.ts +170 -0
  12. package/test/integration/server.test.ts +333 -0
  13. package/test/v8.test.ts +103 -0
  14. package/ts_build/package.json +22 -11
  15. package/ts_build/src/externalservices/banxa.d.ts.map +1 -1
  16. package/ts_build/src/externalservices/banxa.js +0 -1
  17. package/ts_build/src/externalservices/banxa.js.map +1 -1
  18. package/ts_build/src/externalservices/changelly.d.ts +2 -2
  19. package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
  20. package/ts_build/src/externalservices/moonpay.js +6 -0
  21. package/ts_build/src/externalservices/moonpay.js.map +1 -1
  22. package/ts_build/src/lib/blockchainexplorers/v8.d.ts +12 -0
  23. package/ts_build/src/lib/blockchainexplorers/v8.d.ts.map +1 -1
  24. package/ts_build/src/lib/blockchainexplorers/v8.js +39 -0
  25. package/ts_build/src/lib/blockchainexplorers/v8.js.map +1 -1
  26. package/ts_build/src/lib/chain/eth/index.d.ts.map +1 -1
  27. package/ts_build/src/lib/chain/eth/index.js +3 -2
  28. package/ts_build/src/lib/chain/eth/index.js.map +1 -1
  29. package/ts_build/src/lib/expressapp.d.ts.map +1 -1
  30. package/ts_build/src/lib/expressapp.js +23 -0
  31. package/ts_build/src/lib/expressapp.js.map +1 -1
  32. package/ts_build/src/lib/model/txproposal.d.ts +3 -0
  33. package/ts_build/src/lib/model/txproposal.d.ts.map +1 -1
  34. package/ts_build/src/lib/model/txproposal.js +6 -0
  35. package/ts_build/src/lib/model/txproposal.js.map +1 -1
  36. package/ts_build/src/lib/routes/aave.d.ts +12 -0
  37. package/ts_build/src/lib/routes/aave.d.ts.map +1 -0
  38. package/ts_build/src/lib/routes/aave.js +47 -0
  39. package/ts_build/src/lib/routes/aave.js.map +1 -0
  40. package/ts_build/src/lib/routes/tss.js +2 -2
  41. package/ts_build/src/lib/routes/tss.js.map +1 -1
  42. package/ts_build/src/lib/server.d.ts +5 -0
  43. package/ts_build/src/lib/server.d.ts.map +1 -1
  44. package/ts_build/src/lib/server.js +131 -6
  45. package/ts_build/src/lib/server.js.map +1 -1
  46. package/ts_build/src/lib/tss.d.ts +1 -0
  47. package/ts_build/src/lib/tss.d.ts.map +1 -1
  48. package/ts_build/src/lib/tss.js +6 -2
  49. package/ts_build/src/lib/tss.js.map +1 -1
  50. package/ts_build/test/expressapp.test.js +162 -0
  51. package/ts_build/test/expressapp.test.js.map +1 -1
  52. package/ts_build/test/integration/server.test.js +281 -0
  53. package/ts_build/test/integration/server.test.js.map +1 -1
  54. package/ts_build/test/v8.test.js +89 -0
  55. package/ts_build/test/v8.test.js.map +1 -1
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.6.5",
5
+ "version": "11.7.0",
6
6
  "license": "MIT",
7
7
  "main": "ts_build/src/index.js",
8
8
  "types": "./ts_build/src/index.d.ts",
@@ -24,10 +24,9 @@
24
24
  "scripts": {
25
25
  "start": "npm run clean && npm run build && ./start.sh",
26
26
  "stop": "./stop.sh",
27
- "coverage": "npm run compile && ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --reporter spec test",
28
- "test": "npm run compile && mocha --exit -r tsx 'test/**/*.test.ts'",
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
29
  "test:ci": "BWS_LOG_LEVEL=none npm run test",
30
- "coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage",
31
30
  "build": "tsc && npm run cp:templates",
32
31
  "build:prod": "tsc -p tsconfig.prod.json && npm run cp:templates",
33
32
  "build:docker": "docker build -t bitpay/bitcore-wallet-service:latest -f ./Dockerfile ../..",
@@ -44,8 +43,8 @@
44
43
  "cp:templates": "cp -r ./templates ./ts_build/templates"
45
44
  },
46
45
  "dependencies": {
47
- "@bitpay-labs/bitcore-tss": "^11.6.4",
48
- "@bitpay-labs/crypto-wallet-core": "^11.6.4",
46
+ "@bitpay-labs/bitcore-tss": "^11.7.0",
47
+ "@bitpay-labs/crypto-wallet-core": "^11.7.0",
49
48
  "@sendgrid/mail": "6.5.4",
50
49
  "abi-decoder": "2.4.0",
51
50
  "async": "0.9.2",
@@ -55,7 +54,6 @@
55
54
  "express": "4.19.2",
56
55
  "express-rate-limit": "2.14.2",
57
56
  "inherits": "2.0.4",
58
- "istanbul": "0.4.5",
59
57
  "json-stable-stringify": "1.0.1",
60
58
  "juice": "^11.0.1",
61
59
  "lodash": "4.17.19",
@@ -87,15 +85,28 @@
87
85
  "@types/nodemailer": "^4.6.6",
88
86
  "@types/request": "^2.48.1",
89
87
  "chai": "^5.2.0",
90
- "coveralls": "^3.0.2",
91
88
  "memdown": "^1.0.0",
92
89
  "mocha": "^5.2.0",
93
- "mocha-lcov-reporter": "^1.3.0",
90
+ "nyc": "^17.1.0",
94
91
  "sinon": "^7.1.1",
95
92
  "supertest": "*",
96
- "tsx": "^4.21.0",
97
93
  "typescript": "^5.7.3"
98
94
  },
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
+ },
99
110
  "bitcoreNode": "./bitcorenode",
100
111
  "contributors": [
101
112
  {
@@ -115,5 +126,5 @@
115
126
  "email": "kjoseph@bitpay.com"
116
127
  }
117
128
  ],
118
- "gitHead": "a1890ef287c9e5d0e966fb445d15bc59a9393761"
129
+ "gitHead": "6c5a1566aa725431db579f3c89a5c9f186bf5a30"
119
130
  }
@@ -214,6 +214,9 @@ export class MoonpayService {
214
214
  qs.push('externalTransactionId=' + encodeURIComponent(req.body.externalTransactionId));
215
215
  qs.push('redirectURL=' + encodeURIComponent(req.body.redirectURL));
216
216
  if (req.body.lockAmount) qs.push('lockAmount=' + encodeURIComponent(req.body.lockAmount));
217
+ if (req.body.colorCode) qs.push('colorCode=' + encodeURIComponent(req.body.colorCode));
218
+ if (req.body.theme) qs.push('theme=' + encodeURIComponent(req.body.theme));
219
+ if (req.body.language) qs.push('language=' + encodeURIComponent(req.body.language));
217
220
  if (req.body.showWalletAddressForm)
218
221
  qs.push('showWalletAddressForm=' + encodeURIComponent(req.body.showWalletAddressForm));
219
222
  if (req.body.paymentMethod) qs.push('paymentMethod=' + encodeURIComponent(req.body.paymentMethod));
@@ -486,6 +486,48 @@ export class V8 {
486
486
  });
487
487
  }
488
488
 
489
+ getAaveUserAccountData(opts: { address: string; version?: string }, cb) {
490
+ const url = this.baseUrl + '/aave/account/' + opts.address + '?version=' + (opts.version || 'v3');
491
+ logger.debug('[v8.js] GETTING AAVE USER ACCOUNT DATA %o', url);
492
+ this.request
493
+ .get(url, {})
494
+ .then(accountData => {
495
+ accountData = JSON.parse(accountData);
496
+ return cb(null, accountData);
497
+ })
498
+ .catch(err => {
499
+ return cb(err);
500
+ });
501
+ }
502
+
503
+ getAaveReserveData(opts: { asset: string; version?: string }, cb) {
504
+ const url = this.baseUrl + '/aave/reserve/' + opts.asset + '?version=' + (opts.version || 'v3');
505
+ logger.debug('[v8.js] GETTING AAVE RESERVE DATA %o', url);
506
+ this.request
507
+ .get(url, {})
508
+ .then(reserveData => {
509
+ reserveData = JSON.parse(reserveData);
510
+ return cb(null, reserveData);
511
+ })
512
+ .catch(err => {
513
+ return cb(err);
514
+ });
515
+ }
516
+
517
+ getAaveReserveTokensAddresses(opts: { asset: string; version?: string }, cb) {
518
+ const url = this.baseUrl + '/aave/reserve-tokens/' + opts.asset + '?version=' + (opts.version || 'v3');
519
+ logger.debug('[v8.js] GETTING AAVE RESERVE TOKENS ADDRESSES %o', url);
520
+ this.request
521
+ .get(url, {})
522
+ .then(tokensAddresses => {
523
+ tokensAddresses = JSON.parse(tokensAddresses);
524
+ return cb(null, tokensAddresses);
525
+ })
526
+ .catch(err => {
527
+ return cb(err);
528
+ });
529
+ }
530
+
489
531
  getMultisigTxpsInfo(opts: { multisigContractAddress: string }, cb) {
490
532
  const url = this.baseUrl + '/ethmultisig/txps/' + opts.multisigContractAddress;
491
533
  logger.debug('[v8.js] CHECKING CONTRACT TXPS INFO %o', url);
@@ -317,9 +317,10 @@ export class EthChain implements IChain {
317
317
  }
318
318
  const unsignedTxs = [];
319
319
 
320
+ const nonceNum = txp.nonce != null ? Number(txp.nonce) : 0;
320
321
  if (multiSendContractAddress) {
321
322
  const multiSendParams = {
322
- nonce: Number(txp.nonce),
323
+ nonce: nonceNum,
323
324
  recipients,
324
325
  contractAddress: multiSendContractAddress
325
326
  };
@@ -330,7 +331,7 @@ export class EthChain implements IChain {
330
331
  // Uses gas limit from the txp output level
331
332
  const params = {
332
333
  ...recipients[index],
333
- nonce: Number(txp.nonce) + Number(index),
334
+ nonce: nonceNum + Number(index),
334
335
  recipients: [recipients[index]]
335
336
  };
336
337
  unsignedTxs.push(Transactions.create({ ...txp, chain, ...params }));
@@ -13,6 +13,7 @@ import { Common } from './common';
13
13
  import { ClientError } from './errors/clienterror';
14
14
  import { Errors } from './errors/errordefinitions';
15
15
  import { logger, transports } from './logger';
16
+ import { AaveRouter } from './routes/aave';
16
17
  import { error } from './routes/helpers';
17
18
  import { createWalletLimiter } from './routes/middleware/createWalletLimiter';
18
19
  import { LogMiddleware } from './routes/middleware/log';
@@ -973,6 +974,16 @@ export class ExpressApp {
973
974
  });
974
975
  });
975
976
 
977
+ router.post('/v1/token/allowance', async (req, res) => {
978
+ try {
979
+ const server = getServer(req, res);
980
+ const allowance = await server.getTokenAllowance(req.body);
981
+ res.json(allowance);
982
+ } catch (err) {
983
+ returnError(err, res, req);
984
+ }
985
+ });
986
+
976
987
  router.get('/v1/sendmaxinfo/', (req, res) => {
977
988
  getServerWithAuth(req, res, server => {
978
989
  const q = req.query;
@@ -1069,6 +1080,19 @@ export class ExpressApp {
1069
1080
  });
1070
1081
  */
1071
1082
 
1083
+ router.post('/v1/txproposals/:id/prepare/', (req, res) => {
1084
+ getServerWithAuth(req, res, server => {
1085
+ req.body.txProposalId = req.params['id'];
1086
+ server.prepareTx(req.body, (err, txp) => {
1087
+ if (err) return returnError(err, res, req);
1088
+ res.json(txp);
1089
+ res.end();
1090
+ });
1091
+ });
1092
+ });
1093
+
1094
+
1095
+
1072
1096
  //
1073
1097
  router.post('/v1/txproposals/:id/publish/', (req, res) => {
1074
1098
  getServerWithAuth(req, res, server => {
@@ -2391,6 +2415,7 @@ export class ExpressApp {
2391
2415
  });
2392
2416
 
2393
2417
  /** Imported routes */
2418
+ router.use(new AaveRouter({ returnError, getServer }).router);
2394
2419
  router.use(new TssRouter({ returnError, opts }).router);
2395
2420
 
2396
2421
 
@@ -66,6 +66,7 @@ export interface ITxProposal {
66
66
  signingMethod: string;
67
67
  lowFees?: boolean;
68
68
  nonce?: number | string;
69
+ deferNonce?: boolean;
69
70
  gasPrice?: number;
70
71
  maxGasFee?: number;
71
72
  priorityGasFee?: number;
@@ -150,6 +151,7 @@ export class TxProposal implements ITxProposal {
150
151
  lowFees?: boolean;
151
152
  raw?: Array<string> | string;
152
153
  nonce?: number | string;
154
+ deferNonce?: boolean;
153
155
  gasPrice?: number;
154
156
  maxGasFee?: number;
155
157
  priorityGasFee?: number;
@@ -273,6 +275,7 @@ export class TxProposal implements ITxProposal {
273
275
  x.txType = opts.txType;
274
276
  x.from = opts.from;
275
277
  x.nonce = opts.nonce;
278
+ x.deferNonce = opts.deferNonce;
276
279
  x.gasLimit = opts.gasLimit; // Backward compatibility for BWC <= 8.9.0
277
280
  x.data = opts.data; // Backward compatibility for BWC <= 8.9.0
278
281
  x.tokenAddress = opts.tokenAddress;
@@ -363,6 +366,7 @@ export class TxProposal implements ITxProposal {
363
366
  x.txType = obj.txType;
364
367
  x.from = obj.from;
365
368
  x.nonce = obj.nonce;
369
+ x.deferNonce = obj.deferNonce;
366
370
  x.gasLimit = obj.gasLimit; // Backward compatibility for BWC <= 8.9.0
367
371
  x.data = obj.data; // Backward compatibility for BWC <= 8.9.0
368
372
  x.tokenAddress = obj.tokenAddress;
@@ -531,6 +535,10 @@ export class TxProposal implements ITxProposal {
531
535
  return !!this.refreshOnPublish;
532
536
  }
533
537
 
538
+ hasMutableTxData() {
539
+ return this.isRepublishEnabled() || !!this.deferNonce;
540
+ }
541
+
534
542
  isTemporary() {
535
543
  return this.status === 'temporary';
536
544
  }
@@ -0,0 +1,48 @@
1
+ import express from 'express';
2
+ import * as Types from '../../types/expressapp';
3
+
4
+ interface AaveRouterOpts {
5
+ returnError: Types.ReturnErrorFn;
6
+ getServer: Types.GetServerFn;
7
+ }
8
+
9
+ export class AaveRouter {
10
+ router: express.Router;
11
+
12
+ constructor(params: AaveRouterOpts) {
13
+ const { returnError, getServer } = params;
14
+ const router = express.Router();
15
+
16
+ router.post('/v1/service/aave/userAccountData', async (req, res) => {
17
+ try {
18
+ const server = getServer(req, res);
19
+ const accountData = await server.getAaveUserAccountData(req.body);
20
+ res.json(accountData);
21
+ } catch (err) {
22
+ returnError(err, res, req);
23
+ }
24
+ });
25
+
26
+ router.post('/v1/service/aave/reserveData', async (req, res) => {
27
+ try {
28
+ const server = getServer(req, res);
29
+ const reserveData = await server.getAaveReserveData(req.body);
30
+ res.json(reserveData);
31
+ } catch (err) {
32
+ returnError(err, res, req);
33
+ }
34
+ });
35
+
36
+ router.post('/v1/service/aave/reserveTokensAddresses', async (req, res) => {
37
+ try {
38
+ const server = getServer(req, res);
39
+ const tokensAddresses = await server.getAaveReserveTokensAddresses(req.body);
40
+ res.json(tokensAddresses);
41
+ } catch (err) {
42
+ returnError(err, res, req);
43
+ }
44
+ });
45
+
46
+ this.router = router;
47
+ }
48
+ }
@@ -100,8 +100,8 @@ export class TssRouter {
100
100
  try {
101
101
  const { id, round } = req.params as { [key: string]: string };
102
102
  const copayerId = req.headers['x-identity'];
103
- const { messages, signature } = await TssSign.getMessagesForParty({ id, round: parseInt(round), copayerId });
104
- return res.json({ messages, signature });
103
+ const { messages, signature, participants } = await TssSign.getMessagesForParty({ id, round: parseInt(round), copayerId });
104
+ return res.json({ messages, signature, participants });
105
105
  } catch (err) {
106
106
  return returnError(err ?? 'unknown', res, req);
107
107
  }
package/src/lib/server.ts CHANGED
@@ -2559,6 +2559,120 @@ export class WalletService implements IWalletService {
2559
2559
  });
2560
2560
  }
2561
2561
 
2562
+ /**
2563
+ * Get ERC20 token allowance for a given owner/spender pair.
2564
+ * @param {Object} opts
2565
+ * @param {string} opts.chain - EVM chain name (e.g. 'eth', 'matic')
2566
+ * @param {string} opts.network - Network name (e.g. 'mainnet', 'sepolia')
2567
+ * @param {string} opts.tokenAddress - Token contract address
2568
+ * @param {string} opts.ownerAddress - Token owner address
2569
+ * @param {string} opts.spenderAddress - Spender contract address
2570
+ * @returns {Promise<number>} Token allowance amount
2571
+ */
2572
+ getTokenAllowance(opts) {
2573
+ if (!checkRequired(opts, ['chain', 'network', 'tokenAddress', 'ownerAddress', 'spenderAddress'])) {
2574
+ return Promise.reject(new ClientError('getTokenAllowance request missing arguments'));
2575
+ }
2576
+ const bc = this._getBlockchainExplorer(opts.chain, opts.network);
2577
+ return new Promise((resolve, reject) => {
2578
+ if (!bc) return reject(new Error('Could not get blockchain explorer instance'));
2579
+ bc.getTokenAllowance(opts, (err, allowance) => {
2580
+ if (err) {
2581
+ this.logw('Error getting token allowance:', err);
2582
+ return reject(err);
2583
+ }
2584
+ return resolve(allowance);
2585
+ });
2586
+ });
2587
+ }
2588
+
2589
+ /**
2590
+ * Get Aave user account data (health factor, collateral, debt, etc.).
2591
+ * @param {Object} opts
2592
+ * @param {string} opts.chain - EVM chain name (e.g. 'eth', 'matic')
2593
+ * @param {string} opts.network - Network name (e.g. 'mainnet', 'sepolia')
2594
+ * @param {string} opts.address - User wallet address
2595
+ * @param {string} [opts.version='v3'] - Aave protocol version ('v2' or 'v3')
2596
+ * @returns {Promise<Object>} Aave account data (totalCollateralBase, totalDebtBase, healthFactor, etc.)
2597
+ */
2598
+ getAaveUserAccountData(opts) {
2599
+ if (!checkRequired(opts, ['chain', 'network', 'address'])) {
2600
+ return Promise.reject(new ClientError('getAaveUserAccountData request missing arguments'));
2601
+ }
2602
+ if (opts.version && !['v2', 'v3'].includes(opts.version)) {
2603
+ return Promise.reject(new ClientError('Invalid Aave version. Supported: v2, v3'));
2604
+ }
2605
+ const bc = this._getBlockchainExplorer(opts.chain, opts.network);
2606
+ return new Promise((resolve, reject) => {
2607
+ if (!bc) return reject(new Error('Could not get blockchain explorer instance'));
2608
+ bc.getAaveUserAccountData(opts, (err, accountData) => {
2609
+ if (err) {
2610
+ this.logw('Error getting Aave user account data:', err);
2611
+ return reject(err);
2612
+ }
2613
+ return resolve(accountData);
2614
+ });
2615
+ });
2616
+ }
2617
+
2618
+ /**
2619
+ * Get Aave reserve data (variable borrow rate, etc.).
2620
+ * @param {Object} opts
2621
+ * @param {string} opts.chain - EVM chain name (e.g. 'eth', 'matic')
2622
+ * @param {string} opts.network - Network name (e.g. 'mainnet', 'sepolia')
2623
+ * @param {string} opts.asset - Reserve asset token address
2624
+ * @param {string} [opts.version='v3'] - Aave protocol version ('v2' or 'v3')
2625
+ * @returns {Promise<Object>} Aave reserve data (currentVariableBorrowRate, etc.)
2626
+ */
2627
+ getAaveReserveData(opts) {
2628
+ if (!checkRequired(opts, ['chain', 'network', 'asset'])) {
2629
+ return Promise.reject(new ClientError('getAaveReserveData request missing arguments'));
2630
+ }
2631
+ if (opts.version && !['v2', 'v3'].includes(opts.version)) {
2632
+ return Promise.reject(new ClientError('Invalid Aave version. Supported: v2, v3'));
2633
+ }
2634
+ const bc = this._getBlockchainExplorer(opts.chain, opts.network);
2635
+ return new Promise((resolve, reject) => {
2636
+ if (!bc) return reject(new Error('Could not get blockchain explorer instance'));
2637
+ bc.getAaveReserveData(opts, (err, reserveData) => {
2638
+ if (err) {
2639
+ this.logw('Error getting Aave reserve data:', err);
2640
+ return reject(err);
2641
+ }
2642
+ return resolve(reserveData);
2643
+ });
2644
+ });
2645
+ }
2646
+
2647
+ /**
2648
+ * Get Aave reserve token addresses (aToken, variableDebtToken, etc.).
2649
+ * @param {Object} opts
2650
+ * @param {string} opts.chain - EVM chain name (e.g. 'eth', 'matic')
2651
+ * @param {string} opts.network - Network name (e.g. 'mainnet', 'sepolia')
2652
+ * @param {string} opts.asset - Reserve asset token address
2653
+ * @param {string} [opts.version='v3'] - Aave protocol version ('v2' or 'v3')
2654
+ * @returns {Promise<Object>} Aave reserve token addresses (variableDebtTokenAddress, etc.)
2655
+ */
2656
+ getAaveReserveTokensAddresses(opts) {
2657
+ if (!checkRequired(opts, ['chain', 'network', 'asset'])) {
2658
+ return Promise.reject(new ClientError('getAaveReserveTokensAddresses request missing arguments'));
2659
+ }
2660
+ if (opts.version && !['v2', 'v3'].includes(opts.version)) {
2661
+ return Promise.reject(new ClientError('Invalid Aave version. Supported: v2, v3'));
2662
+ }
2663
+ const bc = this._getBlockchainExplorer(opts.chain, opts.network);
2664
+ return new Promise((resolve, reject) => {
2665
+ if (!bc) return reject(new Error('Could not get blockchain explorer instance'));
2666
+ bc.getAaveReserveTokensAddresses(opts, (err, tokensAddresses) => {
2667
+ if (err) {
2668
+ this.logw('Error getting Aave reserve tokens addresses:', err);
2669
+ return reject(err);
2670
+ }
2671
+ return resolve(tokensAddresses);
2672
+ });
2673
+ });
2674
+ }
2675
+
2562
2676
  getMultisigTxpsInfo(opts) {
2563
2677
  const bc = this._getBlockchainExplorer(opts.chain || Defaults.EVM_CHAIN, opts.network);
2564
2678
  return new Promise((resolve, reject) => {
@@ -2694,7 +2808,7 @@ export class WalletService implements IWalletService {
2694
2808
  },
2695
2809
  async next => {
2696
2810
  // SOL is skipped since its a non necessary field that is expected to be provided by the client.
2697
- if (!opts.nonce && !Constants.SVM_CHAINS[wallet.chain.toUpperCase()]) {
2811
+ if (!opts.nonce && !Constants.SVM_CHAINS[wallet.chain.toUpperCase()] && !opts.deferNonce) {
2698
2812
  try {
2699
2813
  opts.nonce = await ChainService.getTransactionCount(this, wallet, opts.from);
2700
2814
  } catch (error) {
@@ -2794,7 +2908,8 @@ export class WalletService implements IWalletService {
2794
2908
  memo: opts.memo,
2795
2909
  fromAta: opts.fromAta,
2796
2910
  decimals: opts.decimals,
2797
- refreshOnPublish: opts.refreshOnPublish
2911
+ refreshOnPublish: opts.refreshOnPublish,
2912
+ deferNonce: opts.deferNonce
2798
2913
  };
2799
2914
  txp = TxProposal.create(txOpts);
2800
2915
  next();
@@ -2906,7 +3021,7 @@ export class WalletService implements IWalletService {
2906
3021
  this.storage.fetchTx(this.walletId, opts.txProposalId, (err, txp) => {
2907
3022
  if (err) return cb(err);
2908
3023
  if (!txp) return cb(Errors.TX_NOT_FOUND);
2909
- if (!txp.isTemporary() && !txp.isRepublishEnabled()) return cb(null, txp);
3024
+ if (!txp.isTemporary() && !txp.hasMutableTxData()) return cb(null, txp);
2910
3025
 
2911
3026
  const copayer = wallet.getCopayer(this.copayerId);
2912
3027
 
@@ -2920,7 +3035,7 @@ export class WalletService implements IWalletService {
2920
3035
  let signingKey = this._getSigningKey(raw, opts.proposalSignature, copayer.requestPubKeys);
2921
3036
  if (!signingKey) {
2922
3037
  // If the txp has been published previously, we will verify the signature against the previously published raw tx
2923
- if (txp.isRepublishEnabled() && txp.prePublishRaw) {
3038
+ if (txp.hasMutableTxData() && txp.prePublishRaw) {
2924
3039
  raw = txp.prePublishRaw;
2925
3040
  signingKey = this._getSigningKey(raw, opts.proposalSignature, copayer.requestPubKeys);
2926
3041
  }
@@ -2943,7 +3058,7 @@ export class WalletService implements IWalletService {
2943
3058
  txp.status = 'pending';
2944
3059
  ChainService.refreshTxData(this, txp, opts, (err, txp) => {
2945
3060
  if (err) return cb(err);
2946
- if (txp.isRepublishEnabled() && !txp.prePublishRaw) {
3061
+ if (txp.hasMutableTxData() && !txp.prePublishRaw) {
2947
3062
  // We save the original raw transaction for verification on republish
2948
3063
  txp.prePublishRaw = raw;
2949
3064
  }
@@ -3207,7 +3322,7 @@ export class WalletService implements IWalletService {
3207
3322
  try {
3208
3323
  const txps = await this.getPendingTxsPromise({});
3209
3324
  for (const t of txps) {
3210
- if (t.id !== txp.id && t.nonce <= txp.nonce && t.status !== 'rejected') {
3325
+ if (t.id !== txp.id && t.nonce != null && txp.nonce != null && t.nonce <= txp.nonce && t.status !== 'rejected') {
3211
3326
  return cb(Errors.TX_NONCE_CONFLICT);
3212
3327
  }
3213
3328
  }
@@ -3268,6 +3383,68 @@ export class WalletService implements IWalletService {
3268
3383
  });
3269
3384
  }
3270
3385
 
3386
+ /**
3387
+ * Prepare a transaction proposal for signing.
3388
+ * Assigns JIT values (nonce, and in the future: fee, gas) to a deferred txp.
3389
+ * Called by the client just before signing.
3390
+ * @param {Object} opts
3391
+ * @param {string} opts.txProposalId - The identifier of the transaction.
3392
+ */
3393
+ prepareTx(opts, cb) {
3394
+ if (!checkRequired(opts, ['txProposalId'], cb)) return;
3395
+
3396
+ this._runLocked(cb, cb => {
3397
+ this.getWallet({}, (err, wallet) => {
3398
+ if (err) return cb(err);
3399
+
3400
+ this.storage.fetchTx(this.walletId, opts.txProposalId, async (err, txp) => {
3401
+ if (err) return cb(err);
3402
+ if (!txp) return cb(Errors.TX_NOT_FOUND);
3403
+ if (!txp.isPending()) return cb(Errors.TX_NOT_PENDING);
3404
+
3405
+ if (!txp.deferNonce) {
3406
+ // Not a deferred-nonce txp. Return it as-is
3407
+ return cb(null, txp);
3408
+ }
3409
+
3410
+ if (!Constants.EVM_CHAINS[wallet.chain.toUpperCase()]) {
3411
+ return cb(null, txp);
3412
+ }
3413
+
3414
+ try {
3415
+ // 1. Get confirmed nonce from blockchain
3416
+ const confirmedNonce = await ChainService.getTransactionCount(this, wallet, txp.from);
3417
+
3418
+ // 2. Get pending TXP nonces from BWS's own database
3419
+ const pendingTxps = await this.getPendingTxsPromise({});
3420
+ const pendingNonces = pendingTxps
3421
+ .filter(t => t.id !== txp.id && t.nonce != null && t.status !== 'rejected')
3422
+ .map(t => Number(t.nonce));
3423
+
3424
+ // 3. Calculate gap-free nonce
3425
+ let suggestedNonce = Number(confirmedNonce);
3426
+ const allNonces = pendingNonces.sort((a, b) => a - b);
3427
+ for (const n of allNonces) {
3428
+ if (n === suggestedNonce) {
3429
+ suggestedNonce++;
3430
+ }
3431
+ }
3432
+
3433
+ txp.nonce = suggestedNonce;
3434
+
3435
+ // 4. Store the updated txp
3436
+ this.storage.storeTx(this.walletId, txp, err => {
3437
+ if (err) return cb(err);
3438
+ return cb(null, txp);
3439
+ });
3440
+ } catch (err) {
3441
+ return cb(err);
3442
+ }
3443
+ });
3444
+ });
3445
+ });
3446
+ }
3447
+
3271
3448
  _processBroadcast(txp, opts, cb) {
3272
3449
  $.checkState(txp.txid, 'Failed state: txp.txid undefined at <_processBroadcast()>');
3273
3450
  opts = opts || {};
package/src/lib/tss.ts CHANGED
@@ -279,9 +279,9 @@ class TssKeyGenClass {
279
279
  export const TssKeyGen = new TssKeyGenClass();
280
280
 
281
281
  class TssSignClass {
282
- async getMessagesForParty(params: { id: string; round: number; copayerId: string }): Promise<{ messages?: ITssSigMessageObject[]; signature?: ITssSigMessageObject['signature'] }> {
282
+ async getMessagesForParty(params: { id: string; round: number; copayerId: string }): Promise<{ messages?: ITssSigMessageObject[]; signature?: ITssSigMessageObject['signature']; participants?: string[] }> {
283
283
  const { id, round, copayerId } = params;
284
-
284
+
285
285
  const storage = WalletService.getStorage();
286
286
  const session = await storage.fetchTssSigSession({ id });
287
287
  if (!session) {
@@ -297,14 +297,19 @@ class TssSignClass {
297
297
  }
298
298
 
299
299
  const otherPartyMsgs = session.rounds[round].filter(m => m.fromPartyId != party.partyId);
300
+ const participants = otherPartyMsgs.map(m => {
301
+ const p = session.participants.find(p => p.partyId === m.fromPartyId);
302
+ return p?.copayerId;
303
+ }).filter(Boolean) as string[];
304
+
300
305
  if (otherPartyMsgs.length === session.m - 1) {
301
306
  const messages = otherPartyMsgs.map(m => m.messages);
302
307
  for (const m of messages) {
303
308
  m.p2pMessages = m.p2pMessages.filter(m => m.to == party.partyId);
304
309
  }
305
- return { messages, signature: session.signature };
310
+ return { messages, signature: session.signature, participants };
306
311
  }
307
- return {};
312
+ return { participants };
308
313
  }
309
314
 
310
315
  async processMessage(params: { id: string; message: ITssSigMessageObject; m?: string | number; copayerId: string }) {