Merge pull request #73 from isocolsky/history

History
This commit is contained in:
Ivan Socolsky 2015-02-23 17:26:49 -03:00
commit 36b7fb0a48
8 changed files with 577 additions and 113 deletions

View File

@ -15,6 +15,7 @@ program
.command('reject <txpId> [reason]', 'reject a transaction proposal')
.command('broadcast <txpId>', 'broadcast a transaction proposal to the Bitcoin network')
.command('rm <txpId>', 'remove a transaction proposal')
.command('history', 'list of past incoming and outgoing transactions')
.command('export', 'export wallet critical data')
.command('import', 'import wallet critical data')
.command('confirm', 'show copayer\'s data for confirmation')

43
bit-wallet/bit-history Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/env node
var _ = require('lodash');
var fs = require('fs');
var moment = require('moment');
var program = require('commander');
var Utils = require('./cli-utils');
program = Utils.configureCommander(program);
program
.parse(process.argv);
var args = program.args;
var client = Utils.getClient(program);
var txData;
client.getTxHistory({}, function (err, txs) {
if (_.isEmpty(txs))
return;
console.log("* TX History:")
_.each(txs, function(tx) {
var time = moment(tx.time * 1000).fromNow();
var amount = Utils.renderAmount(tx.amount);
var confirmations = tx.confirmations || 0;
var proposal = tx.proposalId ? '["' + tx.message + '" by ' + tx.creatorName + '] ' : '';
switch (tx.action) {
case 'received':
direction = '<=';
break;
case 'moved':
direction = '==';
break;
case 'sent':
direction = '=>';
break;
}
console.log("\t%s: %s %s %s %s(%s confirmations)", time, direction, tx.action, amount, proposal, confirmations);
});
});

View File

@ -36,6 +36,7 @@ function _decryptMessage(message, encryptingKey) {
};
function _processTxps(txps, encryptingKey) {
if (!txps) return;
_.each([].concat(txps), function(txp) {
txp.encryptedMessage = txp.message;
txp.message = _decryptMessage(txp.message, encryptingKey);
@ -482,10 +483,6 @@ API.prototype.getMainAddresses = function(opts, cb) {
});
};
API.prototype.history = function(limit, cb) {
};
API.prototype.getBalance = function(cb) {
var self = this;
@ -768,4 +765,20 @@ API.prototype.removeTxProposal = function(txp, cb) {
});
};
API.prototype.getTxHistory = function(opts, cb) {
var self = this;
this._loadAndCheck(function(err, data) {
if (err) return cb(err);
var url = '/v1/txhistory/';
self._doGetRequest(url, data, function(err, txs) {
if (err) return cb(err);
_processTxps(txs, data.sharedEncryptingKey);
return cb(null, txs);
});
});
};
module.exports = API;

View File

@ -263,6 +263,18 @@ ExpressApp.start = function(opts) {
});
});
router.get('/v1/txhistory/', function(req, res) {
getServerWithAuth(req, res, function(server) {
server.getTxHistory({}, function(err, txs) {
if (err) return returnError(err, res, req);
res.json(txs);
res.end();
});
});
});
// TODO: DEBUG only!
router.get('/v1/dump', function(req, res) {
var server = WalletService.getInstance();

View File

@ -80,8 +80,7 @@ WalletService.getInstanceWithAuth = function(opts, cb) {
if (!copayer) return cb(new ClientError('NOTAUTHORIZED', 'Copayer not found'));
var pubKey = opts.readOnly ? copayer.roPubKey : copayer.rwPubKey;
var isValid = server._verifySignature(opts.message, opts.signature,
pubKey);
var isValid = server._verifySignature(opts.message, opts.signature, pubKey);
if (!isValid)
return cb(new ClientError('NOTAUTHORIZED', 'Invalid signature'));
@ -289,11 +288,11 @@ WalletService.prototype.getMainAddresses = function(opts, cb) {
self.storage.fetchAddresses(self.walletId, function(err, addresses) {
if (err) return cb(err);
var mainAddresses = _.filter(addresses, {
isChange: false
});
return cb(null, mainAddresses);
var onlyMain = _.reject(addresses, {
isChange: true
});
return cb(null, onlyMain);
});
};
@ -324,6 +323,20 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) {
WalletService.prototype._getBlockExplorer = function(provider, network) {
var url;
function getTransactionsInsight(url, addresses, cb) {
var request = require('request');
request({
method: "POST",
url: url + '/api/addrs/txs',
json: {
addrs: [].concat(addresses).join(',')
}
}, function(err, res, body) {
if (err || res.statusCode != 200) return cb(err || res);
return cb(null, body);
});
};
if (this.blockExplorer)
return this.blockExplorer;
@ -339,7 +352,9 @@ WalletService.prototype._getBlockExplorer = function(provider, network) {
url = 'https://test-insight.bitpay.com:443'
break;
}
return new Explorers.Insight(url, network);
var bc = new Explorers.Insight(url, network);
bc.getTransactions = _.bind(getTransactionsInsight, bc, url);
return bc;
break;
}
};
@ -797,7 +812,7 @@ WalletService.prototype.rejectTx = function(opts, cb) {
};
/**
* Retrieves all pending transaction proposals.
* Retrieves pending transaction proposals.
* @param {Object} opts
* @returns {TxProposal[]} Transaction proposal.
*/
@ -812,7 +827,7 @@ WalletService.prototype.getPendingTxs = function(opts, cb) {
};
/**
* Retrieves pending transaction proposals in the range (maxTs-minTs)
* Retrieves all transaction proposals in the range (maxTs-minTs)
* Times are in UNIX EPOCH
*
* @param {Object} opts.minTs (defaults to 0)
@ -830,7 +845,7 @@ WalletService.prototype.getTxs = function(opts, cb) {
/**
* Retrieves notifications in the range (maxTs-minTs).
* Retrieves notifications in the range (maxTs-minTs).
* Times are in UNIX EPOCH. Order is assured even for events with the same time
*
* @param {Object} opts.minTs (defaults to 0)
@ -848,7 +863,148 @@ WalletService.prototype.getNotifications = function(opts, cb) {
};
WalletService.prototype._normalizeTxHistory = function(txs) {
return _.map(txs, function(tx) {
var inputs = _.map(tx.vin, function(item) {
return {
address: item.addr,
amount: item.valueSat,
}
});
var outputs = _.map(tx.vout, function(item) {
var itemAddr;
// If classic multisig, ignore
if (item.scriptPubKey && item.scriptPubKey.addresses.length == 1) {
itemAddr = item.scriptPubKey.addresses[0];
}
return {
address: itemAddr,
amount: parseInt((item.value * 1e8).toFixed(0)),
}
});
return {
txid: tx.txid,
confirmations: tx.confirmations,
fees: parseInt((tx.fees * 1e8).toFixed(0)),
time: !_.isNaN(tx.time) ? tx.time : Math.floor(Date.now() / 1000),
inputs: inputs,
outputs: outputs,
};
});
};
/**
* 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
* @returns {TxProposal[]} Transaction proposals, first newer
*/
WalletService.prototype.getTxHistory = function(opts, cb) {
var self = this;
function decorate(txs, addresses, proposals) {
function sum(items, isMine, isChange) {
var filter = {};
if (_.isBoolean(isMine)) filter.isMine = isMine;
if (_.isBoolean(isChange)) filter.isChange = isChange;
return _.reduce(_.where(items, filter),
function(memo, item) {
return memo + item.amount;
}, 0);
};
var indexedAddresses = _.indexBy(addresses, 'address');
var indexedProposals = _.indexBy(proposals, 'txid');
_.each(txs, function(tx) {
_.each(tx.inputs.concat(tx.outputs), 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';
}
tx.amount = Math.abs(amount);
if (tx.action == 'sent' || tx.action == 'moved') {
tx.addressTo = tx.outputs[0].address;
};
delete tx.inputs;
delete tx.outputs;
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) {
return _.pick(action, ['createdOn', 'type', 'copayerId', 'copayerName', 'comment']);
});
// tx.sentTs = proposal.sentTs;
// tx.merchant = proposal.merchant;
//tx.paymentAckMemo = proposal.paymentAckMemo;
}
});
};
function paginate(txs) {
// TODO
};
// Get addresses for this wallet
self.storage.fetchAddresses(self.walletId, function(err, addresses) {
if (err) return cb(err);
if (addresses.length == 0) return cb(null, []);
var addressStrs = _.pluck(addresses, 'address');
var networkName = Bitcore.Address(addressStrs[0]).toObject().network;
var bc = self._getBlockExplorer('insight', networkName);
async.parallel([
function(next) {
self.storage.fetchTxs(self.walletId, opts, function(err, txps) {
if (err) return next(err);
next(null, txps);
});
},
function(next) {
bc.getTransactions(addressStrs, function(err, txs) {
if (err) return next(err);
next(null, self._normalizeTxHistory(txs));
});
},
], function(err, res) {
if (err) return cb(err);
var proposals = res[0];
var txs = res[1];
decorate(txs, addresses, proposals);
paginate(txs);
return cb(null, txs);
});
});
};
module.exports = WalletService;

View File

@ -29,6 +29,7 @@
"levelup": "^0.19.0",
"lodash": "*",
"mocha-lcov-reporter": "0.0.1",
"moment": "^2.9.0",
"morgan": "*",
"npmlog": "^0.1.1",
"preconditions": "^1.0.7",

View File

@ -2,12 +2,15 @@
var _ = require('lodash');
var async = require('async');
var inspect = require('util').inspect;
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var levelup = require('levelup');
var memdown = require('memdown');
var log = require('npmlog');
log.debug = log.verbose;
var Bitcore = require('bitcore');
var Utils = require('../../lib/utils');
@ -91,7 +94,7 @@ helpers.toSatoshi = function(btc) {
helpers.stubUtxos = function(server, wallet, amounts, cb) {
var amounts = [].concat(amounts);
async.map(_.range(Math.ceil(amounts.length / 2)), function(i, next) {
async.map(_.range(1, Math.ceil(amounts.length / 2) + 1), function(i, next) {
server.createAddress({}, function(err, address) {
next(err, address);
});
@ -126,6 +129,9 @@ helpers.stubBroadcastFail = function() {
blockExplorer.broadcast = sinon.stub().callsArgWith(1, 'broadcast error');
};
helpers.stubHistory = function(txs) {
blockExplorer.getTransactions = sinon.stub().callsArgWith(1, null, txs);
};
helpers.clientSign = function(txp, xprivHex) {
//Derive proper key to sign, for each input
@ -175,6 +181,19 @@ helpers.createProposalOpts = function(toAddress, amount, message, signingKey) {
return opts;
};
helpers.createAddresses = function(server, wallet, main, change, cb) {
async.map(_.range(main + change), function(i, next) {
var address = wallet.createAddress(i >= main);
server.storage.storeAddressAndWallet(wallet, address, function(err) {
if (err) return next(err);
next(null, address);
});
}, function(err, addresses) {
if (err) throw new Error('Could not generate addresses');
return cb(_.take(addresses, main), _.takeRight(addresses, change));
});
};
var db, storage, blockExplorer;
@ -650,9 +669,7 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
done();
});
done();
});
});
@ -661,7 +678,7 @@ describe('Copay server', function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
tx.should.exist;
should.exist(tx);
tx.message.should.equal('some message');
tx.isAccepted().should.equal.false;
tx.isRejected().should.equal.false;
@ -791,11 +808,11 @@ describe('Copay server', function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
tx.should.exist;
should.exist(tx);
var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey);
server.createTx(txOpts2, function(err, tx) {
should.not.exist(err);
tx.should.exist;
should.exist(tx);
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
txs.length.should.equal(2);
@ -816,7 +833,7 @@ describe('Copay server', function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
tx.should.exist;
should.exist(tx);
var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, TestData.copayers[0].privKey);
server.createTx(txOpts2, function(err, tx) {
err.code.should.equal('INSUFFICIENTFUNDS');
@ -839,9 +856,7 @@ describe('Copay server', function() {
it('should create tx using different UTXOs for simultaneous requests', function(done) {
var N = 5;
helpers.stubUtxos(server, wallet, _.times(N, function() {
return 100;
}), function(utxos) {
helpers.stubUtxos(server, wallet, _.range(100, 100 + N, 0), function(utxos) {
server.getBalance({}, function(err, balance) {
should.not.exist(err);
balance.totalAmount.should.equal(helpers.toSatoshi(N * 100));
@ -877,15 +892,13 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
tx.should.exist;
txid = tx.id;
done();
});
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
should.exist(tx);
txid = tx.id;
done();
});
});
});
@ -926,15 +939,13 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
should.exist(tx);
txid = tx.id;
done();
});
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
should.exist(tx);
txid = tx.id;
done();
});
});
});
@ -1084,10 +1095,8 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
done();
});
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
done();
});
});
});
@ -1163,11 +1172,9 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
helpers.stubBroadcast('999');
done();
});
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
helpers.stubBroadcast('999');
done();
});
});
});
@ -1363,17 +1370,15 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.stubUtxos(server, wallet, _.range(10), function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, TestData.copayers[0].privKey);
async.eachSeries(_.range(10), function(i, next) {
clock.tick(10000);
server.createTx(txOpts, function(err, tx) {
next();
});
}, function(err) {
return done(err);
helpers.stubUtxos(server, wallet, _.range(10), function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, TestData.copayers[0].privKey);
async.eachSeries(_.range(10), function(i, next) {
clock.tick(10000);
server.createTx(txOpts, function(err, tx) {
next();
});
}, function(err) {
return done(err);
});
});
});
@ -1449,17 +1454,15 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.stubUtxos(server, wallet, helpers.toSatoshi(_.range(4)), function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey);
async.eachSeries(_.range(3), function(i, next) {
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
next();
});
}, function(err) {
return done(err);
helpers.stubUtxos(server, wallet, helpers.toSatoshi(_.range(4)), function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey);
async.eachSeries(_.range(3), function(i, next) {
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
next();
});
}, function(err) {
return done(err);
});
});
});
@ -1583,18 +1586,16 @@ describe('Copay server', function() {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.stubUtxos(server, wallet, _.range(2), function() {
var txOpts = {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(0.1),
};
async.eachSeries(_.range(2), function(i, next) {
server.createTx(txOpts, function(err, tx) {
next();
});
}, done);
});
helpers.stubUtxos(server, wallet, _.range(2), function() {
var txOpts = {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(0.1),
};
async.eachSeries(_.range(2), function(i, next) {
server.createTx(txOpts, function(err, tx) {
next();
});
}, done);
});
});
});
@ -1632,27 +1633,25 @@ describe('Copay server', function() {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.stubUtxos(server, wallet, _.range(2), function() {
var txOpts = {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(0.1),
};
async.eachSeries(_.range(2), function(i, next) {
server.createTx(txOpts, function(err, tx) {
next();
});
}, function() {
server.removeWallet({}, function(err) {
db = [];
server.storage._dump(function() {
var after = _.clone(db);
after.should.deep.equal(before);
done();
}, cat);
});
}, cat);
});
helpers.stubUtxos(server, wallet, _.range(2), function() {
var txOpts = {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(0.1),
};
async.eachSeries(_.range(2), function(i, next) {
server.createTx(txOpts, function(err, tx) {
next();
});
}, function() {
server.removeWallet({}, function(err) {
db = [];
server.storage._dump(function() {
var after = _.clone(db);
after.should.deep.equal(before);
done();
}, cat);
});
}, cat);
});
});
}, cat);
@ -1666,14 +1665,12 @@ describe('Copay server', function() {
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
server.getPendingTxs({}, function(err, txs) {
txp = txs[0];
done();
});
helpers.stubUtxos(server, wallet, [100, 200], function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
server.getPendingTxs({}, function(err, txs) {
txp = txs[0];
done();
});
});
});
@ -1742,4 +1739,169 @@ describe('Copay server', function() {
});
});
});
describe('#getTxHistory', function() {
var server, wallet, mainAddresses, changeAddresses;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
helpers.createAddresses(server, wallet, 1, 1, function(main, change) {
mainAddresses = main;
changeAddresses = change;
done();
});
});
});
it('should get tx history from insight', function(done) {
helpers.stubHistory(TestData.history);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(2);
done();
});
});
it('should get tx history for incoming txs', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '1',
confirmations: 1,
fees: 100,
minedTs: 1,
inputs: [{
address: 'external',
amount: 500,
}],
outputs: [{
address: mainAddresses[0].address,
amount: 200,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('received');
tx.amount.should.equal(200);
tx.fees.should.equal(100);
done();
});
});
it('should get tx history for outgoing txs', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '1',
confirmations: 1,
fees: 100,
minedTs: 1,
inputs: [{
address: mainAddresses[0].address,
amount: 500,
}],
outputs: [{
address: 'external',
amount: 400,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('sent');
tx.amount.should.equal(400);
tx.fees.should.equal(100);
done();
});
});
it('should get tx history for outgoing txs + change', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '1',
confirmations: 1,
fees: 100,
minedTs: 1,
inputs: [{
address: mainAddresses[0].address,
amount: 500,
}],
outputs: [{
address: 'external',
amount: 300,
}, {
address: changeAddresses[0].address,
amount: 100,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('sent');
tx.amount.should.equal(300);
tx.fees.should.equal(100);
done();
});
});
it('should get tx history with accepted proposal', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
helpers.stubUtxos(server, wallet, [100, 200], function(utxos) {
var txOpts = helpers.createProposalOpts(mainAddresses[0].address, 80, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
should.exist(tx);
helpers.stubBroadcast('1122334455');
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
server.signTx({
txProposalId: tx.id,
signatures: signatures,
}, function(err, tx) {
should.not.exist(err);
var txs = [{
txid: '1122334455',
confirmations: 1,
fees: 5460,
minedTs: 1,
inputs: [{
address: tx.inputs[0].address,
amount: utxos[0].satoshis,
}],
outputs: [{
address: 'external',
amount: helpers.toSatoshi(80) - 5460,
}, {
address: changeAddresses[0].address,
amount: helpers.toSatoshi(20) - 5460,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('sent');
tx.amount.should.equal(helpers.toSatoshi(80));
tx.message.should.equal('some message');
tx.actions.length.should.equal(1);
tx.actions[0].type.should.equal('accept');
tx.actions[0].copayerName.should.equal('copayer 1');
done();
});
});
});
});
});
});
});

View File

@ -62,6 +62,82 @@ var copayers = [{
}, ];
var history = [{
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
vin: [{
txid: "c8e221141e8bb60977896561b77fa59d6dacfcc10db82bf6f5f923048b11c70d",
vout: 0,
n: 0,
addr: "2N6Zutg26LEC4iYVxi7SHhopVLP1iZPU1rZ",
valueSat: 485645,
value: 0.00485645,
}, {
txid: "6e599eea3e2898b91087eb87e041c5d8dec5362447a8efba185ed593f6dc64c0",
vout: 1,
n: 1,
addr: "2MyqmcWjmVxW8i39wdk1CVPdEqKyFSY9H1S",
valueSat: 885590,
value: 0.0088559,
}],
vout: [{
value: "0.00045753",
n: 0,
scriptPubKey: {
addresses: [
"2NAVFnsHqy5JvqDJydbHPx393LFqFFBQ89V"
]
},
}, {
value: "0.01300000",
n: 1,
scriptPubKey: {
addresses: [
"mq4D3Va5mYHohMEHrgHNGzCjKhBKvuEhPE"
]
}
}],
confirmations: 2,
time: 1424471041,
blocktime: 1424471041,
valueOut: 0.01345753,
valueIn: 0.01371235,
fees: 0.00025482
}, {
txid: "fad88682ccd2ff34cac6f7355fe9ecd8addd9ef167e3788455972010e0d9d0de",
vin: [{
txid: "0279ef7b21630f859deb723e28beac9e7011660bd1346c2da40321d2f7e34f04",
vout: 0,
n: 0,
addr: "2NAVFnsHqy5JvqDJydbHPx393LFqFFBQ89V",
valueSat: 45753,
value: 0.00045753,
}],
vout: [{
value: "0.00011454",
n: 0,
scriptPubKey: {
addresses: [
"2N7GT7XaN637eBFMmeczton2aZz5rfRdZso"
]
}
}, {
value: "0.00020000",
n: 1,
scriptPubKey: {
addresses: [
"mq4D3Va5mYHohMEHrgHNGzCjKhBKvuEhPE"
]
}
}],
confirmations: 1,
time: 1424472242,
blocktime: 1424472242,
valueOut: 0.00031454,
valueIn: 0.00045753,
fees: 0.00014299
}];
module.exports.keyPair = keyPair;
module.exports.message = message;
module.exports.copayers = copayers;
module.exports.history = history;