import { Transaction } from './transaction.js'; import { binToHex } from '@bitauth/libauth'; /** * Sort transactions by the order in which they should be broadcast. */ export function sortTransactions( transactions: Array, ): Array { const transactionMap = transactions.reduce( (map, transaction) => { const txHash = transaction.toHash().toHex(); map[txHash] = transaction; return map; }, {} as Record, ); const visited = new Set(); const order: Transaction[] = []; const visit = (txid: string) => { if (visited.has(txid)) return; const tx = transactionMap[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 Object.values(transactionMap)) { visit(tx.toHash().toHex()); } return order; } export class ExtMap extends Map { /** * Creates a new ExtMap from an array using a key selector */ static fromArray( array: readonly T[], keySelector: (item: T) => K, ): ExtMap { const collection = new ExtMap(); array.forEach((item) => { const key = keySelector(item); collection.set(key, item); }); return collection; } static fromObject(object: { [key: PropertyKey]: T }): ExtMap { const collection = new ExtMap(); for (let [key, value] of Object.entries(object)) { collection.set(key, value); } return collection; } /** * Flattens an array of ExtMaps into a single ExtMap * Last collection's values take precedence for duplicate keys */ static flatten(collections: ExtMap[]): ExtMap { const flattened = new ExtMap(); collections.forEach((collection) => { for (const [key, value] of collection.entries()) { flattened.set(key, value); } }); return flattened; } getOrFail(key: string): T { const value = this.get(key); if (!value) { throw new Error(`Failed to find key "${key}"`); } return value; } /** * Returns a new ExtMap with filtered entries */ filter(predicate: (value: T, key: PropertyKey) => boolean): ExtMap { const filtered = new ExtMap(); for (const [key, value] of this.entries()) { if (predicate(value, key)) { filtered.set(key, value); } } return filtered; } /** * Returns a new ExtMap with mapped values */ map(mapper: (value: T, key: PropertyKey) => U): ExtMap { const mapped = new ExtMap(); for (const [key, value] of this.entries()) { mapped.set(key, mapper(value, key)); } return mapped; } /** * Returns a new ExtMap with entries sorted by the given comparator * @param comparator - Function to determine the sort order * @returns A new ExtMap with sorted entries */ sort(comparator: (a: T, b: T) => number): ExtMap { const entries = Array.from(this.entries()); const sorted = new ExtMap(); entries .sort(([, valueA], [, valueB]) => comparator(valueA, valueB)) .forEach(([key, value]) => { sorted.set(key, value); }); return sorted; } /** * Converts the collection to a plain object */ toObject(): Record { return Object.fromEntries(this.entries()); } /** * Converts the collection to an array of values */ toArray(): T[] { return Array.from(this.values()); } /** * Converts the collection to a Set * @param callback - Optional function to transform keys before adding to Set * @returns A Set containing either the keys or transformed values */ toSet(callback?: (key: PropertyKey, value: T) => U): Set { const set = new Set(); for (const [key, value] of this.entries()) { set.add(callback ? callback(key, value) : (key as U)); } return set; } }