@bitpay-labs/bitcore-wallet-service 11.7.0 → 11.7.2

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/README.md CHANGED
@@ -231,6 +231,7 @@ Optional Arguments:
231
231
 
232
232
  - skip: Records to skip from the result (defaults to 0)
233
233
  - limit: Total number of records to return (return all available records if not specified).
234
+ - reverse: Return oldest transactions first.
234
235
 
235
236
  Returns:
236
237
 
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.7.0",
5
+ "version": "11.7.2",
6
6
  "license": "MIT",
7
7
  "main": "ts_build/src/index.js",
8
8
  "types": "./ts_build/src/index.d.ts",
@@ -43,8 +43,8 @@
43
43
  "cp:templates": "cp -r ./templates ./ts_build/templates"
44
44
  },
45
45
  "dependencies": {
46
- "@bitpay-labs/bitcore-tss": "^11.7.0",
47
- "@bitpay-labs/crypto-wallet-core": "^11.7.0",
46
+ "@bitpay-labs/bitcore-tss": "^11.7.2",
47
+ "@bitpay-labs/crypto-wallet-core": "^11.7.2",
48
48
  "@sendgrid/mail": "6.5.4",
49
49
  "abi-decoder": "2.4.0",
50
50
  "async": "0.9.2",
@@ -126,5 +126,5 @@
126
126
  "email": "kjoseph@bitpay.com"
127
127
  }
128
128
  ],
129
- "gitHead": "6c5a1566aa725431db579f3c89a5c9f186bf5a30"
129
+ "gitHead": "d7f26cd2fae506cba9bb8965c5a196b13d8f0586"
130
130
  }
@@ -190,6 +190,12 @@ export class BanxaService {
190
190
  return reject(new ClientError("Banxa's request missing arguments"));
191
191
  }
192
192
 
193
+ if (!req.body.payment_method || req.body.payment_method === 'other') {
194
+ // Workaround to allow older versions of the app to freely choose the payment method on the checkout page when they select "other".
195
+ delete req.body.payment_method_id;
196
+ }
197
+ delete req.body.payment_method;
198
+
193
199
  const UriPath = '/orders';
194
200
  const URL: string = API + UriPath;
195
201
  const auth = this.getBanxaSignature('post', UriPath, API_KEY, SECRET_KEY, req.body);
@@ -161,7 +161,7 @@ export class XrpChain implements IChain {
161
161
  ...txp,
162
162
  tag: _tag ? Number(_tag) : undefined,
163
163
  chain,
164
- nonce: Number(txp.nonce) + Number(index),
164
+ nonce: (txp.nonce != null ? Number(txp.nonce) : 0) + Number(index),
165
165
  recipients: [recepient]
166
166
  });
167
167
  unsignedTxs.push(rawTx);
@@ -1180,12 +1180,14 @@ export class ExpressApp {
1180
1180
  const opts: {
1181
1181
  skip?: number;
1182
1182
  limit?: number;
1183
+ reverse?: boolean;
1183
1184
  includeExtendedInfo?: boolean;
1184
1185
  tokenAddress?: string;
1185
1186
  multisigContractAddress?: string;
1186
1187
  } = {};
1187
1188
  if (req.query.skip) opts.skip = +req.query.skip;
1188
1189
  if (req.query.limit) opts.limit = +req.query.limit;
1190
+ if (req.query.reverse == '1') opts.reverse = true;
1189
1191
  if (req.query.tokenAddress) opts.tokenAddress = req.query.tokenAddress as string;
1190
1192
  if (req.query.multisigContractAddress)
1191
1193
  opts.multisigContractAddress = req.query.multisigContractAddress as string;
package/src/lib/server.ts CHANGED
@@ -3314,6 +3314,11 @@ export class WalletService implements IWalletService {
3314
3314
  if (action) return cb(Errors.COPAYER_VOTED);
3315
3315
  if (!txp.isPending()) return cb(Errors.TX_NOT_PENDING);
3316
3316
 
3317
+ // Client-supplied nonce override
3318
+ if (opts.nonce != null && [...Object.keys(Constants.EVM_CHAINS), 'XRP'].includes(wallet.chain.toUpperCase())) {
3319
+ txp.nonce = Number(opts.nonce);
3320
+ }
3321
+
3317
3322
  if (this._upgradeNeeded(UPGRADES.BCH_schnorr, { signingMethod: txp.signingMethod, supportBchSchnorr: opts.supportBchSchnorr })) {
3318
3323
  return cb(Errors.UPGRADE_NEEDED);
3319
3324
  }
@@ -3407,7 +3412,7 @@ export class WalletService implements IWalletService {
3407
3412
  return cb(null, txp);
3408
3413
  }
3409
3414
 
3410
- if (!Constants.EVM_CHAINS[wallet.chain.toUpperCase()]) {
3415
+ if (!Constants.EVM_CHAINS[wallet.chain.toUpperCase()] && wallet.chain.toUpperCase() !== 'XRP') {
3411
3416
  return cb(null, txp);
3412
3417
  }
3413
3418
 
@@ -4482,6 +4487,13 @@ export class WalletService implements IWalletService {
4482
4487
  next => {
4483
4488
  if (streamData) {
4484
4489
  lastTxs = streamData;
4490
+ if (cacheStatus.tipTxId) {
4491
+ // Stream data can outlive cache promotion, so trim any entries that
4492
+ // are now part of the durable cache before paging.
4493
+ lastTxs = _.takeWhile(lastTxs, (tx: any) => {
4494
+ return tx.txid != cacheStatus.tipTxId;
4495
+ });
4496
+ }
4485
4497
  return next();
4486
4498
  }
4487
4499
 
@@ -4514,6 +4526,24 @@ export class WalletService implements IWalletService {
4514
4526
  });
4515
4527
  },
4516
4528
  next => {
4529
+ if (opts.reverse) {
4530
+ const cachedTxs = _.isNumber(cacheStatus.tipIndex) ? cacheStatus.tipIndex + 1 : 0;
4531
+ const totalTxs = cachedTxs + lastTxs.length;
4532
+ const oldestFirstSkip = skip;
4533
+
4534
+ // Keep the existing newest-first cache math by translating the requested
4535
+ // oldest-first cursor into the equivalent newest-first slice.
4536
+ limit = Math.max(0, Math.min(limit, totalTxs - oldestFirstSkip));
4537
+ skip = Math.max(0, totalTxs - oldestFirstSkip - limit);
4538
+ }
4539
+
4540
+ if (limit === 0) {
4541
+ resultTxs = [];
4542
+ fromCache = false;
4543
+ fromBc = false;
4544
+ return next();
4545
+ }
4546
+
4517
4547
  // Case 1.
4518
4548
  // t -->
4519
4549
  // | Old TXS | ======= LAST TXS ========== \
@@ -4598,6 +4628,9 @@ export class WalletService implements IWalletService {
4598
4628
  ],
4599
4629
  err => {
4600
4630
  if (err) return cb(err);
4631
+ if (opts.reverse) {
4632
+ resultTxs.reverse();
4633
+ }
4601
4634
  return cb(null, {
4602
4635
  items: resultTxs,
4603
4636
  fromCache,
@@ -4615,10 +4648,11 @@ export class WalletService implements IWalletService {
4615
4648
  * @param {Object} opts
4616
4649
  * @param {Number} opts.skip (defaults to 0)
4617
4650
  * @param {Number} opts.limit
4651
+ * @param {Boolean} opts.reverse[=false] - Return oldest transactions first.
4618
4652
  * @param {String} opts.tokenAddress ERC20 Token Contract Address
4619
4653
  * @param {String} opts.multisigContractAddress MULTISIG ETH Contract Address
4620
4654
  * @param {Number} opts.includeExtendedInfo[=false] - Include all inputs/outputs for every tx.
4621
- * @returns {TxProposal[]} Transaction proposals, first newer
4655
+ * @returns {TxProposal[]} Transaction proposals, newer first unless opts.reverse is set
4622
4656
  */
4623
4657
  getTxHistory(opts, cb) {
4624
4658
  opts = opts || {};
@@ -5516,4 +5550,4 @@ export const UPGRADES = {
5516
5550
  bwc_$lt_1_2: 'bwc<1.2',
5517
5551
  } as const;
5518
5552
 
5519
- type Upgrade = typeof UPGRADES[keyof typeof UPGRADES];
5553
+ type Upgrade = typeof UPGRADES[keyof typeof UPGRADES];
@@ -559,6 +559,7 @@ class Helpers {
559
559
  // For eth => account, 0, change = 0
560
560
  const priv = xpriv.derive('m/0/0').privateKey;
561
561
  const privKey = priv.toString('hex');
562
+ const pubKey = priv.publicKey.toString('hex');
562
563
  let tx = ChainService.getBitcoreTx(txp).uncheckedSerialize();
563
564
  const isERC20 = txp.tokenAddress && !txp.payProUrl;
564
565
  const chain = isERC20 ? Utils.getChain(txp.coin) + 'ERC20' : Utils.getChain(txp.coin);
@@ -568,7 +569,7 @@ class Helpers {
568
569
  const signed = Transactions.getSignature({
569
570
  chain: chain.toUpperCase(),
570
571
  tx: rawTx,
571
- key: { privKey: privKey.toString('hex') },
572
+ key: { privKey: privKey.toString('hex'), pubKey },
572
573
  });
573
574
  signatures.push(signed);
574
575
  }
@@ -85,6 +85,158 @@ describe('History', function() {
85
85
  });
86
86
  });
87
87
 
88
+ it('should page from oldest first with reverse and keep skip stable if new txs arrive', async function() {
89
+ const _cache = Defaults.CONFIRMATIONS_TO_START_CACHING;
90
+ (Defaults.CONFIRMATIONS_TO_START_CACHING as any) = 1;
91
+
92
+ try {
93
+ const baseTxs = helpers.createTxsV8(5, BCHEIGHT);
94
+ helpers.stubHistory(null, null, baseTxs);
95
+
96
+ const firstPage = await new Promise<any[]>((resolve, reject) => {
97
+ server.getTxHistory({ limit: 2, reverse: true }, function(err, txs) {
98
+ if (err) return reject(err);
99
+ resolve(txs);
100
+ });
101
+ });
102
+
103
+ firstPage.map(tx => tx.id).should.deep.equal(['id4', 'id3']);
104
+
105
+ const txTemplate = baseTxs[0];
106
+ const updatedTxs = [
107
+ {
108
+ ...txTemplate,
109
+ id: 'newid0',
110
+ txid: 'newtxid0',
111
+ height: -1,
112
+ blockTime: '2018-09-21T18:08:33.000Z'
113
+ },
114
+ {
115
+ ...txTemplate,
116
+ id: 'newid1',
117
+ txid: 'newtxid1',
118
+ height: -1,
119
+ blockTime: '2018-09-21T18:08:32.000Z'
120
+ },
121
+ ...baseTxs
122
+ ];
123
+ helpers.stubHistory(null, null, updatedTxs);
124
+
125
+ const secondPage = await new Promise<any[]>((resolve, reject) => {
126
+ server.getTxHistory({ skip: 2, limit: 2, reverse: true }, function(err, txs) {
127
+ if (err) return reject(err);
128
+ resolve(txs);
129
+ });
130
+ });
131
+
132
+ secondPage.map(tx => tx.id).should.deep.equal(['id2', 'id1']);
133
+ } finally {
134
+ (Defaults.CONFIRMATIONS_TO_START_CACHING as any) = _cache;
135
+ }
136
+ });
137
+
138
+ it('should trim reused stream data against the current cache tip in reverse mode', async function() {
139
+ const _cache = Defaults.CONFIRMATIONS_TO_START_CACHING;
140
+ (Defaults.CONFIRMATIONS_TO_START_CACHING as any) = 1;
141
+
142
+ try {
143
+ const baseTxs = helpers.createTxsV8(4, BCHEIGHT);
144
+ helpers.stubHistory(null, null, baseTxs);
145
+
146
+ await new Promise<void>((resolve, reject) => {
147
+ server.storage.storeTxHistoryCacheV8(
148
+ wallet.id,
149
+ null,
150
+ [
151
+ {
152
+ id: baseTxs[3].id,
153
+ txid: baseTxs[3].txid,
154
+ blockheight: baseTxs[3].height
155
+ }
156
+ ],
157
+ 0,
158
+ err => {
159
+ if (err) return reject(err);
160
+ resolve();
161
+ }
162
+ );
163
+ });
164
+
165
+ const firstPage = await new Promise<{ txs: any[]; useStream: boolean }>((resolve, reject) => {
166
+ server.getTxHistory({ limit: 2, reverse: true }, function(err, txs, _fromCache, useStream) {
167
+ if (err) return reject(err);
168
+ resolve({ txs, useStream });
169
+ });
170
+ });
171
+
172
+ firstPage.useStream.should.equal(false);
173
+ firstPage.txs.map(tx => tx.id).should.deep.equal(['id3', 'id2']);
174
+
175
+ const secondPage = await new Promise<{ txs: any[]; useStream: boolean }>((resolve, reject) => {
176
+ server.getTxHistory({ skip: 2, limit: 2, reverse: true }, function(err, txs, _fromCache, useStream) {
177
+ if (err) return reject(err);
178
+ resolve({ txs, useStream });
179
+ });
180
+ });
181
+
182
+ secondPage.useStream.should.equal(true);
183
+ secondPage.txs.map(tx => tx.id).should.deep.equal(['id1', 'id0']);
184
+ } finally {
185
+ (Defaults.CONFIRMATIONS_TO_START_CACHING as any) = _cache;
186
+ }
187
+ });
188
+
189
+ it('should trim reused stream data against the current cache tip in newest-first mode', async function() {
190
+ const _cache = Defaults.CONFIRMATIONS_TO_START_CACHING;
191
+ (Defaults.CONFIRMATIONS_TO_START_CACHING as any) = 1;
192
+
193
+ try {
194
+ const baseTxs = helpers.createTxsV8(4, BCHEIGHT);
195
+ helpers.stubHistory(null, null, baseTxs);
196
+
197
+ await new Promise<void>((resolve, reject) => {
198
+ server.storage.storeTxHistoryCacheV8(
199
+ wallet.id,
200
+ null,
201
+ [
202
+ {
203
+ id: baseTxs[3].id,
204
+ txid: baseTxs[3].txid,
205
+ blockheight: baseTxs[3].height
206
+ }
207
+ ],
208
+ 0,
209
+ err => {
210
+ if (err) return reject(err);
211
+ resolve();
212
+ }
213
+ );
214
+ });
215
+
216
+ const firstPage = await new Promise<{ txs: any[]; useStream: boolean }>((resolve, reject) => {
217
+ server.getTxHistory({ limit: 2 }, function(err, txs, _fromCache, useStream) {
218
+ if (err) return reject(err);
219
+ resolve({ txs, useStream });
220
+ });
221
+ });
222
+
223
+ firstPage.useStream.should.equal(false);
224
+ firstPage.txs.map(tx => tx.id).should.deep.equal(['id0', 'id1']);
225
+
226
+ const secondPage = await new Promise<{ txs: any[]; useStream: boolean }>((resolve, reject) => {
227
+ server.getTxHistory({ skip: 2, limit: 2 }, function(err, txs, _fromCache, useStream) {
228
+ if (err) return reject(err);
229
+ resolve({ txs, useStream });
230
+ });
231
+ });
232
+
233
+ secondPage.useStream.should.equal(true);
234
+ secondPage.txs.map(tx => tx.id).should.deep.equal(['id2', 'id3']);
235
+ } finally {
236
+ (Defaults.CONFIRMATIONS_TO_START_CACHING as any) = _cache;
237
+ }
238
+ });
239
+
88
240
  it('should filter out DUST amount', function(done) {
89
241
  const txs= helpers.createTxsV8(50, BCHEIGHT);
90
242
  txs[5].satoshis=100;
@@ -1139,4 +1291,3 @@ describe('History', function() {
1139
1291
  });
1140
1292
  });
1141
1293
  });
1142
-
@@ -8134,6 +8134,224 @@ describe('Wallet service', function() {
8134
8134
  });
8135
8135
  });
8136
8136
 
8137
+ describe('#prepareTx XRP (deferred nonce)', function() {
8138
+ const XRP_ADDR = 'rDzTZxa7NwD9vmNf5dvTbW4FQDNSRsfPv6';
8139
+ let server, wallet, fromAddr;
8140
+
8141
+ beforeEach(async function() {
8142
+ ({ server, wallet } = await helpers.createAndJoinWallet(1, 1, { coin: 'xrp' }));
8143
+ const address = await util.promisify(server.createAddress).call(server, {});
8144
+ fromAddr = address.address;
8145
+ await helpers.stubUtxos(server, wallet, [1, 2], { coin: 'xrp' });
8146
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '5');
8147
+ });
8148
+
8149
+ it('should create XRP txp with nonce=null when deferNonce is true', async function() {
8150
+ const txp = await util.promisify(server.createTx).call(server, {
8151
+ outputs: [{ toAddress: XRP_ADDR, amount: 8000 }],
8152
+ feePerKb: 123e2,
8153
+ from: fromAddr,
8154
+ deferNonce: true
8155
+ });
8156
+ should.exist(txp);
8157
+ txp.deferNonce.should.be.true;
8158
+ should.not.exist(txp.nonce);
8159
+ });
8160
+
8161
+ it('should assign nonce to a deferred XRP txp via prepareTx', async function() {
8162
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '10');
8163
+
8164
+ const txp = await helpers.createAndPublishTx(server, {
8165
+ outputs: [{ toAddress: XRP_ADDR, amount: 8000 }],
8166
+ feePerKb: 123e2,
8167
+ from: fromAddr,
8168
+ deferNonce: true
8169
+ }, TestData.copayers[0].privKey_1H_0);
8170
+
8171
+ should.not.exist(txp.nonce);
8172
+
8173
+ const result = await util.promisify(server.prepareTx).call(server, {
8174
+ txProposalId: txp.id
8175
+ });
8176
+ result.nonce.should.equal(10);
8177
+ });
8178
+
8179
+ it('should calculate gap-free nonce for XRP skipping pending nonces', async function() {
8180
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '5');
8181
+
8182
+ const normalTxp = await helpers.createAndPublishTx(server, {
8183
+ outputs: [{ toAddress: XRP_ADDR, amount: 1000 }],
8184
+ feePerKb: 123e2,
8185
+ from: fromAddr
8186
+ }, TestData.copayers[0].privKey_1H_0);
8187
+ normalTxp.nonce.should.equal('5');
8188
+
8189
+ const deferred = await helpers.createAndPublishTx(server, {
8190
+ outputs: [{ toAddress: XRP_ADDR, amount: 2000 }],
8191
+ feePerKb: 123e2,
8192
+ from: fromAddr,
8193
+ deferNonce: true
8194
+ }, TestData.copayers[0].privKey_1H_0);
8195
+
8196
+ const result = await util.promisify(server.prepareTx).call(server, {
8197
+ txProposalId: deferred.id
8198
+ });
8199
+ result.nonce.should.equal(6);
8200
+ });
8201
+
8202
+ it('should sign XRP txp after prepareTx assigns nonce', async function() {
8203
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '7');
8204
+ helpers.stubBroadcast('txid123');
8205
+
8206
+ const txp = await helpers.createAndPublishTx(server, {
8207
+ outputs: [{ toAddress: XRP_ADDR, amount: 8000 }],
8208
+ feePerKb: 123e2,
8209
+ from: fromAddr,
8210
+ deferNonce: true
8211
+ }, TestData.copayers[0].privKey_1H_0);
8212
+
8213
+ const withNonce = await util.promisify(server.prepareTx).call(server, {
8214
+ txProposalId: txp.id
8215
+ });
8216
+ withNonce.nonce.should.equal(7);
8217
+
8218
+ const fetched = await util.promisify(server.getTx).call(server, { txProposalId: txp.id });
8219
+ const signatures = helpers.clientSign(fetched, TestData.copayers[0].xPrivKey_44H_0H_0H);
8220
+ const signed = await util.promisify(server.signTx).call(server, {
8221
+ txProposalId: txp.id,
8222
+ signatures
8223
+ });
8224
+ signed.status.should.equal('accepted');
8225
+ });
8226
+ });
8227
+
8228
+ describe('#signTx nonce override', function() {
8229
+ const ETH_ADDR = '0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A';
8230
+ let server, wallet, fromAddr;
8231
+
8232
+ beforeEach(async function() {
8233
+ ({ server, wallet } = await helpers.createAndJoinWallet(1, 1, { coin: 'eth' }));
8234
+ const address = await util.promisify(server.createAddress).call(server, {});
8235
+ fromAddr = address.address;
8236
+ await helpers.stubUtxos(server, wallet, [1, 2], { coin: 'eth' });
8237
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '5');
8238
+ });
8239
+
8240
+ it('should accept nonce passthrough in signTx', async function() {
8241
+ const txp = await helpers.createAndPublishTx(server, {
8242
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
8243
+ feePerKb: 123e2,
8244
+ from: fromAddr
8245
+ }, TestData.copayers[0].privKey_1H_0);
8246
+ txp.nonce.should.equal('5');
8247
+
8248
+ const signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey_44H_0H_0H);
8249
+ const signed = await util.promisify(server.signTx).call(server, {
8250
+ txProposalId: txp.id,
8251
+ signatures,
8252
+ nonce: Number(txp.nonce)
8253
+ });
8254
+ signed.status.should.equal('accepted');
8255
+ signed.nonce.should.equal(5);
8256
+ });
8257
+
8258
+ it('should accept nonce override in signTx', async function() {
8259
+ const txp = await helpers.createAndPublishTx(server, {
8260
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
8261
+ feePerKb: 123e2,
8262
+ from: fromAddr
8263
+ }, TestData.copayers[0].privKey_1H_0);
8264
+ txp.nonce.should.equal('5');
8265
+
8266
+ const overrideNonce = 99;
8267
+ const fetched = await util.promisify(server.getTx).call(server, { txProposalId: txp.id });
8268
+ fetched.nonce = overrideNonce;
8269
+
8270
+ const signatures = helpers.clientSign(fetched, TestData.copayers[0].xPrivKey_44H_0H_0H);
8271
+ const signed = await util.promisify(server.signTx).call(server, {
8272
+ txProposalId: txp.id,
8273
+ signatures,
8274
+ nonce: overrideNonce
8275
+ });
8276
+ signed.status.should.equal('accepted');
8277
+ signed.nonce.should.equal(overrideNonce);
8278
+ });
8279
+
8280
+ it('should reject nonce override that conflicts with pending txp', async function() {
8281
+ const txp1 = await helpers.createAndPublishTx(server, {
8282
+ outputs: [{ toAddress: ETH_ADDR, amount: 1000 }],
8283
+ feePerKb: 123e2,
8284
+ from: fromAddr
8285
+ }, TestData.copayers[0].privKey_1H_0);
8286
+ txp1.nonce.should.equal('5');
8287
+
8288
+ blockchainExplorer.getTransactionCount = sinon.stub().callsArgWith(1, null, '6');
8289
+ const txp2 = await helpers.createAndPublishTx(server, {
8290
+ outputs: [{ toAddress: ETH_ADDR, amount: 2000 }],
8291
+ feePerKb: 123e2,
8292
+ from: fromAddr
8293
+ }, TestData.copayers[0].privKey_1H_0);
8294
+ txp2.nonce.should.equal('6');
8295
+
8296
+ // sign txp2 with nonce 5 (conflicts with txp1)
8297
+ const fetched = await util.promisify(server.getTx).call(server, { txProposalId: txp2.id });
8298
+ fetched.nonce = 5;
8299
+ const signatures = helpers.clientSign(fetched, TestData.copayers[0].xPrivKey_44H_0H_0H);
8300
+
8301
+ try {
8302
+ await util.promisify(server.signTx).call(server, {
8303
+ txProposalId: txp2.id,
8304
+ signatures,
8305
+ nonce: 5
8306
+ });
8307
+ throw new Error('should have thrown');
8308
+ } catch (err) {
8309
+ err.code.should.equal('TX_NONCE_CONFLICT');
8310
+ }
8311
+ });
8312
+
8313
+ it('should not crash on nonce conflict check when nonce is null', async function() {
8314
+ const txp = await helpers.createAndPublishTx(server, {
8315
+ outputs: [{ toAddress: ETH_ADDR, amount: 8000 }],
8316
+ feePerKb: 123e2,
8317
+ from: fromAddr,
8318
+ deferNonce: true
8319
+ }, TestData.copayers[0].privKey_1H_0);
8320
+ should.not.exist(txp.nonce);
8321
+
8322
+ // null nonce on other txps should not throw in conflict check
8323
+ const fetched = await util.promisify(server.getTx).call(server, { txProposalId: txp.id });
8324
+ fetched.nonce = 99;
8325
+ const signatures = helpers.clientSign(fetched, TestData.copayers[0].xPrivKey_44H_0H_0H);
8326
+ const signed = await util.promisify(server.signTx).call(server, {
8327
+ txProposalId: txp.id,
8328
+ signatures,
8329
+ nonce: 99
8330
+ });
8331
+ signed.status.should.equal('accepted');
8332
+ signed.nonce.should.equal(99);
8333
+ });
8334
+
8335
+ it('should ignore nonce override for non-EVM chains', async function() {
8336
+ const { server: btcServer, wallet: btcWallet } = await helpers.createAndJoinWallet(1, 1);
8337
+ await helpers.stubUtxos(btcServer, btcWallet, [1, 2]);
8338
+
8339
+ const txp = await helpers.createAndPublishTx(btcServer, {
8340
+ outputs: [{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 8000 }],
8341
+ feePerKb: 100e2,
8342
+ }, TestData.copayers[0].privKey_1H_0);
8343
+
8344
+ const signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey_44H_0H_0H);
8345
+ const signed = await util.promisify(btcServer.signTx).call(btcServer, {
8346
+ txProposalId: txp.id,
8347
+ signatures,
8348
+ nonce: 99
8349
+ });
8350
+ signed.status.should.equal('accepted');
8351
+ should.not.exist(signed.nonce);
8352
+ });
8353
+ });
8354
+
8137
8355
  describe('#broadcastTx & #broadcastRawTx', function() {
8138
8356
  let server: WalletService;
8139
8357
  let wallet: Model.Wallet;