|
|
@ -1,80 +1,51 @@ |
|
|
|
|
|
|
|
/* |
|
|
|
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 |
|
|
|
* { |
|
|
|
* remainderOut: 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 remainderOut is given, and there are remainder coins, the |
|
|
|
* first IN out will be used to return the coins. remainderOut has the form: |
|
|
|
* remainderOut = { address: 1xxxxx} |
|
|
|
* or |
|
|
|
* remainderOut = { pubkeys: ['hex1','hex2',...} for multisig |
|
|
|
* |
|
|
|
* |
|
|
|
*/ |
|
|
|
// Creates a bitcore Transaction object
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Synopsis
|
|
|
|
// --------
|
|
|
|
// ```
|
|
|
|
// 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());
|
|
|
|
//
|
|
|
|
// //Searialize it and pass it around...
|
|
|
|
// var string = JSON.serialize(builder.toObj());
|
|
|
|
// // then...
|
|
|
|
// var builder = TransactionBuilder.fromObj(JSON.parse(str);
|
|
|
|
// builder.sign(keys);
|
|
|
|
// // Also
|
|
|
|
// var builder2 = TransactionBuilder.fromObj(JSON.parse(str2);
|
|
|
|
// builder2.merge(builder); // Will merge signatures for p2sh mulsig txs.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
'use strict'; |
|
|
@ -94,6 +65,35 @@ var log = imports.log || require('../util/log'); |
|
|
|
var Transaction = imports.Transaction || require('./Transaction'); |
|
|
|
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); |
|
|
|
|
|
|
|
// Methods
|
|
|
|
// -------
|
|
|
|
//
|
|
|
|
// TransactionBuilder
|
|
|
|
// ------------------
|
|
|
|
// Creates a TransactionBuilder instance
|
|
|
|
// `opts`
|
|
|
|
// ```
|
|
|
|
// {
|
|
|
|
// remainderOut: 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 remainderOut is given, and there are remainder coins, the
|
|
|
|
// first IN out will be used to return the coins. remainderOut has the form:
|
|
|
|
// ```
|
|
|
|
// remainderOut = { address: 1xxxxx}
|
|
|
|
// ```
|
|
|
|
// or
|
|
|
|
// ```
|
|
|
|
// remainderOut = { pubkeys: ['hex1','hex2',...} for multisig
|
|
|
|
// ```
|
|
|
|
|
|
|
|
function TransactionBuilder(opts) { |
|
|
|
opts = opts || {}; |
|
|
|
this.lockTime = opts.lockTime || 0; |
|
|
@ -179,8 +179,27 @@ TransactionBuilder.infoForP2sh = function(opts, networkName) { |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
TransactionBuilder.prototype.setUnspent = function(utxos) { |
|
|
|
this.utxos = utxos; |
|
|
|
// setUnspent
|
|
|
|
// ----------
|
|
|
|
// Sets the `unspent` available for the transaction. Some (or all)
|
|
|
|
// of them to fullfil the transaction's outputs and fee.
|
|
|
|
// The expected format is:
|
|
|
|
// ```
|
|
|
|
// [{
|
|
|
|
// 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.
|
|
|
|
TransactionBuilder.prototype.setUnspent = function(unspent) { |
|
|
|
this.utxos = unspent; |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
@ -209,6 +228,12 @@ TransactionBuilder.prototype._setInputMap = function() { |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// getSelectedUnspent
|
|
|
|
// ------------------
|
|
|
|
//
|
|
|
|
// Returns the selected unspent outputs, to be used in the transaction.
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.getSelectedUnspent = function() { |
|
|
|
return this.selectedUtxos; |
|
|
|
}; |
|
|
@ -312,12 +337,12 @@ TransactionBuilder.prototype._setRemainder = function(txobj, remainderIndex) { |
|
|
|
typeof this.valueOutSat === 'undefined') |
|
|
|
throw new Error('valueInSat / valueOutSat undefined'); |
|
|
|
|
|
|
|
// add remainder (without modifying outs[])
|
|
|
|
/* add remainder (without modifying outs[]) */ |
|
|
|
var remainderSat = this.valueInSat.sub(this.valueOutSat).sub(this.feeSat); |
|
|
|
var l =txobj.outs.length; |
|
|
|
this.remainderSat = bignum(0); |
|
|
|
|
|
|
|
//remove old remainder?
|
|
|
|
/*remove old remainder? */ |
|
|
|
if (l > remainderIndex) { |
|
|
|
txobj.outs.pop(); |
|
|
|
} |
|
|
@ -339,10 +364,10 @@ TransactionBuilder.prototype._setRemainder = function(txobj, remainderIndex) { |
|
|
|
|
|
|
|
TransactionBuilder.prototype._setFeeAndRemainder = function(txobj) { |
|
|
|
|
|
|
|
//starting size estimation
|
|
|
|
/* starting size estimation */ |
|
|
|
var size = 500, maxSizeK, remainderIndex = txobj.outs.length; |
|
|
|
do { |
|
|
|
// based on https://en.bitcoin.it/wiki/Transaction_fees
|
|
|
|
/* based on https://en.bitcoin.it/wiki/Transaction_fees */ |
|
|
|
maxSizeK = parseInt(size / 1000) + 1; |
|
|
|
|
|
|
|
var feeSat = this.givenFeeSat ? |
|
|
@ -361,6 +386,21 @@ TransactionBuilder.prototype._setFeeAndRemainder = function(txobj) { |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
// setOutputs
|
|
|
|
// ----------
|
|
|
|
// Sets the outputs for the transaction. Format is:
|
|
|
|
// ```
|
|
|
|
// an array of [{
|
|
|
|
// address: xx,
|
|
|
|
// amount:0.001
|
|
|
|
// },...]
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Note that only some of this outputs will be selected
|
|
|
|
// to create the transaction. The selected ones can be checked
|
|
|
|
// after calling `setOutputs`, with `.getSelectedUnspent`
|
|
|
|
//
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.setOutputs = function(outs) { |
|
|
|
var valueOutSat = bignum(0); |
|
|
|
|
|
|
@ -394,7 +434,7 @@ TransactionBuilder.prototype.setOutputs = function(outs) { |
|
|
|
}; |
|
|
|
|
|
|
|
TransactionBuilder._mapKeys = function(keys) { |
|
|
|
//prepare keys
|
|
|
|
/* prepare keys */ |
|
|
|
var walletKeyMap = {}; |
|
|
|
var l = keys.length; |
|
|
|
var wk; |
|
|
@ -505,15 +545,15 @@ TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txS |
|
|
|
return {inputFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()}; |
|
|
|
}; |
|
|
|
|
|
|
|
// FOR TESTING
|
|
|
|
// var _dumpChunks = function (scriptSig, label) {
|
|
|
|
// console.log('## DUMP: ' + label + ' ##');
|
|
|
|
// for(var i=0; i<scriptSig.chunks.length; i++) {
|
|
|
|
// console.log('\tCHUNK ', i, Buffer.isBuffer(scriptSig.chunks[i])
|
|
|
|
// ?scriptSig.chunks[i].toString('hex'):scriptSig.chunks[i] );
|
|
|
|
// }
|
|
|
|
// };
|
|
|
|
|
|
|
|
/* FOR TESTING |
|
|
|
var _dumpChunks = function (scriptSig, label) { |
|
|
|
console.log('## DUMP: ' + label + ' ##'); |
|
|
|
for(var i=0; i<scriptSig.chunks.length; i++) { |
|
|
|
console.log('\tCHUNK ', i, Buffer.isBuffer(scriptSig.chunks[i]) |
|
|
|
?scriptSig.chunks[i].toString('hex'):scriptSig.chunks[i] ); |
|
|
|
} |
|
|
|
}; |
|
|
|
*/ |
|
|
|
|
|
|
|
TransactionBuilder.prototype._chunkSignedWithKey = function(scriptSig, txSigHash, publicKey) { |
|
|
|
var ret; |
|
|
@ -599,7 +639,6 @@ TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSig |
|
|
|
var signaturesAdded = 0; |
|
|
|
|
|
|
|
for(var j=0; j<l && scriptSig.countSignatures() < nreq ; j++) { |
|
|
|
//console.log('[TransactionBuilder.js] pubkey [j]',j, pubkeys[j].toString('hex')); //TODO
|
|
|
|
var wk = this._findWalletKey(walletKeyMap, {pubKeyBuf: pubkeys[j]}); |
|
|
|
if (!wk) continue; |
|
|
|
|
|
|
@ -609,8 +648,6 @@ TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSig |
|
|
|
signaturesAdded++; |
|
|
|
} |
|
|
|
} |
|
|
|
if (scriptSig.countSignatures() === nreq) { |
|
|
|
} |
|
|
|
|
|
|
|
var ret = { |
|
|
|
inputFullySigned: scriptSig.countSignatures() === nreq, |
|
|
@ -621,7 +658,6 @@ TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSig |
|
|
|
}; |
|
|
|
|
|
|
|
var fnToSign = {}; |
|
|
|
|
|
|
|
TransactionBuilder.prototype._scriptIsAppended = function(script, scriptToAddBuf) { |
|
|
|
var len = script.chunks.length; |
|
|
|
|
|
|
@ -647,7 +683,7 @@ TransactionBuilder.prototype._addScript = function(scriptBuf, scriptToAddBuf) { |
|
|
|
|
|
|
|
TransactionBuilder.prototype._getInputForP2sh = function(script, index) { |
|
|
|
var scriptType = script.classify(); |
|
|
|
// pubKeyHash is needed for TX_PUBKEYHASH and TX_PUBKEY to retrieve the keys.
|
|
|
|
/* pubKeyHash is needed for TX_PUBKEYHASH and TX_PUBKEY to retrieve the keys. */ |
|
|
|
var pubKeyHash; |
|
|
|
switch(scriptType) { |
|
|
|
case Script.TX_PUBKEYHASH: |
|
|
@ -705,6 +741,20 @@ fnToSign[Script.TX_PUBKEY] = TransactionBuilder.prototype._signPubKey; |
|
|
|
fnToSign[Script.TX_MULTISIG] = TransactionBuilder.prototype._signMultiSig; |
|
|
|
fnToSign[Script.TX_SCRIPTHASH] = TransactionBuilder.prototype._signScriptHash; |
|
|
|
|
|
|
|
// sign
|
|
|
|
// ----
|
|
|
|
// Signs a transaction.
|
|
|
|
// `keys`: an array of strings representing private keys to sign the
|
|
|
|
// transaction in WIF private key format OR bitcore's `WalletKey` objects
|
|
|
|
//
|
|
|
|
// If multiple keys are given, each will be tested against the transaction's
|
|
|
|
// scriptPubKeys. Only the valid private keys will be used to sign.
|
|
|
|
// This method is fully compatible with *multisig* transactions.
|
|
|
|
//
|
|
|
|
// `.isFullySigned` can be queried to check is the transactions have all the needed
|
|
|
|
// signatures.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
TransactionBuilder.prototype.sign = function(keys) { |
|
|
|
this._checkTx(); |
|
|
|
var tx = this.tx, |
|
|
@ -728,13 +778,24 @@ TransactionBuilder.prototype.sign = function(keys) { |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
// [ { address:scriptHex }]
|
|
|
|
// setHashToScriptMap
|
|
|
|
// ------------------
|
|
|
|
// Needed for setup Address to Script maps
|
|
|
|
// for p2sh transactions. See `.infoForP2sh`
|
|
|
|
// for generate the input for this call.
|
|
|
|
//
|
|
|
|
TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) { |
|
|
|
this.hashToScriptMap= hashToScriptMap; |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// isFullySigned
|
|
|
|
// -------------
|
|
|
|
// Checks if the transaction have all the necesary signatures.
|
|
|
|
// Also, `.signaturesAdded` and `.inputsSigned` can be queried
|
|
|
|
// for more information about the transaction signature status.
|
|
|
|
//
|
|
|
|
TransactionBuilder.prototype.isFullySigned = function() { |
|
|
|
return this.inputsSigned === this.tx.ins.length; |
|
|
|
}; |
|
|
@ -744,7 +805,13 @@ TransactionBuilder.prototype.build = function() { |
|
|
|
return this.tx; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// toObj
|
|
|
|
// -----
|
|
|
|
// Returns a plain Javascript object that contains
|
|
|
|
// the full status of the TransactionBuilder instance,
|
|
|
|
// suitable for serialization, storage and transmition.
|
|
|
|
// See `.fromObj`
|
|
|
|
//
|
|
|
|
TransactionBuilder.prototype.toObj = function() { |
|
|
|
var data = { |
|
|
|
valueInSat : this.valueInSat.toString(), |
|
|
@ -758,7 +825,6 @@ TransactionBuilder.prototype.toObj = function() { |
|
|
|
inputsSigned : this.inputsSigned, |
|
|
|
signaturesAdded : this.signaturesAdded, |
|
|
|
|
|
|
|
//opts :
|
|
|
|
signhash : this.signhash, |
|
|
|
spendUnconfirmed : this.spendUnconfirmed, |
|
|
|
}; |
|
|
@ -768,6 +834,11 @@ TransactionBuilder.prototype.toObj = function() { |
|
|
|
return data; |
|
|
|
}; |
|
|
|
|
|
|
|
// fromObj
|
|
|
|
// -------
|
|
|
|
// Returns a TransactionBuilder instance given
|
|
|
|
// a plain Javascript object created previously
|
|
|
|
// with `.toObj`. See `.toObj`.
|
|
|
|
|
|
|
|
TransactionBuilder.fromObj = function(data) { |
|
|
|
var b = new TransactionBuilder(); |
|
|
@ -950,6 +1021,11 @@ TransactionBuilder.prototype._mergeTx = function(tx, ignoreConflictingSignatures |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// merge
|
|
|
|
// -----
|
|
|
|
// Merge to TransactionBuilder objects, merging inputs signatures.
|
|
|
|
// This function supports multisig p2sh inputs.
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.merge = function(b) { |
|
|
|
this._checkMergeability(b); |
|
|
|
|
|
|
|