The one behavioral change is that unknown message types now panic instead of being silently ignored. Previously, a message with an unrecognized type ID would produce a debug log and be discarded. Now:
- An unrecognized ID in the reserved range (< 2^16) panics with a link to
/errors/2 - An unrecognized custom ID (>= 2^16) with no handler configured panics with a link to
/errors/1
This only affects contracts that were already receiving messages they couldn't process, so it surfaces latent bugs rather than breaking working flows.
Existing contracts that use #[aztec] with no arguments continue to work identically. The #[aztec] macro now accepts an optional AztecConfig argument via #[varargs], but the zero-argument form is unchanged.
Configures the #[aztec] macro. Currently has one option:
#[aztec(AztecConfig::new().custom_message_handler(my_handler))]
contract MyContract { ... }The signature a custom handler must conform to:
pub type CustomMessageHandler<Env> = unconstrained fn[Env](
AztecAddress, // contract_address
u64, // msg_type_id
u64, // msg_metadata
BoundedVec<Field, MAX_MESSAGE_CONTENT_LEN>, // msg_content
MessageContext, // message_context (contains tx_hash, recipient, etc.)
);The handler must be a #[contract_library_method].
pub comptime fn custom_msg_type_id(local_id: u64) -> u64Offsets a local index by MIN_CUSTOM_MSG_TYPE_ID (2^16). Custom messages must use IDs >= this threshold; IDs below it are reserved for aztec-nr built-in types.
Custom message handlers that reassemble notes can now call this to feed notes back to PXE. Previously internal-only.
Same as above but for events. This is the primary way custom handlers deliver reassembled events to PXE.
aztec::capsules (wrapping store, load, delete oracles for per-contract non-volatile storage) is now publicly accessible. Custom handlers need this to accumulate state across multiple message parts (e.g. multi-log patterns).
Contains tx_hash and recipient, passed to custom handlers so they can enqueue notes/events with proper provenance.
All custom message processing happens inside the contract's Noir code during sync_state. From PXE's perspective, the contract still calls the same enqueue_note_for_validation / enqueue_event_for_validation / validate_and_store_enqueued_notes_and_events pipeline. No new oracles, no TS API changes, no protocol-level changes.
Events reassembled by a custom handler are indistinguishable from standard events once enqueued — wallet.getPrivateEvents() works unchanged.
| Change | Who cares | Breaking? |
|---|---|---|
#[aztec] now accepts optional AztecConfig |
Contract devs (opt-in) | No |
AztecConfig::custom_message_handler() |
Contract devs writing custom handlers | N/A (new) |
custom_msg_type_id() helper |
Contract devs defining custom msg types | N/A (new) |
CustomMessageHandler type alias |
Contract devs | N/A (new) |
enqueue_note_for_validation now pub |
Contract devs in custom handlers | No |
enqueue_event_for_validation now pub |
Contract devs in custom handlers | No |
capsules module now pub |
Contract devs in custom handlers | No |
| Unknown msg types now panic (not silently dropped) | Contracts receiving unknown msgs | Behavioral (bug-surfacing) |
MIN_CUSTOM_MSG_TYPE_ID constant |
Contract devs | N/A (new) |
Error pages /errors/1, /errors/2 |
Devs debugging panics | N/A (new) |