bitfeed/client/src/models/BitcoinTx.js
2022-04-24 17:42:41 -06:00

196 lines
5.4 KiB
JavaScript

import TxView from './TxView.js'
import config from '../config.js'
import { subsidyAt } from '../utils/bitcoin.js'
import { mixColor, pink, bluegreen, orange, teal, blue, green, purple } from '../utils/color.js'
export default class BitcoinTx {
constructor (data, vertexArray, isCoinbase = false) {
this.vertexArray = vertexArray
this.setData(data, isCoinbase)
if (vertexArray) this.view = new TxView(this)
}
setCoinbaseData (block) {
if (this.is_preview) {
const subsidy = subsidyAt(block.height)
this.coinbase = {
height: block.height,
fees: this.value - subsidy,
subsidy
}
} else {
const cbInfo = this.inputs[0].script_sig
// number of bytes encoding the block height
const height_bytes = parseInt(cbInfo.substring(0,2), 16)
// extract the specified number of bytes, reverse the endianness (reverse pairs of hex characters), parse as a hex string
const parsed_height = parseInt(cbInfo.substring(2,2 + (height_bytes * 2)).match(/../g).reverse().join(''),16)
// save remaining bytes as free data
const sig = cbInfo.substring(2 + (height_bytes * 2))
const sigAscii = (sig && sig.length) ? sig.match(/../g).reduce((parsed, hexChar) => {
return parsed + String.fromCharCode(parseInt(hexChar, 16))
}, "") : ""
const height = block.height == null ? parsed_height : block.height
const subsidy = subsidyAt(height)
this.coinbase = {
height,
sig,
sigAscii,
fees: this.value - subsidy,
subsidy
}
}
}
setVertexArray (vertexArray) {
this.vertexArray = vertexArray
this.view = new TxView(this)
}
setData ({ version, inflated, preview, id, value, fee, vbytes, numInputs, inputs, outputs, time, block }, isCoinbase=false) {
this.version = version
this.is_inflated = !!inflated
this.is_preview = !!preview
this.id = id
this.pixelPosition = { x: 0, y: 0, r: 0}
this.screenPosition = { x: 0, y: 0, r: 0}
this.gridPosition = { x: 0, y: 0, r: 0}
this.inputs = inputs
if (numInputs != null) this.numInputs = numInputs
this.outputs = outputs
this.value = value
this.fee = fee
this.vbytes = vbytes
if (this.fee != null) this.feerate = fee / vbytes
if (inputs && outputs && value == null) {
this.value = this.calcValue()
}
this.time = time
this.highlight = false
// is a coinbase transaction?
this.isCoinbase = isCoinbase
if (this.isCoinbase || this.fee == null || this.fee < 0) {
this.fee = null
this.feerate = null
}
if (!this.block) this.setBlock(block)
const feeColor = ((this.isCoinbase || this.feerate == null)
? orange
: mixColor(teal, purple, 1, Math.log2(128), Math.log2(this.feerate))
)
this.colors = {
age: {
block: { color: orange },
pool: { color: orange, endColor: blue, duration: 60000 },
},
fee: {
block: { color: feeColor },
pool: { color: feeColor },
}
}
}
destroy () {
if (this.view) this.view.destroy()
}
calcValue () {
if (this.outputs && this.outputs.length) {
return this.outputs.reduce((acc, output) => {
return acc + output.value
}, 0)
} else return 0
}
setBlock (block) {
this.block = block
this.state = this.block ? 'block' : 'pool'
if (this.block && (this.isCoinbase || (this.block.coinbase && this.id == this.block.coinbase.id))) {
this.isCoinbase = true
this.setCoinbaseData(this.block)
}
}
onEnterScene () {
this.enteredTime = performance.now()
}
getColor (scene, mode) {
return this.colors[mode][scene]
}
hoverOn (color = bluegreen) {
if (this.view) this.view.setHover(true, color)
}
hoverOff () {
if (this.view) this.view.setHover(false)
}
highlightOn (color = pink) {
if (this.view) this.view.setHighlight(true, color)
this.highlight = true
}
highlightOff () {
if (this.view) this.view.setHighlight(false)
this.highlight = false
}
applyHighlighting (criteria) {
let color
this.highlight = false
criteria.forEach(criterion => {
if (criterion.txid === this.id) {
this.highlight = true
color = criterion.color
} else if (criterion.address && criterion.scriptPubKey) {
this.outputs.forEach(output => {
if (output.script_pub_key === criterion.scriptPubKey) {
this.highlight = true
color = criterion.color
}
})
}
})
this.view.setHighlight(this.highlight, color || pink)
}
static fromRPCData(txData, isCoinbase) {
return {
version: txData.version,
inflated: false,
preview: true,
id: txData.txid,
value: null, // calculated in constructor
fee: txData.fee * 100000000,
vbytes: txData.vsize,
inputs: txData.vin.map(vin => { return { script_sig: vin.coinbase || vin.scriptSig.hex, prev_txid: vin.txid, prev_vout: vin.vout }}),
outputs: txData.vout.map(vout => { return { value: vout.value * 100000000, script_pub_key: vout.scriptPubKey.hex }}),
}
}
// unpack compact array format tx data
static decompress (data, blockData) {
return {
version: data[0],
inflated: false,
preview: true,
id: data[1],
fee: data[2],
value: data[3],
vbytes: data[4],
numInputs: data[5],
outputs: data[6].map(vout => { return { value: vout[0], script_pub_key: vout[1] }}),
}
}
}