Fetching Transactions

Overview

The provider exposes methods to retrieve transaction data from the OP_NET network. Transactions can be fetched individually by their hash or id, returning comprehensive details about the operation including contract interactions, deployments, gas consumption, and execution status. Transaction receipts provide additional information such as events emitted during execution and the final state changes applied to the blockchain.

Fetching a Transaction by Hash

Using getTransaction()

The getTransaction() method retrieves a transaction by its hash or id. This method returns the transaction data including sender and recipient addresses, calldata, gas usage, and block inclusion information. If the transaction is not found, the method throws an error.

The returned TransactionData object provides access to all transaction fields, including the contract address for contract interactions, the decoded input data, and the transaction's position within its containing block.

Method Signature

typescript
getTransaction() signature
async getTransaction(
    txHash: string
): Promise<TransactionBase<OPNetTransactionTypes>>
        

Transaction Types

OP_NET supports multiple transaction types including contract deployments, contract interactions, and generic transfers. For a comprehensive explanation of transaction structures, fields, and type-specific behavior, refer to the Transaction Types Explained section.

Transaction Inputs

The TransactionInput interface represents a transaction input, referencing the UTXO being spent. The originalTransactionId and outputTransactionIndex identify the source output, while scriptSignature contains the unlocking script. The sequenceId field specifies the input's sequence number for relative time-locks, and transactionInWitness holds the witness data for SegWit transactions.

typescript
TransactionInput
interface TransactionInput {
    originalTransactionId: string | undefined;   // Source transaction ID
    outputTransactionIndex: number | undefined;  // Source output index
    scriptSignature: ScriptSig | undefined;      // Script signature
    sequenceId: number;                          // Sequence number
    transactionInWitness: string[];              // Witness data
}

Transaction Outputs

The TransactionOutput interface represents a transaction output, defining where funds are sent. The value field specifies the amount in satoshis, while scriptPubKey contains the locking script with hex representation and associated addresses. The index indicates the output's position within the transaction, and script provides the decompiled script as an array of opcodes and data elements.

typescript
TransactionOutput
interface TransactionOutput {
    value: bigint;                                      // Output value in satoshis
    scriptPubKey: ScriptPubKey;                         // Output script (object with hex, addresses, address)
    index: number;                                      // Output index
    script: Array<number | Uint8Array> | null;    // Decompiled script
}

Basic Transaction Query

typescript
Basic transaction query
import { JSONRpcProvider } from 'opnet';
import { networks } from '@btc-vision/bitcoin';

const network = networks.regtest;
const provider = new JSONRpcProvider({ url: 'https://regtest.opnet.org', network });

const txHash = '63e77ba9fa4262b3d4d0d9d97fa8a7359534606c3f3af096284662e3f619f374';
const transaction = await provider.getTransaction(txHash);

console.log('Transaction:');
console.log('  ID:', transaction.id);
console.log('  Hash:', transaction.hash);
console.log('  Type:', transaction.OPNetType);
console.log('  Block:', transaction.blockNumber);

Working with Transactions

Check Transaction Type

Each transaction includes an OPNetType field that identifies its category. The OPNetTransactionTypes enum defines the available types, allowing applications to determine how to process the transaction and cast it to the appropriate interface for accessing type-specific properties.

typescript
Transaction type
import { OPNetTransactionTypes } from 'opnet';

const transaction = await provider.getTransaction(txHash);

switch (transaction.OPNetType) {
    case OPNetTransactionTypes.Interaction:
        console.log('Contract interaction');
        // Cast to InteractionTransaction for specific properties
        break;
    case OPNetTransactionTypes.Deployment:
        console.log('Contract deployment');
        // Cast to DeploymentTransaction for specific properties
        break;
    case OPNetTransactionTypes.Generic:
        console.log('Generic Bitcoin transaction');
        break;
}

Handle Interaction Transactions

Interaction transactions represent calls to deployed contracts. After verifying the transaction type, cast it to InteractionTransaction to access contract-specific fields such as the target contract address, sender address, calldata payload, and compression status.

typescript
Interaction transactions
import { OPNetTransactionTypes, InteractionTransaction } from 'opnet';

const tx = await provider.getTransaction(txHash);

if (tx.OPNetType === OPNetTransactionTypes.Interaction) {
    const interactionTx = tx as InteractionTransaction;

    console.log('Contract:', interactionTx.contractAddress);
    console.log('From:', interactionTx.from?.toHex());
    console.log('Calldata:', toHex(interactionTx.calldata ?? new Uint8Array(0)));
    console.log('Was compressed:', interactionTx.wasCompressed);
}

Handle Deployment Transactions

Deployment transactions represent the creation of new contracts on the network. After verifying the transaction type, cast it to DeploymentTransaction to access deployment-specific fields such as the newly created contract address, deployer address, and contract bytecode.

typescript
Deployment transactions
import { OPNetTransactionTypes, DeploymentTransaction } from 'opnet';

const tx = await provider.getTransaction(txHash);

if (tx.OPNetType === OPNetTransactionTypes.Deployment) {
    const deployTx = tx as DeploymentTransaction;

    console.log('Deployed contract:', deployTx.contractAddress);
    console.log('Deployer:', deployTx.from?.toHex());
    console.log('Bytecode size:', deployTx.bytecode?.length ?? 0, 'bytes');
}

Analyze Transaction Inputs and Outputs

Every transaction contains arrays of inputs and outputs representing the flow of funds. Inputs reference UTXOs being spent from previous transactions, while outputs define new UTXOs created by the transaction. Iterating through these arrays enables applications to trace fund origins, calculate total values, and build transaction graphs for analytics or visualization purposes.

typescript
Analyze inputs and outputs
const tx = await provider.getTransaction(txHash);

console.log('Inputs:', tx.inputs.length);
for (const input of tx.inputs) {
    console.log(`  ${input.originalTransactionId}:${input.outputTransactionIndex}`);
}

console.log('Outputs:', tx.outputs.length);
for (const output of tx.outputs) {
    console.log(`  Index ${output.index}: ${output.value} sats`);
}

Hanlde Multiple Transactions

Transactions can be retrieved in bulk by fetching a block with the prefetchTxs parameter enabled. This returns all transactions included in the block within a single request, avoiding the need to fetch each transaction individually.

Get Block Transactions

typescript
Multiple transactions
async function getBlockTransactions(
    provider: JSONRpcProvider,
    blockNumber: bigint
): Promise<TransactionBase<OPNetTransactionTypes>[]> {
    const block = await provider.getBlock(blockNumber, true);
    return block.transactions;
}

// Usage
const transactions = await getBlockTransactions(provider, 100000n);
console.log('Transactions in block:', transactions.length);

for (const tx of transactions) {
    console.log(`${tx.hash}: ${tx.OPNetType}`);
}

Get Filtered Block Transactions

typescript
Filter by type
async function getInteractionTransactions(
    provider: JSONRpcProvider,
    blockNumber: bigint
): Promise<InteractionTransaction[]> {
    const transactions = await getBlockTransactions(provider, blockNumber);

    return transactions.filter(
        (tx): tx is InteractionTransaction =>
            tx.OPNetType === OPNetTransactionTypes.Interaction
    );
}

// Usage
const interactions = await getInteractionTransactions(provider, 100000n);
console.log('Interactions:', interactions.length);

Transaction Analysis

Transaction data includes fields for analyzing execution cost and status. Gas usage metrics indicate the computational resources consumed, while the revert field reveals whether the transaction failed and the reason for failure. These details enable applications to monitor transaction outcomes, calculate costs, and provide meaningful feedback to users.

Calculate Gas Cost

typescript
Gas cost
function analyzeGasCost(tx: TransactionBase<OPNetTransactionTypes>): {
    gasUsed: bigint;
    specialGasUsed: bigint;
    totalGas: bigint;
    burnedBitcoin: bigint;
} {
    return {
        gasUsed: tx.gasUsed,
        specialGasUsed: tx.specialGasUsed,
        totalGas: tx.gasUsed + tx.specialGasUsed,
        burnedBitcoin: tx.burnedBitcoin,
    };
}

// Usage
const tx = await provider.getTransaction(txHash);
const gasAnalysis = analyzeGasCost(tx);
console.log('Gas used:', gasAnalysis.gasUsed);
console.log('Total gas:', gasAnalysis.totalGas);
        

Check Transaction Status

typescript
Transaction status
async function getTransactionStatus(
    provider: JSONRpcProvider,
    txHash: string
): Promise<{
    confirmed: boolean;
    blockNumber?: bigint;
    reverted: boolean;
    revertReason?: string;
}> {
    try {
        const tx = await provider.getTransaction(txHash);

        return {
            confirmed: tx.blockNumber !== undefined,
            blockNumber: tx.blockNumber,
            reverted: tx.revert !== undefined,
            revertReason: tx.revert,
        };
    } catch {
        return {
            confirmed: false,
            reverted: false,
        };
    }
}

// Usage
const status = await getTransactionStatus(provider, txHash);
if (status.confirmed) {
    console.log('Confirmed in block:', status.blockNumber);
} else {
    console.log('Not yet confirmed');
}

if (status.reverted) {
    console.log('Transaction reverted:', status.revertReason);
}

Complete Transaction Service

typescript
TransactionService
class TransactionService {
    constructor(private provider: JSONRpcProvider) {}

    async get(txHash: string): Promise<TransactionBase<OPNetTransactionTypes>> {
        return this.provider.getTransaction(txHash);
    }

    async getInteraction(txHash: string): Promise<InteractionTransaction | null> {
        const tx = await this.get(txHash);
        if (tx.OPNetType === OPNetTransactionTypes.Interaction) {
            return tx as InteractionTransaction;
        }
        return null;
    }

    async getDeployment(txHash: string): Promise<DeploymentTransaction | null> {
        const tx = await this.get(txHash);
        if (tx.OPNetType === OPNetTransactionTypes.Deployment) {
            return tx as DeploymentTransaction;
        }
        return null;
    }

    async isConfirmed(txHash: string): Promise<boolean> {
        try {
            const tx = await this.get(txHash);
            return tx.blockNumber !== undefined;
        } catch {
            return false;
        }
    }

    async isReverted(txHash: string): Promise<boolean> {
        try {
            const tx = await this.get(txHash);
            return tx.revert !== undefined;
        } catch {
            return false;
        }
    }

    async getRevertReason(txHash: string): Promise<string | undefined> {
        const tx = await this.get(txHash);
        return tx.revert;
    }

    async waitForConfirmation(
        txHash: string,
        timeoutMs: number = 60000
    ): Promise<TransactionBase<OPNetTransactionTypes> | null> {
        const startTime = Date.now();

        while (Date.now() - startTime < timeoutMs) {
            try {
                const tx = await this.get(txHash);
                if (tx.blockNumber !== undefined) {
                    return tx;
                }
            } catch {
                // Transaction not found yet
            }

            await new Promise(r => setTimeout(r, 5000));
        }

        return null;
    }
}

// Usage
const txService = new TransactionService(provider);

// Get transaction
const tx = await txService.get(txHash);
console.log('Transaction type:', tx.OPNetType);

// Check if confirmed
const confirmed = await txService.isConfirmed(txHash);
console.log('Confirmed:', confirmed);

// Wait for confirmation
const confirmedTx = await txService.waitForConfirmation(txHash, 120000);
if (confirmedTx) {
    console.log('Confirmed in block:', confirmedTx.blockNumber);
}