@ -126,13 +126,12 @@ Transaction.prototype.uncheckedSerialize = function() {
Transaction . prototype . checkedSerialize = Transaction . prototype . toString = function ( ) {
var feeError = this . _ validateFees ( ) ;
if ( feeError ) {
var changeError = this . _ validateChange ( ) ;
if ( changeError ) {
var missingChange = this . _ missingChange ( ) ;
if ( feeError && missingChange ) {
throw new errors . Transaction . ChangeAddressMissing ( ) ;
} else {
throw new errors . Transaction . FeeError ( feeError ) ;
}
if ( feeError && ! missingChange ) {
throw new errors . Transaction . FeeError ( feeError ) ;
}
if ( this . _ hasDustOutputs ( ) ) {
throw new errors . Transaction . DustOutputs ( ) ;
@ -148,10 +147,8 @@ Transaction.prototype._validateFees = function() {
}
} ;
Transaction . prototype . _ validateChange = function ( ) {
if ( ! this . _ change ) {
return 'Missing change address' ;
}
Transaction . prototype . _ missingChange = function ( ) {
return ! this . _ changeScript ;
} ;
Transaction . DUST_AMOUNT = 5460 ;
@ -217,23 +214,7 @@ Transaction.prototype.fromJSON = function(json) {
if ( JSUtil . isValidJSON ( json ) ) {
json = JSON . parse ( json ) ;
}
var self = this ;
this . inputs = [ ] ;
var inputs = json . inputs || json . txins ;
inputs . forEach ( function ( input ) {
self . inputs . push ( Input . fromJSON ( input ) ) ;
} ) ;
this . outputs = [ ] ;
var outputs = json . outputs || json . txouts ;
outputs . forEach ( function ( output ) {
self . outputs . push ( Output . fromJSON ( output ) ) ;
} ) ;
if ( json . change ) {
this . change ( json . change ) ;
}
this . version = json . version ;
this . nLockTime = json . nLockTime ;
return this ;
return this . fromObject ( json ) ;
} ;
Transaction . prototype . toObject = function toObject ( ) {
@ -246,7 +227,8 @@ Transaction.prototype.toObject = function toObject() {
outputs . push ( output . toObject ( ) ) ;
} ) ;
return {
change : this . _ change ? this . _ change . toString ( ) : undefined ,
changeScript : this . _ changeScript ? this . _ changeScript . toString ( ) : undefined ,
changeIndex : ! _ . isUndefined ( this . _ changeIndex ) ? this . _ changeIndex : undefined ,
fee : this . _ fee ? this . _ fee : undefined ,
version : this . version ,
inputs : inputs ,
@ -275,14 +257,29 @@ Transaction.prototype.fromObject = function(transaction) {
_ . each ( transaction . outputs , function ( output ) {
self . addOutput ( new Output ( output ) ) ;
} ) ;
if ( transaction . change ) {
this . change ( transaction . change ) ;
if ( transaction . changeIndex ) {
this . _ changeIndex = transaction . changeIndex ;
}
if ( transaction . changeScript ) {
this . _ changeScript = new Script ( transaction . changeScript ) ;
}
if ( transaction . fee ) {
this . fee ( transaction . fee ) ;
}
this . nLockTime = transaction . nLockTime ;
this . version = transaction . version ;
this . _ checkConsistency ( ) ;
return this ;
} ;
Transaction . prototype . _ checkConsistency = function ( ) {
if ( ! _ . isUndefined ( this . _ changeIndex ) ) {
$ . checkState ( this . _ changeScript ) ;
$ . checkState ( this . outputs [ this . _ changeIndex ] ) ;
$ . checkState ( this . outputs [ this . _ changeIndex ] . script . toString ( ) ===
this . _ changeScript . toString ( ) ) ;
}
// TODO: add other checks
} ;
/ * *
@ -533,11 +530,22 @@ Transaction.prototype.fee = function(amount) {
* @ return { Transaction } this , for chaining
* /
Transaction . prototype . change = function ( address ) {
this . _ change = new Address ( address ) ;
this . _ changeScript = Script . from Address ( address ) ;
this . _ updateChangeOutput ( ) ;
return this ;
} ;
/ * *
* @ return { Output } change output , if it exists
* /
Transaction . prototype . getChangeOutput = function ( ) {
if ( ! _ . isUndefined ( this . _ changeIndex ) ) {
return this . outputs [ this . _ changeIndex ] ;
}
return null ;
} ;
/ * *
* Add an output to the transaction .
*
@ -586,33 +594,46 @@ Transaction.prototype._addOutput = function(output) {
} ;
Transaction . prototype . _ updateChangeOutput = function ( ) {
if ( ! this . _ change ) {
if ( ! this . _ changeScript ) {
return ;
}
this . _ clearSignatures ( ) ;
if ( ! _ . isUndefined ( this . _ changeOutput ) ) {
this . _ removeOutput ( this . _ changeOutput ) ;
if ( ! _ . isUndefined ( this . _ changeIndex ) ) {
this . _ removeOutput ( this . _ changeIndex ) ;
}
var available = this . _ getUnspentValue ( ) ;
var fee = this . getFee ( ) ;
if ( available - fee > 0 ) {
this . _ changeOutput = this . outputs . length ;
var changeAmount = available - fee ;
if ( changeAmount > 0 ) {
this . _ changeIndex = this . outputs . length ;
this . _ addOutput ( new Output ( {
script : Script . fromAddress ( this . _ change ) ,
satoshis : available - fee
script : this . _ changeScript ,
satoshis : changeAmount
} ) ) ;
} else {
this . _ changeOutput = undefined ;
this . _ changeIndex = undefined ;
}
} ;
/ * *
* Calculates the fees for the transaction .
*
* If there is no change output set , the fee will be the
* output amount minus the input amount .
* If there ' s a fixed fee set , return that
* If there ' s no fee set , estimate it based on size
* @ return { Number } miner fee for this transaction in satoshis
* /
Transaction . prototype . getFee = function ( ) {
if ( ! this . _ change ) {
// if no change output is set, fees should equal all the unspent amount
if ( ! this . _ changeScript ) {
return this . _ getUnspentValue ( ) ;
}
return this . _ fee || this . _ estimateFee ( ) ;
} ;
/ * *
* Estimates fee from serialized transaction size in bytes .
* /
Transaction . prototype . _ estimateFee = function ( ) {
var estimatedSize = this . _ estimateSize ( ) ;
var available = this . _ getUnspentValue ( ) ;
@ -630,12 +651,12 @@ Transaction.prototype._clearSignatures = function() {
} ;
Transaction . FEE_PER_KB = 10000 ;
// Safe upper bound for change address script
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 ;