Skip to main content
Events allow your program to emit structured data that clients can subscribe to and monitor. Anchor provides two ways to emit events: through program logs with emit! or through Cross Program Invocations with emit_cpi!.

Defining Events

Define events using the #[event] attribute on a struct:
#[event]
pub struct TransferEvent {
    pub from: Pubkey,
    pub to: Pubkey,
    pub amount: u64,
    pub timestamp: i64,
}
Events can contain any data types that implement AnchorSerialize and AnchorDeserialize.

Emitting Events with emit!

The emit! macro is the simpler approach, writing events to program logs using the sol_log_data() syscall:
use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

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

    pub fn transfer(
        ctx: Context<Transfer>,
        amount: u64,
    ) -> Result<()> {
        // Transfer logic here
        
        emit!(TransferEvent {
            from: ctx.accounts.from.key(),
            to: ctx.accounts.to.key(),
            amount,
            timestamp: Clock::get()?.unix_timestamp,
        });
        
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Transfer<'info> {
    pub from: Signer<'info>,
    /// CHECK: This is safe because we don't read or write
    pub to: AccountInfo<'info>,
}

#[event]
pub struct TransferEvent {
    pub from: Pubkey,
    pub to: Pubkey,
    pub amount: u64,
    pub timestamp: i64,
}

How emit! Works

  1. Serializes the event data
  2. Calls sol_log_data() to write to program logs
  3. Encodes data as base64 with the prefix Program data:

Program Logs Output

When an event is emitted, it appears in the logs:
Program log: Instruction: Transfer
Program data: Zb1eU3aiYdwOAAAASGVsbG8sIFNvbGFuYSE=
Program consumed 1012 of 200000 compute units
Program success

Emitting Events with emit_cpi!

The emit_cpi! macro emits events through a Cross Program Invocation, including the data in the instruction data instead of logs. This approach is more reliable when working with data providers that might truncate logs.

Setup

Enable the event-cpi feature in Cargo.toml:
[dependencies]
anchor-lang = { version = "0.32.1", features = ["event-cpi"] }

Usage

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

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

    pub fn transfer(
        ctx: Context<Transfer>,
        amount: u64,
    ) -> Result<()> {
        // Transfer logic here
        
        emit_cpi!(TransferEvent {
            from: ctx.accounts.from.key(),
            to: ctx.accounts.to.key(),
            amount,
            timestamp: Clock::get()?.unix_timestamp,
        });
        
        Ok(())
    }
}

// Add #[event_cpi] to the Accounts struct
#[event_cpi]
#[derive(Accounts)]
pub struct Transfer<'info> {
    pub from: Signer<'info>,
    /// CHECK: This is safe because we don't read or write
    pub to: AccountInfo<'info>,
}

#[event]
pub struct TransferEvent {
    pub from: Pubkey,
    pub to: Pubkey,
    pub amount: u64,
    pub timestamp: i64,
}
The #[event_cpi] attribute must be added to the accounts struct. It automatically includes additional accounts required for the self-CPI.

Subscribing to Events in TypeScript

Using emit! Events

Listen to events with addEventListener():
import * as anchor from "@anchor-lang/anchor";
import { Program } from "@anchor-lang/anchor";
import { Events } from "../target/types/events";

const program = anchor.workspace.Events as Program<Events>;

// Set up event listener
const listenerId = program.addEventListener(
  "transferEvent",
  (event, slot) => {
    console.log(`Event in slot ${slot}:`);
    console.log(`  From: ${event.from.toString()}`);
    console.log(`  To: ${event.to.toString()}`);
    console.log(`  Amount: ${event.amount.toString()}`);
    console.log(`  Timestamp: ${event.timestamp.toString()}`);
  }
);

// Send transaction
await program.methods
  .transfer(new anchor.BN(1000))
  .accounts({ from, to })
  .rpc();

// Remove listener when done
await program.removeEventListener(listenerId);

Using emit_cpi! Events

For CPI events, fetch the transaction and decode manually:
import * as anchor from "@anchor-lang/anchor";

const txSignature = await program.methods
  .transfer(new anchor.BN(1000))
  .accounts({ from, to })
  .rpc();

// Wait for confirmation
await program.provider.connection.confirmTransaction(
  txSignature,
  "confirmed"
);

// Fetch transaction data
const txData = await program.provider.connection.getTransaction(
  txSignature,
  { commitment: "confirmed" }
);

// Decode event from CPI instruction data
const eventIx = txData.meta.innerInstructions[0].instructions[0];
const rawData = anchor.utils.bytes.bs58.decode(eventIx.data);
const base64Data = anchor.utils.bytes.base64.encode(rawData.subarray(8));
const event = program.coder.events.decode(base64Data);

console.log("Event:", event);

Complete Example

Here’s a real-world example from a token staking program:
use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

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

    pub fn stake(
        ctx: Context<Stake>,
        amount: u64,
    ) -> Result<()> {
        let stake_account = &mut ctx.accounts.stake_account;
        let clock = Clock::get()?;
        
        stake_account.staked_amount = stake_account.staked_amount
            .checked_add(amount)
            .ok_or(ErrorCode::Overflow)?;
        stake_account.last_stake_time = clock.unix_timestamp;
        
        emit!(StakeEvent {
            user: ctx.accounts.user.key(),
            amount,
            total_staked: stake_account.staked_amount,
            timestamp: clock.unix_timestamp,
        });
        
        msg!("Staked {} tokens", amount);
        Ok(())
    }

    pub fn unstake(
        ctx: Context<Unstake>,
        amount: u64,
    ) -> Result<()> {
        let stake_account = &mut ctx.accounts.stake_account;
        let clock = Clock::get()?;
        
        require!(
            stake_account.staked_amount >= amount,
            ErrorCode::InsufficientStake
        );
        
        stake_account.staked_amount = stake_account.staked_amount
            .checked_sub(amount)
            .ok_or(ErrorCode::Underflow)?;
        
        emit!(UnstakeEvent {
            user: ctx.accounts.user.key(),
            amount,
            remaining_staked: stake_account.staked_amount,
            timestamp: clock.unix_timestamp,
        });
        
        msg!("Unstaked {} tokens", amount);
        Ok(())
    }

    pub fn claim_rewards(ctx: Context<ClaimRewards>) -> Result<()> {
        let stake_account = &ctx.accounts.stake_account;
        let clock = Clock::get()?;
        
        let time_elapsed = clock.unix_timestamp - stake_account.last_stake_time;
        let rewards = calculate_rewards(stake_account.staked_amount, time_elapsed);
        
        emit!(RewardsClaimedEvent {
            user: ctx.accounts.user.key(),
            rewards,
            timestamp: clock.unix_timestamp,
        });
        
        msg!("Claimed {} reward tokens", rewards);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Stake<'info> {
    #[account(mut)]
    pub stake_account: Account<'info, StakeAccount>,
    pub user: Signer<'info>,
}

#[derive(Accounts)]
pub struct Unstake<'info> {
    #[account(mut, has_one = user)]
    pub stake_account: Account<'info, StakeAccount>,
    pub user: Signer<'info>,
}

#[derive(Accounts)]
pub struct ClaimRewards<'info> {
    #[account(has_one = user)]
    pub stake_account: Account<'info, StakeAccount>,
    pub user: Signer<'info>,
}

#[account]
#[derive(InitSpace)]
pub struct StakeAccount {
    pub user: Pubkey,
    pub staked_amount: u64,
    pub last_stake_time: i64,
}

// Events
#[event]
pub struct StakeEvent {
    pub user: Pubkey,
    pub amount: u64,
    pub total_staked: u64,
    pub timestamp: i64,
}

#[event]
pub struct UnstakeEvent {
    pub user: Pubkey,
    pub amount: u64,
    pub remaining_staked: u64,
    pub timestamp: i64,
}

#[event]
pub struct RewardsClaimedEvent {
    pub user: Pubkey,
    pub rewards: u64,
    pub timestamp: i64,
}

#[error_code]
pub enum ErrorCode {
    #[msg("Insufficient staked tokens")]
    InsufficientStake,
    #[msg("Arithmetic overflow")]
    Overflow,
    #[msg("Arithmetic underflow")]
    Underflow,
}

fn calculate_rewards(staked_amount: u64, time_elapsed: i64) -> u64 {
    // Simple reward calculation: 10% APY
    let annual_rate = 10; // 10%
    let seconds_per_year = 365 * 24 * 60 * 60;
    
    (staked_amount as u128 * time_elapsed as u128 * annual_rate as u128 
        / (100 * seconds_per_year) as u128) as u64
}

emit! vs emit_cpi!

Featureemit!emit_cpi!
ComplexitySimpleRequires feature flag
PerformanceLow compute costHigher compute cost (CPI)
ReliabilityMay be truncatedMore reliable
SetupNo special setupNeeds #[event_cpi] attribute
SubscriptionaddEventListener()Manual decoding
Best forMost use casesHigh-value events

Best Practices

  1. Keep events small: Only include necessary data to minimize compute costs
  2. Add timestamps: Always include a timestamp for event ordering
  3. Use descriptive names: Name events clearly (e.g., TokensMinted, UserRegistered)
  4. Version events: Consider adding a version field for future compatibility
  5. Test event emission: Write tests to verify events are emitted correctly
  6. Document events: Explain when each event is emitted and what data it contains

Limitations

  • Log truncation: RPC providers may truncate program logs (use emit_cpi! for critical events)
  • No event history: Events are only available in transaction logs, not stored on-chain
  • Size limits: Very large events may hit transaction size limits
For production applications requiring reliable event indexing, consider using Geyser gRPC services from providers like Triton or Helius.

Build docs developers (and LLMs) love