@@ -10,8 +10,8 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build-old": "rollup -c"
|
||||
"build": "npm run clean && tsc",
|
||||
"clean": "rm -r ./dist"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
|
||||
@@ -8,3 +8,5 @@ export * from './public-key.js';
|
||||
export * from './satoshis.js';
|
||||
export * from './script.js';
|
||||
export * from './transaction.js';
|
||||
export * from './transaction-graph.js';
|
||||
export * from './transaction-hash.js';
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { Transaction } from '@xo/primitives';
|
||||
|
||||
import { binToHex, Output } from '@bitauth/libauth';
|
||||
|
||||
export interface UTXO extends Output {
|
||||
outpointTransactionHash: string;
|
||||
outpointIndex: number;
|
||||
}
|
||||
|
||||
export class TransactionGraph {
|
||||
private transactions: Map<string, Transaction>;
|
||||
|
||||
constructor() {
|
||||
this.transactions = new Map();
|
||||
}
|
||||
|
||||
addTransaction(tx: Transaction) {
|
||||
this.transactions.set(tx.toHashHex(), tx);
|
||||
}
|
||||
|
||||
removeTransaction(txHash: string) {
|
||||
this.transactions.delete(txHash);
|
||||
}
|
||||
|
||||
getTransaction(txHash: string): Transaction | undefined {
|
||||
return this.transactions.get(txHash);
|
||||
}
|
||||
|
||||
getAllTransactions(): Transaction[] {
|
||||
return Array.from(this.transactions.values());
|
||||
}
|
||||
|
||||
deriveUtxoState(): UTXO[] {
|
||||
const utxoMap = new Map<string, UTXO>();
|
||||
|
||||
// First, add all outputs as potential UTXOs
|
||||
for (const tx of this.transactions.values()) {
|
||||
tx.getOutputs().forEach((output, outpointIndex) => {
|
||||
const txHashHex = tx.toHashHex();
|
||||
|
||||
const utxo: UTXO = {
|
||||
outpointTransactionHash: txHashHex,
|
||||
outpointIndex,
|
||||
valueSatoshis: output.valueSatoshis,
|
||||
lockingBytecode: output.lockingBytecode,
|
||||
};
|
||||
utxoMap.set(`${txHashHex}:${outpointIndex}`, utxo);
|
||||
});
|
||||
}
|
||||
|
||||
// Then, remove all spent outputs
|
||||
for (const tx of this.transactions.values()) {
|
||||
tx.getInputs().forEach((input) => {
|
||||
const outpointTransactionHashHex = binToHex(
|
||||
input.outpointTransactionHash
|
||||
);
|
||||
|
||||
utxoMap.delete(`${outpointTransactionHashHex}:${input.outpointIndex}`);
|
||||
});
|
||||
}
|
||||
|
||||
return Array.from(utxoMap.values());
|
||||
}
|
||||
|
||||
isTransactionValid(tx: Transaction): boolean {
|
||||
const currentUtxos = this.deriveUtxoState();
|
||||
const utxoMap = new Map(
|
||||
currentUtxos.map((utxo) => [
|
||||
`${utxo.outpointTransactionHash}:${utxo.outpointIndex}`,
|
||||
utxo,
|
||||
])
|
||||
);
|
||||
|
||||
return tx.getInputs().every((input) => {
|
||||
const outpointTransactionHashHex = binToHex(
|
||||
input.outpointTransactionHash
|
||||
);
|
||||
return utxoMap.has(
|
||||
`${outpointTransactionHashHex}:${input.outpointIndex}`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getDependentTransactions(txHash: string): Transaction[] {
|
||||
return this.getAllTransactions().filter((tx) => {
|
||||
tx.getInputs().some((input) => {
|
||||
const outpointTransactionHashHex = binToHex(
|
||||
input.outpointTransactionHash
|
||||
);
|
||||
return outpointTransactionHashHex === txHash;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBroadcastOrder(): Transaction[] {
|
||||
const visited = new Set<string>();
|
||||
const order: Transaction[] = [];
|
||||
|
||||
const visit = (txid: string) => {
|
||||
if (visited.has(txid)) return;
|
||||
|
||||
const tx = this.transactions.get(txid);
|
||||
if (!tx) return;
|
||||
|
||||
visited.add(txid);
|
||||
|
||||
// First, visit all transactions that this transaction depends on
|
||||
for (const input of tx.getInputs()) {
|
||||
const outpointTransactionHashHex = binToHex(
|
||||
input.outpointTransactionHash
|
||||
);
|
||||
visit(outpointTransactionHashHex);
|
||||
}
|
||||
|
||||
order.push(tx);
|
||||
};
|
||||
|
||||
// Start the topological sort from each transaction
|
||||
for (const tx of this.transactions.values()) {
|
||||
visit(tx.toHashHex());
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
binToHex,
|
||||
hexToBin,
|
||||
hashTransaction,
|
||||
isHex,
|
||||
} from '@bitauth/libauth';
|
||||
|
||||
/**
|
||||
* Transaction Hash Primitive.
|
||||
*/
|
||||
export class TransactionHash
|
||||
{
|
||||
public readonly bytes: Uint8Array;
|
||||
|
||||
/**
|
||||
* Instantiates TransactionHash from a Transaction Hash's bytes.
|
||||
*
|
||||
* @param bytes {Uint8Array} The Transaction Hash's bytes.
|
||||
*/
|
||||
constructor(bytes: Uint8Array)
|
||||
{
|
||||
// If bytes is not exactly 32, then this is an invalid transaction hash...
|
||||
if(bytes.length !== 32)
|
||||
{
|
||||
throw(new Error(`Invalid Transaction Hash: Expected 32 bytes but received ${bytes.length}`));
|
||||
}
|
||||
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates TransactionHash from a hex string.
|
||||
*
|
||||
* @param hex {string} The hex string.
|
||||
*
|
||||
* @throws {Error} If hex is invalid.
|
||||
*
|
||||
* @returns {TransactionHash} Instance of TransactionHash.
|
||||
*/
|
||||
public static fromHex(hex: string): TransactionHash
|
||||
{
|
||||
if(!isHex(hex))
|
||||
{
|
||||
throw(new Error('Invalid hex provided'));
|
||||
}
|
||||
|
||||
const bytes = hexToBin(hex);
|
||||
|
||||
return new TransactionHash(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates TransactionHash from a Transaction's bytes.
|
||||
*
|
||||
* @param transactionBytes {Uint8Array} The Transaction's bytes.
|
||||
*
|
||||
* @returns {TransactionHash} Instance of TransactionHash.
|
||||
*/
|
||||
public static fromTransactionBytes(transactionBytes: Uint8Array): TransactionHash
|
||||
{
|
||||
const transactionHash = hashTransaction(transactionBytes);
|
||||
|
||||
return new TransactionHash(hexToBin(transactionHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates TransactionHash from a Transaction's bytes in hex.
|
||||
*
|
||||
* @param transactionHex {Uint8Array} The Transaction's bytes in hex.
|
||||
*
|
||||
* @throws {Error} If hex is invalid.
|
||||
*
|
||||
* @returns {TransactionHash} Instance of TransactionHash.
|
||||
*/
|
||||
public static fromTransactionHex(transactionHex: string): TransactionHash
|
||||
{
|
||||
if(!isHex(transactionHex))
|
||||
{
|
||||
throw(new Error('Invalid hex provided'));
|
||||
}
|
||||
|
||||
const bytes = hexToBin(transactionHex);
|
||||
|
||||
return TransactionHash.fromTransactionBytes(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns transaction hash as a hex string.
|
||||
*
|
||||
* @returns {string} Hex string of the Transaction Hash.
|
||||
*/
|
||||
public toHex(): string
|
||||
{
|
||||
return binToHex(this.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns transaction hash as a Uint8Array.
|
||||
*
|
||||
* @returns {Uint8Array} Uint8Array of the Transaction Hash.
|
||||
*/
|
||||
public toBytes(): Uint8Array
|
||||
{
|
||||
return this.bytes;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TransactionHash } from './transaction-hash.js';
|
||||
|
||||
import {
|
||||
binToHex,
|
||||
hexToBin,
|
||||
@@ -69,6 +71,18 @@ export class Transaction {
|
||||
return binToHex(bytes);
|
||||
}
|
||||
|
||||
public toHash(): TransactionHash {
|
||||
return TransactionHash.fromTransactionBytes(this.toBytes());
|
||||
}
|
||||
|
||||
public toHashHex(): string {
|
||||
return this.toHash().toHex();
|
||||
}
|
||||
|
||||
public toHashBytes(): Uint8Array {
|
||||
return this.toHash().toBytes();
|
||||
}
|
||||
|
||||
public getVersion() {
|
||||
return this.transaction.version;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user