UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

195 lines (174 loc) 5.73 kB
import { hash256 } from '../primitives/Hash.js' import { Reader, Writer, toHex, toArray } from '../primitives/utils.js' import Transaction from './Transaction.js' import { BEEF_V2, TX_DATA_FORMAT } from './Beef.js' /** * A single bitcoin transaction associated with a `Beef` validity proof set. * * Simple case is transaction data included directly, either as raw bytes or fully parsed data, or both. * * Supports 'known' transactions which are represented by just their txid. * It is assumed that intended consumer of this beef already has validity proof for such a transaction, * which they can merge if necessary to create a valid beef. */ export default class BeefTx { _bumpIndex?: number _tx?: Transaction _rawTx?: number[] _txid?: string inputTxids: string[] = [] /** * true if `hasProof` or all inputs chain to `hasProof`. * * Typically set by sorting transactions by proven dependency chains. */ isValid?: boolean = undefined get bumpIndex (): number | undefined { return this._bumpIndex } set bumpIndex (v: number | undefined) { this._bumpIndex = v this.updateInputTxids() } get hasProof (): boolean { return this._bumpIndex !== undefined } get isTxidOnly (): boolean { return this._txid !== undefined && this._txid !== null && (this._rawTx == null) && (this._tx == null) } get txid (): string { if (this._txid !== undefined && this._txid !== null && this._txid !== '') return this._txid if (this._tx != null) { this._txid = this._tx.id('hex') return this._txid } if (this._rawTx != null) { this._txid = toHex(hash256(this._rawTx)) return this._txid } throw new Error('Internal') } get tx (): Transaction | undefined { if (this._tx != null) return this._tx if (this._rawTx != null) { this._tx = Transaction.fromBinary(this._rawTx) return this._tx } return undefined } get rawTx (): number[] | undefined { if (this._rawTx != null) return this._rawTx if (this._tx != null) { this._rawTx = this._tx.toBinary() return this._rawTx } return undefined } /** * @param tx If string, must be a valid txid. If `number[]` must be a valid serialized transaction. * @param bumpIndex If transaction already has a proof in the beef to which it will be added. */ constructor (tx: Transaction | number[] | string, bumpIndex?: number) { if (typeof tx === 'string') { this._txid = tx } else if (Array.isArray(tx)) { this._rawTx = tx } else { this._tx = tx } this.bumpIndex = bumpIndex this.updateInputTxids() } static fromTx (tx: Transaction, bumpIndex?: number): BeefTx { return new BeefTx(tx, bumpIndex) } static fromRawTx (rawTx: number[], bumpIndex?: number): BeefTx { return new BeefTx(rawTx, bumpIndex) } static fromTxid (txid: string, bumpIndex?: number): BeefTx { return new BeefTx(txid, bumpIndex) } private updateInputTxids (): void { if (this.hasProof || (this.tx == null)) { // If we have a proof, or don't have a parsed transaction this.inputTxids = [] } else { const inputTxids: Record<string, boolean> = {} // ✅ Explicit object type for (const input of this.tx.inputs) { if (input.sourceTXID !== undefined && input.sourceTXID !== null && input.sourceTXID !== '') { // ✅ Ensure sourceTXID is defined inputTxids[input.sourceTXID] = true } } this.inputTxids = Object.keys(inputTxids) } } toWriter (writer: Writer, version: number): void { const writeByte = (bb: number): void => { writer.writeUInt8(bb) } const writeTxid = (): void => { if (this._txid == null) { throw new Error('Transaction ID (_txid) is undefined') } writer.writeReverse(toArray(this._txid, 'hex')) } const writeTx = (): void => { if (this._rawTx != null) { writer.write(this._rawTx) } else if (this._tx != null) { writer.write(this._tx.toBinary()) } else { throw new Error('a valid serialized Transaction is expected') } } const writeBumpIndex = (): void => { if (this.bumpIndex === undefined) { writeByte(TX_DATA_FORMAT.RAWTX) // 0 } else { writeByte(TX_DATA_FORMAT.RAWTX_AND_BUMP_INDEX) // 1 writer.writeVarIntNum(this.bumpIndex) // the index of the associated bump } } if (version === BEEF_V2) { if (this.isTxidOnly) { writeByte(TX_DATA_FORMAT.TXID_ONLY) writeTxid() } else if (this.bumpIndex !== undefined) { writeByte(TX_DATA_FORMAT.RAWTX_AND_BUMP_INDEX) writer.writeVarIntNum(this.bumpIndex) writeTx() } else { writeByte(TX_DATA_FORMAT.RAWTX) writeTx() } } else { writeTx() writeBumpIndex() } } static fromReader (br: Reader, version: number): BeefTx { let data: Transaction | number[] | string | undefined let bumpIndex: number | undefined let beefTx: BeefTx | undefined if (version === BEEF_V2) { // V2 const format = br.readUInt8() if (format === TX_DATA_FORMAT.TXID_ONLY) { beefTx = BeefTx.fromTxid(toHex(br.readReverse(32))) } else { if (format === TX_DATA_FORMAT.RAWTX_AND_BUMP_INDEX) { bumpIndex = br.readVarIntNum() } data = Transaction.fromReader(br) beefTx = BeefTx.fromTx(data, bumpIndex) } } else { // V1 data = Transaction.fromReader(br) bumpIndex = br.readUInt8() !== 0 ? br.readVarIntNum() : undefined beefTx = BeefTx.fromTx(data, bumpIndex) } return beefTx } }