@bitpay-labs/bitcore-wallet-service 11.7.1 → 11.7.3

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 (33) hide show
  1. package/README.md +1 -0
  2. package/package.json +4 -4
  3. package/src/externalservices/banxa.ts +6 -0
  4. package/src/lib/chain/xrp/index.ts +1 -1
  5. package/src/lib/expressapp.ts +2 -0
  6. package/src/lib/server.ts +32 -3
  7. package/test/integration/helpers.ts +2 -1
  8. package/test/integration/history.test.ts +152 -1
  9. package/test/integration/server.test.ts +91 -0
  10. package/test/txhistory.paging.test.ts +166 -0
  11. package/ts_build/package.json +4 -4
  12. package/ts_build/src/externalservices/banxa.d.ts.map +1 -1
  13. package/ts_build/src/externalservices/banxa.js +4 -0
  14. package/ts_build/src/externalservices/banxa.js.map +1 -1
  15. package/ts_build/src/lib/chain/xrp/index.js +1 -1
  16. package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
  17. package/ts_build/src/lib/expressapp.d.ts.map +1 -1
  18. package/ts_build/src/lib/expressapp.js +2 -0
  19. package/ts_build/src/lib/expressapp.js.map +1 -1
  20. package/ts_build/src/lib/server.d.ts.map +1 -1
  21. package/ts_build/src/lib/server.js +22 -1
  22. package/ts_build/src/lib/server.js.map +1 -1
  23. package/ts_build/test/integration/helpers.d.ts.map +1 -1
  24. package/ts_build/test/integration/helpers.js +2 -1
  25. package/ts_build/test/integration/helpers.js.map +1 -1
  26. package/ts_build/test/integration/history.test.js +130 -0
  27. package/ts_build/test/integration/history.test.js.map +1 -1
  28. package/ts_build/test/integration/server.test.js +76 -0
  29. package/ts_build/test/integration/server.test.js.map +1 -1
  30. package/ts_build/test/txhistory.paging.test.d.ts +2 -0
  31. package/ts_build/test/txhistory.paging.test.d.ts.map +1 -0
  32. package/ts_build/test/txhistory.paging.test.js +162 -0
  33. package/ts_build/test/txhistory.paging.test.js.map +1 -0
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.1",
5
+ "version": "11.7.3",
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.3",
47
+ "@bitpay-labs/crypto-wallet-core": "^11.7.3",
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": "51eda38e01a792cd8dda45db1a520ff459e2ecae"
129
+ "gitHead": "a1890ef287c9e5d0e966fb445d15bc59a9393761"
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
@@ -3412,7 +3412,7 @@ export class WalletService implements IWalletService {
3412
3412
  return cb(null, txp);
3413
3413
  }
3414
3414
 
3415
- if (!Constants.EVM_CHAINS[wallet.chain.toUpperCase()]) {
3415
+ if (!Constants.EVM_CHAINS[wallet.chain.toUpperCase()] && wallet.chain.toUpperCase() !== 'XRP') {
3416
3416
  return cb(null, txp);
3417
3417
  }
3418
3418
 
@@ -4487,6 +4487,13 @@ export class WalletService implements IWalletService {
4487
4487
  next => {
4488
4488
  if (streamData) {
4489
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
+ }
4490
4497
  return next();
4491
4498
  }
4492
4499
 
@@ -4519,6 +4526,24 @@ export class WalletService implements IWalletService {
4519
4526
  });
4520
4527
  },
4521
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
+
4522
4547
  // Case 1.
4523
4548
  // t -->
4524
4549
  // | Old TXS | ======= LAST TXS ========== \
@@ -4603,6 +4628,9 @@ export class WalletService implements IWalletService {
4603
4628
  ],
4604
4629
  err => {
4605
4630
  if (err) return cb(err);
4631
+ if (opts.reverse) {
4632
+ resultTxs.reverse();
4633
+ }
4606
4634
  return cb(null, {
4607
4635
  items: resultTxs,
4608
4636
  fromCache,
@@ -4620,10 +4648,11 @@ export class WalletService implements IWalletService {
4620
4648
  * @param {Object} opts
4621
4649
  * @param {Number} opts.skip (defaults to 0)
4622
4650
  * @param {Number} opts.limit
4651
+ * @param {Boolean} opts.reverse[=false] - Return oldest transactions first.
4623
4652
  * @param {String} opts.tokenAddress ERC20 Token Contract Address
4624
4653
  * @param {String} opts.multisigContractAddress MULTISIG ETH Contract Address
4625
4654
  * @param {Number} opts.includeExtendedInfo[=false] - Include all inputs/outputs for every tx.
4626
- * @returns {TxProposal[]} Transaction proposals, first newer
4655
+ * @returns {TxProposal[]} Transaction proposals, newer first unless opts.reverse is set
4627
4656
  */
4628
4657
  getTxHistory(opts, cb) {
4629
4658
  opts = opts || {};
@@ -5521,4 +5550,4 @@ export const UPGRADES = {
5521
5550
  bwc_$lt_1_2: 'bwc<1.2',
5522
5551
  } as const;
5523
5552
 
5524
- 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,97 @@ 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
+
8137
8228
  describe('#signTx nonce override', function() {
8138
8229
  const ETH_ADDR = '0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A';
8139
8230
  let server, wallet, fromAddr;
@@ -0,0 +1,166 @@
1
+ 'use strict';
2
+
3
+ import * as chai from 'chai';
4
+ import 'chai/register-should';
5
+ import { WalletService } from '../src/lib/server';
6
+
7
+ chai.should();
8
+
9
+ type TxItem = {
10
+ id: string;
11
+ txid: string;
12
+ confirmations: number;
13
+ blockheight: number;
14
+ };
15
+
16
+ function makeTx(id: number): TxItem {
17
+ return {
18
+ id: `id${id}`,
19
+ txid: `txid${id}`,
20
+ confirmations: 100 - id,
21
+ blockheight: 1000 - id
22
+ };
23
+ }
24
+
25
+ function callGetTxHistoryV8(service: WalletService, bc, wallet, opts, skip: number, limit: number) {
26
+ return new Promise<any>((resolve, reject) => {
27
+ service.getTxHistoryV8(bc, wallet, opts, skip, limit, (err, res) => {
28
+ if (err) return reject(err);
29
+ resolve(res);
30
+ });
31
+ });
32
+ }
33
+
34
+ describe('TxHistory Paging', function() {
35
+ function buildService(initialCacheNewest: TxItem[], getBcNewest: () => TxItem[]) {
36
+ let cacheNewest = initialCacheNewest.slice();
37
+ let streamKey: string | null = null;
38
+ let streamItems: TxItem[] | null = null;
39
+
40
+ const service = Object.create(WalletService.prototype) as WalletService & {
41
+ storage: any;
42
+ syncWallet: any;
43
+ _getBlockchainHeight: any;
44
+ _normalizeTxHistory: any;
45
+ userAgent: string;
46
+ };
47
+
48
+ service.userAgent = 'txhistory-test-agent';
49
+ service.syncWallet = (_wallet, next) => next();
50
+ service._getBlockchainHeight = (_chain, _network, cb) => cb(null, 1000, 'test-hash');
51
+ service._normalizeTxHistory = (_walletCacheKey, _txs, _dustThreshold, _bcHeight, cb) => cb(null, getBcNewest());
52
+ service.storage = {
53
+ getTxHistoryCacheStatusV8: (_walletCacheKey, cb) =>
54
+ cb(null, {
55
+ updatedHeight: 0,
56
+ tipIndex: cacheNewest.length ? cacheNewest.length - 1 : null,
57
+ tipTxId: cacheNewest.length ? cacheNewest[0].txid : null,
58
+ tipHeight: cacheNewest.length ? cacheNewest[0].blockheight : null
59
+ }),
60
+ getTxHistoryStreamV8: (_walletCacheKey, cb) =>
61
+ cb(null, streamKey && streamItems ? { streamKey, items: streamItems.slice() } : null),
62
+ clearTxHistoryStreamV8: (_walletCacheKey, cb) => {
63
+ streamKey = null;
64
+ streamItems = null;
65
+ cb();
66
+ },
67
+ storeTxHistoryStreamV8: (_walletCacheKey, inStreamKey, items, cb) => {
68
+ streamKey = inStreamKey;
69
+ streamItems = items.slice();
70
+ cb();
71
+ },
72
+ getTxHistoryCacheV8: (_walletCacheKey, skip, limit, cb) => cb(null, cacheNewest.slice(skip, skip + limit)),
73
+ storeTxHistoryCacheV8: (_walletCacheKey, _tipIndex, items, _updateHeight, cb) => {
74
+ cacheNewest = items.concat(cacheNewest);
75
+ cb();
76
+ }
77
+ };
78
+
79
+ const bc = {
80
+ getTransactions: (_wallet, _startBlock, cb) => cb(null, [])
81
+ };
82
+ const wallet = {
83
+ id: 'wallet1',
84
+ chain: 'btc',
85
+ network: 'livenet'
86
+ };
87
+
88
+ return { service, bc, wallet };
89
+ }
90
+
91
+ it('should preserve newest-first skip behavior by default', async function() {
92
+ const cacheNewest = [makeTx(5), makeTx(6), makeTx(7)];
93
+ const bcNewest = [makeTx(0), makeTx(1), makeTx(2), makeTx(3), makeTx(4)];
94
+ const { service, bc, wallet } = buildService(cacheNewest, () => bcNewest);
95
+
96
+ const result = await callGetTxHistoryV8(service, bc, wallet, {}, 2, 2);
97
+
98
+ result.items.map(tx => tx.id).should.deep.equal(['id2', 'id3']);
99
+ });
100
+
101
+ it('should return oldest-first pages when reverse is set', async function() {
102
+ const cacheNewest = [makeTx(5), makeTx(6), makeTx(7)];
103
+ const bcNewest = [makeTx(0), makeTx(1), makeTx(2), makeTx(3), makeTx(4)];
104
+ const { service, bc, wallet } = buildService(cacheNewest, () => bcNewest);
105
+
106
+ const result = await callGetTxHistoryV8(service, bc, wallet, { reverse: true }, 0, 2);
107
+
108
+ result.items.map(tx => tx.id).should.deep.equal(['id7', 'id6']);
109
+ });
110
+
111
+ it('should keep reverse skip stable when new transactions arrive at the tip', async function() {
112
+ const cacheNewest = [makeTx(5), makeTx(6), makeTx(7)];
113
+ let bcNewest = [makeTx(0), makeTx(1), makeTx(2), makeTx(3), makeTx(4)];
114
+ const { service, bc, wallet } = buildService(cacheNewest, () => bcNewest);
115
+
116
+ const firstPage = await callGetTxHistoryV8(service, bc, wallet, { reverse: true }, 0, 2);
117
+ firstPage.items.map(tx => tx.id).should.deep.equal(['id7', 'id6']);
118
+
119
+ bcNewest = [
120
+ {
121
+ id: 'newid0',
122
+ txid: 'newtxid0',
123
+ confirmations: 0,
124
+ blockheight: -1
125
+ },
126
+ {
127
+ id: 'newid1',
128
+ txid: 'newtxid1',
129
+ confirmations: 0,
130
+ blockheight: -1
131
+ },
132
+ ...bcNewest
133
+ ];
134
+
135
+ const secondPage = await callGetTxHistoryV8(service, bc, wallet, { reverse: true }, 2, 2);
136
+ secondPage.items.map(tx => tx.id).should.deep.equal(['id5', 'id4']);
137
+ });
138
+
139
+ it('should trim streamed txs that were promoted into cache before reverse paging', async function() {
140
+ const cacheNewest = [makeTx(3)];
141
+ const bcNewest = [makeTx(0), makeTx(1), makeTx(2), makeTx(3)];
142
+ const { service, bc, wallet } = buildService(cacheNewest, () => bcNewest);
143
+
144
+ const firstPage = await callGetTxHistoryV8(service, bc, wallet, { reverse: true }, 0, 2);
145
+ firstPage.useStream.should.equal(false);
146
+ firstPage.items.map(tx => tx.id).should.deep.equal(['id3', 'id2']);
147
+
148
+ const secondPage = await callGetTxHistoryV8(service, bc, wallet, { reverse: true }, 2, 2);
149
+ secondPage.useStream.should.equal(true);
150
+ secondPage.items.map(tx => tx.id).should.deep.equal(['id1', 'id0']);
151
+ });
152
+
153
+ it('should trim streamed txs that were promoted into cache before newest-first paging', async function() {
154
+ const cacheNewest = [makeTx(3)];
155
+ const bcNewest = [makeTx(0), makeTx(1), makeTx(2), makeTx(3)];
156
+ const { service, bc, wallet } = buildService(cacheNewest, () => bcNewest);
157
+
158
+ const firstPage = await callGetTxHistoryV8(service, bc, wallet, {}, 0, 2);
159
+ firstPage.useStream.should.equal(false);
160
+ firstPage.items.map(tx => tx.id).should.deep.equal(['id0', 'id1']);
161
+
162
+ const secondPage = await callGetTxHistoryV8(service, bc, wallet, {}, 2, 2);
163
+ secondPage.useStream.should.equal(true);
164
+ secondPage.items.map(tx => tx.id).should.deep.equal(['id2', 'id3']);
165
+ });
166
+ });
@@ -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": "a1890ef287c9e5d0e966fb445d15bc59a9393761"
130
130
  }
@@ -1 +1 @@
1
- {"version":3,"file":"banxa.d.ts","sourceRoot":"","sources":["../../../src/externalservices/banxa.ts"],"names":[],"mappings":"AAOA,qBAAa,YAAY;IACvB,OAAO,EAAE,GAAG,CAAW;IAEvB,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,iBAAiB;IAsBzB,aAAa,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAwChC,sBAAsB,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAqCzC,aAAa,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IA+ChC,gBAAgB,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAsCnC,aAAa,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;CAuCjC"}
1
+ {"version":3,"file":"banxa.d.ts","sourceRoot":"","sources":["../../../src/externalservices/banxa.ts"],"names":[],"mappings":"AAOA,qBAAa,YAAY;IACvB,OAAO,EAAE,GAAG,CAAW;IAEvB,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,iBAAiB;IAsBzB,aAAa,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAwChC,sBAAsB,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IAqCzC,aAAa,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IA+ChC,gBAAgB,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;IA4CnC,aAAa,CAAC,GAAG,KAAA,GAAG,OAAO,CAAC,GAAG,CAAC;CAuCjC"}
@@ -195,6 +195,10 @@ class BanxaService {
195
195
  if (!(0, server_1.checkRequired)(req.body, ['account_reference', 'source', 'target', 'wallet_address', 'return_url_on_success'])) {
196
196
  return reject(new clienterror_1.ClientError("Banxa's request missing arguments"));
197
197
  }
198
+ if (!req.body.payment_method || req.body.payment_method === 'other') {
199
+ delete req.body.payment_method_id;
200
+ }
201
+ delete req.body.payment_method;
198
202
  const UriPath = '/orders';
199
203
  const URL = API + UriPath;
200
204
  const auth = this.getBanxaSignature('post', UriPath, API_KEY, SECRET_KEY, req.body);