Merge branch 'feature/TransactionBuilder'
This commit is contained in:
commit
93050e3e92
|
@ -50,6 +50,6 @@ module.exports = function(grunt) {
|
|||
|
||||
});
|
||||
|
||||
grunt.registerTask('default', ['watch']);
|
||||
grunt.registerTask('default', ['shell','watch']);
|
||||
|
||||
};
|
||||
|
|
61
README.md
61
README.md
|
@ -124,19 +124,23 @@ rpc.getBlock(hash, function(err, ret) {
|
|||
Check the list of all supported RPC call at [RpcClient.js](RpcClient.js)
|
||||
|
||||
## Creating and sending a Transaction through P2P
|
||||
For this example you need a running bitcoind instance with RPC enabled.
|
||||
|
||||
The fee of the transaction can be given in `opts` or it will be determined
|
||||
by the transaction size. Documentation on the paramets of `TransactionBuilder`
|
||||
can be found on the source file.
|
||||
|
||||
```js
|
||||
var bitcore = require('bitcore');
|
||||
var networks = bitcore.networks;
|
||||
var Peer = bitcore.Peer;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||
var PeerManager = require('soop').load('../PeerManager', {
|
||||
network: networks.testnet
|
||||
});
|
||||
|
||||
// this can be get from insight.bitcore.io API o blockchain.info
|
||||
var utxos = {
|
||||
"unspent": [
|
||||
|
||||
var unspent = [
|
||||
{
|
||||
"address": "n4g2TFaQo8UgedwpkYdcQFF6xE2Ei9Czvy",
|
||||
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
|
@ -153,29 +157,15 @@ var utxos = {
|
|||
"confirmations": 1,
|
||||
"amount": 10
|
||||
},
|
||||
};
|
||||
];
|
||||
|
||||
//private keys in WIF format (see Transaction.js for other options)
|
||||
//private keys in WIF format (see TransactionBuilder.js for other options)
|
||||
var keys = [
|
||||
"cSq7yo4fvsbMyWVN945VUGUWMaSazZPWqBVJZyoGsHmNq6W4HVBV",
|
||||
"cPa87VgwZfowGZYaEenoQeJgRfKW6PhZ1R65EHTkN1K19cSvc92G",
|
||||
"cPQ9DSbBRLva9av5nqeF5AGrh3dsdW8p2E5jS4P8bDWZAoQTeeKB"
|
||||
];
|
||||
|
||||
function createTx() {
|
||||
var outs = [{address:'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount:0.08}];
|
||||
|
||||
var ret = Transaction.createAndSign(utxos, outs, keys);
|
||||
|
||||
/ * create and signing can be done in 2 steps using:
|
||||
* var ret = Transaction.create(utxos,outs);
|
||||
* and later:
|
||||
* ret.tx.sign(ret.tx.selectedUtxos, outs, keys);
|
||||
*/
|
||||
|
||||
return ret.tx.serialize().toString('hex');
|
||||
};
|
||||
|
||||
|
||||
var peerman = new PeerManager();
|
||||
peerman.addPeer(new Peer('127.0.0.1', 18333));
|
||||
|
@ -183,7 +173,36 @@ peerman.addPeer(new Peer('127.0.0.1', 18333));
|
|||
peerman.on('connect', function() {
|
||||
var conn = peerman.getActiveConnection();
|
||||
if (conn) {
|
||||
conn.sendTx(createTx());
|
||||
var outs = [{address:toAddress, amount:amt}];
|
||||
var opts = {remainderAddress: changeAddressString};
|
||||
var Builder = bitcore.TransactionBuilder;
|
||||
|
||||
var tx = new Builder(opts)
|
||||
.setUnspent(Unspent)
|
||||
.setOutputs(outs)
|
||||
.sign(keys)
|
||||
.build();
|
||||
|
||||
/* create and signing can be done in multiple steps using:
|
||||
*
|
||||
* var builder = new bitcore.TransactionBuilder(opts)
|
||||
* .setUnspent(utxos)
|
||||
* .setOutputs(outs);
|
||||
* //later
|
||||
* builder.sign(key1);
|
||||
* // get partially signed tx
|
||||
* var tx = builder.build();
|
||||
*
|
||||
* //later
|
||||
* builder.sign(key2);
|
||||
* if (builder.isFullySigned()){
|
||||
* var tx = builder.build();
|
||||
* }
|
||||
*
|
||||
* The selected Unspent Outputs for the transaction can be retrieved with:
|
||||
* var selectedUnspent = build.getSelectedUnspent();
|
||||
*/
|
||||
conn.sendTx(tx.serialize().toString('hex'));
|
||||
}
|
||||
conn.on('reject', function() {
|
||||
console.log('Transaction Rejected');
|
||||
|
|
404
Transaction.js
404
Transaction.js
|
@ -302,9 +302,6 @@ Transaction.prototype.hashForSignature =
|
|||
"(" + this.ins.length + " inputs)");
|
||||
}
|
||||
|
||||
// Clone transaction
|
||||
var txTmp = new Transaction(this);
|
||||
|
||||
// In case concatenating two scripts ends up with two codeseparators,
|
||||
// or an extra one at the end, this prevents all those possible
|
||||
// incompatibilities.
|
||||
|
@ -355,7 +352,7 @@ Transaction.prototype.hashForSignature =
|
|||
} else {
|
||||
var outsLen;
|
||||
if (hashTypeMode === SIGHASH_SINGLE) {
|
||||
if (inIndex >= txTmp.outs.length) {
|
||||
if (inIndex >= this.outs.length) {
|
||||
// bug present in bitcoind which must be also present in bitcore
|
||||
// see https://bitcointalk.org/index.php?topic=260595
|
||||
// Transaction.hashForSignature(): SIGHASH_SINGLE
|
||||
|
@ -530,173 +527,6 @@ Transaction.prototype.parse = function(parser) {
|
|||
|
||||
|
||||
|
||||
/*
|
||||
* selectUnspent
|
||||
*
|
||||
* Selects some unspent outputs for later usage in tx inputs
|
||||
*
|
||||
* @utxos
|
||||
* @totalNeededAmount: output transaction amount in BTC, including fee
|
||||
* @allowUnconfirmed: false (allow selecting unconfirmed utxos)
|
||||
*
|
||||
* Note that the sum of the selected unspent is >= the desired amount.
|
||||
* Returns the selected unspent outputs if the totalNeededAmount was reach.
|
||||
* 'null' if not.
|
||||
*
|
||||
* TODO: utxo selection is not optimized to minimize mempool usage.
|
||||
*
|
||||
*/
|
||||
|
||||
Transaction.selectUnspent = function(utxos, totalNeededAmount, allowUnconfirmed) {
|
||||
|
||||
var minConfirmationSteps = [6, 1];
|
||||
if (allowUnconfirmed) minConfirmationSteps.push(0);
|
||||
|
||||
var ret = [];
|
||||
var l = utxos.length;
|
||||
var totalSat = bignum(0);
|
||||
var totalNeededAmountSat = util.parseValue(totalNeededAmount);
|
||||
var fulfill = false;
|
||||
var maxConfirmations = null;
|
||||
|
||||
do {
|
||||
var minConfirmations = minConfirmationSteps.shift();
|
||||
for (var i = 0; i < l; i++) {
|
||||
var u = utxos[i];
|
||||
|
||||
var c = u.confirmations || 0;
|
||||
|
||||
if (c < minConfirmations || (maxConfirmations && c >= maxConfirmations))
|
||||
continue;
|
||||
|
||||
|
||||
var sat = u.amountSat || util.parseValue(u.amount);
|
||||
totalSat = totalSat.add(sat);
|
||||
ret.push(u);
|
||||
if (totalSat.cmp(totalNeededAmountSat) >= 0) {
|
||||
fulfill = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
maxConfirmations = minConfirmations;
|
||||
} while (!fulfill && minConfirmationSteps.length);
|
||||
|
||||
//TODO(?): sort ret and check is some inputs can be avoided.
|
||||
//If the initial utxos are sorted, this step would be necesary only if
|
||||
//utxos were selected from different minConfirmationSteps.
|
||||
|
||||
return fulfill ? ret : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* _scriptForAddress
|
||||
*
|
||||
* Returns a scriptPubKey for the given address type
|
||||
*/
|
||||
|
||||
Transaction._scriptForAddress = function(addressString) {
|
||||
|
||||
var livenet = networks.livenet;
|
||||
var testnet = networks.testnet;
|
||||
var address = new Address(addressString);
|
||||
|
||||
var version = address.version();
|
||||
var script;
|
||||
if (version == livenet.addressPubkey || version == testnet.addressPubkey)
|
||||
script = Script.createPubKeyHashOut(address.payload());
|
||||
else if (version == livenet.addressScript || version == testnet.addressScript)
|
||||
script = Script.createP2SH(address.payload());
|
||||
else
|
||||
throw new Error('invalid output address');
|
||||
|
||||
return script;
|
||||
};
|
||||
|
||||
Transaction._sumOutputs = function(outs) {
|
||||
var valueOutSat = bignum(0);
|
||||
var l = outs.length;
|
||||
|
||||
for (var i = 0; i < outs.length; i++) {
|
||||
var sat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
||||
valueOutSat = valueOutSat.add(sat);
|
||||
}
|
||||
return valueOutSat;
|
||||
}
|
||||
|
||||
/*
|
||||
* createWithFee
|
||||
* Create a TX given ins (selected already), outs, and a FIXED fee
|
||||
* details on the input on .create
|
||||
*/
|
||||
|
||||
Transaction.createWithFee = function(ins, outs, feeSat, opts) {
|
||||
opts = opts || {};
|
||||
feeSat = feeSat || 0;
|
||||
|
||||
var txobj = {};
|
||||
txobj.version = 1;
|
||||
txobj.lock_time = opts.lockTime || 0;
|
||||
txobj.ins = [];
|
||||
txobj.outs = [];
|
||||
|
||||
|
||||
var l = ins.length;
|
||||
var valueInSat = bignum(0);
|
||||
for (var i = 0; i < l; i++) {
|
||||
valueInSat = valueInSat.add(util.parseValue(ins[i].amount));
|
||||
|
||||
var txin = {};
|
||||
txin.s = util.EMPTY_BUFFER;
|
||||
txin.q = 0xffffffff;
|
||||
|
||||
var hash = new Buffer(ins[i].txid, 'hex');
|
||||
var hashReversed = buffertools.reverse(hash);
|
||||
|
||||
var vout = parseInt(ins[i].vout);
|
||||
var voutBuf = new Buffer(4);
|
||||
voutBuf.writeUInt32LE(vout, 0);
|
||||
|
||||
txin.o = Buffer.concat([hashReversed, voutBuf]);
|
||||
txobj.ins.push(txin);
|
||||
}
|
||||
|
||||
var valueOutSat = Transaction._sumOutputs(outs);
|
||||
valueOutSat = valueOutSat.add(feeSat);
|
||||
|
||||
if (valueInSat.cmp(valueOutSat) < 0) {
|
||||
var inv = valueInSat.toString();
|
||||
var ouv = valueOutSat.toString();
|
||||
throw new Error('transaction input amount is less than outputs: ' +
|
||||
inv + ' < ' + ouv + ' [SAT]');
|
||||
}
|
||||
|
||||
for (var i = 0; i < outs.length; i++) {
|
||||
var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
||||
var value = util.bigIntToValue(amountSat);
|
||||
var script = Transaction._scriptForAddress(outs[i].address);
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
txobj.outs.push(txout);
|
||||
}
|
||||
|
||||
// add remainder (without modifiying outs[])
|
||||
var remainderSat = valueInSat.sub(valueOutSat);
|
||||
if (remainderSat.cmp(0) > 0) {
|
||||
var remainderAddress = opts.remainderAddress || ins[0].address;
|
||||
var value = util.bigIntToValue(remainderSat);
|
||||
var script = Transaction._scriptForAddress(remainderAddress);
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
txobj.outs.push(txout);
|
||||
}
|
||||
|
||||
|
||||
return new Transaction(txobj);
|
||||
};
|
||||
|
||||
Transaction.prototype.calcSize = function() {
|
||||
var totalSize = 8; // version + lock_time
|
||||
|
@ -722,7 +552,6 @@ Transaction.prototype.getSize = function getHash() {
|
|||
return this.size;
|
||||
};
|
||||
|
||||
|
||||
Transaction.prototype.isComplete = function() {
|
||||
var l = this.ins.length;
|
||||
|
||||
|
@ -737,235 +566,4 @@ Transaction.prototype.isComplete = function() {
|
|||
};
|
||||
|
||||
|
||||
/*
|
||||
* sign
|
||||
*
|
||||
* signs the transaction
|
||||
*
|
||||
* @ utxos
|
||||
* @keypairs
|
||||
* @opts
|
||||
* signhash: Transaction.SIGHASH_ALL
|
||||
*
|
||||
* Return the 'completeness' status of the tx (i.e, if all inputs are signed).
|
||||
*
|
||||
*/
|
||||
|
||||
Transaction.prototype.sign = function(selectedUtxos, keys, opts) {
|
||||
var self = this;
|
||||
var complete = false;
|
||||
var m = keys.length;
|
||||
opts = opts || {};
|
||||
var signhash = opts.signhash || SIGHASH_ALL;
|
||||
|
||||
if (selectedUtxos.length !== self.ins.length)
|
||||
throw new Error('given selectedUtxos do not match tx inputs');
|
||||
|
||||
var inputMap = [];
|
||||
var l = selectedUtxos.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
inputMap[i] = {
|
||||
address: selectedUtxos[i].address,
|
||||
scriptPubKey: selectedUtxos[i].scriptPubKey
|
||||
};
|
||||
}
|
||||
|
||||
//prepare keys
|
||||
var walletKeyMap = {};
|
||||
var l = keys.length;
|
||||
var wk;
|
||||
for (var i = 0; i < l; i++) {
|
||||
var k = keys[i];
|
||||
|
||||
if (typeof k === 'string') {
|
||||
var pk = new PrivateKey(k);
|
||||
wk = new WalletKey({
|
||||
network: pk.network()
|
||||
});
|
||||
wk.fromObj({
|
||||
priv: k
|
||||
});
|
||||
} else if (k instanceof WalletKey) {
|
||||
wk = k;
|
||||
} else {
|
||||
throw new Error('argument must be an array of strings (WIF format) or WalletKey objects');
|
||||
}
|
||||
walletKeyMap[wk.storeObj().addr] = wk;
|
||||
}
|
||||
|
||||
var inputSigned = 0;
|
||||
l = self.ins.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
var aIn = self.ins[i];
|
||||
var wk = walletKeyMap[inputMap[i].address];
|
||||
|
||||
if (typeof wk === 'undefined') {
|
||||
if (buffertools.compare(aIn.s, util.EMPTY_BUFFER) !== 0)
|
||||
inputSigned++;
|
||||
continue;
|
||||
}
|
||||
var scriptBuf = new Buffer(inputMap[i].scriptPubKey, 'hex');
|
||||
var s = new Script(scriptBuf);
|
||||
if (s.classify() !== Script.TX_PUBKEYHASH) {
|
||||
throw new Error('input:' + i + ' script type:' + s.getRawOutType() + ' not supported yet');
|
||||
}
|
||||
|
||||
var txSigHash = self.hashForSignature(s, i, signhash);
|
||||
|
||||
var sigRaw;
|
||||
var triesLeft = 10;
|
||||
do {
|
||||
sigRaw = wk.privKey.signSync(txSigHash);
|
||||
} while (wk.privKey.verifySignatureSync(txSigHash, sigRaw) === false && triesLeft--);
|
||||
|
||||
if (!triesLeft) {
|
||||
log.debug('could not sign input:' + i + ' verification failed');
|
||||
continue;
|
||||
}
|
||||
|
||||
var sigType = new Buffer(1);
|
||||
sigType[0] = signhash;
|
||||
var sig = Buffer.concat([sigRaw, sigType]);
|
||||
|
||||
var scriptSig = new Script();
|
||||
scriptSig.chunks.push(sig);
|
||||
scriptSig.chunks.push(wk.privKey.public);
|
||||
scriptSig.updateBuffer();
|
||||
self.ins[i].s = scriptSig.getBuffer();
|
||||
inputSigned++;
|
||||
}
|
||||
var complete = inputSigned === l;
|
||||
return complete;
|
||||
};
|
||||
|
||||
/*
|
||||
* create
|
||||
*
|
||||
* creates a transaction without signing it.
|
||||
*
|
||||
* @utxos
|
||||
* @outs
|
||||
* @opts
|
||||
*
|
||||
* See createAndSign for documentation on the inputs
|
||||
*
|
||||
* Returns:
|
||||
* { tx: {}, selectedUtxos: []}
|
||||
* see createAndSign for details
|
||||
*
|
||||
*/
|
||||
|
||||
Transaction.create = function(utxos, outs, opts) {
|
||||
|
||||
//starting size estimation
|
||||
var size = 500;
|
||||
var opts = opts || {};
|
||||
|
||||
var givenFeeSat;
|
||||
if (opts.fee || opts.feeSat) {
|
||||
givenFeeSat = opts.fee ? opts.fee * util.COIN : opts.feeSat;
|
||||
}
|
||||
|
||||
var selectedUtxos;
|
||||
do {
|
||||
// based on https://en.bitcoin.it/wiki/Transaction_fees
|
||||
maxSizeK = parseInt(size / 1000) + 1;
|
||||
var feeSat = givenFeeSat ? givenFeeSat : maxSizeK * FEE_PER_1000B_SAT;
|
||||
|
||||
var valueOutSat = Transaction
|
||||
._sumOutputs(outs)
|
||||
.add(feeSat);
|
||||
|
||||
selectedUtxos = Transaction
|
||||
.selectUnspent(utxos, valueOutSat / util.COIN, opts.allowUnconfirmed);
|
||||
|
||||
if (!selectedUtxos) {
|
||||
throw new Error(
|
||||
'the given UTXOs dont sum up the given outputs: ' + valueOutSat.toString() + ' (fee is ' + feeSat + ' )SAT'
|
||||
);
|
||||
}
|
||||
var tx = Transaction.createWithFee(selectedUtxos, outs, feeSat, {
|
||||
remainderAddress: opts.remainderAddress,
|
||||
lockTime: opts.lockTime,
|
||||
});
|
||||
|
||||
size = tx.getSize();
|
||||
} while (size > (maxSizeK + 1) * 1000);
|
||||
|
||||
return {
|
||||
tx: tx,
|
||||
selectedUtxos: selectedUtxos
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* createAndSign
|
||||
*
|
||||
* creates and signs a transaction
|
||||
*
|
||||
* @utxos
|
||||
* unspent outputs array (UTXO), using the following format:
|
||||
* [{
|
||||
* address: "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
* hash: "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
* scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
* vout: 1,
|
||||
* amount: 0.01,
|
||||
* confirmations: 3
|
||||
* }, ...
|
||||
* ]
|
||||
* This is compatible con insight's utxo API.
|
||||
* That amount is in BTCs (as returned in insight and bitcoind).
|
||||
* amountSat (instead of amount) can be given to provide amount in satochis.
|
||||
*
|
||||
|
||||
* @outs
|
||||
* an array of [{
|
||||
* address: xx,
|
||||
* amount:0.001
|
||||
* },...]
|
||||
*
|
||||
* @keys
|
||||
* an array of strings representing private keys to sign the
|
||||
* transaction in WIF private key format OR WalletKey objects
|
||||
*
|
||||
* @opts
|
||||
* {
|
||||
* remainderAddress: null,
|
||||
* fee: 0.001,
|
||||
* lockTime: null,
|
||||
* allowUnconfirmed: false,
|
||||
* signhash: SIGHASH_ALL
|
||||
* }
|
||||
*
|
||||
*
|
||||
* Retuns:
|
||||
* {
|
||||
* tx: The new created transaction,
|
||||
* selectedUtxos: The UTXOs selected as inputs for this transaction
|
||||
* }
|
||||
*
|
||||
* Amounts are in BTC. instead of fee and amount; feeSat and amountSat can be given,
|
||||
* repectively, to provide amounts in satoshis.
|
||||
*
|
||||
* If no remainderAddress is given, and there are remainder coins, the
|
||||
* first IN address will be used to return the coins. (TODO: is this is reasonable?)
|
||||
*
|
||||
* The Transaction creation is handled in 2 steps:
|
||||
* .create
|
||||
* .selectUnspent
|
||||
* .createWithFee
|
||||
* .sign
|
||||
*
|
||||
* If you need just to create a TX and not sign it, use .create
|
||||
*
|
||||
*/
|
||||
|
||||
Transaction.createAndSign = function(utxos, outs, keys, opts) {
|
||||
var ret = Transaction.create(utxos, outs, opts);
|
||||
ret.tx.sign(ret.selectedUtxos, keys);
|
||||
return ret;
|
||||
};
|
||||
|
||||
module.exports = require('soop')(Transaction);
|
||||
|
|
|
@ -0,0 +1,438 @@
|
|||
|
||||
/*
|
||||
var tx = (new TransactionBuilder(opts))
|
||||
.setUnspent(utxos)
|
||||
.setOutputs(outs)
|
||||
.sign(keys)
|
||||
.build();
|
||||
|
||||
|
||||
var builder = (new TransactionBuilder(opts))
|
||||
.setUnspent(spent)
|
||||
.setOutputs(outs);
|
||||
|
||||
// Uncomplete tx (no signed or partially signed)
|
||||
var tx = builder.build();
|
||||
|
||||
..later..
|
||||
|
||||
builder.sign(keys);
|
||||
while ( builder.isFullySigned() ) {
|
||||
|
||||
... get new keys ...
|
||||
|
||||
builder.sign(keys);
|
||||
}
|
||||
|
||||
var tx = builder.build();
|
||||
broadcast(tx.serialize());
|
||||
|
||||
To get selected unspent outputs:
|
||||
var selectedUnspent = builder.getSelectedUnspent();
|
||||
|
||||
|
||||
@unspent
|
||||
* unspent outputs array (UTXO), using the following format:
|
||||
* [{
|
||||
* address: "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
|
||||
* hash: "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
|
||||
* scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
|
||||
* vout: 1,
|
||||
* amount: 0.01,
|
||||
* confirmations: 3
|
||||
* }, ...
|
||||
* ]
|
||||
* This is compatible con insight's utxo API.
|
||||
* That amount is in BTCs (as returned in insight and bitcoind).
|
||||
* amountSat (instead of amount) can be given to provide amount in satochis.
|
||||
*
|
||||
* @outs
|
||||
* an array of [{
|
||||
* address: xx,
|
||||
* amount:0.001
|
||||
* },...]
|
||||
*
|
||||
* @keys
|
||||
* an array of strings representing private keys to sign the
|
||||
* transaction in WIF private key format OR WalletKey objects
|
||||
*
|
||||
* @opts
|
||||
* {
|
||||
* remainderAddress: null,
|
||||
* fee: 0.001,
|
||||
* lockTime: null,
|
||||
* spendUnconfirmed: false,
|
||||
* signhash: SIGHASH_ALL
|
||||
* }
|
||||
* Amounts are in BTC. instead of fee and amount; feeSat and amountSat can be given,
|
||||
* repectively, to provide amounts in satoshis.
|
||||
*
|
||||
* If no remainderAddress is given, and there are remainder coins, the
|
||||
* first IN address will be used to return the coins. (TODO: is this is reasonable?)
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
'use strict';
|
||||
|
||||
var imports = require('soop').imports();
|
||||
var Address = imports.Address || require('./Address');
|
||||
var Script = imports.Script || require('./Script');
|
||||
var util = imports.util || require('./util/util');
|
||||
var bignum = imports.bignum || require('bignum');
|
||||
var buffertools = imports.buffertools || require('buffertools');
|
||||
var networks = imports.networks || require('./networks');
|
||||
var WalletKey = imports.WalletKey || require('./WalletKey');
|
||||
var PrivateKey = imports.PrivateKey || require('./PrivateKey');
|
||||
|
||||
var Transaction = imports.Transaction || require('./Transaction');
|
||||
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN);
|
||||
|
||||
function TransactionBuilder(opts) {
|
||||
var opts = opts || {};
|
||||
this.txobj = {};
|
||||
this.txobj.version = 1;
|
||||
this.txobj.lock_time = opts.lockTime || 0;
|
||||
this.txobj.ins = [];
|
||||
this.txobj.outs = [];
|
||||
|
||||
this.spendUnconfirmed = opts.spendUnconfirmed || false;
|
||||
|
||||
if (opts.fee || opts.feeSat) {
|
||||
this.givenFeeSat = opts.fee ? opts.fee * util.COIN : opts.feeSat;
|
||||
}
|
||||
this.remainderAddress = opts.remainderAddress;
|
||||
this.signhash = opts.signhash || Transaction.SIGHASH_ALL;
|
||||
|
||||
this.tx = {};
|
||||
this.inputsSigned= 0;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* _scriptForAddress
|
||||
*
|
||||
* Returns a scriptPubKey for the given address type
|
||||
*/
|
||||
|
||||
TransactionBuilder._scriptForAddress = function(addressString) {
|
||||
|
||||
var livenet = networks.livenet;
|
||||
var testnet = networks.testnet;
|
||||
var address = new Address(addressString);
|
||||
|
||||
var version = address.version();
|
||||
var script;
|
||||
if (version === livenet.addressPubkey || version === testnet.addressPubkey)
|
||||
script = Script.createPubKeyHashOut(address.payload());
|
||||
else if (version === livenet.addressScript || version === testnet.addressScript)
|
||||
script = Script.createP2SH(address.payload());
|
||||
else
|
||||
throw new Error('invalid output address');
|
||||
|
||||
return script;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype.setUnspent = function(utxos) {
|
||||
this.utxos = utxos;
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._setInputMap = function() {
|
||||
var inputMap = [];
|
||||
|
||||
var l = this.selectedUtxos.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
var s = this.selectedUtxos[i];
|
||||
|
||||
inputMap.push({
|
||||
address: s.address,
|
||||
scriptPubKey: s.scriptPubKey
|
||||
});
|
||||
}
|
||||
this.inputMap = inputMap;
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype.getSelectedUnspent = function(neededAmountSat) {
|
||||
return this.selectedUtxos;
|
||||
};
|
||||
|
||||
/* _selectUnspent
|
||||
* TODO(?): sort sel (at the end) and check is some inputs can be avoided.
|
||||
* If the initial utxos are sorted, this step would be necesary only if
|
||||
* utxos were selected from different minConfirmationSteps.
|
||||
*/
|
||||
|
||||
TransactionBuilder.prototype._selectUnspent = function(neededAmountSat) {
|
||||
|
||||
if (!this.utxos || !this.utxos.length)
|
||||
throw new Error('unspent not set');
|
||||
|
||||
var minConfirmationSteps = [6, 1];
|
||||
if (this.spendUnconfirmed) minConfirmationSteps.push(0);
|
||||
|
||||
var sel = [],
|
||||
totalSat = bignum(0),
|
||||
fulfill = false,
|
||||
maxConfirmations = null,
|
||||
l = this.utxos.length;
|
||||
|
||||
do {
|
||||
var minConfirmations = minConfirmationSteps.shift();
|
||||
for (var i = 0; i < l; i++) {
|
||||
var u = this.utxos[i];
|
||||
var c = u.confirmations || 0;
|
||||
|
||||
if (c < minConfirmations || (maxConfirmations && c >= maxConfirmations))
|
||||
continue;
|
||||
|
||||
var sat = u.amountSat || util.parseValue(u.amount);
|
||||
totalSat = totalSat.add(sat);
|
||||
sel.push(u);
|
||||
if (totalSat.cmp(neededAmountSat) >= 0) {
|
||||
fulfill = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
maxConfirmations = minConfirmations;
|
||||
} while (!fulfill && minConfirmationSteps.length);
|
||||
|
||||
if (!fulfill)
|
||||
throw new Error('no enough unspent to fulfill totalNeededAmount');
|
||||
|
||||
this.selectedUtxos = sel;
|
||||
this._setInputMap();
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._setInputs = function() {
|
||||
var ins = this.selectedUtxos;
|
||||
var l = ins.length;
|
||||
var valueInSat = bignum(0);
|
||||
|
||||
this.txobj.ins=[];
|
||||
for (var i = 0; i < l; i++) {
|
||||
valueInSat = valueInSat.add(util.parseValue(ins[i].amount));
|
||||
|
||||
var txin = {};
|
||||
txin.s = util.EMPTY_BUFFER;
|
||||
txin.q = 0xffffffff;
|
||||
|
||||
var hash = new Buffer(ins[i].txid, 'hex');
|
||||
var hashReversed = buffertools.reverse(hash);
|
||||
|
||||
var vout = parseInt(ins[i].vout);
|
||||
var voutBuf = new Buffer(4);
|
||||
voutBuf.writeUInt32LE(vout, 0);
|
||||
|
||||
txin.o = Buffer.concat([hashReversed, voutBuf]);
|
||||
this.txobj.ins.push(txin);
|
||||
}
|
||||
this.valueInSat = valueInSat;
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._setFee = function(feeSat) {
|
||||
if ( typeof this.valueOutSat === 'undefined')
|
||||
throw new Error('valueOutSat undefined');
|
||||
|
||||
|
||||
var valueOutSat = this.valueOutSat.add(feeSat);
|
||||
|
||||
if (this.valueInSat.cmp(valueOutSat) < 0) {
|
||||
var inv = this.valueInSat.toString();
|
||||
var ouv = valueOutSat.toString();
|
||||
throw new Error('transaction input amount is less than outputs: ' +
|
||||
inv + ' < ' + ouv + ' [SAT]');
|
||||
}
|
||||
this.feeSat = feeSat;
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._setRemainder = function(remainderIndex) {
|
||||
|
||||
if ( typeof this.valueInSat === 'undefined' ||
|
||||
typeof this.valueOutSat === 'undefined')
|
||||
throw new Error('valueInSat / valueOutSat undefined');
|
||||
|
||||
// add remainder (without modifying outs[])
|
||||
var remainderSat = this.valueInSat.sub(this.valueOutSat).sub(this.feeSat);
|
||||
var l =this.txobj.outs.length;
|
||||
this.remainderSat = bignum(0);
|
||||
|
||||
//remove old remainder?
|
||||
if (l > remainderIndex) {
|
||||
this.txobj.outs.pop();
|
||||
}
|
||||
|
||||
if (remainderSat.cmp(0) > 0) {
|
||||
var remainderAddress = this.remainderAddress || this.selectedUtxos[0].address;
|
||||
var value = util.bigIntToValue(remainderSat);
|
||||
var script = TransactionBuilder._scriptForAddress(remainderAddress);
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
this.txobj.outs.push(txout);
|
||||
this.remainderSat = remainderSat;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._setFeeAndRemainder = function() {
|
||||
|
||||
//starting size estimation
|
||||
var size = 500, maxSizeK, remainderIndex = this.txobj.outs.length;
|
||||
|
||||
do {
|
||||
// based on https://en.bitcoin.it/wiki/Transaction_fees
|
||||
maxSizeK = parseInt(size / 1000) + 1;
|
||||
|
||||
var feeSat = this.givenFeeSat ?
|
||||
this.givenFeeSat : maxSizeK * FEE_PER_1000B_SAT;
|
||||
|
||||
var neededAmountSat = this.valueOutSat.add(feeSat);
|
||||
|
||||
this._selectUnspent(neededAmountSat)
|
||||
._setInputs()
|
||||
._setFee(feeSat)
|
||||
._setRemainder(remainderIndex);
|
||||
|
||||
|
||||
size = new Transaction(this.txobj).getSize();
|
||||
} while (size > (maxSizeK + 1) * 1000);
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype.setOutputs = function(outs) {
|
||||
var valueOutSat = bignum(0);
|
||||
|
||||
this.txobj.outs = [];
|
||||
var l =outs.length;
|
||||
|
||||
for (var i = 0; i < l; i++) {
|
||||
var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
||||
var value = util.bigIntToValue(amountSat);
|
||||
var script = TransactionBuilder._scriptForAddress(outs[i].address);
|
||||
var txout = {
|
||||
v: value,
|
||||
s: script.getBuffer(),
|
||||
};
|
||||
this.txobj.outs.push(txout);
|
||||
|
||||
var sat = outs[i].amountSat || util.parseValue(outs[i].amount);
|
||||
valueOutSat = valueOutSat.add(sat);
|
||||
}
|
||||
|
||||
this.valueOutSat = valueOutSat;
|
||||
|
||||
this._setFeeAndRemainder();
|
||||
|
||||
this.tx = new Transaction(this.txobj);
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder._mapKeys = function(keys) {
|
||||
|
||||
//prepare keys
|
||||
var walletKeyMap = {};
|
||||
var l = keys.length;
|
||||
var wk;
|
||||
for (var i = 0; i < l; i++) {
|
||||
var k = keys[i];
|
||||
|
||||
if (typeof k === 'string') {
|
||||
var pk = new PrivateKey(k);
|
||||
wk = new WalletKey({ network: pk.network() });
|
||||
wk.fromObj({ priv: k });
|
||||
}
|
||||
else if (k instanceof WalletKey) {
|
||||
wk = k;
|
||||
}
|
||||
else {
|
||||
throw new Error('argument must be an array of strings (WIF format) or WalletKey objects');
|
||||
}
|
||||
walletKeyMap[wk.storeObj().addr] = wk;
|
||||
}
|
||||
return walletKeyMap;
|
||||
};
|
||||
|
||||
TransactionBuilder._checkSupportedScriptType = function (s) {
|
||||
if (s.classify() !== Script.TX_PUBKEYHASH) {
|
||||
throw new Error('scriptSig type:' + s.getRawOutType() +
|
||||
' not supported yet');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TransactionBuilder._signHashAndVerify = function(wk, txSigHash) {
|
||||
var triesLeft = 10, sigRaw;
|
||||
|
||||
do {
|
||||
sigRaw = wk.privKey.signSync(txSigHash);
|
||||
} while (wk.privKey.verifySignatureSync(txSigHash, sigRaw) === false &&
|
||||
triesLeft--);
|
||||
|
||||
if (triesLeft<0)
|
||||
throw new Error('could not sign input: verification failed');
|
||||
|
||||
return sigRaw;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype._checkTx = function() {
|
||||
if (! this.tx || !this.tx.ins.length || !this.tx.outs.length)
|
||||
throw new Error('tx is not defined');
|
||||
};
|
||||
|
||||
|
||||
TransactionBuilder.prototype.sign = function(keys) {
|
||||
this._checkTx();
|
||||
|
||||
var tx = this.tx,
|
||||
ins = tx.ins,
|
||||
l = ins.length;
|
||||
|
||||
var walletKeyMap = TransactionBuilder._mapKeys(keys);
|
||||
|
||||
for (var i = 0; i < l; i++) {
|
||||
var im = this.inputMap[i];
|
||||
if (typeof im === 'undefined') continue;
|
||||
var wk = walletKeyMap[im.address];
|
||||
if (!wk) continue;
|
||||
|
||||
var scriptBuf = new Buffer(im.scriptPubKey, 'hex');
|
||||
|
||||
//TODO: support p2sh
|
||||
var s = new Script(scriptBuf);
|
||||
TransactionBuilder._checkSupportedScriptType(s);
|
||||
|
||||
var txSigHash = this.tx.hashForSignature(s, i, this.signhash);
|
||||
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash);
|
||||
var sigType = new Buffer(1);
|
||||
sigType[0] = this.signhash;
|
||||
var sig = Buffer.concat([sigRaw, sigType]);
|
||||
|
||||
var scriptSig = new Script();
|
||||
scriptSig.chunks.push(sig);
|
||||
scriptSig.chunks.push(wk.privKey.public);
|
||||
scriptSig.updateBuffer();
|
||||
tx.ins[i].s = scriptSig.getBuffer();
|
||||
this.inputsSigned++;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype.isFullySigned = function() {
|
||||
return this.inputsSigned === this.tx.ins.length;
|
||||
};
|
||||
|
||||
TransactionBuilder.prototype.build = function() {
|
||||
this._checkTx();
|
||||
return this.tx;
|
||||
};
|
||||
|
||||
module.exports = require('soop')(TransactionBuilder);
|
||||
|
|
@ -29,6 +29,7 @@ requireWhenAccessed('Point', './Point');
|
|||
requireWhenAccessed('Opcode', './Opcode');
|
||||
requireWhenAccessed('Script', './Script');
|
||||
requireWhenAccessed('Transaction', './Transaction');
|
||||
requireWhenAccessed('TransactionBuilder', './TransactionBuilder');
|
||||
requireWhenAccessed('Connection', './Connection');
|
||||
requireWhenAccessed('Peer', './Peer');
|
||||
requireWhenAccessed('Block', './Block');
|
||||
|
|
|
@ -45,6 +45,7 @@ var modules = [
|
|||
'ScriptInterpreter',
|
||||
'Sign',
|
||||
'Transaction',
|
||||
'TransactionBuilder',
|
||||
'Wallet',
|
||||
'WalletKey',
|
||||
'config',
|
||||
|
|
|
@ -25,23 +25,44 @@ var run = function() {
|
|||
|
||||
var outs = [{address:toAddress, amount:amt}];
|
||||
var keys = [priv];
|
||||
var opts = {remainderAddress: changeAddressString};
|
||||
var Builder = bitcore.TransactionBuilder;
|
||||
|
||||
var ret = bitcore.Transaction.createAndSign(utxos, outs, keys,
|
||||
{remainderAddress: changeAddressString});
|
||||
var tx = new Builder(opts)
|
||||
.setUnspent(utxos)
|
||||
.setOutputs(outs)
|
||||
.sign(keys)
|
||||
.build();
|
||||
|
||||
/* create and signing can be done in 2 steps using:
|
||||
* var ret = Transaction.create(utxos,outs);
|
||||
* and later:
|
||||
* ret.tx.sign(ret.tx.selectedUtxos, outs, keys);
|
||||
/* create and signing can be done in multiple steps using:
|
||||
*
|
||||
* var builder = new bitcore.TransactionBuilder(opts)
|
||||
* .setUnspent(utxos)
|
||||
* .setOutputs(outs);
|
||||
*
|
||||
* builder.sign(key1);
|
||||
* builder.sign(key2);
|
||||
* ...
|
||||
* if (builder.isFullySigned()){
|
||||
* var tx = builder.build();
|
||||
* }
|
||||
*
|
||||
* The selected Unspent Outputs for the transaction can be retrieved with:
|
||||
*
|
||||
* var selectedUnspent = build.getSelectedUnspent();
|
||||
*/
|
||||
|
||||
var txHex = ret.tx.serialize().toString('hex');
|
||||
var txHex = tx.serialize().toString('hex');
|
||||
console.log('TX HEX IS: ', txHex);
|
||||
};
|
||||
|
||||
|
||||
module.exports.run = run;
|
||||
if (require.main === module) {
|
||||
// This is just for browser & mocha compatibility
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports.run = run;
|
||||
if (require.main === module) {
|
||||
run();
|
||||
}
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
|
||||
var run = function() {
|
||||
// replace '../bitcore' with 'bitcore' if you use this code elsewhere.
|
||||
var bitcore = require('../bitcore');
|
||||
var networks = require('../networks');
|
||||
var Script = bitcore.Script;
|
||||
var WalletKey = bitcore.WalletKey;
|
||||
var buffertools = bitcore.buffertools;
|
||||
var Address = bitcore.Address;
|
||||
var util = bitcore.util;
|
||||
var opts = {network: networks.livenet};
|
||||
|
||||
var p = console.log;
|
||||
|
||||
var wk = new WalletKey(opts);
|
||||
wk.generate();
|
||||
var wkObj = wk.storeObj();
|
||||
|
||||
var s = Script.createPubKeyOut(wk.privKey.public);
|
||||
p('\nScript PubKey:');
|
||||
p('\tHex : ' + buffertools.toHex(s.buffer));
|
||||
p('\tHuman : ' + s.toHumanReadable());
|
||||
p('\tKey -------------------------------');
|
||||
console.log ('\tPrivate: ' + wkObj.priv);
|
||||
console.log ('\tPublic : ' + wkObj.pub);
|
||||
console.log ('\tAddr : ' + wkObj.addr);
|
||||
|
||||
s = Script.createPubKeyHashOut(wk.privKey.public);
|
||||
p('\nScript PubKeyHash:');
|
||||
p('\tHex : ' + buffertools.toHex(s.buffer));
|
||||
p('\tHuman : ' + s.toHumanReadable());
|
||||
p('\tKey -------------------------------');
|
||||
console.log ('\tPrivate: ' + wkObj.priv);
|
||||
console.log ('\tPublic : ' + wkObj.pub);
|
||||
console.log ('\tAddr : ' + wkObj.addr);
|
||||
|
||||
var wks=[];
|
||||
var pubs = [];
|
||||
for (var i =0; i<5; i++) {
|
||||
wks[i] = new WalletKey(opts);
|
||||
wks[i].generate();
|
||||
pubs.push(wks[i].privKey.public);
|
||||
}
|
||||
|
||||
s = Script.createMultisig(3,pubs);
|
||||
p('\nScript MultiSig (3 out of 5 required signatures):');
|
||||
p('\tHex : ' + buffertools.toHex(s.buffer));
|
||||
p('\tHuman : ' + s.toHumanReadable());
|
||||
|
||||
for (i =0; i<5; i++) {
|
||||
wkObj = wks[i].storeObj();
|
||||
p('\tKey ['+i+'] -------------------------------');
|
||||
console.log ('\tPrivate: ' + wkObj.priv);
|
||||
console.log ('\tPublic : ' + wkObj.pub);
|
||||
console.log ('\tAddr : ' + wkObj.addr);
|
||||
}
|
||||
|
||||
var hash = util.sha256ripe160(s.buffer);
|
||||
|
||||
s = Script.createP2SH(hash);
|
||||
p('\nScript P2SH:');
|
||||
p('\tHex : ' + buffertools.toHex(s.buffer));
|
||||
p('\tHuman : ' + s.toHumanReadable());
|
||||
p('\tScript Hash: ' + buffertools.toHex(hash));
|
||||
var a = new Address(networks.livenet.addressScript,hash);
|
||||
p('\tp2sh Addr: ' + a.toString());
|
||||
|
||||
};
|
||||
|
||||
module.exports.run = run;
|
||||
if (require.main === module) {
|
||||
run();
|
||||
}
|
|
@ -14,9 +14,9 @@
|
|||
<script type="text/javascript">
|
||||
var Address = require('bitcore').Address;
|
||||
|
||||
print = function(s){
|
||||
print = function(s,s2,s3){
|
||||
var div = document.getElementById('content');
|
||||
div.innerHTML += s + '<br />';
|
||||
div.innerHTML += s + (s2||'') + (s3||'') + '<br />';
|
||||
};
|
||||
|
||||
print('<hr> <h1>Address</h1>' );
|
||||
|
@ -39,21 +39,16 @@
|
|||
}
|
||||
|
||||
});
|
||||
print('<hr> <h1>KeyModule</h1>' );
|
||||
print('<hr> <h1>Key</h1>' );
|
||||
/*
|
||||
Using bitcore root module
|
||||
*/
|
||||
var bitcore = require('bitcore');
|
||||
var k = bitcore.KeyModule.Key.generateSync();
|
||||
|
||||
var k = bitcore.Key.generateSync();
|
||||
print ('Generate Key Pair:');
|
||||
print ('Private:' + bitcore.buffertools.toHex(k.private));
|
||||
print ('Public:' + bitcore.buffertools.toHex(k.public));
|
||||
|
||||
print('<hr> <h1>PeerManager</h1>' );
|
||||
|
||||
var p = new bitcore.PeerManager();
|
||||
|
||||
print('<hr> <h1>Util</h1>' );
|
||||
var coinUtil = bitcore.util;
|
||||
|
||||
|
@ -81,6 +76,7 @@
|
|||
var WalletKey = bitcore.WalletKey;
|
||||
var networks = bitcore.networks;
|
||||
|
||||
|
||||
var priv = 'L4cEVwoNDeYdCQfFJAGkGKPnE2TmqLEuBn4znQChD2ojjQRJVKpU';
|
||||
var s = new WalletKey({
|
||||
network: networks.livenet
|
||||
|
@ -91,11 +87,9 @@
|
|||
print("Public: " + o.pub);
|
||||
print("Addr: " + o.addr);
|
||||
|
||||
var module = { exports: {} };
|
||||
print('<hr><h1>TransactionBuilder</h1>');
|
||||
console.log = print;
|
||||
</script>
|
||||
<script src="./CreateAndSignTx.js"></script>
|
||||
<script>
|
||||
run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<script src="test.SIN.js"></script>
|
||||
<script src="test.SINKey.js"></script>
|
||||
<script src="test.Transaction.js"></script>
|
||||
<script src="test.TransactionBuilder.js"></script>
|
||||
<script src="test.util.js"></script>
|
||||
<script src="test.VersionedData.js"></script>
|
||||
<script src="test.Wallet.js"></script>
|
||||
|
|
|
@ -62,278 +62,6 @@ describe('Transaction', function() {
|
|||
should.exist(t);
|
||||
});
|
||||
|
||||
|
||||
it('#selectUnspent should be able to select utxos', function() {
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspent, 1.0, true);
|
||||
u.length.should.equal(3);
|
||||
|
||||
should.exist(u[0].amount);
|
||||
should.exist(u[0].txid);
|
||||
should.exist(u[0].scriptPubKey);
|
||||
should.exist(u[0].vout);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent, 0.5, true);
|
||||
u.length.should.equal(3);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent, 0.1, true);
|
||||
u.length.should.equal(2);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent, 0.05, true);
|
||||
u.length.should.equal(2);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent, 0.015, true);
|
||||
u.length.should.equal(2);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent, 0.01, true);
|
||||
u.length.should.equal(1);
|
||||
});
|
||||
|
||||
it('#selectUnspent should return null if not enough utxos', function() {
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspent, 1.12);
|
||||
should.not.exist(u);
|
||||
});
|
||||
|
||||
|
||||
it('#selectUnspent should check confirmations', function() {
|
||||
var u = Transaction.selectUnspent(testdata.dataUnspent, 0.9);
|
||||
should.not.exist(u);
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent, 0.9, true);
|
||||
u.length.should.equal(3);
|
||||
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent, 0.11);
|
||||
u.length.should.equal(2);
|
||||
u = Transaction.selectUnspent(testdata.dataUnspent, 0.111);
|
||||
should.not.exist(u);
|
||||
});
|
||||
|
||||
|
||||
var opts = {
|
||||
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
||||
allowUnconfirmed: true,
|
||||
};
|
||||
|
||||
it('#create should be able to create instance', function() {
|
||||
var utxos = testdata.dataUnspent;
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
|
||||
var ret = Transaction.create(utxos, outs, opts);
|
||||
should.exist(ret.tx);
|
||||
should.exist(ret.selectedUtxos);
|
||||
ret.selectedUtxos.length.should.equal(2);
|
||||
|
||||
var tx = ret.tx;
|
||||
|
||||
tx.version.should.equal(1);
|
||||
tx.ins.length.should.equal(2);
|
||||
tx.outs.length.should.equal(2);
|
||||
|
||||
util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0);
|
||||
|
||||
// remainder is 0.0299 here because unspent select utxos in order
|
||||
util.valueToBigInt(tx.outs[1].v).cmp(2990000).should.equal(0);
|
||||
tx.isComplete().should.equal(false);
|
||||
});
|
||||
|
||||
it('#create should fail if not enough inputs ', function() {
|
||||
var utxos = testdata.dataUnspent;
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 80
|
||||
}];
|
||||
Transaction
|
||||
.create
|
||||
.bind(utxos, outs, opts)
|
||||
.should.
|
||||
throw ();
|
||||
|
||||
var outs2 = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.5
|
||||
}];
|
||||
should.exist(Transaction.create(utxos, outs2, opts));
|
||||
|
||||
// do not allow unconfirmed
|
||||
Transaction.create.bind(utxos, outs2).should.
|
||||
throw ();
|
||||
});
|
||||
|
||||
|
||||
it('#create should create same output as bitcoind createrawtransaction ', function() {
|
||||
var utxos = testdata.dataUnspent;
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
var ret = Transaction.create(utxos, outs, opts);
|
||||
var tx = ret.tx;
|
||||
|
||||
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08,"mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd":0.0299}'
|
||||
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0200127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388acb09f2d00000000001976a914b00127584485a7cff0949ef0f6bc5575f06ce00d88ac00000000');
|
||||
|
||||
});
|
||||
|
||||
it('#create should create same output as bitcoind createrawtransaction wo remainder', function() {
|
||||
var utxos = testdata.dataUnspent;
|
||||
// no remainder
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
var ret = Transaction.create(utxos, outs, {
|
||||
fee: 0.03
|
||||
});
|
||||
var tx = ret.tx;
|
||||
|
||||
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08}'
|
||||
//
|
||||
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000');
|
||||
});
|
||||
|
||||
it('#createAndSign should sign a tx', function() {
|
||||
var utxos = testdata.dataUnspentSign.unspent;
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
||||
var tx = ret.tx;
|
||||
tx.isComplete().should.equal(true);
|
||||
tx.ins.length.should.equal(1);
|
||||
tx.outs.length.should.equal(2);
|
||||
|
||||
var outs2 = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 16
|
||||
}];
|
||||
var ret2 = Transaction.createAndSign(utxos, outs2, testdata.dataUnspentSign.keyStrings, opts);
|
||||
var tx2 = ret2.tx;
|
||||
tx2.isComplete().should.equal(true);
|
||||
tx2.ins.length.should.equal(3);
|
||||
tx2.outs.length.should.equal(2);
|
||||
});
|
||||
|
||||
it('#createAndSign should sign an incomplete tx ', function() {
|
||||
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
||||
var utxos = testdata.dataUnspentSign.unspent;
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
||||
var tx = ret.tx;
|
||||
tx.ins.length.should.equal(1);
|
||||
tx.outs.length.should.equal(2);
|
||||
});
|
||||
it('#isComplete should return TX signature status', function() {
|
||||
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
||||
var utxos = testdata.dataUnspentSign.unspent;
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
var ret = Transaction.createAndSign(utxos, outs, keys, opts);
|
||||
var tx = ret.tx;
|
||||
tx.isComplete().should.equal(false);
|
||||
tx.sign(ret.selectedUtxos, testdata.dataUnspentSign.keyStrings);
|
||||
tx.isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
it('#sign should sign a tx in multiple steps (case1)', function() {
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 1.08
|
||||
}];
|
||||
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
||||
var tx = ret.tx;
|
||||
var selectedUtxos = ret.selectedUtxos;
|
||||
|
||||
var k1 = testdata.dataUnspentSign.keyStrings.slice(0, 1);
|
||||
|
||||
tx.isComplete().should.equal(false);
|
||||
|
||||
tx.sign(selectedUtxos, k1).should.equal(false);
|
||||
|
||||
var k23 = testdata.dataUnspentSign.keyStrings.slice(1, 3);
|
||||
tx.sign(selectedUtxos, k23).should.equal(true);
|
||||
tx.isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
it('#sign should sign a tx in multiple steps (case2)', function() {
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 16
|
||||
}];
|
||||
var ret = Transaction.create(testdata.dataUnspentSign.unspent, outs, opts);
|
||||
var tx = ret.tx;
|
||||
var selectedUtxos = ret.selectedUtxos;
|
||||
|
||||
var k1 = testdata.dataUnspentSign.keyStrings.slice(0, 1);
|
||||
var k2 = testdata.dataUnspentSign.keyStrings.slice(1, 2);
|
||||
var k3 = testdata.dataUnspentSign.keyStrings.slice(2, 3);
|
||||
tx.sign(selectedUtxos, k1).should.equal(false);
|
||||
tx.sign(selectedUtxos, k2).should.equal(false);
|
||||
tx.sign(selectedUtxos, k3).should.equal(true);
|
||||
|
||||
});
|
||||
|
||||
it('#createAndSign: should generate dynamic fee and readjust (and not) the selected UTXOs', function() {
|
||||
//this cases exceeds the input by 1mbtc AFTEr calculating the dynamic fee,
|
||||
//so, it should trigger adding a new 10BTC utxo
|
||||
var utxos = testdata.dataUnspentSign.unspent;
|
||||
var outs = [];
|
||||
var n = 101;
|
||||
for (var i = 0; i < n; i++) {
|
||||
outs.push({
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.01
|
||||
});
|
||||
}
|
||||
|
||||
var ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
||||
var tx = ret.tx;
|
||||
tx.getSize().should.equal(3560);
|
||||
|
||||
// ins = 11.0101 BTC (2 inputs: 1.0101 + 10 );
|
||||
tx.ins.length.should.equal(2);
|
||||
// outs = 101 outs:
|
||||
// 101 * 0.01 = 1.01BTC; + 0.0004 fee = 1.0104btc
|
||||
// remainder = 11.0101-1.0104 = 9.9997
|
||||
tx.outs.length.should.equal(102);
|
||||
util.valueToBigInt(tx.outs[n].v).cmp(999970000).should.equal(0);
|
||||
tx.isComplete().should.equal(true);
|
||||
|
||||
|
||||
//this is the complementary case, it does not trigger a new utxo
|
||||
utxos = testdata.dataUnspentSign.unspent;
|
||||
outs = [];
|
||||
n = 100;
|
||||
for (i = 0; i < n; i++) {
|
||||
outs.push({
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.01
|
||||
});
|
||||
}
|
||||
|
||||
ret = Transaction.createAndSign(utxos, outs, testdata.dataUnspentSign.keyStrings, opts);
|
||||
tx = ret.tx;
|
||||
tx.getSize().should.equal(3485);
|
||||
|
||||
// ins = 1.0101 BTC (1 inputs: 1.0101);
|
||||
tx.ins.length.should.equal(1);
|
||||
// outs = 100 outs:
|
||||
// 100 * 0.01 = 1BTC; + 0.0004 fee = 1.0004btc
|
||||
// remainder = 1.0101-1.0004 = 0.0097
|
||||
tx.outs.length.should.equal(101);
|
||||
util.valueToBigInt(tx.outs[n].v).cmp(970000).should.equal(0);
|
||||
tx.isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Bitcoin core transaction tests
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,398 @@
|
|||
'use strict';
|
||||
|
||||
var chai = chai || require('chai');
|
||||
chai.Assertion.includeStack = true;
|
||||
var bitcore = bitcore || require('../bitcore');
|
||||
|
||||
var should = chai.should();
|
||||
|
||||
var Transaction = bitcore.Transaction;
|
||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||
var In;
|
||||
var Out;
|
||||
var Script = bitcore.Script;
|
||||
var util = bitcore.util;
|
||||
var buffertools = require('buffertools');
|
||||
var testdata = testdata || require('./testdata');
|
||||
|
||||
describe('TransactionBuilder', function() {
|
||||
it('should initialze the main object', function() {
|
||||
should.exist(TransactionBuilder);
|
||||
});
|
||||
|
||||
|
||||
it('should be able to create instance', function() {
|
||||
var t = new TransactionBuilder();
|
||||
should.exist(t);
|
||||
});
|
||||
|
||||
it('should be able to create instance with params', function() {
|
||||
var t = new TransactionBuilder({spendUnconfirmed: true, lockTime: 10});
|
||||
should.exist(t);
|
||||
should.exist(t.txobj.version);
|
||||
t.spendUnconfirmed.should.equal(true);
|
||||
t.txobj.lock_time.should.equal(10);
|
||||
});
|
||||
|
||||
|
||||
var getBuilder = function (spendUnconfirmed) {
|
||||
var t = new TransactionBuilder({spendUnconfirmed: spendUnconfirmed})
|
||||
.setUnspent(testdata.dataUnspent);
|
||||
|
||||
return t;
|
||||
};
|
||||
|
||||
function f(amount, spendUnconfirmed) {
|
||||
spendUnconfirmed = typeof spendUnconfirmed === 'undefined'?true:false;
|
||||
return getBuilder(spendUnconfirmed)
|
||||
._selectUnspent(amount * util.COIN).selectedUtxos;
|
||||
}
|
||||
|
||||
it('#_selectUnspent should be able to select utxos', function() {
|
||||
var u = f(1);
|
||||
u.length.should.equal(3);
|
||||
|
||||
should.exist(u[0].amount);
|
||||
should.exist(u[0].txid);
|
||||
should.exist(u[0].scriptPubKey);
|
||||
should.exist(u[0].vout);
|
||||
|
||||
f(0.5).length.should.equal(3);
|
||||
f(0.1).length.should.equal(2);
|
||||
f(0.05).length.should.equal(2);
|
||||
f(0.015).length.should.equal(2);
|
||||
f(0.001).length.should.equal(1);
|
||||
});
|
||||
|
||||
/*jshint -W068 */
|
||||
it('#_selectUnspent should return null if not enough utxos', function() {
|
||||
(function() { f(1.12); }).should.throw();
|
||||
});
|
||||
|
||||
|
||||
it('#_selectUnspent should check confirmations', function() {
|
||||
(function() { f(0.9,false); }).should.throw();
|
||||
f(0.9).length.should.equal(3);
|
||||
|
||||
f(0.11,false).length.should.equal(2);
|
||||
(function() { f(0.111,false); }).should.throw();
|
||||
});
|
||||
|
||||
|
||||
|
||||
it('#_setInputs sets inputs', function() {
|
||||
var b = getBuilder()
|
||||
.setUnspent(testdata.dataUnspent)
|
||||
._selectUnspent(0.1 * util.COIN)
|
||||
._setInputs();
|
||||
|
||||
should.exist(b.txobj.ins[0].s);
|
||||
should.exist(b.txobj.ins[0].q);
|
||||
should.exist(b.txobj.ins[0].o);
|
||||
});
|
||||
|
||||
it('#_setInputMap set inputMap', function() {
|
||||
var b = getBuilder()
|
||||
.setUnspent(testdata.dataUnspent)
|
||||
._selectUnspent(0.1 * util.COIN)
|
||||
._setInputs()
|
||||
._setInputMap();
|
||||
|
||||
should.exist(b.inputMap);
|
||||
b.inputMap.length.should.equal(2);
|
||||
});
|
||||
|
||||
var getBuilder2 = function (fee) {
|
||||
var opts = {
|
||||
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
||||
spendUnconfirmed: true,
|
||||
};
|
||||
|
||||
if (fee) opts.fee = fee;
|
||||
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
|
||||
return new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspent)
|
||||
.setOutputs(outs);
|
||||
};
|
||||
|
||||
|
||||
it('should fail to create tx', function() {
|
||||
|
||||
(function() {
|
||||
getBuilder()
|
||||
.setUnspent(testdata.dataUnspent)
|
||||
.build();
|
||||
}).should.throw();
|
||||
});
|
||||
|
||||
it('should fail if not enough inputs ', function() {
|
||||
var utxos = testdata.dataUnspent;
|
||||
|
||||
var opts = {
|
||||
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
||||
spendUnconfirmed: true,
|
||||
};
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 80
|
||||
}];
|
||||
|
||||
(function() {
|
||||
new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspent)
|
||||
.setOutputs(outs);
|
||||
}).should.throw();
|
||||
|
||||
var outs2 = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.5
|
||||
}];
|
||||
|
||||
should.exist(
|
||||
new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspent)
|
||||
.setOutputs(outs2)
|
||||
);
|
||||
|
||||
// do not allow unconfirmed
|
||||
opts.spendUnconfirmed = false;
|
||||
(function() {
|
||||
new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspent)
|
||||
.setOutputs(outs2);
|
||||
}).should.throw();
|
||||
});
|
||||
|
||||
it('should be able to create a tx', function() {
|
||||
var b = getBuilder2();
|
||||
|
||||
b.isFullySigned().should.equal(false);
|
||||
b.getSelectedUnspent().length.should.equal(2);
|
||||
|
||||
var tx = b.build();
|
||||
should.exist(tx);
|
||||
|
||||
tx.version.should.equal(1);
|
||||
tx.ins.length.should.equal(2);
|
||||
tx.outs.length.should.equal(2);
|
||||
|
||||
util.valueToBigInt(tx.outs[0].v).cmp(8000000).should.equal(0);
|
||||
|
||||
// remainder is 0.0299 here because unspent select utxos in order
|
||||
util.valueToBigInt(tx.outs[1].v).cmp(2990000).should.equal(0);
|
||||
});
|
||||
|
||||
|
||||
it('should create same output as bitcoind createrawtransaction ', function() {
|
||||
var tx = getBuilder2().build();
|
||||
|
||||
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08,"mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd":0.0299}'
|
||||
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0200127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388acb09f2d00000000001976a914b00127584485a7cff0949ef0f6bc5575f06ce00d88ac00000000');
|
||||
|
||||
});
|
||||
|
||||
it('should create same output as bitcoind createrawtransaction wo remainder', function() {
|
||||
|
||||
//no remainder
|
||||
var tx = getBuilder2(0.03).build();
|
||||
|
||||
// string output generated from: bitcoind createrawtransaction '[{"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1","vout":1},{"txid":"2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc2","vout":0} ]' '{"mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE":0.08}'
|
||||
//
|
||||
tx.serialize().toString('hex').should.equal('0100000002c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0100000000ffffffffc2cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a0000000000ffffffff0100127a00000000001976a914774e603bafb717bd3f070e68bbcccfd907c77d1388ac00000000');
|
||||
});
|
||||
|
||||
|
||||
|
||||
var getBuilder3 = function (outs) {
|
||||
|
||||
var opts = {
|
||||
remainderAddress: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd',
|
||||
spendUnconfirmed: true,
|
||||
};
|
||||
|
||||
var outs = outs || [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
|
||||
//console.log('[test.TransactionBuilder.js.216:outs:]',outs, outs.length); //TODO
|
||||
return new TransactionBuilder(opts)
|
||||
.setUnspent(testdata.dataUnspentSign.unspent)
|
||||
.setOutputs(outs);
|
||||
};
|
||||
|
||||
it('should sign a tx (case 1)', function() {
|
||||
var b = getBuilder3();
|
||||
b.isFullySigned().should.equal(false);
|
||||
|
||||
b.sign(testdata.dataUnspentSign.keyStrings);
|
||||
|
||||
b.isFullySigned().should.equal(true);
|
||||
|
||||
var tx = b.build();
|
||||
tx.isComplete().should.equal(true);
|
||||
tx.ins.length.should.equal(1);
|
||||
tx.outs.length.should.equal(2);
|
||||
});
|
||||
|
||||
it('should sign a tx (case 2)', function() {
|
||||
var b = getBuilder3([{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 16
|
||||
}])
|
||||
.sign(testdata.dataUnspentSign.keyStrings);
|
||||
|
||||
b.isFullySigned().should.equal(true);
|
||||
var tx = b.build();
|
||||
tx.isComplete().should.equal(true);
|
||||
tx.ins.length.should.equal(3);
|
||||
tx.outs.length.should.equal(2);
|
||||
});
|
||||
|
||||
it('should sign an incomplete tx', function() {
|
||||
var keys = ['cNpW8B7XPAzCdRR9RBWxZeveSNy3meXgHD8GuhcqUyDuy8ptCDzJ'];
|
||||
var outs = [{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.08
|
||||
}];
|
||||
|
||||
var b = new TransactionBuilder()
|
||||
.setUnspent(testdata.dataUnspentSign.unspent)
|
||||
.setOutputs(outs)
|
||||
.sign(keys);
|
||||
|
||||
b.isFullySigned().should.equal(false);
|
||||
|
||||
var tx = b.build();
|
||||
tx.ins.length.should.equal(1);
|
||||
tx.outs.length.should.equal(2);
|
||||
tx.isComplete().should.equal(false);
|
||||
});
|
||||
|
||||
|
||||
it('should sign a tx in multiple steps (case1)', function() {
|
||||
|
||||
var b = getBuilder3([{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 16
|
||||
}]);
|
||||
|
||||
b.isFullySigned().should.equal(false);
|
||||
(b.build()).isComplete().should.equal(false);
|
||||
|
||||
var k1 = testdata.dataUnspentSign.keyStrings.slice(0, 1);
|
||||
b.sign(k1);
|
||||
b.isFullySigned().should.equal(false);
|
||||
(b.build()).isComplete().should.equal(false);
|
||||
|
||||
var k23 = testdata.dataUnspentSign.keyStrings.slice(1, 3);
|
||||
b.sign(k23);
|
||||
b.isFullySigned().should.equal(true);
|
||||
(b.build()).isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
it('#sign should sign a tx in multiple steps (case2)', function() {
|
||||
var b = getBuilder3([{
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 16
|
||||
}]);
|
||||
|
||||
b.isFullySigned().should.equal(false);
|
||||
(b.build()).isComplete().should.equal(false);
|
||||
|
||||
var k1 = testdata.dataUnspentSign.keyStrings.slice(0, 1);
|
||||
b.sign(k1);
|
||||
b.isFullySigned().should.equal(false);
|
||||
(b.build()).isComplete().should.equal(false);
|
||||
|
||||
var k2 = testdata.dataUnspentSign.keyStrings.slice(1, 2);
|
||||
b.sign(k2);
|
||||
b.isFullySigned().should.equal(false);
|
||||
(b.build()).isComplete().should.equal(false);
|
||||
|
||||
var k3 = testdata.dataUnspentSign.keyStrings.slice(2, 3);
|
||||
b.sign(k3);
|
||||
b.isFullySigned().should.equal(true);
|
||||
(b.build()).isComplete().should.equal(true);
|
||||
});
|
||||
|
||||
it('should generate dynamic fee and readjust (and not) the selected UTXOs (case1)', function() {
|
||||
//this cases exceeds the input by 1mbtc AFTEr calculating the dynamic fee,
|
||||
//so, it should trigger adding a new 10BTC utxo
|
||||
//
|
||||
|
||||
var outs = [];
|
||||
var N = 101;
|
||||
for (var i = 0; i < N; i++) {
|
||||
outs.push({
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.01
|
||||
});
|
||||
}
|
||||
var b = getBuilder3(outs);
|
||||
var tx = b.build();
|
||||
|
||||
tx.getSize().should.equal(3560);
|
||||
|
||||
// ins = 11.0101 BTC (2 inputs: 1.0101 + 10 );
|
||||
parseInt(b.valueInSat.toString()).should.equal(11.0101 * util.COIN);
|
||||
tx.ins.length.should.equal(2);
|
||||
|
||||
// outs = 101 outs + 1 remainder
|
||||
tx.outs.length.should.equal(N+1);
|
||||
|
||||
|
||||
// 3560 bytes tx -> 0.0004
|
||||
b.feeSat.should.equal(0.0004 * util.COIN);
|
||||
|
||||
// 101 * 0.01 = 1.01BTC; + 0.0004 fee = 1.0104btc
|
||||
// remainder = 11.0101-1.0104 = 9.9997
|
||||
|
||||
parseInt(b.remainderSat.toString()).should.equal(parseInt(9.9997 * util.COIN));
|
||||
|
||||
util.valueToBigInt(tx.outs[N].v).cmp(999970000).should.equal(0);
|
||||
tx.isComplete().should.equal(false);
|
||||
});
|
||||
|
||||
it('should generate dynamic fee and readjust (and not) the selected UTXOs(case2)', function() {
|
||||
//this is the complementary case, it does not trigger a new utxo
|
||||
var outs = [];
|
||||
var N = 100;
|
||||
for (var i = 0; i < N; i++) {
|
||||
outs.push({
|
||||
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
|
||||
amount: 0.01
|
||||
});
|
||||
}
|
||||
var b = getBuilder3(outs);
|
||||
var tx = b.build();
|
||||
|
||||
tx.getSize().should.equal(3485);
|
||||
|
||||
// ins = 1.0101 BTC (1 inputs: 1.0101 );
|
||||
parseInt(b.valueInSat.toString()).should.equal(1.0101 * util.COIN);
|
||||
tx.ins.length.should.equal(1);
|
||||
|
||||
// outs = 100 outs:
|
||||
// 100 * 0.01 = 1BTC; + 0.0004 fee = 1.0004btc
|
||||
// remainder = 1.0101-1.0004 = 0.0097
|
||||
|
||||
// outs = 101 outs + 1 remainder
|
||||
tx.outs.length.should.equal(N+1);
|
||||
|
||||
|
||||
// 3560 bytes tx -> 0.0004
|
||||
b.feeSat.should.equal(0.0004 * util.COIN);
|
||||
|
||||
// 101 * 0.01 = 1.01BTC; + 0.0004 fee = 1.0104btc
|
||||
// remainder = 11.0101-1.0104 = 9.9997
|
||||
parseInt(b.remainderSat.toString()).should.equal(parseInt(0.0097 * util.COIN));
|
||||
util.valueToBigInt(tx.outs[N].v).cmp(970000).should.equal(0);
|
||||
tx.isComplete().should.equal(false);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue