Prize LP-0012 · Logos Execution Zone
Structured Events for LEZ Programs
A production-ready event emission SDK for LEZ programs — events survive transaction failures, are deterministically encoded with Borsh, and are available to clients via RPC.
32Tests Passing
0Clippy Warnings
64Max Events/Tx
1 KBMax Payload
4Error Codes
How It Works
Success Path — Token Transfer
1
emit_event(TransferInitiated)
→ buffered in thread-local Vec
2
emit_event(TransferCompleted)
→ buffered (seq=1)
3
execute_program()
→ events sealed in RISC0 journal via LEZE frame
4
TxReceipt { success: true, events: [ev0, ev1] }
→ client sees 2 events
Failure Path — Insufficient Funds (Critical Feature)
1
emit_event(WithdrawAttempted)
→ buffered (seq=0)
2
emit_event(InsufficientFunds)
→ buffered (seq=1) — balance < amount
3
execute_program()
→ adapter catches panic, frames events, seals journal
4
panic!("Insufficient funds")
→ state reverted — journal already sealed
5
TxReceipt { success: false, events: [ev0, ev1] }
→ events STILL present despite failure ✓
Borsh Wire Format (EventRecord)
Annotated Binary Encoding — InsufficientFunds Event
Vec length (4B LE)
program_id (32B)
sequence (4B LE)
discriminant (8B LE)
schema_version (1B)
payload (borsh-encoded fields)
API Usage
// Emit a structured event — never panics, returns Result
pub fn emit_event<E: LezEvent>(
program_id: [u8; 32],
event: E,
) -> Result<(), EventError> { ... }
// Usage in a LEZ program:
emit_event(program_id, InsufficientFunds {
account: pre_state.account.address,
requested: amount,
available: balance,
}).expect("emit InsufficientFunds");
// Returns Err (NOT panic) when:
// → payload > 1024 bytes (0xEE01)
// → more than 64 events (0xEE02)
// → total bytes > 65536 (0xEE03)
// → Borsh serialization fail (0xEE04)
// Runtime adapter — catches panics to flush events
pub fn execute_program<F, R>(f: F) -> R { ... }
// CRITICAL PATTERN — the only way to preserve events through a panic:
execute_program(|| {
let amount = 2000;
emit_event(program_id, WithdrawAttempted { amount }).unwrap();
panic!("Transaction failed"); // Adapter catches this panic!
// Adapter drains events, creates LEZE frame, seals journal, then resumes panic.
});
// Define a custom event type:
#[derive(BorshSerialize)]
pub struct InsufficientFunds {
pub account: [u8; 32],
pub requested: u64,
pub available: u64,
}
// Register it with a stable discriminant:
impl_lez_event!(InsufficientFunds, discriminant = 0x0011);
// Optional: set schema_version for forward compatibility:
impl_lez_event!(InsufficientFundsV2, discriminant = 0x0011, schema_version = 2);
0xEE01
PayloadTooLarge
Borsh-encoded payload exceeds limit
limit: 1,024 bytes
0xEE02
TooManyEvents
Per-transaction event count exceeded
limit: 64 events
0xEE03
TotalSizeTooLarge
Cumulative payload bytes exceeded
limit: 65,536 bytes (64 × 1,024)
0xEE04
EncodingFailed
Borsh serialization error
e.g. I/O error during to_vec()
Interactive Decoder (Borsh → Human Readable)
lez-event-cli decode-raw — Browser Version
Paste a Borsh-encoded Vec<EventRecord> in hex. This decoder runs entirely in your browser — no server required.
Decoded events will appear here...
Submission Compliance
Instant-Fail Conditions (10/10 Cleared)
Events preserved when tx panicsdrain-before-panic
emit_event() returns Err, not panic0xEE01–0xEE04
RISC0_DEV_MODE=0 everywhereCI + demo.sh
Video narrated (not silent)Human task
CI green on main branch3 jobs
demo.sh works from clean envoffline mode
program_id attribution designsequencer override
cargo clippy zero warnings-D warnings
docs/event-format.md present514 lines
Reference indexer exampleexamples/indexer
Repository Checklist
lez-events/ SDK crate
lez-event-decoder/ + CLI
examples/token-transfer/
examples/withdraw/
examples/indexer/
docs/event-format.md
docs/benchmarks.md
docs/submission-writeup.md
docs/deployments.md (IDs pending)
.github/workflows/ci.yml
scripts/demo.sh + run-integration-tests.sh
README.md (all required sections)
LICENSE (MIT)
rust-toolchain.toml (1.94.0 = LEZ)