Merge pull request #146 from isocolsky/feat/history_pagination
Feat/history pagination
This commit is contained in:
commit
fb81a4f551
|
@ -261,7 +261,12 @@ ExpressApp.start = function(opts) {
|
|||
|
||||
router.get('/v1/txhistory/', function(req, res) {
|
||||
getServerWithAuth(req, res, function(server) {
|
||||
server.getTxHistory({}, function(err, txs) {
|
||||
var opts = {};
|
||||
if (req.query.minTs) opts.minTs = +req.query.minTs;
|
||||
if (req.query.maxTs) opts.maxTs = +req.query.maxTs;
|
||||
if (req.query.limit) opts.limit = +req.query.limit;
|
||||
|
||||
server.getTxHistory(opts, function(err, txs) {
|
||||
if (err) return returnError(err, res, req);
|
||||
res.json(txs);
|
||||
res.end();
|
||||
|
|
105
lib/server.js
105
lib/server.js
|
@ -916,15 +916,20 @@ WalletService.prototype._normalizeTxHistory = function(txs) {
|
|||
* Retrieves all transactions (incoming & outgoing) in the range (maxTs-minTs)
|
||||
* Times are in UNIX EPOCH
|
||||
*
|
||||
* @param {Object} opts.minTs (defaults to 0)
|
||||
* @param {Object} opts.maxTs (defaults to now)
|
||||
* @param {Object} opts.limit
|
||||
* @param {Object} opts
|
||||
* @param {Number} opts.minTs (defaults to 0)
|
||||
* @param {Number} opts.maxTs (defaults to now)
|
||||
* @param {Number} opts.limit
|
||||
* @returns {TxProposal[]} Transaction proposals, first newer
|
||||
*/
|
||||
WalletService.prototype.getTxHistory = function(opts, cb) {
|
||||
var self = this;
|
||||
|
||||
function decorate(txs, addresses, proposals) {
|
||||
|
||||
var indexedAddresses = _.indexBy(addresses, 'address');
|
||||
var indexedProposals = _.indexBy(proposals, 'txid');
|
||||
|
||||
function sum(items, isMine, isChange) {
|
||||
var filter = {};
|
||||
if (_.isBoolean(isMine)) filter.isMine = isMine;
|
||||
|
@ -935,53 +940,78 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
|||
}, 0);
|
||||
};
|
||||
|
||||
var indexedAddresses = _.indexBy(addresses, 'address');
|
||||
var indexedProposals = _.indexBy(proposals, 'txid');
|
||||
|
||||
_.each(txs, function(tx) {
|
||||
_.each(tx.inputs.concat(tx.outputs), function(item) {
|
||||
function classify(items) {
|
||||
return _.map(items, function(item) {
|
||||
var address = indexedAddresses[item.address];
|
||||
item.isMine = !!address;
|
||||
item.isChange = address ? address.isChange : false;
|
||||
});
|
||||
|
||||
var amountIn = sum(tx.inputs, true);
|
||||
var amountOut = sum(tx.outputs, true, false);
|
||||
var amountOutChange = sum(tx.outputs, true, true);
|
||||
var amount;
|
||||
if (amountIn == (amountOut + amountOutChange + (amountIn > 0 ? tx.fees : 0))) {
|
||||
tx.action = 'moved';
|
||||
amount = amountOut;
|
||||
} else {
|
||||
amount = amountIn - amountOut - amountOutChange - (amountIn > 0 ? tx.fees : 0);
|
||||
tx.action = amount > 0 ? 'sent' : 'received';
|
||||
return {
|
||||
address: item.address,
|
||||
amount: item.amount,
|
||||
isMine: !!address,
|
||||
isChange: address ? address.isChange : false,
|
||||
}
|
||||
|
||||
tx.amount = Math.abs(amount);
|
||||
if (tx.action == 'sent' || tx.action == 'moved') {
|
||||
tx.addressTo = tx.outputs[0].address;
|
||||
});
|
||||
};
|
||||
|
||||
delete tx.inputs;
|
||||
delete tx.outputs;
|
||||
|
||||
return _.map(txs, function(tx) {
|
||||
var inputs = classify(tx.inputs);
|
||||
var outputs = classify(tx.outputs);
|
||||
|
||||
var amountIn = sum(inputs, true);
|
||||
var amountOut = sum(outputs, true, false);
|
||||
var amountOutChange = sum(outputs, true, true);
|
||||
var amount, action, addressTo;
|
||||
if (amountIn == (amountOut + amountOutChange + (amountIn > 0 ? tx.fees : 0))) {
|
||||
amount = amountOut;
|
||||
action = 'moved';
|
||||
} else {
|
||||
amount = amountIn - amountOut - amountOutChange - (amountIn > 0 ? tx.fees : 0);
|
||||
action = amount > 0 ? 'sent' : 'received';
|
||||
}
|
||||
|
||||
amount = Math.abs(amount);
|
||||
if (action == 'sent' || action == 'moved') {
|
||||
addressTo = outputs[0].address;
|
||||
};
|
||||
|
||||
var newTx = {
|
||||
txid: tx.txid,
|
||||
action: action,
|
||||
amount: amount,
|
||||
fees: tx.fees,
|
||||
time: tx.time,
|
||||
addressTo: addressTo,
|
||||
confirmations: tx.confirmations,
|
||||
};
|
||||
|
||||
var proposal = indexedProposals[tx.txid];
|
||||
if (proposal) {
|
||||
tx.proposalId = proposal.id;
|
||||
tx.creatorName = proposal.creatorName;
|
||||
tx.message = proposal.message;
|
||||
tx.actions = _.map(proposal.actions, function(action) {
|
||||
newTx.proposalId = proposal.id;
|
||||
newTx.creatorName = proposal.creatorName;
|
||||
newTx.message = proposal.message;
|
||||
newTx.actions = _.map(proposal.actions, function(action) {
|
||||
return _.pick(action, ['createdOn', 'type', 'copayerId', 'copayerName', 'comment']);
|
||||
});
|
||||
// tx.sentTs = proposal.sentTs;
|
||||
// tx.merchant = proposal.merchant;
|
||||
//tx.paymentAckMemo = proposal.paymentAckMemo;
|
||||
// newTx.sentTs = proposal.sentTs;
|
||||
// newTx.merchant = proposal.merchant;
|
||||
//newTx.paymentAckMemo = proposal.paymentAckMemo;
|
||||
}
|
||||
|
||||
return newTx;
|
||||
});
|
||||
};
|
||||
|
||||
function paginate(txs) {
|
||||
// TODO
|
||||
var limited = opts.limit && opts.limit != -1;
|
||||
if (!opts.minTs && !opts.maxTs && !limited) return;
|
||||
var minTs = opts.minTs || 0;
|
||||
var maxTs = opts.maxTs || Math.ceil(Date.now() / 1000);
|
||||
|
||||
var filtered = _.sortBy(_.filter(txs, function(tx) {
|
||||
return tx.time >= minTs && tx.time <= maxTs;
|
||||
}), 'time');
|
||||
|
||||
return limited ? _.take(filtered, opts.limit) : filtered;
|
||||
};
|
||||
|
||||
// Get addresses for this wallet
|
||||
|
@ -1014,8 +1044,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
|||
var proposals = res[0];
|
||||
var txs = res[1];
|
||||
|
||||
decorate(txs, addresses, proposals);
|
||||
paginate(txs);
|
||||
txs = paginate(decorate(txs, addresses, proposals));
|
||||
|
||||
return cb(null, txs);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "bitcore-wallet-service",
|
||||
"description": "A service for Mutisig HD Bitcoin Wallets",
|
||||
"author": "BitPay Inc",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.9",
|
||||
"keywords": [
|
||||
"bitcoin",
|
||||
"copay",
|
||||
|
|
|
@ -2206,7 +2206,7 @@ describe('Copay server', function() {
|
|||
txid: '1',
|
||||
confirmations: 1,
|
||||
fees: 100,
|
||||
minedTs: 1,
|
||||
time: 1,
|
||||
inputs: [{
|
||||
address: 'external',
|
||||
amount: 500,
|
||||
|
@ -2234,7 +2234,7 @@ describe('Copay server', function() {
|
|||
txid: '1',
|
||||
confirmations: 1,
|
||||
fees: 100,
|
||||
minedTs: 1,
|
||||
time: 1,
|
||||
inputs: [{
|
||||
address: mainAddresses[0].address,
|
||||
amount: 500,
|
||||
|
@ -2262,7 +2262,7 @@ describe('Copay server', function() {
|
|||
txid: '1',
|
||||
confirmations: 1,
|
||||
fees: 100,
|
||||
minedTs: 1,
|
||||
time: 1,
|
||||
inputs: [{
|
||||
address: mainAddresses[0].address,
|
||||
amount: 500,
|
||||
|
@ -2312,7 +2312,7 @@ describe('Copay server', function() {
|
|||
txid: '1122334455',
|
||||
confirmations: 1,
|
||||
fees: 5460,
|
||||
minedTs: 1,
|
||||
time: 1,
|
||||
inputs: [{
|
||||
address: tx.inputs[0].address,
|
||||
amount: utxos[0].satoshis,
|
||||
|
@ -2345,5 +2345,66 @@ describe('Copay server', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
it('should get various paginated tx history', function(done) {
|
||||
var testCases = [{
|
||||
opts: {
|
||||
minTs: 15,
|
||||
maxTs: 45,
|
||||
},
|
||||
expected: [20, 30, 40],
|
||||
}, {
|
||||
opts: {
|
||||
minTs: 15,
|
||||
maxTs: 45,
|
||||
limit: 2,
|
||||
},
|
||||
expected: [20, 30],
|
||||
}, {
|
||||
opts: {
|
||||
maxTs: 35,
|
||||
},
|
||||
expected: [10, 20, 30],
|
||||
}, {
|
||||
opts: {
|
||||
minTs: 15,
|
||||
},
|
||||
expected: [20, 30, 40, 50],
|
||||
}, {
|
||||
opts: {
|
||||
minTs: 15,
|
||||
limit: 3,
|
||||
},
|
||||
expected: [20, 30, 40],
|
||||
}];
|
||||
|
||||
server._normalizeTxHistory = sinon.stub().returnsArg(0);
|
||||
var timestamps = [10, 50, 30, 40, 20];
|
||||
var txs = _.map(timestamps, function(ts, idx) {
|
||||
return {
|
||||
txid: (idx + 1).toString(),
|
||||
confirmations: ts / 10,
|
||||
fees: 100,
|
||||
time: ts,
|
||||
inputs: [{
|
||||
address: 'external',
|
||||
amount: 500,
|
||||
}],
|
||||
outputs: [{
|
||||
address: mainAddresses[0].address,
|
||||
amount: 200,
|
||||
}],
|
||||
};
|
||||
});
|
||||
helpers.stubHistory(txs);
|
||||
|
||||
async.each(testCases, function(testCase, next) {
|
||||
server.getTxHistory(testCase.opts, function(err, txs) {
|
||||
should.not.exist(err);
|
||||
should.exist(txs);
|
||||
_.pluck(txs, 'time').should.deep.equal(testCase.expected);
|
||||
next();
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue