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
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.
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
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.
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.
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.
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
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
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.
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
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);
}