@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.
- package/README.md +1 -0
- package/package.json +4 -4
- package/src/externalservices/banxa.ts +6 -0
- package/src/lib/chain/xrp/index.ts +1 -1
- package/src/lib/expressapp.ts +2 -0
- package/src/lib/server.ts +32 -3
- package/test/integration/helpers.ts +2 -1
- package/test/integration/history.test.ts +152 -1
- package/test/integration/server.test.ts +91 -0
- package/test/txhistory.paging.test.ts +166 -0
- package/ts_build/package.json +4 -4
- package/ts_build/src/externalservices/banxa.d.ts.map +1 -1
- package/ts_build/src/externalservices/banxa.js +4 -0
- package/ts_build/src/externalservices/banxa.js.map +1 -1
- package/ts_build/src/lib/chain/xrp/index.js +1 -1
- package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
- package/ts_build/src/lib/expressapp.d.ts.map +1 -1
- package/ts_build/src/lib/expressapp.js +2 -0
- package/ts_build/src/lib/expressapp.js.map +1 -1
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +22 -1
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/test/integration/helpers.d.ts.map +1 -1
- package/ts_build/test/integration/helpers.js +2 -1
- package/ts_build/test/integration/helpers.js.map +1 -1
- package/ts_build/test/integration/history.test.js +130 -0
- package/ts_build/test/integration/history.test.js.map +1 -1
- package/ts_build/test/integration/server.test.js +76 -0
- package/ts_build/test/integration/server.test.js.map +1 -1
- package/ts_build/test/txhistory.paging.test.d.ts +2 -0
- package/ts_build/test/txhistory.paging.test.d.ts.map +1 -0
- package/ts_build/test/txhistory.paging.test.js +162 -0
- package/ts_build/test/txhistory.paging.test.js.map +1 -0
package/README.md
CHANGED
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.
|
|
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.
|
|
47
|
-
"@bitpay-labs/crypto-wallet-core": "^11.7.
|
|
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": "
|
|
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);
|
package/src/lib/expressapp.ts
CHANGED
|
@@ -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
|
|
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
|
+
});
|
package/ts_build/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.
|
|
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.
|
|
47
|
-
"@bitpay-labs/crypto-wallet-core": "^11.7.
|
|
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": "
|
|
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;
|
|
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);
|