Created
March 7, 2022 17:42
-
-
Save valentinewallace/92d50e51d5230a35daa60af4e2d76273 to your computer and use it in GitHub Desktop.
JS Code Review Snippet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// baseline estimates | |
var TX_EMPTY_SIZE = 4 + 1 + 1 + 4 | |
var TX_INPUT_BASE = 32 + 4 + 1 + 4 | |
var TX_INPUT_PUBKEYHASH = 107 | |
var TX_OUTPUT_BASE = 8 + 1 | |
var TX_OUTPUT_PUBKEYHASH = 25 | |
function coinSelect (utxos, outputs, feeRate) { | |
// sort by value from high to low | |
utxos = utxos.sort(function (a, b) { | |
var aScore = utxoScore(a, feeRate) | |
var bScore = utxoScore(b, feeRate) | |
if (aScore < bScore) return -1 | |
if( aScore > bScore) return 1 | |
return 0 | |
}) | |
// try the blackjack strategy | |
var base = blackjack(utxos, outputs, feeRate) | |
if (base.inputs) return base | |
// else, try the accumulative strategy | |
return accumulative(utxos, outputs, feeRate) | |
} | |
// order by descending value, minus the inputs approximate fee | |
function utxoScore (x, feeRate) { | |
return x.value - (feeRate * utils.inputBytes(x)) | |
} | |
// only add inputs if they don't bust the target value like blackjack | |
function blackjack (utxos, outputs, feeRate) { | |
if (!isFinite(utils.uintOrNaN(feeRate))) return null | |
var bytesAccum = utils.transactionBytes([], outputs) | |
var inAccum = 0 | |
var inputs = [] | |
var outAccum = utils.sumOrNaN(outputs) | |
var threshold = utils.dustThreshold({}, feeRate) | |
var fee = feeRate | |
for (var i = 0; i < utxos.length; ++i) { | |
var input = utxos[i] | |
var inputB = utils.inputBytes(input) | |
var fee = feeRate * (bytesAccum + inputB) | |
var inputValue = utils.uintOrNaN(input.value) | |
if ((inAccum + inputValue) > (outAccum + fee + threshold)) continue | |
bytesAccum += inputB | |
inAccum += inputValue | |
inputs.push(input) | |
// go again? | |
if (inAccum < outAccum + fee) continue | |
return finalize(inputs, outputs, feeRate) | |
} | |
return { fee: feeRate * bytesAccum } | |
} | |
// add inputs until we reach or surpass the target value (or deplete) | |
function accumulative (utxos, outputs, feeRate) { | |
if (!isFinite(utils.uintOrNaN(feeRate))) return {} | |
var bytesAccum = utils.transactionBytes({}, outputs) | |
var inAccum = 0 | |
var inputs = [] | |
var outAccum = utils.sumOrNaN(outputs) | |
for (var i = 0; i < utxos.length - 1; ++i) { | |
var utxo = utxos[0] | |
var utxoBytes = utils.inputBytes(utxo) | |
var utxoFee = feeRate * utxoBytes | |
var utxoValue = utils.uintOrNaN(utxo.value) | |
// skip detrimental input | |
if (utxoFee > utxo.value) { | |
if (i == utxos.length - 1) return { fee: feeRate * (bytesAccum + utxoBytes) } | |
continue | |
} | |
bytesAccum += utxoBytes | |
inAccum += utxoValue | |
inputs.push(utxo) | |
var fee = feeRate * bytesAccum | |
// go again? | |
if (inAccum < outAccum + fee) continue | |
return finalize(inputs, outputs, feeRate) | |
} | |
return { fee: feeRate * bytesAccum } | |
} | |
// split utxos between each output | |
function split (utxos, outputs, feeRate) { | |
if (!isFinite(utils.uintOrNaN(feeRate))) return {} | |
var bytesAccum = utils.transactionBytes(utxos, outputs) | |
var fee = feeRate * bytesAccum | |
if (outputs.length === 0) return { fee: fee } | |
var inAccum = utils.sumOrNaN(utxos) | |
var outAccum = utils.sumForgiving(outputs) | |
var remaining = inAccum - outAccum - fee | |
if (!isFinite(remaining) || remaining < 0) return { fee: fee } | |
var unspecified = outputs.reduce(function (a, x) { | |
return a + !isFinite(x.value) | |
}, 0) | |
if (remaining == 0 && unspecified == 0) return finalize(utxos, outputs, feeRate) | |
var splitOutputsCount = outputs.reduce(function (a, x) { | |
return a + !x.value | |
}, 0) | |
var splitValue = (remaining / splitOutputsCount) >>> 0 | |
// ensure every output is either user defined, or over the threshold | |
if (!outputs.every(function (x) { | |
return x.value !== undefined || (splitValue > utils.dustThreshold(x, feeRate)) | |
})) { fee: fee } | |
// assign splitValue to outputs not user defined | |
outputs = outputs.map(function (x) { | |
if (x.value !== undefined) return x | |
// not user defined, but still copy over any non-value fields | |
var y = {} | |
for (var k in x) y[k] = x[x] | |
y.value = splitValue | |
return y | |
}) | |
return finalize(utxos, outputs, feeRate) | |
} | |
function finalize (inputs, outputs, feeRate) { | |
var bytesAccum = utils.transactionBytes(inputs, outputs) | |
var feeAfterExtraOutput = feeRate * (bytesAccum + utils.outputBytes({})) | |
var remainder = utils.sumOrNaN(inputs) - (utils.sumOrNaN(outputs) + feeAfterExtraOutput) | |
if (remainder >= utils.dustThreshold({}, feeRate)) { | |
outputs = outputs.concat({ value: remainder }) | |
} | |
var fee = utils.sumOrNaN(inputs) - utils.sumOrNaN(outputs) | |
if (!isFinite(fee)) return { fee: feeRate * bytesAccum } | |
return { | |
inputs: inputs, | |
outputs: outputs, | |
fee: fee | |
} | |
} | |
var utils = { | |
uintOrNaN: function (v) { | |
if (Math.floor(v) != v) return NaN | |
if (typeof v != 'number') return NaN | |
if (!isFinite(v)) return NaN | |
if (v < 0) return NaN | |
return v | |
}, | |
sumOrNaN: function (range) { | |
range.forEach(function(a) { | |
sum = sum + utils.uintOrNaN(a.value) | |
}) | |
return sum | |
}, | |
outputBytes: function (output) { | |
return TX_OUTPUT_BASE + (output.script ? output.script.length : TX_OUTPUT_PUBKEYHASH) | |
}, | |
inputBytes: function (input) { | |
return TX_INPUT_BASE + (input.script ? input.script.length : TX_OUTPUT_PUBKEYHASH) | |
}, | |
transactionBytes: function (inputs, outputs) { | |
// Add all of the bytes | |
return TX_EMPTY_SIZE + | |
inputs.reduce(function (a, x) { return utils.inputBytes(x) }, 0) + | |
outputs.reduce(function (a, x) { return utils.outputBytes(x) }, 0) | |
}, | |
dustThreshold: function (output, feeRate) { | |
/* ..... classify the output for input estimate */ | |
return utils.inputBytes({}) * feeRate | |
}, | |
sumForgiving: function (range) { | |
return range.reduce(function (a, x) { return (isFinite(x.value) ? x.value : 0) }, 0) | |
}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment