all methods working with coin
This commit is contained in:
parent
4d7a5ee3d5
commit
06b63e0311
30
config.js
30
config.js
|
@ -38,16 +38,28 @@ var config = {
|
|||
},
|
||||
},
|
||||
blockchainExplorerOpts: {
|
||||
livenet: {
|
||||
provider: 'insight',
|
||||
url: 'https://insight.bitpay.com:443',
|
||||
btc: {
|
||||
livenet: {
|
||||
provider: 'insight',
|
||||
url: 'https://insight.bitpay.com:443',
|
||||
},
|
||||
testnet: {
|
||||
provider: 'insight',
|
||||
url: 'https://test-insight.bitpay.com:443',
|
||||
// url: 'http://localhost:3001',
|
||||
// Multiple servers (in priority order)
|
||||
// url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'],
|
||||
},
|
||||
},
|
||||
testnet: {
|
||||
provider: 'insight',
|
||||
url: 'https://test-insight.bitpay.com:443',
|
||||
// url: 'http://localhost:3001',
|
||||
// Multiple servers (in priority order)
|
||||
// url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'],
|
||||
bch: {
|
||||
livenet: {
|
||||
provider: 'insight',
|
||||
url: 'https://cashexplorer.bitcoin.com/api/',
|
||||
},
|
||||
testnet: {
|
||||
provider: 'insight',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
pushNotificationsOpts: {
|
||||
|
|
|
@ -6,11 +6,20 @@ var log = require('npmlog');
|
|||
log.debug = log.verbose;
|
||||
|
||||
var Insight = require('./blockchainexplorers/insight');
|
||||
var Common = require('./common');
|
||||
var Constants = Common.Constants,
|
||||
Defaults = Common.Defaults,
|
||||
Utils = Common.Utils;
|
||||
|
||||
var PROVIDERS = {
|
||||
'insight': {
|
||||
'livenet': 'https://insight.bitpay.com:443',
|
||||
'testnet': 'https://test-insight.bitpay.com:443',
|
||||
'btc': {
|
||||
'livenet': 'https://insight.bitpay.com:443',
|
||||
'testnet': 'https://test-insight.bitpay.com:443',
|
||||
},
|
||||
'bch': {
|
||||
'livenet': 'https://insight.bitpay.com:443',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -18,16 +27,19 @@ function BlockChainExplorer(opts) {
|
|||
$.checkArgument(opts);
|
||||
|
||||
var provider = opts.provider || 'insight';
|
||||
var coin = opts.coin || Defaults.COIN;
|
||||
var network = opts.network || 'livenet';
|
||||
|
||||
$.checkState(PROVIDERS[provider], 'Provider ' + provider + ' not supported');
|
||||
$.checkState(_.contains(_.keys(PROVIDERS[provider]), network), 'Network ' + network + ' not supported by this provider');
|
||||
$.checkState(_.contains(_.keys(PROVIDERS[provider]), coin), 'Coin ' + coin + ' not supported by this provider');
|
||||
$.checkState(_.contains(_.keys(PROVIDERS[provider][coin]), network), 'Network ' + network + ' not supported by this provider for coin ' + coin);
|
||||
|
||||
var url = opts.url || PROVIDERS[provider][network];
|
||||
var url = opts.url || PROVIDERS[provider][coin][network];
|
||||
|
||||
switch (provider) {
|
||||
case 'insight':
|
||||
return new Insight({
|
||||
coin: coin,
|
||||
network: network,
|
||||
url: url,
|
||||
apiPrefix: opts.apiPrefix,
|
||||
|
|
|
@ -6,13 +6,19 @@ var log = require('npmlog');
|
|||
log.debug = log.verbose;
|
||||
var io = require('socket.io-client');
|
||||
var requestList = require('./request-list');
|
||||
var Common = require('../common');
|
||||
var Constants = Common.Constants,
|
||||
Defaults = Common.Defaults,
|
||||
Utils = Common.Utils;
|
||||
|
||||
function Insight(opts) {
|
||||
$.checkArgument(opts);
|
||||
$.checkArgument(_.contains(['livenet', 'testnet'], opts.network));
|
||||
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
|
||||
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
|
||||
$.checkArgument(opts.url);
|
||||
|
||||
this.apiPrefix = opts.apiPrefix || '/api';
|
||||
this.coin = opts.coin || Defaults.COIN;
|
||||
this.network = opts.network || 'livenet';
|
||||
this.hosts = opts.url;
|
||||
this.userAgent = opts.userAgent || 'bws';
|
||||
|
@ -39,7 +45,7 @@ Insight.prototype._doRequest = function(args, cb) {
|
|||
};
|
||||
|
||||
Insight.prototype.getConnectionInfo = function() {
|
||||
return 'Insight (' + this.network + ') @ ' + this.hosts;
|
||||
return 'Insight (' + this.coin + '/' + this.network + ') @ ' + this.hosts;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,21 +30,22 @@ BlockchainMonitor.prototype.start = function(opts, cb) {
|
|||
_.map(_.values(Constants.NETWORKS), function(network) {
|
||||
var explorer;
|
||||
if (opts.blockchainExplorers) {
|
||||
explorer = opts.blockchainExplorers[network];
|
||||
explorer = opts.blockchainExplorers['btc'][network];
|
||||
} else {
|
||||
var config = {}
|
||||
if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[network]) {
|
||||
config = opts.blockchainExplorerOpts[network];
|
||||
if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts['btc'] && opts.blockchainExplorerOpts['btc'][network]) {
|
||||
config = opts.blockchainExplorerOpts['btc'][network];
|
||||
}
|
||||
var explorer = new BlockchainExplorer({
|
||||
provider: config.provider,
|
||||
coin: 'btc',
|
||||
network: network,
|
||||
url: config.url,
|
||||
userAgent: WalletService.getServiceVersion(),
|
||||
});
|
||||
}
|
||||
$.checkState(explorer);
|
||||
self._initExplorer(network, explorer);
|
||||
self._initExplorer('btc', network, explorer);
|
||||
self.explorers[network] = explorer;
|
||||
});
|
||||
done();
|
||||
|
@ -74,7 +75,7 @@ BlockchainMonitor.prototype.start = function(opts, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
BlockchainMonitor.prototype._initExplorer = function(network, explorer) {
|
||||
BlockchainMonitor.prototype._initExplorer = function(coin, network, explorer) {
|
||||
var self = this;
|
||||
|
||||
var socket = explorer.initSocket();
|
||||
|
@ -87,7 +88,7 @@ BlockchainMonitor.prototype._initExplorer = function(network, explorer) {
|
|||
log.error('Error connecting to ' + explorer.getConnectionInfo());
|
||||
});
|
||||
socket.on('tx', _.bind(self._handleIncomingTx, self));
|
||||
socket.on('block', _.bind(self._handleNewBlock, self, network));
|
||||
socket.on('block', _.bind(self._handleNewBlock, self, coin, network));
|
||||
};
|
||||
|
||||
BlockchainMonitor.prototype._handleThirdPartyBroadcasts = function(data, processIt) {
|
||||
|
@ -208,7 +209,7 @@ BlockchainMonitor.prototype._handleIncomingTx = function(data) {
|
|||
this._handleIncomingPayments(data);
|
||||
};
|
||||
|
||||
BlockchainMonitor.prototype._notifyNewBlock = function(network, hash) {
|
||||
BlockchainMonitor.prototype._notifyNewBlock = function(coin, network, hash) {
|
||||
var self = this;
|
||||
|
||||
log.info('New ' + network + ' block: ' + hash);
|
||||
|
@ -217,6 +218,7 @@ BlockchainMonitor.prototype._notifyNewBlock = function(network, hash) {
|
|||
walletId: network, // use network name as wallet id for global notifications
|
||||
data: {
|
||||
hash: hash,
|
||||
coin: coin,
|
||||
network: network,
|
||||
},
|
||||
});
|
||||
|
@ -228,7 +230,7 @@ BlockchainMonitor.prototype._notifyNewBlock = function(network, hash) {
|
|||
});
|
||||
};
|
||||
|
||||
BlockchainMonitor.prototype._handleTxConfirmations = function(network, hash) {
|
||||
BlockchainMonitor.prototype._handleTxConfirmations = function(coin, network, hash) {
|
||||
var self = this;
|
||||
|
||||
function processTriggeredSubs(subs, cb) {
|
||||
|
@ -244,6 +246,7 @@ BlockchainMonitor.prototype._handleTxConfirmations = function(network, hash) {
|
|||
creatorId: sub.copayerId,
|
||||
data: {
|
||||
txid: sub.txid,
|
||||
coin: coin,
|
||||
network: network,
|
||||
// TODO: amount
|
||||
},
|
||||
|
@ -280,9 +283,9 @@ BlockchainMonitor.prototype._handleTxConfirmations = function(network, hash) {
|
|||
});
|
||||
};
|
||||
|
||||
BlockchainMonitor.prototype._handleNewBlock = function(network, hash) {
|
||||
this._notifyNewBlock(network, hash);
|
||||
this._handleTxConfirmations(network, hash);
|
||||
BlockchainMonitor.prototype._handleNewBlock = function(coin, network, hash) {
|
||||
this._notifyNewBlock(coin, network, hash);
|
||||
this._handleTxConfirmations(coin, network, hash);
|
||||
};
|
||||
|
||||
BlockchainMonitor.prototype._storeAndBroadcastNotification = function(notification, cb) {
|
||||
|
|
|
@ -22,6 +22,9 @@ function TxProposal() {};
|
|||
TxProposal.create = function(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
|
||||
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
|
||||
|
||||
var x = new TxProposal();
|
||||
|
||||
x.version = 3;
|
||||
|
@ -31,6 +34,8 @@ TxProposal.create = function(opts) {
|
|||
x.id = opts.id || Uuid.v4();
|
||||
x.walletId = opts.walletId;
|
||||
x.creatorId = opts.creatorId;
|
||||
x.coin = opts.coin;
|
||||
x.network = opts.network;
|
||||
x.message = opts.message;
|
||||
x.payProUrl = opts.payProUrl;
|
||||
x.changeAddress = opts.changeAddress;
|
||||
|
@ -57,10 +62,6 @@ TxProposal.create = function(opts) {
|
|||
x.customData = opts.customData;
|
||||
|
||||
x.amount = x.getTotalAmount();
|
||||
try {
|
||||
x.network = opts.network || Bitcore.Address(x.outputs[0].toAddress).toObject().network;
|
||||
} catch (ex) {}
|
||||
$.checkState(Utils.checkValueInCollection(x.network, Constants.NETWORKS));
|
||||
|
||||
x.setInputs(opts.inputs);
|
||||
x.fee = opts.fee;
|
||||
|
@ -80,6 +81,7 @@ TxProposal.fromObj = function(obj) {
|
|||
x.id = obj.id;
|
||||
x.walletId = obj.walletId;
|
||||
x.creatorId = obj.creatorId;
|
||||
x.coin = obj.coin || Defaults.COIN;
|
||||
x.network = obj.network;
|
||||
x.outputs = obj.outputs;
|
||||
x.amount = obj.amount;
|
||||
|
@ -221,10 +223,6 @@ TxProposal.prototype.getBitcoreTx = function() {
|
|||
return t;
|
||||
};
|
||||
|
||||
TxProposal.prototype.getNetworkName = function() {
|
||||
return this.network;
|
||||
};
|
||||
|
||||
TxProposal.prototype.getRawTx = function() {
|
||||
var t = this.getBitcoreTx();
|
||||
|
||||
|
|
|
@ -93,10 +93,6 @@ TxProposal.prototype.getBitcoreTx = function() {
|
|||
throwUnsupportedError();
|
||||
};
|
||||
|
||||
TxProposal.prototype.getNetworkName = function() {
|
||||
return Bitcore.Address(this.changeAddress.address).toObject().network;
|
||||
};
|
||||
|
||||
TxProposal.prototype.getRawTx = function() {
|
||||
throwUnsupportedError();
|
||||
};
|
||||
|
|
|
@ -23,8 +23,8 @@ Wallet.create = function(opts) {
|
|||
|
||||
$.shouldBeNumber(opts.m);
|
||||
$.shouldBeNumber(opts.n);
|
||||
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
|
||||
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
|
||||
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
|
||||
|
||||
x.version = '1.0.0';
|
||||
x.createdOn = Math.floor(Date.now() / 1000);
|
||||
|
@ -141,10 +141,6 @@ Wallet.prototype.getCopayer = function(copayerId) {
|
|||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.getNetworkName = function() {
|
||||
return this.network;
|
||||
};
|
||||
|
||||
Wallet.prototype.isComplete = function() {
|
||||
return this.status == 'complete';
|
||||
};
|
||||
|
|
143
lib/server.js
143
lib/server.js
|
@ -12,6 +12,10 @@ var EmailValidator = require('email-validator');
|
|||
var Stringify = require('json-stable-stringify');
|
||||
|
||||
var Bitcore = require('bitcore-lib');
|
||||
var Bitcore_ = {
|
||||
btc: Bitcore,
|
||||
bch: require('bitcore-lib-cash')
|
||||
};
|
||||
|
||||
var Common = require('./common');
|
||||
var Utils = Common.Utils;
|
||||
|
@ -158,7 +162,7 @@ WalletService.handleIncomingNotification = function(notification, cb) {
|
|||
|
||||
if (!notification || notification.type != 'NewBlock') return cb();
|
||||
|
||||
WalletService._clearBlockchainHeightCache(notification.data.network);
|
||||
WalletService._clearBlockchainHeightCache(notification.data.coin, notification.data.network);
|
||||
return cb();
|
||||
};
|
||||
|
||||
|
@ -441,10 +445,19 @@ WalletService.prototype.getWalletFromIdentifier = function(opts, cb) {
|
|||
}
|
||||
|
||||
// Is identifier a txid form an incomming tx?
|
||||
async.detectSeries(_.values(Constants.NETWORKS), function(network, nextNetwork) {
|
||||
var bc = self._getBlockchainExplorer(network);
|
||||
var coinNetworkPairs = [];
|
||||
_.each(_.values(Constants.COINS), function(coin) {
|
||||
_.each(_.values(Constants.NETWORKS), function(network) {
|
||||
coinNetworkPairs.push({
|
||||
coin: coin,
|
||||
network: network
|
||||
});
|
||||
});
|
||||
});
|
||||
async.detectSeries(coinNetworkPairs, function(coinNetwork, nextCoinNetwork) {
|
||||
var bc = self._getBlockchainExplorer(coinNetwork.coin, coinNetwork.network);
|
||||
bc.getTransaction(opts.identifier, function(err, tx) {
|
||||
if (err || !tx) return nextNetwork(err, false);
|
||||
if (err || !tx) return nextCoinNetwork(err, false);
|
||||
var outputs = _.first(self._normalizeTxHistory(tx)).outputs;
|
||||
var toAddresses = _.pluck(outputs, 'address');
|
||||
async.detect(toAddresses, function(addressStr, nextAddress) {
|
||||
|
@ -454,7 +467,7 @@ WalletService.prototype.getWalletFromIdentifier = function(opts, cb) {
|
|||
nextAddress(null, true);
|
||||
});
|
||||
}, function(err) {
|
||||
nextNetwork(err, !!walletId);
|
||||
nextCoinNetwork(err, !!walletId);
|
||||
});
|
||||
});
|
||||
}, function(err) {
|
||||
|
@ -915,7 +928,7 @@ WalletService.prototype._canCreateAddress = function(ignoreMaxGap, cb) {
|
|||
hasActivity: true
|
||||
})) return cb(null, true);
|
||||
|
||||
var bc = self._getBlockchainExplorer(latestAddresses[0].network);
|
||||
var bc = self._getBlockchainExplorer(latestAddresses[0].coin, latestAddresses[0].network);
|
||||
var activityFound = false;
|
||||
var i = latestAddresses.length;
|
||||
async.whilst(function() {
|
||||
|
@ -1035,27 +1048,28 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) {
|
|||
};
|
||||
|
||||
|
||||
WalletService.prototype._getBlockchainExplorer = function(network) {
|
||||
WalletService.prototype._getBlockchainExplorer = function(coin, network) {
|
||||
var opts = {};
|
||||
|
||||
if (this.blockchainExplorer) return this.blockchainExplorer;
|
||||
if (this.blockchainExplorerOpts && this.blockchainExplorerOpts[network]) {
|
||||
opts = this.blockchainExplorerOpts[network];
|
||||
if (this.blockchainExplorerOpts && this.blockchainExplorerOpts[coin] && this.blockchainExplorerOpts[coin][network]) {
|
||||
opts = this.blockchainExplorerOpts[coin][network];
|
||||
}
|
||||
// TODO: provider should be configurable
|
||||
opts.provider = 'insight';
|
||||
opts.coin = coin;
|
||||
opts.network = network;
|
||||
opts.userAgent = WalletService.getServiceVersion();
|
||||
return new BlockchainExplorer(opts);
|
||||
};
|
||||
|
||||
WalletService.prototype._getUtxos = function(addresses, cb) {
|
||||
WalletService.prototype._getUtxos = function(coin, addresses, cb) {
|
||||
var self = this;
|
||||
|
||||
if (addresses.length == 0) return cb(null, []);
|
||||
var networkName = Bitcore.Address(addresses[0]).toObject().network;
|
||||
|
||||
var bc = self._getBlockchainExplorer(networkName);
|
||||
var bc = self._getBlockchainExplorer(coin, networkName);
|
||||
bc.getUtxos(addresses, function(err, utxos) {
|
||||
if (err) return cb(err);
|
||||
|
||||
|
@ -1079,10 +1093,16 @@ WalletService.prototype._getUtxosForCurrentWallet = function(addresses, cb) {
|
|||
return utxo.txid + '|' + utxo.vout
|
||||
};
|
||||
|
||||
var allAddresses, allUtxos, utxoIndex;
|
||||
var coin, allAddresses, allUtxos, utxoIndex;
|
||||
|
||||
async.series([
|
||||
|
||||
function(next) {
|
||||
self.getWallet({}, function(err, wallet) {
|
||||
coin = wallet.coin;
|
||||
return next();
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
if (_.isArray(addresses)) {
|
||||
allAddresses = addresses;
|
||||
|
@ -1097,7 +1117,7 @@ WalletService.prototype._getUtxosForCurrentWallet = function(addresses, cb) {
|
|||
if (allAddresses.length == 0) return cb(null, []);
|
||||
|
||||
var addressStrs = _.pluck(allAddresses, 'address');
|
||||
self._getUtxos(addressStrs, function(err, utxos) {
|
||||
self._getUtxos(coin, addressStrs, function(err, utxos) {
|
||||
if (err) return next(err);
|
||||
|
||||
if (utxos.length == 0) return cb(null, []);
|
||||
|
@ -1159,6 +1179,7 @@ WalletService.prototype._getUtxosForCurrentWallet = function(addresses, cb) {
|
|||
/**
|
||||
* Returns list of UTXOs
|
||||
* @param {Object} opts
|
||||
* @param {String} [opts.coin='btc'] (optional)
|
||||
* @param {Array} opts.addresses (optional) - List of addresses from where to fetch UTXOs.
|
||||
* @returns {Array} utxos - List of UTXOs.
|
||||
*/
|
||||
|
@ -1167,10 +1188,14 @@ WalletService.prototype.getUtxos = function(opts, cb) {
|
|||
|
||||
opts = opts || {};
|
||||
|
||||
opts.coin = opts.coin || Defaults.COIN;
|
||||
if (!Utils.checkValueInCollection(opts.coin, Constants.COINS))
|
||||
return cb(new ClientError('Invalid coin'));
|
||||
|
||||
if (_.isUndefined(opts.addresses)) {
|
||||
self._getUtxosForCurrentWallet(null, cb);
|
||||
} else {
|
||||
self._getUtxos(opts.addresses, cb);
|
||||
self._getUtxos(opts.coin, opts.addresses, cb);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1392,6 +1417,7 @@ WalletService.prototype.getSendMaxInfo = function(opts, cb) {
|
|||
|
||||
var txp = Model.TxProposal.create({
|
||||
walletId: self.walletId,
|
||||
coin: wallet.coin,
|
||||
network: wallet.network,
|
||||
walletM: wallet.m,
|
||||
walletN: wallet.n,
|
||||
|
@ -1442,10 +1468,10 @@ WalletService.prototype.getSendMaxInfo = function(opts, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
WalletService.prototype._sampleFeeLevels = function(network, points, cb) {
|
||||
WalletService.prototype._sampleFeeLevels = function(coin, network, points, cb) {
|
||||
var self = this;
|
||||
|
||||
var bc = self._getBlockchainExplorer(network);
|
||||
var bc = self._getBlockchainExplorer(coin, network);
|
||||
bc.estimateFee(points, function(err, result) {
|
||||
if (err) {
|
||||
log.error('Error estimating fee', err);
|
||||
|
@ -1473,6 +1499,7 @@ WalletService.prototype._sampleFeeLevels = function(network, points, cb) {
|
|||
/**
|
||||
* Returns fee levels for the current state of the network.
|
||||
* @param {Object} opts
|
||||
* @param {string} [opts.coin = 'btc'] - The coin to estimate fee levels from.
|
||||
* @param {string} [opts.network = 'livenet'] - The Bitcoin network to estimate fee levels from.
|
||||
* @returns {Object} feeLevels - A list of fee levels & associated amount per kB in satoshi.
|
||||
*/
|
||||
|
@ -1509,11 +1536,15 @@ WalletService.prototype.getFeeLevels = function(opts, cb) {
|
|||
return result;
|
||||
};
|
||||
|
||||
var network = opts.network || 'livenet';
|
||||
if (network != 'livenet' && network != 'testnet')
|
||||
opts.coin = opts.coin || Defaults.COIN;
|
||||
if (!Utils.checkValueInCollection(opts.coin, Constants.COINS))
|
||||
return cb(new ClientError('Invalid coin'));
|
||||
|
||||
opts.network = opts.network || 'livenet';
|
||||
if (!Utils.checkValueInCollection(opts.network, Constants.NETWORKS))
|
||||
return cb(new ClientError('Invalid network'));
|
||||
|
||||
self._sampleFeeLevels(network, samplePoints(), function(err, feeSamples) {
|
||||
self._sampleFeeLevels(opts.coin, opts.network, samplePoints(), function(err, feeSamples) {
|
||||
var values = _.map(Defaults.FEE_LEVELS, function(level) {
|
||||
var result = {
|
||||
level: level.name,
|
||||
|
@ -1565,10 +1596,10 @@ WalletService.prototype._checkTx = function(txp) {
|
|||
return ex;
|
||||
}
|
||||
|
||||
if (bitcoreError instanceof Bitcore.errors.Transaction.FeeError)
|
||||
if (bitcoreError instanceof Bitcore_[txp.coin].errors.Transaction.FeeError)
|
||||
return Errors.INSUFFICIENT_FUNDS_FOR_FEE;
|
||||
|
||||
if (bitcoreError instanceof Bitcore.errors.Transaction.DustOutputs)
|
||||
if (bitcoreError instanceof Bitcore_[txp.coin].errors.Transaction.DustOutputs)
|
||||
return Errors.DUST_AMOUNT;
|
||||
return bitcoreError;
|
||||
};
|
||||
|
@ -1697,7 +1728,7 @@ WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) {
|
|||
var changeAmount = Math.round(total - txpAmount - fee);
|
||||
log.debug('Tx change: ', Utils.formatAmountInBtc(changeAmount));
|
||||
|
||||
var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore.Transaction.DUST_AMOUNT);
|
||||
var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore_[txp.coin].Transaction.DUST_AMOUNT);
|
||||
if (changeAmount > 0 && changeAmount <= dustThreshold) {
|
||||
log.debug('Change below dust threshold (' + Utils.formatAmountInBtc(dustThreshold) + '). Incrementing fee to remove change.');
|
||||
// Remove dust change by incrementing fee
|
||||
|
@ -1850,7 +1881,7 @@ WalletService.prototype._canCreateTx = function(cb) {
|
|||
};
|
||||
|
||||
WalletService.prototype._validateOutputs = function(opts, wallet, cb) {
|
||||
var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore.Transaction.DUST_AMOUNT);
|
||||
var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore_[wallet.coin].Transaction.DUST_AMOUNT);
|
||||
|
||||
if (_.isEmpty(opts.outputs)) return new ClientError('No outputs were specified');
|
||||
|
||||
|
@ -1868,7 +1899,7 @@ WalletService.prototype._validateOutputs = function(opts, wallet, cb) {
|
|||
} catch (ex) {
|
||||
return Errors.INVALID_ADDRESS;
|
||||
}
|
||||
if (toAddress.network != wallet.getNetworkName()) {
|
||||
if (toAddress.network != wallet.network) {
|
||||
return Errors.INCORRECT_ADDRESS_NETWORK;
|
||||
}
|
||||
|
||||
|
@ -1957,6 +1988,7 @@ WalletService.prototype._getFeePerKb = function(wallet, opts, cb) {
|
|||
|
||||
if (_.isNumber(opts.feePerKb)) return cb(null, opts.feePerKb);
|
||||
self.getFeeLevels({
|
||||
coin: wallet.coin,
|
||||
network: wallet.network
|
||||
}, function(err, levels) {
|
||||
if (err) return cb(err);
|
||||
|
@ -2066,6 +2098,8 @@ WalletService.prototype.createTx = function(opts, cb) {
|
|||
id: opts.txProposalId,
|
||||
walletId: self.walletId,
|
||||
creatorId: self.copayerId,
|
||||
coin: wallet.coin,
|
||||
network: wallet.network,
|
||||
outputs: opts.outputs,
|
||||
message: opts.message,
|
||||
changeAddress: changeAddress,
|
||||
|
@ -2330,8 +2364,8 @@ WalletService.prototype.removePendingTx = function(opts, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
WalletService.prototype._broadcastRawTx = function(network, raw, cb) {
|
||||
var bc = this._getBlockchainExplorer(network);
|
||||
WalletService.prototype._broadcastRawTx = function(coin, network, raw, cb) {
|
||||
var bc = this._getBlockchainExplorer(coin, network);
|
||||
bc.broadcast(raw, function(err, txid) {
|
||||
if (err) return cb(err);
|
||||
return cb(null, txid);
|
||||
|
@ -2341,6 +2375,7 @@ WalletService.prototype._broadcastRawTx = function(network, raw, cb) {
|
|||
/**
|
||||
* Broadcast a raw transaction.
|
||||
* @param {Object} opts
|
||||
* @param {string} [opts.coin = 'btc'] - The coin for this transaction.
|
||||
* @param {string} [opts.network = 'livenet'] - The Bitcoin network for this transaction.
|
||||
* @param {string} opts.rawTx - Raw tx data.
|
||||
*/
|
||||
|
@ -2349,17 +2384,21 @@ WalletService.prototype.broadcastRawTx = function(opts, cb) {
|
|||
|
||||
if (!checkRequired(opts, ['network', 'rawTx'], cb)) return;
|
||||
|
||||
var network = opts.network || 'livenet';
|
||||
if (network != 'livenet' && network != 'testnet')
|
||||
opts.coin = opts.coin || Defaults.COIN;
|
||||
if (!Utils.checkValueInCollection(opts.coin, Constants.COINS))
|
||||
return cb(new ClientError('Invalid coin'));
|
||||
|
||||
opts.network = opts.network || 'livenet';
|
||||
if (!Utils.checkValueInCollection(opts.network, Constants.NETWORKS))
|
||||
return cb(new ClientError('Invalid network'));
|
||||
|
||||
self._broadcastRawTx(network, opts.rawTx, cb);
|
||||
self._broadcastRawTx(opts.coin, opts.network, opts.rawTx, cb);
|
||||
};
|
||||
|
||||
|
||||
WalletService.prototype._checkTxInBlockchain = function(txp, cb) {
|
||||
if (!txp.txid) return cb();
|
||||
var bc = this._getBlockchainExplorer(txp.getNetworkName());
|
||||
var bc = this._getBlockchainExplorer(txp.coin, txp.network);
|
||||
bc.getTransaction(txp.txid, function(err, tx) {
|
||||
if (err) return cb(err);
|
||||
return cb(null, !!tx);
|
||||
|
@ -2487,7 +2526,7 @@ WalletService.prototype.broadcastTx = function(opts, cb) {
|
|||
} catch (ex) {
|
||||
return cb(ex);
|
||||
}
|
||||
self._broadcastRawTx(txp.getNetworkName(), raw, function(err, txid) {
|
||||
self._broadcastRawTx(wallet.coin, wallet.network, raw, function(err, txid) {
|
||||
if (err) {
|
||||
var broadcastErr = err;
|
||||
// Check if tx already in blockchain
|
||||
|
@ -2693,29 +2732,35 @@ WalletService._cachedBlockheight;
|
|||
WalletService._initBlockchainHeightCache = function() {
|
||||
if (WalletService._cachedBlockheight) return;
|
||||
WalletService._cachedBlockheight = {
|
||||
livenet: {},
|
||||
testnet: {}
|
||||
btc: {
|
||||
livenet: {},
|
||||
testnet: {}
|
||||
},
|
||||
bch: {
|
||||
livenet: {},
|
||||
testnet: {}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
WalletService._clearBlockchainHeightCache = function(network) {
|
||||
WalletService._clearBlockchainHeightCache = function(coin, network) {
|
||||
WalletService._initBlockchainHeightCache();
|
||||
if (!Utils.checkValueInCollection(network, Constants.NETWORKS)) {
|
||||
log.error('Incorrect network in new block: ' + network);
|
||||
log.error('Incorrect network in new block: ' + coin + '/' + network);
|
||||
return;
|
||||
}
|
||||
WalletService._cachedBlockheight[network].current = null;
|
||||
WalletService._cachedBlockheight[coin][network].current = null;
|
||||
};
|
||||
|
||||
WalletService.prototype._getBlockchainHeight = function(network, cb) {
|
||||
WalletService.prototype._getBlockchainHeight = function(coin, network, cb) {
|
||||
var self = this;
|
||||
|
||||
var now = Date.now();
|
||||
WalletService._initBlockchainHeightCache();
|
||||
var cache = WalletService._cachedBlockheight[network];
|
||||
var cache = WalletService._cachedBlockheight[coin][network];
|
||||
|
||||
function fetchFromBlockchain(cb) {
|
||||
var bc = self._getBlockchainExplorer(network);
|
||||
var bc = self._getBlockchainExplorer(coin, network);
|
||||
bc.getBlockchainHeight(function(err, height) {
|
||||
if (!err && height > 0) {
|
||||
cache.current = height;
|
||||
|
@ -2883,10 +2928,9 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
function getNormalizedTxs(addresses, from, to, cb) {
|
||||
function getNormalizedTxs(wallet, addresses, from, to, cb) {
|
||||
var txs, fromCache, totalItems;
|
||||
var useCache = addresses.length >= Defaults.HISTORY_CACHE_ADDRESS_THRESOLD;
|
||||
var network = Bitcore.Address(addresses[0].address).toObject().network;
|
||||
|
||||
async.series([
|
||||
|
||||
|
@ -2907,7 +2951,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
|||
if (txs) return next();
|
||||
|
||||
var addressStrs = _.pluck(addresses, 'address');
|
||||
var bc = self._getBlockchainExplorer(network);
|
||||
var bc = self._getBlockchainExplorer(wallet.coin, wallet.network);
|
||||
bc.getTransactions(addressStrs, from, to, function(err, rawTxs, total) {
|
||||
if (err) return next(err);
|
||||
|
||||
|
@ -2934,7 +2978,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
|||
if (!txs) return next();
|
||||
|
||||
// Fix tx confirmations for cached txs
|
||||
self._getBlockchainHeight(network, function(err, height) {
|
||||
self._getBlockchainHeight(wallet.coin, wallet.network, function(err, height) {
|
||||
if (err || !height) return next(err);
|
||||
_.each(txs, function(tx) {
|
||||
if (tx.blockheight >= 0) {
|
||||
|
@ -2961,6 +3005,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
|||
if (_.isEmpty(unconfirmed)) return cb();
|
||||
|
||||
self.getFeeLevels({
|
||||
coin: wallet.coin,
|
||||
network: wallet.network
|
||||
}, function(err, levels) {
|
||||
if (err) {
|
||||
|
@ -2997,7 +3042,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
|||
async.waterfall([
|
||||
|
||||
function(next) {
|
||||
getNormalizedTxs(addresses, from, to, next);
|
||||
getNormalizedTxs(wallet, addresses, from, to, next);
|
||||
},
|
||||
function(txs, next) {
|
||||
// Fetch all proposals in [t - 7 days, t + 1 day]
|
||||
|
@ -3056,12 +3101,12 @@ WalletService.prototype.scan = function(opts, cb) {
|
|||
|
||||
opts = opts || {};
|
||||
|
||||
function checkActivity(address, network, cb) {
|
||||
var bc = self._getBlockchainExplorer(network);
|
||||
function checkActivity(wallet, address, cb) {
|
||||
var bc = self._getBlockchainExplorer(wallet.coin, wallet.network);
|
||||
bc.getAddressActivity(address, cb);
|
||||
};
|
||||
|
||||
function scanBranch(derivator, cb) {
|
||||
function scanBranch(wallet, derivator, cb) {
|
||||
var inactiveCounter = 0;
|
||||
var allAddresses = [];
|
||||
var gap = Defaults.SCAN_ADDRESS_GAP;
|
||||
|
@ -3070,7 +3115,7 @@ WalletService.prototype.scan = function(opts, cb) {
|
|||
return inactiveCounter < gap;
|
||||
}, function(next) {
|
||||
var address = derivator.derive();
|
||||
checkActivity(address.address, address.network, function(err, activity) {
|
||||
checkActivity(wallet, address.address, function(err, activity) {
|
||||
if (err) return next(err);
|
||||
|
||||
allAddresses.push(address);
|
||||
|
@ -3114,7 +3159,7 @@ WalletService.prototype.scan = function(opts, cb) {
|
|||
});
|
||||
|
||||
async.eachSeries(derivators, function(derivator, next) {
|
||||
scanBranch(derivator, function(err, addresses) {
|
||||
scanBranch(wallet, derivator, function(err, addresses) {
|
||||
if (err) return next(err);
|
||||
self.storage.storeAddressAndWallet(wallet, addresses, next);
|
||||
});
|
||||
|
|
|
@ -49,8 +49,10 @@ describe('Blockchain monitor', function() {
|
|||
messageBroker: server.messageBroker,
|
||||
storage: storage,
|
||||
blockchainExplorers: {
|
||||
'testnet': blockchainExplorer,
|
||||
'livenet': blockchainExplorer
|
||||
'btc': {
|
||||
'testnet': blockchainExplorer,
|
||||
'livenet': blockchainExplorer
|
||||
}
|
||||
},
|
||||
}, function(err) {
|
||||
should.not.exist(err);
|
||||
|
|
|
@ -6636,6 +6636,7 @@ describe('Wallet service', function() {
|
|||
|
||||
blockchainExplorer.getBlockchainHeight = sinon.stub().callsArgWith(0, null, 2000);
|
||||
server._notify('NewBlock', {
|
||||
coin: 'btc',
|
||||
network: 'livenet',
|
||||
hash: 'dummy hash',
|
||||
}, {
|
||||
|
|
|
@ -24,6 +24,11 @@ describe('TxProposal', function() {
|
|||
should.exist(txp);
|
||||
txp.amount.should.equal(aTXP().amount);
|
||||
});
|
||||
it('should default to BTC coin', function() {
|
||||
var txp = TxProposal.fromObj(aTXP());
|
||||
should.exist(txp);
|
||||
txp.coin.should.equal('btc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getBitcoreTx', function() {
|
||||
|
@ -108,8 +113,10 @@ var theXPub = 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2
|
|||
var theSignatures = ['304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6'];
|
||||
var theRawTx = '0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e08000000910047304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0380969800000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac002d3101000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70f62b040000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000';
|
||||
|
||||
var aTxpOpts = function(type) {
|
||||
var aTxpOpts = function() {
|
||||
var opts = {
|
||||
coin: 'btc',
|
||||
network: 'livenet',
|
||||
message: 'some message'
|
||||
};
|
||||
opts.outputs = [{
|
||||
|
@ -125,7 +132,7 @@ var aTxpOpts = function(type) {
|
|||
return opts;
|
||||
};
|
||||
|
||||
var aTXP = function(type) {
|
||||
var aTXP = function() {
|
||||
var txp = {
|
||||
"version": 3,
|
||||
"createdOn": 1423146231,
|
||||
|
|
|
@ -150,6 +150,8 @@ describe('Storage', function() {
|
|||
proposals = _.map(_.range(4), function(i) {
|
||||
var tx = Model.TxProposal.create({
|
||||
walletId: '123',
|
||||
coin: 'btc',
|
||||
network: 'livenet',
|
||||
outputs: [{
|
||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||
amount: i + 100,
|
||||
|
|
Loading…
Reference in New Issue