@bsv/sdk
Version:
BSV Blockchain Software Development Kit
189 lines • 6.17 kB
JavaScript
import { hash256 } from '../primitives/Hash.js';
import { 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;
_tx;
_rawTx;
_txid;
inputTxids = [];
/**
* true if `hasProof` or all inputs chain to `hasProof`.
*
* Typically set by sorting transactions by proven dependency chains.
*/
isValid = undefined;
get bumpIndex() {
return this._bumpIndex;
}
set bumpIndex(v) {
this._bumpIndex = v;
this.updateInputTxids();
}
get hasProof() {
return this._bumpIndex !== undefined;
}
get isTxidOnly() {
return this._txid !== undefined && this._txid !== null && (this._rawTx == null) && (this._tx == null);
}
get txid() {
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() {
if (this._tx != null)
return this._tx;
if (this._rawTx != null) {
this._tx = Transaction.fromBinary(this._rawTx);
return this._tx;
}
return undefined;
}
get rawTx() {
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, bumpIndex) {
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, bumpIndex) {
return new BeefTx(tx, bumpIndex);
}
static fromRawTx(rawTx, bumpIndex) {
return new BeefTx(rawTx, bumpIndex);
}
static fromTxid(txid, bumpIndex) {
return new BeefTx(txid, bumpIndex);
}
updateInputTxids() {
if (this.hasProof || (this.tx == null)) {
// If we have a proof, or don't have a parsed transaction
this.inputTxids = [];
}
else {
const inputTxids = {}; // ✅ 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, version) {
const writeByte = (bb) => {
writer.writeUInt8(bb);
};
const writeTxid = () => {
if (this._txid == null) {
throw new Error('Transaction ID (_txid) is undefined');
}
writer.writeReverse(toArray(this._txid, 'hex'));
};
const writeTx = () => {
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 = () => {
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, version) {
let data;
let bumpIndex;
let beefTx;
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;
}
}
//# sourceMappingURL=BeefTx.js.map