@bitpay-labs/bitcore-wallet-service 11.8.2 → 11.10.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 (127) hide show
  1. package/bws.example.config.js +4 -2
  2. package/package.json +4 -4
  3. package/src/config.ts +4 -2
  4. package/src/externalservices/moonpay.ts +85 -1
  5. package/src/lib/blockchainexplorers/v8.ts +1 -1
  6. package/src/lib/chain/eth/index.ts +8 -7
  7. package/src/lib/chain/index.ts +1 -1
  8. package/src/lib/chain/sol/index.ts +4 -4
  9. package/src/lib/chain/xrp/index.ts +3 -3
  10. package/src/lib/common/utils.ts +12 -5
  11. package/src/lib/errors/errordefinitions.ts +2 -0
  12. package/src/lib/expressapp.ts +6 -3
  13. package/src/lib/model/txnote.ts +3 -0
  14. package/src/lib/model/txproposal.ts +153 -68
  15. package/src/lib/model/txproposal_legacy.ts +5 -4
  16. package/src/lib/model/txproposalaction.ts +9 -0
  17. package/src/lib/routes/blockchain.ts +2 -3
  18. package/src/lib/routes/context.ts +10 -1
  19. package/src/lib/routes/maintenance.ts +6 -3
  20. package/src/lib/routes/services.ts +12 -0
  21. package/src/lib/routes/transactions.ts +17 -2
  22. package/src/lib/routes/walletdata.ts +1 -2
  23. package/src/lib/routes/wallets.ts +9 -4
  24. package/src/lib/server.ts +62 -9
  25. package/src/lib/storage.ts +13 -12
  26. package/src/types/expressapp.d.ts +2 -1
  27. package/test/chain/bch.test.ts +8 -1
  28. package/test/chain/btc.test.ts +2 -2
  29. package/test/chain/doge.test.ts +4 -6
  30. package/test/chain/ltc.test.ts +4 -5
  31. package/test/chain/xrp.test.ts +4 -39
  32. package/test/integration/externalservices/moonpay.test.ts +149 -0
  33. package/test/model/txproposal.test.ts +64 -1
  34. package/test/utils.test.ts +29 -0
  35. package/ts_build/package.json +3 -3
  36. package/ts_build/src/config.d.ts.map +1 -1
  37. package/ts_build/src/config.js.map +1 -1
  38. package/ts_build/src/externalservices/moonpay.d.ts +4 -0
  39. package/ts_build/src/externalservices/moonpay.d.ts.map +1 -1
  40. package/ts_build/src/externalservices/moonpay.js +68 -1
  41. package/ts_build/src/externalservices/moonpay.js.map +1 -1
  42. package/ts_build/src/lib/blockchainexplorers/v8.js +1 -1
  43. package/ts_build/src/lib/chain/eth/index.d.ts +6 -5
  44. package/ts_build/src/lib/chain/eth/index.d.ts.map +1 -1
  45. package/ts_build/src/lib/chain/eth/index.js +5 -5
  46. package/ts_build/src/lib/chain/eth/index.js.map +1 -1
  47. package/ts_build/src/lib/chain/index.d.ts +1 -1
  48. package/ts_build/src/lib/chain/index.d.ts.map +1 -1
  49. package/ts_build/src/lib/chain/index.js.map +1 -1
  50. package/ts_build/src/lib/chain/sol/index.js +4 -4
  51. package/ts_build/src/lib/chain/sol/index.js.map +1 -1
  52. package/ts_build/src/lib/chain/xrp/index.js +3 -3
  53. package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
  54. package/ts_build/src/lib/common/utils.d.ts.map +1 -1
  55. package/ts_build/src/lib/common/utils.js +14 -9
  56. package/ts_build/src/lib/common/utils.js.map +1 -1
  57. package/ts_build/src/lib/errors/errordefinitions.d.ts +1 -0
  58. package/ts_build/src/lib/errors/errordefinitions.d.ts.map +1 -1
  59. package/ts_build/src/lib/errors/errordefinitions.js +1 -0
  60. package/ts_build/src/lib/errors/errordefinitions.js.map +1 -1
  61. package/ts_build/src/lib/expressapp.d.ts.map +1 -1
  62. package/ts_build/src/lib/expressapp.js +5 -3
  63. package/ts_build/src/lib/expressapp.js.map +1 -1
  64. package/ts_build/src/lib/model/txnote.d.ts +1 -0
  65. package/ts_build/src/lib/model/txnote.d.ts.map +1 -1
  66. package/ts_build/src/lib/model/txnote.js +1 -0
  67. package/ts_build/src/lib/model/txnote.js.map +1 -1
  68. package/ts_build/src/lib/model/txproposal.d.ts +72 -52
  69. package/ts_build/src/lib/model/txproposal.d.ts.map +1 -1
  70. package/ts_build/src/lib/model/txproposal.js +69 -17
  71. package/ts_build/src/lib/model/txproposal.js.map +1 -1
  72. package/ts_build/src/lib/model/txproposal_legacy.d.ts +3 -3
  73. package/ts_build/src/lib/model/txproposal_legacy.d.ts.map +1 -1
  74. package/ts_build/src/lib/model/txproposal_legacy.js +1 -0
  75. package/ts_build/src/lib/model/txproposal_legacy.js.map +1 -1
  76. package/ts_build/src/lib/model/txproposalaction.d.ts +2 -0
  77. package/ts_build/src/lib/model/txproposalaction.d.ts.map +1 -1
  78. package/ts_build/src/lib/model/txproposalaction.js +2 -0
  79. package/ts_build/src/lib/model/txproposalaction.js.map +1 -1
  80. package/ts_build/src/lib/routes/blockchain.d.ts.map +1 -1
  81. package/ts_build/src/lib/routes/blockchain.js +2 -36
  82. package/ts_build/src/lib/routes/blockchain.js.map +1 -1
  83. package/ts_build/src/lib/routes/context.d.ts +1 -0
  84. package/ts_build/src/lib/routes/context.d.ts.map +1 -1
  85. package/ts_build/src/lib/routes/context.js +8 -1
  86. package/ts_build/src/lib/routes/context.js.map +1 -1
  87. package/ts_build/src/lib/routes/maintenance.d.ts.map +1 -1
  88. package/ts_build/src/lib/routes/maintenance.js +6 -36
  89. package/ts_build/src/lib/routes/maintenance.js.map +1 -1
  90. package/ts_build/src/lib/routes/services.d.ts.map +1 -1
  91. package/ts_build/src/lib/routes/services.js +10 -0
  92. package/ts_build/src/lib/routes/services.js.map +1 -1
  93. package/ts_build/src/lib/routes/transactions.d.ts +1 -0
  94. package/ts_build/src/lib/routes/transactions.d.ts.map +1 -1
  95. package/ts_build/src/lib/routes/transactions.js +14 -2
  96. package/ts_build/src/lib/routes/transactions.js.map +1 -1
  97. package/ts_build/src/lib/routes/walletdata.d.ts.map +1 -1
  98. package/ts_build/src/lib/routes/walletdata.js +1 -35
  99. package/ts_build/src/lib/routes/walletdata.js.map +1 -1
  100. package/ts_build/src/lib/routes/wallets.d.ts +1 -0
  101. package/ts_build/src/lib/routes/wallets.d.ts.map +1 -1
  102. package/ts_build/src/lib/routes/wallets.js +8 -4
  103. package/ts_build/src/lib/routes/wallets.js.map +1 -1
  104. package/ts_build/src/lib/server.d.ts +6 -2
  105. package/ts_build/src/lib/server.d.ts.map +1 -1
  106. package/ts_build/src/lib/server.js +45 -3
  107. package/ts_build/src/lib/server.js.map +1 -1
  108. package/ts_build/src/lib/storage.d.ts +6 -6
  109. package/ts_build/src/lib/storage.d.ts.map +1 -1
  110. package/ts_build/src/lib/storage.js +6 -6
  111. package/ts_build/src/lib/storage.js.map +1 -1
  112. package/ts_build/test/chain/bch.test.js +7 -0
  113. package/ts_build/test/chain/bch.test.js.map +1 -1
  114. package/ts_build/test/chain/btc.test.js +2 -2
  115. package/ts_build/test/chain/btc.test.js.map +1 -1
  116. package/ts_build/test/chain/doge.test.js +3 -5
  117. package/ts_build/test/chain/doge.test.js.map +1 -1
  118. package/ts_build/test/chain/ltc.test.js +3 -4
  119. package/ts_build/test/chain/ltc.test.js.map +1 -1
  120. package/ts_build/test/chain/xrp.test.js +3 -38
  121. package/ts_build/test/chain/xrp.test.js.map +1 -1
  122. package/ts_build/test/integration/externalservices/moonpay.test.js +139 -0
  123. package/ts_build/test/integration/externalservices/moonpay.test.js.map +1 -1
  124. package/ts_build/test/model/txproposal.test.js +57 -0
  125. package/ts_build/test/model/txproposal.test.js.map +1 -1
  126. package/ts_build/test/utils.test.js +23 -0
  127. package/ts_build/test/utils.test.js.map +1 -1
@@ -1,11 +1,13 @@
1
+ import { Utils as CWCUtils } from '@bitpay-labs/crypto-wallet-core';
1
2
  import _ from 'lodash';
2
3
  import { singleton } from 'preconditions';
3
4
  import Uuid from 'uuid';
4
5
  import { ChainService } from '../chain/index';
5
6
  import { Common } from '../common';
6
7
  import logger from '../logger';
7
- import { IAddress } from './address';
8
- import { TxProposalLegacy } from './txproposal_legacy';
8
+ import { type IAddress } from './address';
9
+ import { type ITxNote, TxNote } from './txnote';
10
+ import { type ITxProposalLegacy, TxProposalLegacy } from './txproposal_legacy';
9
11
  import { TxProposalAction } from './txproposalaction';
10
12
 
11
13
  const $ = singleton();
@@ -13,9 +15,9 @@ const { Constants, Defaults, Utils } = Common;
13
15
 
14
16
  type TxProposalStatus = 'temporary' | 'pending' | 'accepted' | 'rejected' | 'broadcasted';
15
17
 
16
- export interface ITxProposal {
18
+ export interface ITxProposal<NumberType = number> {
19
+ version: number;
17
20
  type?: string;
18
- creatorName: string;
19
21
  createdOn: number;
20
22
  txid: string;
21
23
  txids?: Array<string>;
@@ -28,19 +30,20 @@ export interface ITxProposal {
28
30
  message: string;
29
31
  payProUrl?: string;
30
32
  from: string;
31
- changeAddress?: IAddress;
32
- escrowAddress?: IAddress;
33
+ sendMax?: boolean;
34
+ changeAddress?: Partial<IAddress>;
35
+ escrowAddress?: Partial<IAddress>;
33
36
  inputs: any[];
34
37
  outputs: Array<{
35
- amount: number;
36
- address: string;
38
+ amount: NumberType;
39
+ address?: string;
37
40
  toAddress?: string;
38
41
  sourceAddress?: string;
39
42
  message?: string;
40
43
  data?: string;
41
- gasLimit?: number;
44
+ gasLimit?: NumberType;
42
45
  script?: string;
43
- satoshis?: number;
46
+ satoshis?: NumberType;
44
47
  tag?: number;
45
48
  }>;
46
49
  outputOrder: number[];
@@ -51,13 +54,12 @@ export interface ITxProposal {
51
54
  status: TxProposalStatus;
52
55
  actions: any[];
53
56
  feeLevel: string;
54
- feePerKb: number;
57
+ feePerKb: NumberType;
55
58
  excludeUnconfirmedUtxos: boolean;
56
59
  addressType: string;
57
60
  customData: any;
58
- amount: string;
59
- fee: number;
60
- version: number;
61
+ amount: NumberType;
62
+ fee: NumberType;
61
63
  broadcastedOn: number;
62
64
  inputPaths: string | any[];
63
65
  proposalSignature: string;
@@ -65,41 +67,51 @@ export interface ITxProposal {
65
67
  proposalSignaturePubKeySig: string;
66
68
  signingMethod: string;
67
69
  lowFees?: boolean;
68
- nonce?: number | string;
70
+ raw?: Array<string> | string;
71
+ nonce?: NumberType | string;
69
72
  deferNonce?: boolean;
70
- gasPrice?: number;
71
- maxGasFee?: number;
72
- priorityGasFee?: number;
73
+ gasPrice?: NumberType;
74
+ maxGasFee?: NumberType;
75
+ priorityGasFee?: NumberType;
73
76
  txType?: number | string;
74
- gasLimit?: number; // Backward compatibility for BWC <= 8.9.0
77
+ gasLimit?: NumberType; // Backward compatibility for BWC <= 8.9.0
75
78
  data?: string; // Backward compatibility for BWC <= 8.9.0
76
79
  tokenAddress?: string;
77
80
  multisigContractAddress?: string;
81
+ multisigTxId?: string;
78
82
  destinationTag?: string;
79
83
  invoiceID?: string;
80
- lockUntilBlockHeight?: number;
81
- instantAcceptanceEscrow?: number;
84
+ lockUntilBlockHeight?: NumberType;
85
+ instantAcceptanceEscrow?: NumberType;
82
86
  isTokenSwap?: boolean;
87
+ multiSendContractAddress?: string;
83
88
  enableRBF?: boolean;
84
89
  replaceTxByFee?: boolean;
85
90
  multiTx?: boolean; // proposal contains multiple transactions
86
- space?: number;
91
+ space?: NumberType;
87
92
  nonceAddress?: string;
88
93
  blockHash?: string;
89
- blockHeight?: number;
94
+ blockHeight?: NumberType;
90
95
  category?: string;
91
- priorityFee?: number;
92
- computeUnits?: number;
96
+ priorityFee?: NumberType;
97
+ computeUnits?: NumberType;
93
98
  memo?: string;
94
99
  fromAta?: string;
95
100
  decimals?: number;
96
101
  refreshOnPublish?: boolean;
97
102
  prePublishRaw?: string;
103
+
104
+ // Non-persistent fields - populated on fetch
105
+ creatorName: string;
106
+ derivationStrategy?: string;
107
+ note?: ITxNote;
98
108
  }
99
109
 
100
- export class TxProposal implements ITxProposal {
110
+ export type NumberFormat = 'hex' | 'number' | 'string' | 'bigint';
111
+
112
+ export class TxProposal<NumberType = number> implements ITxProposal<NumberType> {
113
+ version: number;
101
114
  type?: string;
102
- creatorName: string;
103
115
  createdOn: number;
104
116
  id: string;
105
117
  txid: string;
@@ -112,19 +124,20 @@ export class TxProposal implements ITxProposal {
112
124
  message: string;
113
125
  payProUrl?: string;
114
126
  from: string;
115
- changeAddress?: IAddress;
116
- escrowAddress?: IAddress;
127
+ sendMax?: boolean;
128
+ changeAddress?: Partial<IAddress>;
129
+ escrowAddress?: Partial<IAddress>;
117
130
  inputs: any[];
118
131
  outputs: Array<{
119
- amount: number;
120
- address: string;
132
+ amount: NumberType;
133
+ address?: string;
121
134
  toAddress?: string;
122
135
  sourceAddress?: string;
123
136
  message?: string;
124
137
  data?: string;
125
- gasLimit?: number;
138
+ gasLimit?: NumberType;
126
139
  script?: string;
127
- satoshis?: number;
140
+ satoshis?: NumberType;
128
141
  tag?: number;
129
142
  }>;
130
143
  outputOrder: number[];
@@ -135,13 +148,12 @@ export class TxProposal implements ITxProposal {
135
148
  status: TxProposalStatus;
136
149
  actions: any[] = [];
137
150
  feeLevel: string;
138
- feePerKb: number;
151
+ feePerKb: NumberType;
139
152
  excludeUnconfirmedUtxos: boolean;
140
153
  addressType: string;
141
154
  customData: any;
142
- amount: string;
143
- fee: number;
144
- version: number;
155
+ amount: NumberType;
156
+ fee: NumberType;
145
157
  broadcastedOn: number;
146
158
  inputPaths: string | any[];
147
159
  proposalSignature: string;
@@ -150,38 +162,43 @@ export class TxProposal implements ITxProposal {
150
162
  signingMethod: string;
151
163
  lowFees?: boolean;
152
164
  raw?: Array<string> | string;
153
- nonce?: number | string;
165
+ nonce?: NumberType;
154
166
  deferNonce?: boolean;
155
- gasPrice?: number;
156
- maxGasFee?: number;
157
- priorityGasFee?: number;
167
+ gasPrice?: NumberType;
168
+ maxGasFee?: NumberType;
169
+ priorityGasFee?: NumberType;
158
170
  txType?: number | string;
159
- gasLimit?: number; // Backward compatibility for BWC <= 8.9.0
171
+ gasLimit?: NumberType; // Backward compatibility for BWC <= 8.9.0
160
172
  data?: string; // Backward compatibility for BWC <= 8.9.0
161
173
  tokenAddress?: string;
162
174
  multisigContractAddress?: string;
163
175
  multisigTxId?: string;
164
176
  destinationTag?: string;
165
177
  invoiceID?: string;
166
- lockUntilBlockHeight?: number;
167
- instantAcceptanceEscrow?: number;
178
+ lockUntilBlockHeight?: NumberType;
179
+ instantAcceptanceEscrow?: NumberType;
168
180
  isTokenSwap?: boolean;
169
181
  multiSendContractAddress?: string;
170
182
  enableRBF?: boolean;
171
183
  replaceTxByFee?: boolean;
172
184
  multiTx?: boolean;
173
- space?: number;
185
+ space?: NumberType;
174
186
  nonceAddress?: string;
175
187
  blockHash?: string;
176
- blockHeight?: number;
188
+ blockHeight?: NumberType;
177
189
  category?: string;
178
- priorityFee?: number;
179
- computeUnits?: number;
190
+ priorityFee?: NumberType;
191
+ computeUnits?: NumberType;
180
192
  memo?: string;
181
193
  fromAta?: string;
182
194
  decimals?: number;
183
195
  refreshOnPublish?: boolean;
184
196
  prePublishRaw?: string;
197
+
198
+ // Non-persistent fields - populated on fetch
199
+ creatorName: string;
200
+ derivationStrategy?: string;
201
+ note?: TxNote;
185
202
 
186
203
  static create(opts) {
187
204
  opts = opts || {};
@@ -211,6 +228,7 @@ export class TxProposal implements ITxProposal {
211
228
  x.signingMethod = opts.signingMethod;
212
229
  x.message = opts.message;
213
230
  x.payProUrl = opts.payProUrl;
231
+ x.sendMax = opts.sendMax;
214
232
  x.changeAddress = opts.changeAddress;
215
233
  x.escrowAddress = opts.escrowAddress;
216
234
  x.instantAcceptanceEscrow = opts.instantAcceptanceEscrow;
@@ -305,6 +323,8 @@ export class TxProposal implements ITxProposal {
305
323
  return x;
306
324
  }
307
325
 
326
+ static fromObj(obj: Partial<ITxProposal>): TxProposal;
327
+ static fromObj(obj: Partial<ITxProposalLegacy>): TxProposalLegacy;
308
328
  static fromObj(obj) {
309
329
  if (!(obj.version >= 3)) {
310
330
  return TxProposalLegacy.fromObj(obj);
@@ -317,6 +337,7 @@ export class TxProposal implements ITxProposal {
317
337
  x.id = obj.id;
318
338
  x.walletId = obj.walletId;
319
339
  x.creatorId = obj.creatorId;
340
+ x.creatorName = obj.creatorName;
320
341
  x.coin = obj.coin || Defaults.COIN;
321
342
  x.chain = obj.chain?.toLowerCase() || Utils.getChain(x.coin); // getChain -> backwards compatibility
322
343
  x.network = obj.network;
@@ -324,6 +345,8 @@ export class TxProposal implements ITxProposal {
324
345
  x.amount = obj.amount;
325
346
  x.message = obj.message;
326
347
  x.payProUrl = obj.payProUrl;
348
+ x.derivationStrategy = obj.derivationStrategy;
349
+ x.sendMax = obj.sendMax;
327
350
  x.changeAddress = obj.changeAddress;
328
351
  x.escrowAddress = obj.escrowAddress;
329
352
  x.instantAcceptanceEscrow = obj.instantAcceptanceEscrow;
@@ -337,9 +360,7 @@ export class TxProposal implements ITxProposal {
337
360
  x.txids = obj.txids;
338
361
  x.broadcastedOn = obj.broadcastedOn;
339
362
  x.inputPaths = obj.inputPaths;
340
- x.actions = _.map(obj.actions, action => {
341
- return TxProposalAction.fromObj(action);
342
- });
363
+ x.actions = (obj.actions || []).map(action => TxProposalAction.fromObj(action));
343
364
  x.outputOrder = obj.outputOrder;
344
365
  x.fee = obj.fee;
345
366
  x.feeLevel = obj.feeLevel;
@@ -410,7 +431,7 @@ export class TxProposal implements ITxProposal {
410
431
 
411
432
  setInputs(inputs) {
412
433
  this.inputs = inputs || [];
413
- this.inputPaths = _.map(inputs, 'path') || [];
434
+ this.inputPaths = this.inputs.map(input => input.path);
414
435
  }
415
436
 
416
437
  _updateStatus() {
@@ -440,8 +461,10 @@ export class TxProposal implements ITxProposal {
440
461
  return signatures;
441
462
  }
442
463
 
443
- getRawTx() {
444
- const t = ChainService.getBitcoreTx(this);
464
+ getRawTx(numberFormat?: NumberFormat) {
465
+ // Casting numberFormat to 'number' is to sidestep TS errors, but is not necessarily true.
466
+ const txp = numberFormat ? TxProposal.formatNumbers(this, numberFormat as 'number') : this as TxProposal<number>;
467
+ const t = ChainService.getBitcoreTx(txp);
445
468
  return t.uncheckedSerialize();
446
469
  }
447
470
 
@@ -451,7 +474,7 @@ export class TxProposal implements ITxProposal {
451
474
  * @return {Number} total amount of all outputs excluding change output
452
475
  */
453
476
  getTotalAmount() {
454
- return Number((this.outputs || []).reduce((total, o) => total += BigInt(o.amount), 0n));
477
+ return Number(((this as TxProposal<number>).outputs || []).reduce((total, o) => total += BigInt(o.amount), 0n));
455
478
  }
456
479
 
457
480
  /**
@@ -460,7 +483,7 @@ export class TxProposal implements ITxProposal {
460
483
  * @return {String[]} copayerIds that performed actions in this proposal (accept / reject)
461
484
  */
462
485
  getActors() {
463
- return _.map(this.actions, 'copayerId');
486
+ return (this.actions || []).map(a => a.copayerId);
464
487
  }
465
488
 
466
489
  /**
@@ -469,12 +492,9 @@ export class TxProposal implements ITxProposal {
469
492
  * @return {String[]} copayerIds that approved the tx proposal (accept)
470
493
  */
471
494
  getApprovers() {
472
- return _.map(
473
- _.filter(this.actions, a => {
474
- return a.type == 'accept';
475
- }),
476
- 'copayerId'
477
- );
495
+ return (this.actions || [])
496
+ .filter(a => a.type == 'accept')
497
+ .map(a => a.copayerId);
478
498
  }
479
499
 
480
500
  /**
@@ -484,9 +504,7 @@ export class TxProposal implements ITxProposal {
484
504
  * @return {Object} type / createdOn
485
505
  */
486
506
  getActionBy(copayerId) {
487
- return _.find(this.actions, {
488
- copayerId
489
- });
507
+ return (this.actions || []).find(a => a.copayerId == copayerId);
490
508
  }
491
509
 
492
510
  addAction(copayerId, type, comment, signatures?, xpub?) {
@@ -501,10 +519,11 @@ export class TxProposal implements ITxProposal {
501
519
  this._updateStatus();
502
520
  }
503
521
 
504
- sign(copayerId, signatures, xpub) {
522
+ sign(copayerId, signatures, xpub, numberFormat?: NumberFormat) {
505
523
  try {
506
- // Tests signatures are OK
507
- const tx = ChainService.getBitcoreTx(this);
524
+ // numberFormat as 'number' is to sidestep TS errors, but is not necessarily true.
525
+ const txp = numberFormat ? TxProposal.formatNumbers(this, numberFormat as 'number') : this as TxProposal<number>;
526
+ const tx = ChainService.getBitcoreTx(txp);
508
527
  ChainService.addSignaturesToBitcoreTx(
509
528
  this.chain,
510
529
  tx,
@@ -570,4 +589,70 @@ export class TxProposal implements ITxProposal {
570
589
  this.status = 'broadcasted';
571
590
  this.broadcastedOn = Math.floor(Date.now() / 1000);
572
591
  }
592
+
593
+
594
+ /**
595
+ * Replaces tx-building number values with the specified number format.
596
+ * This is to ensure consistency across BWC and BWS when handling large numbers, especially for chains like ETH and SOL.
597
+ * @param {TxProposal|ITxProposal} txp Transaction Proposal
598
+ * @param {'number'|'string'|'bigint'|'hex'} numberFormat The desired number format for the tx-building values. Can be 'number', 'string', 'bigint', or 'hex'.
599
+ */
600
+ static formatNumbers<T>(txp: TxProposal<T>, numberFormat: 'string'): TxProposal<string>;
601
+ static formatNumbers<T>(txp: TxProposal<T>, numberFormat: 'hex'): TxProposal<string>;
602
+ static formatNumbers<T>(txp: TxProposal<T>, numberFormat: 'bigint'): TxProposal<bigint>;
603
+ static formatNumbers<T>(txp: TxProposal<T>, numberFormat: 'number'): TxProposal<number>;
604
+ static formatNumbers<T>(txp: ITxProposal<T>, numberFormat: 'string'): ITxProposal<string>;
605
+ static formatNumbers<T>(txp: ITxProposal<T>, numberFormat: 'hex'): ITxProposal<string>;
606
+ static formatNumbers<T>(txp: ITxProposal<T>, numberFormat: 'bigint'): ITxProposal<bigint>;
607
+ static formatNumbers<T>(txp: ITxProposal<T>, numberFormat: 'number'): ITxProposal<number>;
608
+ static formatNumbers<T>(txp: TxProposal<T> | ITxProposal<T>, numberFormat: NumberFormat = 'number') {
609
+ let convertFn;
610
+ switch (numberFormat) {
611
+ case 'number':
612
+ convertFn = parseInt;
613
+ break;
614
+ case 'string':
615
+ convertFn = (n) => typeof n === 'string' && n.startsWith('0x') ? BigInt(n).toString() : n.toString();
616
+ break;
617
+ case 'hex':
618
+ convertFn = (n) => CWCUtils.toHex(n);
619
+ break;
620
+ case 'bigint':
621
+ convertFn = (n) => BigInt(n);
622
+ break;
623
+ default:
624
+ logger.warn(`Invalid numberFormat: ${numberFormat}, no conversion will be applied to tx proposal ${txp.id}`);
625
+ return txp;
626
+ }
627
+
628
+ const primitiveTypes = new Set(['number', 'string', 'bigint']);
629
+ const convert = (key, value) => {
630
+ if ((numberFormat === 'hex' || typeof value !== numberFormat) && primitiveTypes.has(typeof value)) {
631
+ try {
632
+ value = convertFn(value);
633
+ } catch (e) {
634
+ logger.warn(`Failed to convert ${txp.id} > ${key} with value ${value} to ${numberFormat}: ${e.message}`);
635
+ }
636
+ }
637
+ return value;
638
+ };
639
+
640
+ const _txp = txp instanceof TxProposal ? TxProposal.fromObj(txp.toObject()) : TxProposal.fromObj(txp as ITxProposal<number>).toObject();
641
+
642
+ const topKeys = ['amount', 'feePerKb', 'fee', 'nonce', 'gasPrice', 'maxGasFee', 'priorityGasFee', 'gasLimit', 'lockUntilBlockHeight', 'instantAcceptanceEscrow', 'space', 'blockHeight', 'computeUnits', 'decimals'];
643
+ for (const key of topKeys) {
644
+ const value = _txp[key];
645
+ _txp[key] = convert(key, value);
646
+ }
647
+
648
+ const outputKeys = ['amount', 'gasLimit', 'satoshis'];
649
+ for (let i = 0; i < _txp.outputs.length; i++) {
650
+ for (const key of outputKeys) {
651
+ const value = _txp.outputs[i][key];
652
+ _txp.outputs[i][key] = convert(`output.${i}.${key}`, value);
653
+ }
654
+ }
655
+
656
+ return _txp;
657
+ }
573
658
  }
@@ -15,7 +15,7 @@ function throwUnsupportedError() {
15
15
  throw new Error(msg);
16
16
  }
17
17
 
18
- export interface ITxProposal {
18
+ export interface ITxProposalLegacy {
19
19
  version: string;
20
20
  type: string;
21
21
  createdOn: number;
@@ -35,7 +35,7 @@ export interface ITxProposal {
35
35
  walletN: number;
36
36
  status: string;
37
37
  txid: string;
38
- broadcastedOn: string;
38
+ broadcastedOn: number;
39
39
  inputPaths: string;
40
40
  actions: any[];
41
41
  outputOrder: number;
@@ -49,8 +49,9 @@ export interface ITxProposal {
49
49
  addressType: string;
50
50
  derivationStrategy: string;
51
51
  customData: any;
52
- }
53
- export class TxProposalLegacy {
52
+ };
53
+
54
+ export class TxProposalLegacy implements ITxProposalLegacy {
54
55
  version: string;
55
56
  type: string;
56
57
  createdOn: number;
@@ -6,6 +6,9 @@ export interface ITxProposalAction {
6
6
  signatures: string[];
7
7
  xpub: string;
8
8
  comment: string;
9
+
10
+ // Non-persistent fields
11
+ copayerName?: string;
9
12
  }
10
13
  export class TxProposalAction {
11
14
  version: string;
@@ -16,6 +19,9 @@ export class TxProposalAction {
16
19
  xpub: string;
17
20
  comment: string;
18
21
 
22
+ // Non-persistent fields
23
+ copayerName?: string;
24
+
19
25
  static create(opts) {
20
26
  opts = opts || {};
21
27
 
@@ -43,6 +49,9 @@ export class TxProposalAction {
43
49
  x.xpub = obj.xpub;
44
50
  x.comment = obj.comment;
45
51
 
52
+ // copayerName is not stored in the actions collection, but it is returned by the server on fetchTxProposal, so we need to set it here.
53
+ x.copayerName = obj.copayerName;
54
+
46
55
  return x;
47
56
  }
48
57
  }
@@ -1,5 +1,4 @@
1
1
  import express from 'express';
2
- import * as _ from 'lodash';
3
2
  import type * as Types from '../../types/expressapp';
4
3
  import type { WalletService } from '../server';
5
4
 
@@ -74,10 +73,10 @@ export function registerBlockchainRoutes(router: express.Router, context: RouteC
74
73
  if (req.query.network) opts.network = req.query.network as string;
75
74
  server.getFeeLevels(opts, resolve);
76
75
  }).then((feeLevels: any[]) => {
77
- _.each(feeLevels, feeLevel => {
76
+ for (const feeLevel of feeLevels) {
78
77
  feeLevel.feePerKB = feeLevel.feePerKb;
79
78
  delete feeLevel.feePerKb;
80
- });
79
+ }
81
80
  return feeLevels;
82
81
  })
83
82
  );
@@ -10,6 +10,7 @@ interface RouteHelpers {
10
10
  getServerWithMultiAuth: Types.GetServerWithMultiAuthFn;
11
11
  logDeprecated: Types.LogDeprecatedFn;
12
12
  setPublicCache: (res: express.Response, seconds: number) => void;
13
+ checkNumberFormat: Types.CheckNumberFormatFn;
13
14
  }
14
15
 
15
16
  export function createRouteHelpers(returnError: Types.ReturnErrorFn): RouteHelpers {
@@ -139,11 +140,19 @@ export function createRouteHelpers(returnError: Types.ReturnErrorFn): RouteHelpe
139
140
  res.setHeader('Cache-Control', `public, max-age=${seconds}, stale-if-error=${10 * seconds}`);
140
141
  };
141
142
 
143
+ const checkNumberFormat = (numberFormat: string | undefined, res: express.Response) => {
144
+ const validFormats = ['hex', 'string', 'number']; // bigint cannot be serialized to JSON, so it's not supported in the API response
145
+ if (numberFormat && !validFormats.includes(numberFormat)) {
146
+ return returnError(Errors.INVALID_NUMBER_FORMAT, res, null);
147
+ }
148
+ };
149
+
142
150
  return {
143
151
  getServer,
144
152
  getServerWithAuth,
145
153
  getServerWithMultiAuth,
146
154
  logDeprecated,
147
- setPublicCache
155
+ setPublicCache,
156
+ checkNumberFormat
148
157
  };
149
158
  }
@@ -1,5 +1,4 @@
1
1
  import express from 'express';
2
- import * as _ from 'lodash';
3
2
  import { WalletService } from '../server';
4
3
  import { Stats } from '../stats';
5
4
  import type * as Types from '../../types/expressapp';
@@ -84,8 +83,12 @@ export function registerMaintenanceRoutes(router: express.Router, context: Route
84
83
  router.get('/v1/txnotes/', (req, res) => {
85
84
  getServerWithAuth(req, res, server => {
86
85
  const opts: { minTs?: number } = {};
87
- if (req.query.minTs && _.isNumber(+req.query.minTs)) {
88
- opts.minTs = +req.query.minTs;
86
+ const rawMinTs = req.query.minTs;
87
+ if (typeof rawMinTs === 'string' && rawMinTs.trim() !== '') {
88
+ const minTs = Number(rawMinTs);
89
+ if (Number.isFinite(minTs)) {
90
+ opts.minTs = minTs;
91
+ }
89
92
  }
90
93
  server.getTxNotes(opts, (err, notes) => {
91
94
  if (err) return returnError(err, res, req);
@@ -281,6 +281,18 @@ export function registerServiceRoutes(router: express.Router, context: RouteCont
281
281
  });
282
282
  });
283
283
 
284
+ router.post('/v1/service/moonpay/createSession', (req, res) => {
285
+ respondWithAuthServer(req, res, context, server => {
286
+ return server.externalServices.moonpay.moonpayCreateSession(req);
287
+ });
288
+ });
289
+
290
+ router.post('/v1/service/moonpay/revokeActiveSession', (req, res) => {
291
+ respondWithAuthServer(req, res, context, server => {
292
+ return server.externalServices.moonpay.moonpayRevokeActiveSession(req);
293
+ });
294
+ });
295
+
284
296
  router.post('/v1/service/ramp/quote', (req, res) => {
285
297
  respondWithAuthServer(req, res, context, server => {
286
298
  return server.externalServices.ramp.rampGetQuote(req);
@@ -5,10 +5,11 @@ import type * as Types from '../../types/expressapp';
5
5
  interface RouteContext {
6
6
  getServerWithAuth: Types.GetServerWithAuthFn;
7
7
  returnError: Types.ReturnErrorFn;
8
+ checkNumberFormat: Types.CheckNumberFormatFn;
8
9
  }
9
10
 
10
11
  export function registerTransactionRoutes(router: express.Router, context: RouteContext) {
11
- const { getServerWithAuth, returnError } = context;
12
+ const { getServerWithAuth, returnError, checkNumberFormat } = context;
12
13
 
13
14
  router.get('/v1/txproposals/', (req, res) => {
14
15
  getServerWithAuth(req, res, server => {
@@ -21,7 +22,13 @@ export function registerTransactionRoutes(router: express.Router, context: Route
21
22
 
22
23
  router.get('/v2/txproposals/', (req, res) => {
23
24
  getServerWithAuth(req, res, server => {
24
- server.getPendingTxs({}, (err, pendings) => {
25
+ checkNumberFormat(req.query.numberFormat, res);
26
+
27
+ const opts = {
28
+ numberFormat: req.query.numberFormat,
29
+ };
30
+
31
+ server.getPendingTxs(opts, (err, pendings) => {
25
32
  if (err) return returnError(err, res, req);
26
33
  res.json(pendings);
27
34
  });
@@ -45,7 +52,9 @@ export function registerTransactionRoutes(router: express.Router, context: Route
45
52
 
46
53
  router.post('/v3/txproposals/', (req, res) => {
47
54
  getServerWithAuth(req, res, server => {
55
+ checkNumberFormat(req.query.numberFormat, res);
48
56
  req.body.txpVersion = 3;
57
+ req.body.numberFormat = req.query.numberFormat;
49
58
  server.createTx(req.body, (err, txp) => {
50
59
  if (err) return returnError(err, res, req);
51
60
  res.json(txp);
@@ -67,9 +76,11 @@ export function registerTransactionRoutes(router: express.Router, context: Route
67
76
 
68
77
  router.post('/v2/txproposals/:id/signatures/', (req, res) => {
69
78
  getServerWithAuth(req, res, server => {
79
+ checkNumberFormat(req.query.numberFormat, res);
70
80
  req.body.txProposalId = req.params['id'];
71
81
  req.body.maxTxpVersion = 3;
72
82
  req.body.supportBchSchnorr = true;
83
+ req.body.numberFormat = req.query.numberFormat;
73
84
  server.signTx(req.body, (err, txp) => {
74
85
  if (err) return returnError(err, res, req);
75
86
  res.json(txp);
@@ -80,7 +91,9 @@ export function registerTransactionRoutes(router: express.Router, context: Route
80
91
 
81
92
  router.post('/v1/txproposals/:id/prepare/', (req, res) => {
82
93
  getServerWithAuth(req, res, server => {
94
+ checkNumberFormat(req.query.numberFormat, res);
83
95
  req.body.txProposalId = req.params['id'];
96
+ req.body.numberFormat = req.query.numberFormat;
84
97
  server.prepareTx(req.body, (err, txp) => {
85
98
  if (err) return returnError(err, res, req);
86
99
  res.json(txp);
@@ -103,7 +116,9 @@ export function registerTransactionRoutes(router: express.Router, context: Route
103
116
 
104
117
  router.post('/v2/txproposals/:id/publish/', (req, res) => {
105
118
  getServerWithAuth(req, res, server => {
119
+ checkNumberFormat(req.query.numberFormat, res);
106
120
  req.body.txProposalId = req.params['id'];
121
+ req.body.numberFormat = req.query.numberFormat;
107
122
  server.publishTx(req.body, (err, txp) => {
108
123
  if (err) return returnError(err, res, req);
109
124
  res.json(txp);
@@ -1,5 +1,4 @@
1
1
  import express from 'express';
2
- import * as _ from 'lodash';
3
2
  import { Common } from '../common';
4
3
  import type * as Types from '../../types/expressapp';
5
4
  import type { GetAddressesOpts } from '../../types/server';
@@ -133,7 +132,7 @@ export function registerWalletDataRoutes(router: express.Router, context: RouteC
133
132
  router.get('/v1/utxos/', (req, res) => {
134
133
  const opts: { addresses?: string[] } = {};
135
134
  const addresses = req.query.addresses;
136
- if (addresses && _.isString(addresses)) {
135
+ if (addresses && typeof addresses === 'string') {
137
136
  opts.addresses = (req.query.addresses as string).split(',');
138
137
  }
139
138