Transaction Receipts

Overview

Transaction receipts provide detailed execution results for completed transactions. Each receipt contains events emitted during contract execution, gas consumption metrics, revert information if the transaction failed, and state changes applied to the blockchain. Receipts are available only for confirmed transactions and serve as the primary source for verifying transaction outcomes and extracting event data.

Getting Transaction Receipt

Using getTransactionReceipt()

The getTransactionReceipt() method retrieves the execution receipt for a confirmed transaction by its hash. This method returns comprehensive details about the transaction's execution outcome. If the transaction is not found or has not been confirmed, the method throws an error.

The returned TransactionReceipt object provides access to the execution result, decoded events, gas metrics, and the block in which the transaction was included.

Method Signature

typescript
getTransactionReceipt() signature
async getTransactionReceipt(
    txHash: string  // Transaction hash
): Promise<TransactionReceipt>

TransactionReceipt Structure

The TransactionReceipt interface encapsulates the complete execution result of a transaction. The receipt field contains raw receipt data with corresponding receiptProofs for verification. Events are available in both parsed (events) and raw (rawEvents) formats. If the transaction failed, revert provides the decoded error message and rawRevert contains the unprocessed revert data. Gas consumption is reported through gasUsed and specialGasUsed fields.

typescript
TransactionReceipt
interface TransactionReceipt {
    // Execution result
    receipt?: Uint8Array;          // Raw receipt data
    receiptProofs: string[];       // Merkle proofs for receipt

    // Events
    events: ContractEvents;        // Parsed events by contract
    rawEvents: ContractEvents;     // Raw events before P2OP conversion

    // Revert information
    revert?: string;               // Decoded revert message
    rawRevert?: Uint8Array;        // Raw revert data

    // Gas metrics
    gasUsed: bigint;               // Gas consumed
    specialGasUsed: bigint;        // Special gas consumed
}

Basic Transaction Receipt 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 receipt = await provider.getTransactionReceipt(txHash);

console.log('Receipt:');
console.log('  Gas Used:', receipt.gasUsed);
console.log('  Special Gas:', receipt.specialGasUsed);
console.log('  Reverted:', receipt.revert !== undefined);

Working with Events

Access Contract Events

Events emitted during transaction execution are grouped by contract address in the events property. Each entry maps a contract address to an array of events emitted by that contract, with each event containing its type identifier and decoded data. This structure simplifies event processing for transactions that interact with multiple contracts.

typescript
Contract events
const receipt = await provider.getTransactionReceipt(txHash);

// Events are grouped by contract address
for (const [contractAddress, events] of Object.entries(receipt.events)) {
    console.log('Contract:', contractAddress);

    for (const event of events) {
        console.log('  Event type:', event.type);
        console.log('  Event data:', event.data);
    }
}

Decode Events with ABI

Raw event data can be decoded into structured objects using a contract instance with its associated ABI. The decodeEvents() method transforms raw events into typed objects with accessible properties, enabling type-safe event processing and eliminating manual parsing of event data.

typescript
Decode events with ABI
import { getContract, OP_20_ABI } from 'opnet';

// Get the contract with ABI
const contract = getContract<IOP20Contract>(
    tokenAddress,
    OP_20_ABI,
    provider,
    network
);

// Get transaction and decode events
const receipt = await provider.getTransactionReceipt(txHash);
const contractEvents = receipt.events[tokenAddress];

if (contractEvents) {
    const decodedEvents = contract.decodeEvents(contractEvents);

    for (const event of decodedEvents) {
        console.log('Event type:', event.type);
        console.log('Event properties:', event.properties);
    }
}

Filter Events by Type

Events can be filtered by their type identifier to extract specific event categories from a transaction. Using TypeScript type guards ensures type safety when processing filtered events, allowing direct access to event-specific properties without additional casting or validation.

typescript
Filter events by type
import { OPNetEvent } from 'opnet';

interface TransferEventData {
    from: string;
    to: string;
    amount: bigint;
}

async function getTransferEvents(
    provider: JSONRpcProvider,
    txHash: string,
    contractAddress: string
): Promise<OPNetEvent<TransferEventData>[]> {
    const receipt = await provider.getTransactionReceipt(txHash);
    const events = receipt.events[contractAddress] || [];

    // Filter for Transfer events (event type depends on contract)
    const transferEvents = events.filter(
        (event: OPNetEvent): event is OPNetEvent<TransferEventData> =>
            event.type === 'Transfer'
    );

    return transferEvents;
}

// Usage
const transfers = await getTransferEvents(provider, txHash, tokenAddress);
console.log('Transfer events:', transfers.length);

Handling Reverts

When a transaction fails, the receipt contains revert information explaining the failure. The revert property provides a human-readable error message, while rawRevert contains the unprocessed revert data for custom decoding. Checking for reverts is essential for error handling and providing meaningful feedback to users about why a transaction did not succeed.

Check for Revert

typescript
Check for Revert
const receipt = await provider.getTransactionReceipt(txHash);

// Using raw revert data
if (receipt.rawRevert) {
    console.log('Raw revert data:', toHex(receipt.rawRevert));
}

if (receipt.revert) {
    console.log('Revert reason:', receipt.revert);
} else {
    console.log('Transaction succeeded');
}

Custom Revert Decoding

typescript
Custom revert decoding
import { decodeRevertData } from 'opnet';

const receipt = await provider.getTransactionReceipt(txHash);

if (receipt.rawRevert) {
    // Use custom decoder if needed
    const customMessage = decodeRevertData(receipt.rawRevert);
    console.log('Custom decode:', customMessage);
}

Receipt Verification

Transaction receipts include Merkle proofs that enable cryptographic verification of the execution result. The receiptProofs array contains the proof data required to validate that the receipt was included in a block. Verifying these proofs ensures receipt authenticity and guards against tampered or fabricated execution results.

typescript
Verify receipt proofs
async function verifyReceiptProofs(
    provider: JSONRpcProvider,
    txHash: string
): Promise<{
    hasProofs: boolean;
    proofCount: number;
}> {
    const receipt = await provider.getTransactionReceipt(txHash);

    return {
        hasProofs: receipt.receiptProofs.length > 0,
        proofCount: receipt.receiptProofs.length,
    };
}

// Usage
const proofStatus = await verifyReceiptProofs(provider, txHash);
console.log('Has proofs:', proofStatus.hasProofs);
console.log('Proof count:', proofStatus.proofCount);

Complete Receipt Service

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

    async get(txHash: string): Promise<TransactionReceipt> {
        return this.provider.getTransactionReceipt(txHash);
    }

    async getGasUsed(txHash: string): Promise<bigint> {
        const receipt = await this.get(txHash);
        return receipt.gasUsed + receipt.specialGasUsed;
    }

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

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

    async getEvents(txHash: string): Promise<ContractEvents> {
        const receipt = await this.get(txHash);
        return receipt.events;
    }

    async getContractEvents(
        txHash: string,
        contractAddress: string
    ): Promise<OPNetEvent[]> {
        const receipt = await this.get(txHash);
        return receipt.events[contractAddress] || [];
    }

    async hasEvents(txHash: string): Promise<boolean> {
        const receipt = await this.get(txHash);
        return Object.keys(receipt.events).length > 0;
    }

    async getReceiptSummary(txHash: string): Promise<{
        gasUsed: bigint;
        specialGasUsed: bigint;
        reverted: boolean;
        revertReason?: string;
        eventCount: number;
        hasProofs: boolean;
    }> {
        const receipt = await this.get(txHash);

        let eventCount = 0;
        for (const events of Object.values(receipt.events)) {
            eventCount += events.length;
        }

        return {
            gasUsed: receipt.gasUsed,
            specialGasUsed: receipt.specialGasUsed,
            reverted: receipt.revert !== undefined,
            revertReason: receipt.revert,
            eventCount,
            hasProofs: receipt.receiptProofs.length > 0,
        };
    }
}

// Usage
const receiptService = new ReceiptService(provider);

// Get summary
const summary = await receiptService.getReceiptSummary(txHash);
console.log('Receipt Summary:');
console.log('  Gas:', summary.gasUsed);
console.log('  Reverted:', summary.reverted);
console.log('  Events:', summary.eventCount);

// Check if reverted
if (await receiptService.isReverted(txHash)) {
    const reason = await receiptService.getRevertReason(txHash);
    console.log('Revert reason:', reason);
}