Add Insight and UTXO class

This commit is contained in:
Esteban Ordano 2014-12-30 22:12:24 -03:00
parent 9f32c1b7ba
commit 476f009b4d
7 changed files with 185 additions and 69 deletions

View File

@ -53,5 +53,9 @@ bitcore.deps.bs58 = require('bs58');
bitcore.deps.Buffer = Buffer;
bitcore.deps.elliptic = require('elliptic');
// blockchain explorers
bitcore.explorers = {};
bitcore.explorers.Insight = require('./lib/explorers/insight');
// Internal usage, exposed for testing/advanced tweaking
bitcore._HDKeyCache = require('./lib/hdkeycache');

80
lib/explorers/insight.js Normal file
View File

@ -0,0 +1,80 @@
'use strict';
var Networks = require('../networks');
var JSUtil = require('../util/js');
var $ = require('../util/preconditions');
var _ = require('lodash');
var Address = require('../address');
var Transaction = require('../transaction');
var UTXO = require('../utxo');
var request = require('request');
// var insight = new Insight(Networks.livenet);
function Insight(url, network) {
if (!url && !network) {
return new Insight(Networks.defaultNetwork);
}
if (Networks.get(url)) {
network = Networks.get(url);
if (network === Networks.livenet) {
url = 'https://insight.bitpay.com';
} else {
url = 'https://test-insight.bitpay.com';
}
}
JSUtil.defineImmutable(this, {
url: url,
network: Networks.get(network) || Networks.defaultNetwork
});
return this;
}
Insight.prototype.getUnspentUtxos = function(addresses, callback) {
$.checkArgument(_.isFunction(callback));
if (!_.isArray(addresses)) {
addresses = [addresses];
}
addresses = _.map(addresses, function(address) { return new Address(address); });
this.requestPost('/api/addrs/utxo', {
addrs: _.map(addresses, function(address) { return address.toString(); }).join(',')
}, function(err, res, unspent) {
if (err || res.statusCode !== 200) {
return callback(err || res);
}
unspent = _.map(unspent, UTXO);
return callback(null, unspent);
});
};
Insight.prototype.broadcast = function(transaction, callback) {
$.checkArgument(JSUtil.isHexa(transaction) || transaction instanceof Transaction);
$.checkArgument(_.isFunction(callback));
if (transaction instanceof Transaction) {
transaction = transaction.serialize();
}
this.requestPost('/api/tx/send', {
rawtx: transaction
}, function(err, res, body) {
if (err || res.statusCode !== 200) {
return callback(err || body);
}
return callback(null, body ? body.txid : null);
});
};
Insight.prototype.requestPost = function(path, data, callback) {
$.checkArgument(_.isString(path));
$.checkArgument(_.isFunction(callback));
request({
method: 'POST',
url: this.url + path,
json: data
}, callback);
};
module.exports = Insight;

View File

@ -15,7 +15,7 @@ var Signature = require('../crypto/signature');
var Sighash = require('./sighash');
var Address = require('../address');
var Unit = require('../unit');
var UTXO = require('../utxo');
var Input = require('./input');
var PublicKeyHashInput = Input.PublicKeyHash;
var MultiSigScriptHashInput = Input.MultiSigScriptHash;
@ -293,68 +293,23 @@ Transaction.prototype._newTransaction = function() {
* @param {number=} threshold
*/
Transaction.prototype.from = function(utxo, pubkeys, threshold) {
if (_.isArray(utxo)) {
var self = this;
_.each(utxo, function(utxo) {
self.from(utxo, pubkeys, threshold);
});
return this;
}
if (pubkeys && threshold) {
this._fromMultiSigP2SH(utxo, pubkeys, threshold);
this._fromMultisigUtxo(utxo, pubkeys, threshold);
} else {
this._fromNonP2SH(utxo);
}
return this;
};
Transaction.prototype._fromMultiSigP2SH = function(utxo, pubkeys, threshold) {
if (Transaction._isNewUtxo(utxo)) {
this._fromMultisigNewUtxo(utxo, pubkeys, threshold);
} else if (Transaction._isOldUtxo(utxo)) {
this._fromMultisigOldUtxo(utxo, pubkeys, threshold);
} else {
throw new Transaction.Errors.UnrecognizedUtxoFormat(utxo);
}
};
Transaction.prototype._fromNonP2SH = function(utxo) {
var self = this;
if (_.isArray(utxo)) {
_.each(utxo, function(single) {
self._fromNonP2SH(single);
});
return;
}
if (Transaction._isNewUtxo(utxo)) {
this._fromNewUtxo(utxo);
} else if (Transaction._isOldUtxo(utxo)) {
this._fromOldUtxo(utxo);
} else {
throw new Transaction.Errors.UnrecognizedUtxoFormat(utxo);
}
};
Transaction._isNewUtxo = function(utxo) {
var isDefined = function(param) {
return !_.isUndefined(param);
};
return _.all(_.map([utxo.txId, utxo.outputIndex, utxo.satoshis, utxo.script], isDefined));
};
Transaction._isOldUtxo = function(utxo) {
var isDefined = function(param) {
return !_.isUndefined(param);
};
return _.all(_.map([utxo.txid, utxo.vout, utxo.scriptPubKey, utxo.amount], isDefined));
};
Transaction.prototype._fromOldUtxo = function(utxo) {
return this._fromNewUtxo({
address: utxo.address && new Address(utxo.address),
txId: utxo.txid,
outputIndex: utxo.vout,
script: util.isHexa(utxo.script) ? new buffer.Buffer(utxo.scriptPubKey, 'hex') : utxo.scriptPubKey,
satoshis: Unit.fromBTC(utxo.amount).satoshis
});
};
Transaction.prototype._fromNewUtxo = function(utxo) {
utxo.address = utxo.address && new Address(utxo.address);
utxo.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script);
utxo = new UTXO(utxo);
this.inputs.push(new PublicKeyHashInput({
output: new Output({
script: utxo.script,
@ -368,19 +323,8 @@ Transaction.prototype._fromNewUtxo = function(utxo) {
this._inputAmount += utxo.satoshis;
};
Transaction.prototype._fromMultisigOldUtxo = function(utxo, pubkeys, threshold) {
return this._fromMultisigNewUtxo({
address: utxo.address && new Address(utxo.address),
txId: utxo.txid,
outputIndex: utxo.vout,
script: new buffer.Buffer(utxo.scriptPubKey, 'hex'),
satoshis: Unit.fromBTC(utxo.amount).satoshis
}, pubkeys, threshold);
};
Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold) {
utxo.address = utxo.address && new Address(utxo.address);
utxo.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script);
Transaction.prototype._fromMultisigUtxo = function(utxo, pubkeys, threshold) {
utxo = new UTXO(utxo);
this.addInput(new MultiSigScriptHashInput({
output: new Output({
script: utxo.script,

View File

@ -55,6 +55,7 @@ module.exports = {
Object.keys(values).forEach(function(key){
Object.defineProperty(target, key, {
configurable: false,
enumerable: true,
value: values[key]
});
});

72
lib/utxo.js Normal file
View File

@ -0,0 +1,72 @@
'use strict';
var _ = require('lodash');
var $ = require('./util/preconditions');
var JSUtil = require('./util/js');
var Script = require('./script');
var Address = require('./address');
var Unit = require('./unit');
function UTXO(data) {
/* jshint maxcomplexity: 20 */
/* jshint maxstatements: 20 */
if (!(this instanceof UTXO)) {
return new UTXO(data);
}
$.checkArgument(_.isObject(data), 'Must provide an object from where to extract data');
var address = data.address ? new Address(data.address) : undefined;
var txId = data.txid ? data.txid : data.txId;
if (!txId || !JSUtil.isHexaString(txId) || txId.length > 64) {
// TODO: Use the errors library
throw new Error('Invalid TXID in object', data);
}
var outputIndex = _.isUndefined(data.vout) ? data.outputIndex : data.vout;
if (!_.isNumber(outputIndex)) {
throw new Error('Invalid outputIndex, received ' + outputIndex);
}
$.checkArgument(data.scriptPubKey || data.script, 'Must provide the scriptPubKey for that output!');
var script = new Script(data.scriptPubKey || data.script);
$.checkArgument(data.amount || data.satoshis, 'Must provide the scriptPubKey for that output!');
var amount = data.amount ? new Unit.fromBTC(data.amount).toSatoshis() : data.satoshis;
$.checkArgument(_.isNumber(amount), 'Amount must be a number');
JSUtil.defineImmutable(this, {
address: address,
txId: txId,
outputIndex: outputIndex,
script: script,
satoshis: amount
});
}
UTXO.prototype.inspect = function() {
return '<UTXO: ' + this.txId + ':' + this.outputIndex +
', satoshis: ' + this.satoshis + ', address: ' + this.address + '>';
};
UTXO.prototype.toString = function() {
return this.txId + ':' + this.outputIndex;
};
UTXO.fromJSON = UTXO.fromObject = function(data) {
if (_.isString(data)) {
data = JSON.parse(data);
}
return new UTXO(data);
};
UTXO.prototype.toJSON = function() {
return JSON.stringify(this.toObject());
};
UTXO.prototype.toObject = function() {
return {
address: this.address.toObject(),
txid: this.txId,
vout: this.outputIndex,
scriptPubKey: this.script.toObject(),
amount: Unit.fromSatoshis(this.satoshis).toBTC()
};
};
module.exports = UTXO;

12
npm-shrinkwrap.json generated
View File

@ -1,6 +1,6 @@
{
"name": "bitcore",
"version": "0.8.0",
"version": "0.8.5",
"dependencies": {
"aes": {
"version": "0.1.0",
@ -17,6 +17,11 @@
"from": "bn.js@0.16.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.0.tgz"
},
"browser-request": {
"version": "0.3.3",
"from": "browser-request@*",
"resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz"
},
"bs58": {
"version": "2.0.0",
"from": "bs58@2.0.0",
@ -89,6 +94,11 @@
"from": "protobufjs@3.0.0",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-3.0.0.tgz"
},
"request": {
"version": "2.51.0",
"from": "request@*",
"resolved": "https://registry.npmjs.org/request/-/request-2.51.0.tgz"
},
"sha512": {
"version": "0.0.1",
"from": "sha512@=0.0.1",

View File

@ -69,9 +69,13 @@
"type": "git",
"url": "https://github.com/bitpay/bitcore.git"
},
"browser": {
"request": "browser-request"
},
"dependencies": {
"asn1.js": "=0.4.1",
"bn.js": "=0.16.0",
"browser-request": "^0.3.3",
"bs58": "=2.0.0",
"bufferput": "^0.1.2",
"buffers": "^0.1.1",
@ -81,6 +85,7 @@
"jsrsasign": "=0.0.3",
"lodash": "=2.4.1",
"protobufjs": "=3.0.0",
"request": "^2.51.0",
"sha512": "=0.0.1",
"socks5-client": "^0.3.6"
},