macro_rules! hopper_emit_cpi {
( $program_id:expr, $event_authority:expr, $bump:expr, $event:expr ) => { ... };
}Expand description
Emit a Hopper event via self-CPI for reliable indexing, the manual-wiring form.
Most programs should not call this directly: #[hopper::context(event_cpi)]
plus ctx.emit_event_cpi(&event) wires the same emission (accounts,
bump, sink) with zero hand-written plumbing. Reach for this macro
only when the context macro is out of the picture (raw handlers,
hand-rolled account handling, a custom sink).
Wraps cpi_event::encode_event_cpi and a call into the active
backend’s invoke_signed so indexers see the event as an inner
instruction in the transaction metadata. Log output is size-capped; inner
instructions are retained. Anchor’s emit_cpi! solves the same problem
with the same trick; Hopper’s lives in pure Rust so it works under
no_std and any of the three backends, and its wire format is
3 bytes of overhead (2-byte marker + 1-byte tag) against Anchor’s 16
(8-byte instruction tag + 8-byte event discriminator).
§Required program plumbing (manual form only)
The caller must declare a sentinel handler so the dispatcher routes the self-CPI somewhere, and should authenticate it rather than no-op, or forged events become possible:
#[instruction(discriminator = [0xE0, 0x1E])]
fn __hopper_event_sink(ctx: &mut Context<'_>) -> ProgramResult {
hopper_runtime::cpi_event::handle_event_sink(ctx, ctx.instruction_data())
}And a PDA account seeded with cpi_event::EVENT_AUTHORITY_SEED
(b"__hopper_event_authority") so the CPI has a signer, plus the
program’s own account in the instruction so the runtime can resolve
the self-CPI target.
§Usage
hopper_emit_cpi!(
ctx.program_id(),
event_authority: &AccountView,
event_authority_bump: u8,
Deposited { amount, depositor }
);$event must be a #[hopper::event] type (anything implementing
cpi_event::CpiEvent). Expands to: build instruction bytes,
invoke_signed with the event_authority PDA as the signer. One CPI,
bounded stack allocation, zero heap.