Skip to content
hopper
Get started
Safety / instruction-introspection

Inspect prior calls on chain

Read a prior call, its accounts, and its privileges with caller-owned buffers and explicit authorization.

This reference covers Hopper 0.4.0, published on crates.io. See the release status for package verification, dated benchmarks, and remaining audit work.

A settlement or treasury program may need to inspect the call that preceded it: which program ran, which accounts it received, and what instruction data it used. Hopper exposes processed siblings through caller-owned scratch buffers. No indexer, service, or heap allocation is required for the read.

Read the instruction and its account privileges

Native programs use hopper_native::introspect; runtime programs use hopper_runtime::crypto. The caller-buffer API is available in native 0.4.3 and runtime 0.4.4.

use hopper_native::{Address, ProgramError};
use hopper_native::introspect::{
    get_processed_instruction_into, ProcessedInstructionAccount,
};

fn require_previous_program(expected: &Address) -> Result<(), ProgramError> {
    let mut data = [0u8; 128];
    let mut accounts: [ProcessedInstructionAccount; 4] =
        core::array::from_fn(|_| ProcessedInstructionAccount::default());
    let previous = get_processed_instruction_into(0, &mut data, &mut accounts)?
        .ok_or(ProgramError::InvalidArgument)?;
    if previous.program_id != *expected {
        return Err(ProgramError::IncorrectProgramId);
    }
    // Also validate the instruction discriminator, data, account identities,
    // signer/writable flags, and the operation your application expects.
    Ok(())
}

The helper queries exact data and account counts before copying. It returns only the used prefixes and leaves unused buffer capacity untouched. Ok(None) means no such sibling; AccountDataTooSmall means either buffer is too small. It never returns truncated instruction data. Choose capacities that fit your program's SBF stack budget; the helper does not allocate a larger buffer for you.

Each account record holds a native Address, is_signer, and is_writable. These are the privileges of that sibling instruction, which can be narrower than the transaction's privileges. Runtime callers can convert a record's native address with hopper_runtime::Address::from(record.address.clone()).

Understand the scope of the read

Index zero is the most recent processed sibling at the same depth and caller; index one is the preceding sibling. Parents and children are excluded. This is not a transaction-wide index or a complete nested execution trace.

The older get_processed_instruction convenience API reads up to 1,232 data bytes and 64 account records. Its None result combines absence and capacity failure. Runtime's get_processed_instruction_data::<N> chooses the data budget but still needs internal space for up to 64 account records. The caller-buffer API is preferable when you need account metadata or distinct capacity errors.

Host stubs return absence. Use a compiled SVM test or a cluster transaction to test execution history.

Keep authorization and outcomes explicit

Inspecting an instruction does not prove a token receipt or grant authority to spend funds. Validate the expected action and account relationships. Use token receipts when your contract requires a specific debit or minimum amount received after CPI, and propagate errors to the instruction boundary for rollback.

The require_ed25519_instruction, require_secp256k1_instruction, and require_secp256r1_instruction helpers check only the sibling's program ID. Applications must validate signature counts, offsets, referenced instruction bytes, and the expected public key and message. WebAuthn additionally requires the application's challenge and relying-party policy. Use the Instructions sysvar for the transaction-level list and absolute cross-instruction references.

For framework applications, start with the stricter Instructions-sysvar helpers in the crypto guide, such as hopper::crypto::check_ed25519_signature_at. Those validate the selected payload and expected signer/message within their documented offset policy. They are different APIs from the low-level require_*_instruction ID checks above.

Regression coverage

The compiled fixture checks native and runtime reads, account identities and privileges, empty and missing instructions, reverse sibling order, exclusion of a nested child, 1,300-byte CPI data, and both kinds of capacity refusal. Fixture and runner. Release and devnet status are recorded in validation.

The September 27, 2026 run finalized all 11 devnet transactions, including the two expected capacity refusals. Complete payer/program snapshots matched, and the deployed ELF matched before and after. Native 0.4.4/runtime 0.4.5 clarify the packaged documentation; their implementation and rebuilt v0/v3 binaries match the tested release. Inspect the evidence.