mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-12 19:20:46 +02:00
196 lines
5.4 KiB
JavaScript
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] }}),
|
|
}
|
|
}
|
|
}
|