commit
36b7fb0a48
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
174
lib/server.js
174
lib/server.js
|
@ -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)
|
||||
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,18 +669,16 @@ describe('Copay server', function() {
|
|||
helpers.createAndJoinWallet(2, 3, function(s, w) {
|
||||
server = s;
|
||||
wallet = w;
|
||||
server.createAddress({}, function(err, address) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a tx', function(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) {
|
||||
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,19 +892,17 @@ 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;
|
||||
should.exist(tx);
|
||||
txid = tx.id;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject a TX', function(done) {
|
||||
server.getPendingTxs({}, function(err, txs) {
|
||||
|
@ -926,7 +939,6 @@ 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) {
|
||||
|
@ -938,7 +950,6 @@ describe('Copay server', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should sign a TX with multiple inputs, different paths', function(done) {
|
||||
server.getPendingTxs({}, function(err, txs) {
|
||||
|
@ -1084,13 +1095,11 @@ 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should sign and broadcast a tx', function(done) {
|
||||
helpers.stubBroadcast('1122334455');
|
||||
|
@ -1163,14 +1172,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, _.range(1, 9), function() {
|
||||
helpers.stubBroadcast('999');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('other copayers should see pending proposal created by one copayer', function(done) {
|
||||
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey);
|
||||
|
@ -1363,7 +1370,6 @@ 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) {
|
||||
|
@ -1377,7 +1383,6 @@ describe('Copay server', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
afterEach(function() {
|
||||
clock.restore();
|
||||
});
|
||||
|
@ -1449,7 +1454,6 @@ 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) {
|
||||
|
@ -1463,7 +1467,6 @@ describe('Copay server', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should pull the last 4 notifications after 3 TXs', function(done) {
|
||||
server.getNotifications({
|
||||
|
@ -1583,7 +1586,6 @@ describe('Copay server', function() {
|
|||
server = s;
|
||||
wallet = w;
|
||||
|
||||
server.createAddress({}, function(err, address) {
|
||||
helpers.stubUtxos(server, wallet, _.range(2), function() {
|
||||
var txOpts = {
|
||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||
|
@ -1597,7 +1599,6 @@ describe('Copay server', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should delete a wallet', function(done) {
|
||||
var i = 0;
|
||||
var count = function() {
|
||||
|
@ -1632,7 +1633,6 @@ describe('Copay server', function() {
|
|||
server = s;
|
||||
wallet = w;
|
||||
|
||||
server.createAddress({}, function(err, address) {
|
||||
helpers.stubUtxos(server, wallet, _.range(2), function() {
|
||||
var txOpts = {
|
||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||
|
@ -1654,7 +1654,6 @@ describe('Copay server', function() {
|
|||
}, cat);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, cat);
|
||||
});
|
||||
});
|
||||
|
@ -1666,7 +1665,6 @@ 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) {
|
||||
|
@ -1678,7 +1676,6 @@ describe('Copay server', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow creator to remove an unsigned TX', function(done) {
|
||||
server.removePendingTx({
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue