XO Integration Part II
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
type BlockchainUnsubscribeCallback,
|
||||
} from './types.js';
|
||||
|
||||
import { Transaction } from '@xocash/primitives';
|
||||
import { BlockHeader, Transaction, TransactionHash } from '@xocash/primitives';
|
||||
|
||||
export type BlockchainEvents = {
|
||||
isConnectedUpdated: boolean;
|
||||
@@ -80,6 +80,15 @@ export abstract class BaseBlockchain<
|
||||
address: string,
|
||||
): Promise<BlockchainTransactions>;
|
||||
|
||||
/**
|
||||
* Retrieves the Block Header at the given height.
|
||||
*
|
||||
* @param {number} blockHeight - The block height of the header to retrieve
|
||||
* @returns {Promise<BlockHeader>} A promise that resolves with the Block Header
|
||||
* @throws {Error} If the block number is invalid or the query fails
|
||||
*/
|
||||
abstract fetchBlockHeader(blockHeight: number): Promise<BlockHeader>;
|
||||
|
||||
/**
|
||||
* Retrieves detailed information about a specific transaction.
|
||||
*
|
||||
@@ -196,15 +205,29 @@ export abstract class BaseBlockchain<
|
||||
tx = Transaction.from(tx);
|
||||
}
|
||||
|
||||
const txHash = tx.toHashHex();
|
||||
// Then wait for either resolution or timeout
|
||||
return this.waitForTransactionHash(tx.toHash(), timeoutMs);
|
||||
}
|
||||
|
||||
async waitForTransactionHash(
|
||||
txHash: TransactionHash | Uint8Array | string,
|
||||
timeoutMs = 10_000,
|
||||
) {
|
||||
// Cast the transaction hash.
|
||||
if (!(txHash instanceof TransactionHash)) {
|
||||
txHash = TransactionHash.from(txHash);
|
||||
}
|
||||
|
||||
// First, subscribe to the transaction
|
||||
let unsubscribeCallback: () => void;
|
||||
const subscriptionPromise = this.subscribeTransaction(txHash, () => {
|
||||
if (unsubscribeCallback) {
|
||||
unsubscribeCallback();
|
||||
}
|
||||
});
|
||||
const subscriptionPromise = this.subscribeTransaction(
|
||||
txHash.toHex(),
|
||||
() => {
|
||||
if (unsubscribeCallback) {
|
||||
unsubscribeCallback();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Then create the wait promise without an async executor
|
||||
const waitForTxPromise = new Promise<void>((resolve, reject) => {
|
||||
@@ -218,7 +241,7 @@ export abstract class BaseBlockchain<
|
||||
// Create a subscribe function so that we get notified when transaction is seen by network
|
||||
unsubscribeCallback = () => {
|
||||
clearTimeout(timeout);
|
||||
this.unsubscribeTransaction(txHash, unsubscribeCallback);
|
||||
this.unsubscribeTransaction(txHash.toHex(), unsubscribeCallback);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { type BaseStore, StoreInMemory } from '../storage/index.js';
|
||||
|
||||
import {
|
||||
Address,
|
||||
BlockHeader,
|
||||
ExtMap,
|
||||
Outpoint,
|
||||
Transaction,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
type TransactionSubscribeNotification,
|
||||
initializeElectrumClient,
|
||||
broadcastTransaction,
|
||||
fetchBlockHeaderFromBlockHeight,
|
||||
fetchUnspentTransactionOutputs,
|
||||
fetchHistory,
|
||||
fetchTransaction,
|
||||
@@ -278,7 +280,7 @@ export class BlockchainElectrum extends BaseBlockchain<
|
||||
return Transaction.fromHex(txHex).toRaw();
|
||||
});
|
||||
|
||||
// Decode the hex into our transaction primitive.
|
||||
// Decode the raw data into our transaction primitive.
|
||||
const transaction = Transaction.fromRaw(transactionRaw);
|
||||
|
||||
// Return the transaction hex.
|
||||
@@ -318,6 +320,32 @@ export class BlockchainElectrum extends BaseBlockchain<
|
||||
return transactionsIndexed;
|
||||
}
|
||||
|
||||
async fetchBlockHeader(blockHeight: number) {
|
||||
// Wait for connection to be established before attempting to subscribe.
|
||||
// TODO: Consider doing this at a higher level.
|
||||
if (!this.isConnected) {
|
||||
await this.waitFor('isConnectedUpdated', (isConnected) => isConnected);
|
||||
}
|
||||
|
||||
// Define the cache key.
|
||||
const cacheKey = `blockHeader.${blockHeight}`;
|
||||
|
||||
// Attempt to get the transaction history from the cache or fetch them if not available.
|
||||
const blockHeaderRaw = await this.store.getOrSet(cacheKey, async () => {
|
||||
const headerHex = await fetchBlockHeaderFromBlockHeight(
|
||||
this.electrumClient,
|
||||
blockHeight,
|
||||
);
|
||||
return BlockHeader.fromHex(headerHex).toRaw();
|
||||
});
|
||||
|
||||
// Decode the raw data into our Block Header primitive.
|
||||
const blockHeader = BlockHeader.fromRaw(blockHeaderRaw);
|
||||
|
||||
// Return the block header.
|
||||
return blockHeader;
|
||||
}
|
||||
|
||||
async broadcastTransaction(
|
||||
tx: Transaction | Uint8Array | string,
|
||||
opts: BroadcastOpts = { timeoutMs: 10_000, waitUntilSeen: true },
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
|
||||
import {
|
||||
Address,
|
||||
BlockHeader,
|
||||
ExtMap,
|
||||
Outpoint,
|
||||
Transaction,
|
||||
@@ -30,6 +31,7 @@ export class BlockchainInMemory extends BaseBlockchain {
|
||||
return blockchain as InstanceType<T>;
|
||||
}
|
||||
|
||||
private blockHeaders: Array<BlockHeader>;
|
||||
private transactions: BlockchainTransactions;
|
||||
private addressSubscriptions: Map<string, Set<BlockchainCallback>>;
|
||||
private transactionSubscriptions: Map<string, Set<BlockchainCallback>>;
|
||||
@@ -40,6 +42,7 @@ export class BlockchainInMemory extends BaseBlockchain {
|
||||
this.transactions = new ExtMap<BlockchainTransaction>();
|
||||
this.addressSubscriptions = new Map();
|
||||
this.transactionSubscriptions = new Map();
|
||||
this.blockHeaders = [];
|
||||
}
|
||||
|
||||
async start() {}
|
||||
@@ -89,6 +92,17 @@ export class BlockchainInMemory extends BaseBlockchain {
|
||||
});
|
||||
}
|
||||
|
||||
async fetchBlockHeader(blockHeight: number) {
|
||||
const blockHeader = this.blockHeaders[blockHeight];
|
||||
|
||||
if (!blockHeader) {
|
||||
throw new Error(`No block available for height ${blockHeight}`);
|
||||
}
|
||||
|
||||
// Return the block header.
|
||||
return blockHeader;
|
||||
}
|
||||
|
||||
async broadcastTransaction(
|
||||
tx: Transaction | Uint8Array | string,
|
||||
opts: { skipValidation: boolean } = { skipValidation: false },
|
||||
@@ -326,6 +340,14 @@ export class BlockchainInMemory extends BaseBlockchain {
|
||||
}
|
||||
|
||||
async mineBlock(outputs: Array<Output>): Promise<void> {
|
||||
// Create a block header from the current timestamp.
|
||||
const newBlockHeader = BlockHeader.generateRandom(
|
||||
Math.floor(Date.now() / 1000),
|
||||
);
|
||||
|
||||
// Add it to our list of block headers.
|
||||
this.blockHeaders.push(newBlockHeader);
|
||||
|
||||
// Create a transaction that pays to our subscribed address.
|
||||
const transaction = Transaction.fromRaw({
|
||||
version: 2,
|
||||
|
||||
Reference in New Issue
Block a user