Skip to main content

map

Attribute Macro map 

Source
#[map]
Expand description

Marks a function as a Substreams map handler, generating the necessary WASM boilerplate.

§Panic Handling

Panics in handlers are trapped by the Substreams Engine and reported as deterministic errors back to the user. This includes panics from Result::Err returns (which the generated code converts to panics) and any explicit panic!() calls in your handler.

§Options

The macro accepts the following comma-separated options:

OptionDescription
no_testableDisables generation of the testable __impl_<name> function
keep_empty_outputPrevents calling substreams::skip_empty_output()

§Basic Usage

#[substreams::handlers::map]
fn map_transfers(blk: eth::Block) -> Result<pb::Transfers, Error> {
    // handler logic
}

§Generated Code (default)

By default, the macro generates two functions:

  1. A testable __impl_<name> function with the original signature (always compiled)
  2. A WASM export function that calls the testable function (only on wasm32 target)

For the example above, the macro generates:

// Testable function - always available, can be called directly in tests
pub fn __impl_map_transfers(blk: eth::Block) -> Result<pb::Transfers, Error> {
    // user's handler body
}

// WASM export - only compiled for wasm32 target
#[cfg(target_arch = "wasm32")]
#[no_mangle]
pub extern "C" fn map_transfers(blk_ptr: *mut u8, blk_len: usize) {
    substreams::register_panic_hook();
    let blk: eth::Block = substreams::proto::decode_ptr(blk_ptr, blk_len)
        .unwrap_or_else(|_| panic!("Unable to decode..."));
    substreams::skip_empty_output();
    let result = __impl_map_transfers(blk);
    if result.is_err() {
        panic!("{:?}", result.unwrap_err())
    }
    substreams::output(result.expect("already checked"));
}

§Testing Your Handlers

You can test your handler directly using the generated __impl_ function:

#[test]
fn test_map_transfers() {
    let blk = eth::Block::default();
    // Call the testable function directly
    let result = __impl_map_transfers(blk);
    assert!(result.is_ok());

    // Or use the test_map! macro for convenience
    let result = substreams::test_map!(map_transfers(blk));
    assert!(result.is_ok());
}

§Disabling Testable Generation

Use no_testable to generate only the WASM export (legacy behavior):

#[substreams::handlers::map(no_testable)]
fn map_transfers(blk: eth::Block) -> Result<pb::Transfers, Error> {
    // handler logic
}

This generates a single function without the __impl_ wrapper:

#[no_mangle]
pub extern "C" fn map_transfers(blk_ptr: *mut u8, blk_len: usize) {
    substreams::register_panic_hook();
    let func = || -> Result<pb::Transfers, Error> {
        let blk: eth::Block = substreams::proto::decode_ptr(blk_ptr, blk_len)
            .unwrap_or_else(|_| panic!("Unable to decode..."));
        // user's handler body
    };
    substreams::skip_empty_output();
    let result = func();
    if result.is_err() {
        panic!("{:?}", result.unwrap_err())
    }
    substreams::output(result.expect("already checked"));
}

§Combining Options

Options can be combined with commas:

#[substreams::handlers::map(no_testable, keep_empty_output)]
fn map_transfers(blk: eth::Block) -> Result<pb::Transfers, Error> {
    // handler logic
}

§Supported Return Types

  • Result<T, Error> - Panics on error, outputs T on success
  • Result<Option<T>, Error> - Panics on error, outputs T if Some, nothing if None
  • Option<T> - Outputs T if Some, nothing if None
  • T - Outputs T directly