Sign in

Errors and events

You're about to write a handler. Half the work is the happy path: read accounts, do the operation, write state. The other half is everything else: someone passed a zero amount, the caller isn't authorized, the lockup hasn't expired, the vault is empty. Every one of these conditions needs to abort the transaction cleanly with a message the user can actually read. Separately, when the happy path succeeds, off-chain consumers want to know what changed: who deposited, who withdrew, how much. The first half is errors. The second half is events. This lecture is the practical mechanics of both.

Defining your error type

A program's errors are a Rust enum tagged with #[error_code]. Each variant is one failure case, with a human-readable message. The macro turns the enum into something Anchor can return and the IDL can describe.

rust
use anchor_lang::prelude::*;

#[error_code]
pub enum CounterError {
    #[msg("Increment must be greater than zero")]
    ZeroIncrement,

    #[msg("Caller lacks admin authority")]
    Unauthorized,

    #[msg("Counter would overflow")]
    Overflow,

    #[msg("Counter would exceed configured maximum")]
    WouldExceedMax,

    #[msg("Counter is locked")]
    Locked,
}

Three details worth knowing. The numeric error codes start at 6000, because Anchor reserves 0 through 5999 for framework errors. The messages end up in the IDL and are what client tools display when the error fires. The variant order is positional, so once a program is deployed, adding new variants is safe but reordering or inserting breaks every later code for clients.

That's the whole enum. Now you need to actually raise these errors from your handlers.

Four ways to throw an error

Anchor gives you a small family of macros for raising errors plus an explicit form for unusual cases.

Four ways to throw an error require!(cond, ERR) general boolean check require!( amount > 0, VaultError::ZeroAmount ); use for: any condition that must hold, covers ~80% of error checks require_eq!(a, b, ERR) equality with diagnostic logs require_eq!( order.size, expected_size, SizeMismatch ); use for: value comparisons where you want both values in the logs require_keys_eq!(a, b, ERR) Pubkey comparison require_keys_eq!( ctx.accounts.signer.key(), vault.owner, Unauthorized ); use for: authority checks, account identity verification return Err(ERR.into()) explicit error return if vault.balance == 0 { msg!("vault is empty"); return Err( Empty.into() ); } use for: paths that need extra logic or logging before erroring Default to require! for boolean checks, require_keys_eq! for pubkeys. Reach for the others when they fit.

require!: the everyday case

The macro that covers most checks. The first argument is a boolean expression that must be true. If it isn't, the named error fires and the transaction aborts.

rust
require!(amount > 0, CounterError::ZeroIncrement);
require!(!counter.locked, CounterError::Locked);
require!(
    counter.value + by <= counter.max,
    CounterError::WouldExceedMax,
);

Read these as assertions: you state what must be true, and if reality disagrees, the named error wins. Three of these stacked at the top of a handler is a perfectly normal pattern. Anchor's macro layer expands each one into the equivalent of an if !condition { return Err(...) }, but reads cleaner.

require_eq! and require_neq!: equality with diagnostic logs

When the check is equality between two values, the eq form has one advantage over plain require!: it logs both values when it fails. That makes debugging much easier. Compare these two failure cases:

rust
// require! version
require!(order.size == expected_size, CounterError::SizeMismatch);
// log on failure: just "SizeMismatch"

// require_eq! version  
require_eq!(order.size, expected_size, CounterError::SizeMismatch);
// log on failure: "SizeMismatch. Left: 100. Right: 250."

When you're chasing down why a test is failing, having the actual values in the log saves a debugging round trip. Reach for require_eq!, or its negation require_neq!, when the comparison is equality of two integers, byte arrays, or anything that implements Debug.

require_keys_eq! and require_keys_neq!: Pubkey comparisons

The most common variant in real handlers. Almost every authority check ends up being "is this signer the right pubkey?" The keys form does the comparison cleanly and logs both pubkeys in base58 on failure.

rust
// authority check
require_keys_eq!(
    ctx.accounts.admin.key(),
    counter.admin,
    CounterError::Unauthorized,
);

// "is this the expected mint" check
require_keys_eq!(
    token_account.mint,
    expected_mint.key(),
    CounterError::WrongMint,
);

This is the form you'll write most often. Auth checks, account-identity checks, "is this the right PDA for this user" checks. All of these are pubkey equalities.

return Err(ERR.into()): when you need extra logic

For the rare path that needs to log diagnostic info, update state, or do anything else before erroring, fall back to the explicit form:

rust
if vault.balance < amount {
    msg!("requested {} but vault has only {}", amount, vault.balance);
    msg!("partial withdrawals are not supported");
    return Err(CounterError::Insufficient.into());
}

The .into() converts your enum variant into the anchor_lang::error::Error type that handlers return. Use this form sparingly. If a require! works, prefer it.

Events: the off-chain channel

Errors stop a transaction. Events report what a successful one did. The mechanism is a structured log entry: you define a Rust struct with #[event], then emit it from your handler.

rust
#[event]
pub struct Incremented {
    pub counter: Pubkey,
    pub by: u64,
    pub new_value: u64,
}

// inside the handler:
emit!(Incremented {
    counter: counter.key(),
    by,
    new_value: counter.value,
});

The #[event] macro registers the struct's schema in the IDL, so off-chain code knows how to decode it. The emit! macro inside the handler serializes one instance of the struct and writes it to the transaction's logs.

That's it on the program side. The off-chain side is where events earn their keep. Indexers like Helius or Triton subscribe to a program's logs, decode every event they care about, and write rows into a database. Frontend code reads from that database to show "the latest deposits" or "recent trades." Discord bots tail the same stream to announce events as they happen. None of this would be possible if you had to scan account state to figure out what changed.

State vs events: where each piece of data belongs

The most common mistake new developers make in this area is treating events as a substitute for on-chain state. They emit an event for some piece of data, then try to read it back from another instruction. It doesn't work, because events are not on-chain readable.

State vs events: two different output channels state (on accounts) written by: vault.total += amount; lives: in the account's data field cost: rent + compute paid once at allocation readable by: on-chain code (other programs, later instructions in this tx) off-chain code (RPC, indexers) good for: - the source of truth - anything that gates logic - balances, ownership, status the chain's enforcement layer events (emit!) written by: emit!(Deposited { ... }); lives: in the transaction's logs cost: compute only no rent, no allocation readable by: only off-chain consumers on-chain code can never read events back good for: - analytics, indexers - frontend notifications - structured logs the chain's notification layer If another instruction needs to read it, put it in state. If only humans and dashboards need it, emit it.

The rule of thumb is short: if another instruction needs to read it, put it in state. If only humans and dashboards need it, emit it.

State is anything that gates program logic. Balances, ownership flags, lockup expiry timestamps, current proposal status. All of these must live on accounts because some future instruction will need to check them. State costs rent and persists across transactions.

Events are anything that off-chain consumers need to know about but no on-chain logic will read. A deposit happened, a trade executed, a vote was cast. Each of these is interesting to analytics and frontends, but if your program needs to know whether a user already deposited, that check lives on an account rather than in a log.

A complete example

Here's the same counter program's increment handler, with all the patterns above working together. Authority is enforced with require_keys_eq!. Boolean preconditions use require!. The arithmetic overflow check uses checked_add and require!. On success, state is updated and an event is emitted.

rust
#[program]
pub mod counter {
    use super::*;

    pub fn increment(ctx: Context<Increment>, by: u64) -> Result<()> {
        let counter = &mut ctx.accounts.counter;

        // authority check
        require_keys_eq!(
            ctx.accounts.caller.key(),
            counter.admin,
            CounterError::Unauthorized,
        );

        // preconditions
        require!(by > 0, CounterError::ZeroIncrement);
        require!(!counter.locked, CounterError::Locked);

        // overflow-safe arithmetic
        let new_value = counter.value
            .checked_add(by)
            .ok_or(CounterError::Overflow)?;

        require!(new_value <= counter.max, CounterError::WouldExceedMax);

        // happy path: update state, emit event
        counter.value = new_value;

        emit!(Incremented {
            counter: counter.key(),
            by,
            new_value: counter.value,
        });

        Ok(())
    }
}

#[event]
pub struct Incremented {
    pub counter: Pubkey,
    pub by: u64,
    pub new_value: u64,
}

#[error_code]
pub enum CounterError {
    #[msg("Caller lacks admin authority")]
    Unauthorized,

    #[msg("Increment must be greater than zero")]
    ZeroIncrement,

    #[msg("Counter is locked")]
    Locked,

    #[msg("Counter would overflow")]
    Overflow,

    #[msg("Counter would exceed configured maximum")]
    WouldExceedMax,
}

A few things to notice in this code. The checks happen up front, before any state changes. If any of them fails, the transaction aborts before counter.value is touched. That's the standard pattern: validate first, mutate last. The checked_add returns Option<u64>, and ok_or converts a None into your custom error. This is the idiomatic way to handle arithmetic that might overflow. Finally, the event is emitted at the end, after the state update, so the values in the event reflect what's now on chain.

Every handler you write will follow some version of this shape: a few require! calls validating inputs, the actual state change, an emit! call announcing what happened. Internalize the pattern once and you'll reach for it without thinking.