Merge pull request #771 from eordano/feature/change
Add Fee Estimation and Change Address to Transaction
This commit is contained in:
commit
bcae1101fa
@ -24,6 +24,12 @@ Now, this could just be serialized to hexadecimal ASCII values (`transaction.ser
|
|||||||
bitcoin-cli sendrawtransaction <serialized transaction>
|
bitcoin-cli sendrawtransaction <serialized transaction>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also override the fee estimation with another amount, specified in satoshis:
|
||||||
|
```javascript
|
||||||
|
var transaction = new Transaction().fee(5430); // Minimum non-dust amount
|
||||||
|
var transaction = new Transaction().fee(1e8); // Generous fee of 1 BTC
|
||||||
|
```
|
||||||
|
|
||||||
## Transaction API
|
## Transaction API
|
||||||
|
|
||||||
You can take a look at the javadocs for the [Transaction class here](link missing).
|
You can take a look at the javadocs for the [Transaction class here](link missing).
|
||||||
@ -71,8 +77,6 @@ There are a number of data structures being stored internally in a `Transaction`
|
|||||||
* `outputs`: This is the ordered set of output scripts
|
* `outputs`: This is the ordered set of output scripts
|
||||||
* `_inputAmount`: sum of the amount for all the inputs
|
* `_inputAmount`: sum of the amount for all the inputs
|
||||||
* `_outputAmount`: sum of the amount for all the outputs
|
* `_outputAmount`: sum of the amount for all the outputs
|
||||||
|
|
||||||
TO BE IMPLEMENTED YET:
|
|
||||||
* `_fee`: if user specified a non-standard fee, the amount (in satoshis) will be stored in this variable so the change amount can be calculated.
|
* `_fee`: if user specified a non-standard fee, the amount (in satoshis) will be stored in this variable so the change amount can be calculated.
|
||||||
* `_change`: stores the value provided by calling the `change` method.
|
* `_change`: stores the value provided by calling the `change` method.
|
||||||
|
|
||||||
|
|||||||
@ -132,6 +132,10 @@ Input.prototype.addSignature = function() {
|
|||||||
throw new errors.AbstractMethodInvoked('Input#addSignature');
|
throw new errors.AbstractMethodInvoked('Input#addSignature');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Input.prototype.clearSignatures = function() {
|
||||||
|
throw new errors.AbstractMethodInvoked('Input#clearSignatures');
|
||||||
|
};
|
||||||
|
|
||||||
Input.prototype.isValidSignature = function(transaction, signature) {
|
Input.prototype.isValidSignature = function(transaction, signature) {
|
||||||
// FIXME: Refactor signature so this is not necessary
|
// FIXME: Refactor signature so this is not necessary
|
||||||
signature.signature.nhashtype = signature.sigtype;
|
signature.signature.nhashtype = signature.sigtype;
|
||||||
|
|||||||
@ -124,4 +124,11 @@ MultiSigScriptHashInput.prototype.isValidSignature = function(transaction, signa
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MultiSigScriptHashInput.OPCODES_SIZE = 10;
|
||||||
|
MultiSigScriptHashInput.SIGNATURE_SIZE = 36;
|
||||||
|
|
||||||
|
MultiSigScriptHashInput.prototype._estimateSize = function() {
|
||||||
|
return MultiSigScriptHashInput.OPCODES_SIZE + this.threshold * MultiSigScriptHashInput.SIGNATURE_SIZE;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = MultiSigScriptHashInput;
|
module.exports = MultiSigScriptHashInput;
|
||||||
|
|||||||
@ -72,7 +72,7 @@ PublicKeyHashInput.prototype.addSignature = function(transaction, signature) {
|
|||||||
* Clear the input's signature
|
* Clear the input's signature
|
||||||
* @return {PublicKeyHashInput} this, for chaining
|
* @return {PublicKeyHashInput} this, for chaining
|
||||||
*/
|
*/
|
||||||
PublicKeyHashInput.prototype.clearSignature = function() {
|
PublicKeyHashInput.prototype.clearSignatures = function() {
|
||||||
this.setScript(Script.empty());
|
this.setScript(Script.empty());
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@ -85,4 +85,10 @@ PublicKeyHashInput.prototype.isFullySigned = function() {
|
|||||||
return this.script.isPublicKeyHashIn();
|
return this.script.isPublicKeyHashIn();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PublicKeyHashInput.FIXED_SIZE = 32 + 4 + 2;
|
||||||
|
PublicKeyHashInput.SCRIPT_MAX_SIZE = 34 + 20;
|
||||||
|
PublicKeyHashInput.prototype._estimateSize = function() {
|
||||||
|
return PublicKeyHashInput.FIXED_SIZE + PublicKeyHashInput.SCRIPT_MAX_SIZE;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = PublicKeyHashInput;
|
module.exports = PublicKeyHashInput;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
var $ = require('../util/preconditions');
|
||||||
var buffer = require('buffer');
|
var buffer = require('buffer');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
|
||||||
@ -27,8 +28,7 @@ var DEFAULT_NLOCKTIME = 0;
|
|||||||
var DEFAULT_SEQNUMBER = 0xFFFFFFFF;
|
var DEFAULT_SEQNUMBER = 0xFFFFFFFF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a transaction, a set of inputs and outputs to change
|
* Represents a transaction, a set of inputs and outputs to change ownership of tokens
|
||||||
* ownership of tokens
|
|
||||||
*
|
*
|
||||||
* @param {*} serialized
|
* @param {*} serialized
|
||||||
* @constructor
|
* @constructor
|
||||||
@ -196,6 +196,45 @@ Transaction.prototype._newTransaction = function() {
|
|||||||
|
|
||||||
/* Transaction creation interface */
|
/* Transaction creation interface */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an input to this transaction. This is a high level interface
|
||||||
|
* to add an input, for more control, use @{link Transaction#addInput}.
|
||||||
|
*
|
||||||
|
* Can receive, as output information, the output of bitcoind's `listunspent` command,
|
||||||
|
* and a slightly fancier format recognized by bitcore:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* address: 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1',
|
||||||
|
* txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||||
|
* outputIndex: 0,
|
||||||
|
* script: Script.empty(),
|
||||||
|
* satoshis: 1020000
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* Where `address` can be either a string or a bitcore Address object. The
|
||||||
|
* same is true for `script`, which can be a string or a bitcore Script.
|
||||||
|
*
|
||||||
|
* Beware that this resets all the signatures for inputs (in further versions,
|
||||||
|
* SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset).
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* var transaction = new Transaction();
|
||||||
|
*
|
||||||
|
* // From a pay to public key hash output from bitcoind's listunspent
|
||||||
|
* transaction.from({'txid': '0000...', vout: 0, amount: 0.1, scriptPubKey: 'OP_DUP ...'});
|
||||||
|
*
|
||||||
|
* // From a pay to public key hash output
|
||||||
|
* transaction.from({'txId': '0000...', outputIndex: 0, satoshis: 1000, script: 'OP_DUP ...'});
|
||||||
|
*
|
||||||
|
* // From a multisig P2SH output
|
||||||
|
* transaction.from({'txId': '0000...', inputIndex: 0, satoshis: 1000, script: '... OP_HASH'},
|
||||||
|
* ['03000...', '02000...'], 2);
|
||||||
|
*
|
||||||
|
* @param {Object} utxo
|
||||||
|
* @param {Array=} pubkeys
|
||||||
|
* @param {number=} threshold
|
||||||
|
*/
|
||||||
Transaction.prototype.from = function(utxo, pubkeys, threshold) {
|
Transaction.prototype.from = function(utxo, pubkeys, threshold) {
|
||||||
if (pubkeys && threshold) {
|
if (pubkeys && threshold) {
|
||||||
this._fromMultiSigP2SH(utxo, pubkeys, threshold);
|
this._fromMultiSigP2SH(utxo, pubkeys, threshold);
|
||||||
@ -283,6 +322,7 @@ Transaction.prototype._fromMultisigOldUtxo = function(utxo, pubkeys, threshold)
|
|||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold) {
|
Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold) {
|
||||||
|
this._changeSetup = false;
|
||||||
utxo.address = utxo.address && new Address(utxo.address);
|
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.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script);
|
||||||
this.inputs.push(new MultiSigScriptHashInput({
|
this.inputs.push(new MultiSigScriptHashInput({
|
||||||
@ -298,24 +338,58 @@ Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold)
|
|||||||
this._inputAmount += utxo.satoshis;
|
this._inputAmount += utxo.satoshis;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the transaction has enough info on all inputs to be correctly validated
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
Transaction.prototype.hasAllUtxoInfo = function() {
|
Transaction.prototype.hasAllUtxoInfo = function() {
|
||||||
return _.all(this.inputs.map(function(input) {
|
return _.all(this.inputs.map(function(input) {
|
||||||
return !!input.output;
|
return !!input.output;
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually set the fee for this transaction. Beware that this resets all the signatures
|
||||||
|
* for inputs (in further versions, SIGHASH_SINGLE or SIGHASH_NONE signatures will not
|
||||||
|
* be reset).
|
||||||
|
*
|
||||||
|
* @param {number} amount satoshis to be sent
|
||||||
|
* @return {Transaction} this, for chaining
|
||||||
|
*/
|
||||||
Transaction.prototype.fee = function(amount) {
|
Transaction.prototype.fee = function(amount) {
|
||||||
this._fee = amount;
|
this._fee = amount;
|
||||||
|
this._changeSetup = false;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Output management */
|
/* Output management */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the change address for this transaction
|
||||||
|
*
|
||||||
|
* Beware that this resets all the signatures for inputs (in further versions,
|
||||||
|
* SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset).
|
||||||
|
*
|
||||||
|
* @param {number} amount satoshis to be sent
|
||||||
|
* @return {Transaction} this, for chaining
|
||||||
|
*/
|
||||||
Transaction.prototype.change = function(address) {
|
Transaction.prototype.change = function(address) {
|
||||||
this._change = address;
|
this._change = new Address(address);
|
||||||
|
this._changeSetup = false;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an output to the transaction.
|
||||||
|
*
|
||||||
|
* Beware that this resets all the signatures for inputs (in further versions,
|
||||||
|
* SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset).
|
||||||
|
*
|
||||||
|
* @param {string|Address} address
|
||||||
|
* @param {number} amount in satoshis
|
||||||
|
* @return {Transaction} this, for chaining
|
||||||
|
*/
|
||||||
Transaction.prototype.to = function(address, amount) {
|
Transaction.prototype.to = function(address, amount) {
|
||||||
this._addOutput(new Output({
|
this._addOutput(new Output({
|
||||||
script: Script(new Address(address)),
|
script: Script(new Address(address)),
|
||||||
@ -324,11 +398,16 @@ Transaction.prototype.to = function(address, amount) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype._addOutput = function(output) {
|
/**
|
||||||
this.outputs.push(output);
|
* Add an OP_RETURN output to the transaction.
|
||||||
this._outputAmount += output.satoshis;
|
*
|
||||||
};
|
* Beware that this resets all the signatures for inputs (in further versions,
|
||||||
|
* SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset).
|
||||||
|
*
|
||||||
|
* @param {Buffer|string} value the data to be stored in the OP_RETURN output.
|
||||||
|
* In case of a string, the UTF-8 representation will be stored
|
||||||
|
* @return {Transaction} this, for chaining
|
||||||
|
*/
|
||||||
Transaction.prototype.addData = function(value) {
|
Transaction.prototype.addData = function(value) {
|
||||||
this._addOutput(new Output({
|
this._addOutput(new Output({
|
||||||
script: Script.buildDataOut(value),
|
script: Script.buildDataOut(value),
|
||||||
@ -337,19 +416,100 @@ Transaction.prototype.addData = function(value) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._addOutput = function(output) {
|
||||||
|
this.outputs.push(output);
|
||||||
|
this._changeSetup = false;
|
||||||
|
this._outputAmount += output.satoshis;
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._updateChangeOutput = function() {
|
||||||
|
if (!this._change) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._changeSetup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_.isUndefined(this._changeSetup)) {
|
||||||
|
this._clearSignatures();
|
||||||
|
}
|
||||||
|
if (!_.isUndefined(this._changeOutput)) {
|
||||||
|
this.removeOutput(this._changeOutput);
|
||||||
|
}
|
||||||
|
var estimatedSize = this._estimateSize();
|
||||||
|
var available = this._inputAmount - this._outputAmount;
|
||||||
|
var fee = this._fee || Transaction._estimateFee(estimatedSize, available);
|
||||||
|
if (available - fee > 0) {
|
||||||
|
this._changeOutput = this.outputs.length;
|
||||||
|
this._addOutput(new Output({
|
||||||
|
script: Script.fromAddress(this._change),
|
||||||
|
satoshis: available - fee
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
this._changeOutput = undefined;
|
||||||
|
}
|
||||||
|
this._changeSetup = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._clearSignatures = function() {
|
||||||
|
_.each(this.inputs, function(input) {
|
||||||
|
input.clearSignatures();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.FEE_PER_KB = 10000;
|
||||||
|
Transaction.CHANGE_OUTPUT_MAX_SIZE = 20 + 4 + 34 + 4;
|
||||||
|
|
||||||
|
Transaction._estimateFee = function(size, amountAvailable) {
|
||||||
|
var fee = Math.ceil(size / Transaction.FEE_PER_KB);
|
||||||
|
if (amountAvailable > fee) {
|
||||||
|
// Safe upper bound for change address script
|
||||||
|
size += Transaction.CHANGE_OUTPUT_MAX_SIZE;
|
||||||
|
}
|
||||||
|
return Math.ceil(size / 1000 / Transaction.FEE_PER_KB) * 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.MAXIMUM_EXTRA_SIZE = 4 + 9 + 9 + 4;
|
||||||
|
|
||||||
|
Transaction.prototype._estimateSize = function() {
|
||||||
|
var result = Transaction.MAXIMUM_EXTRA_SIZE;
|
||||||
|
_.each(this.inputs, function(input) {
|
||||||
|
result += input._estimateSize();
|
||||||
|
});
|
||||||
|
_.each(this.outputs, function(output) {
|
||||||
|
result += output.script.toBuffer().length + 9;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype.removeOutput = function(index) {
|
||||||
|
var output = this.outputs[index];
|
||||||
|
this._outputAmount -= output.satoshis;
|
||||||
|
this.outputs = _.without(this.outputs, this.outputs[this._changeOutput]);
|
||||||
|
};
|
||||||
|
|
||||||
/* Signature handling */
|
/* Signature handling */
|
||||||
|
|
||||||
Transaction.prototype.sign = function(privKey, sigtype) {
|
/**
|
||||||
// TODO: Change for preconditions
|
* Sign the transaction using one or more private keys.
|
||||||
assert(this.hasAllUtxoInfo());
|
*
|
||||||
|
* It tries to sign each input, verifying that the signature will be valid
|
||||||
|
* (matches a public key).
|
||||||
|
*
|
||||||
|
* @param {Array|String|PrivateKey} privateKey
|
||||||
|
* @param {number} sigtype
|
||||||
|
* @return {Transaction} this, for chaining
|
||||||
|
*/
|
||||||
|
Transaction.prototype.sign = function(privateKey, sigtype) {
|
||||||
|
$.checkState(this.hasAllUtxoInfo());
|
||||||
|
this._updateChangeOutput();
|
||||||
var self = this;
|
var self = this;
|
||||||
if (_.isArray(privKey)) {
|
if (_.isArray(privateKey)) {
|
||||||
_.each(privKey, function(privKey) {
|
_.each(privateKey, function(privateKey) {
|
||||||
self.sign(privKey);
|
self.sign(privateKey);
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
_.each(this.getSignatures(privKey, sigtype), function(signature) {
|
_.each(this.getSignatures(privateKey, sigtype), function(signature) {
|
||||||
self.applySignature(signature);
|
self.applySignature(signature);
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
@ -369,6 +529,16 @@ Transaction.prototype._getPrivateKeySignatures = function(privKey, sigtype) {
|
|||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a signature to the transaction
|
||||||
|
*
|
||||||
|
* @param {Object} signature
|
||||||
|
* @param {number} signature.inputIndex
|
||||||
|
* @param {number} signature.sighash
|
||||||
|
* @param {PublicKey} signature.publicKey
|
||||||
|
* @param {Signature} signature.signature
|
||||||
|
* @return {Transaction} this, for chaining
|
||||||
|
*/
|
||||||
Transaction.prototype.applySignature = function(signature) {
|
Transaction.prototype.applySignature = function(signature) {
|
||||||
this.inputs[signature.inputIndex].addSignature(this, signature);
|
this.inputs[signature.inputIndex].addSignature(this, signature);
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@ -119,6 +119,88 @@ describe('Transaction', function() {
|
|||||||
}).should.equal(true);
|
}).should.equal(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('change address', function() {
|
||||||
|
var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
||||||
|
var simpleUtxoWith100000Satoshis = {
|
||||||
|
address: fromAddress,
|
||||||
|
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||||
|
outputIndex: 0,
|
||||||
|
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
||||||
|
satoshis: 100000
|
||||||
|
};
|
||||||
|
var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc';
|
||||||
|
var changeAddress = 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up';
|
||||||
|
var changeAddressP2SH = '2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf';
|
||||||
|
var privateKey = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY';
|
||||||
|
|
||||||
|
it('can calculate simply the output amount', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to(toAddress, 50000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
transaction.outputs.length.should.equal(2);
|
||||||
|
transaction.outputs[1].satoshis.should.equal(49000);
|
||||||
|
transaction.outputs[1].script.toString()
|
||||||
|
.should.equal(Script.fromAddress(changeAddress).toString());
|
||||||
|
});
|
||||||
|
it('accepts a P2SH address for change', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to(toAddress, 50000)
|
||||||
|
.change(changeAddressP2SH)
|
||||||
|
.sign(privateKey);
|
||||||
|
transaction.outputs.length.should.equal(2);
|
||||||
|
transaction.outputs[1].script.isScriptHashOut().should.equal(true);
|
||||||
|
});
|
||||||
|
it('can recalculate the change amount', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to(toAddress, 50000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey)
|
||||||
|
.to(toAddress, 20000)
|
||||||
|
.sign(privateKey);
|
||||||
|
transaction.outputs.length.should.equal(3);
|
||||||
|
transaction.outputs[2].satoshis.should.equal(29000);
|
||||||
|
transaction.outputs[2].script.toString()
|
||||||
|
.should.equal(Script.fromAddress(changeAddress).toString());
|
||||||
|
});
|
||||||
|
it('adds no fee if no change is available', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to(toAddress, 99000)
|
||||||
|
.sign(privateKey);
|
||||||
|
transaction.outputs.length.should.equal(1);
|
||||||
|
});
|
||||||
|
it('adds no fee if no money is available', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to(toAddress, 100000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
transaction.outputs.length.should.equal(1);
|
||||||
|
});
|
||||||
|
it('fee can be set up manually', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to(toAddress, 80000)
|
||||||
|
.fee(10000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey);
|
||||||
|
transaction.outputs.length.should.equal(2);
|
||||||
|
transaction.outputs[1].satoshis.should.equal(10000);
|
||||||
|
});
|
||||||
|
it('coverage: on second call to sign, change is not recalculated', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
.to(toAddress, 100000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.sign(privateKey)
|
||||||
|
.sign(privateKey);
|
||||||
|
transaction.outputs.length.should.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var tx_empty_hex = '01000000000000000000';
|
var tx_empty_hex = '01000000000000000000';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user