Accessing Ledger Transition: Soroban Events

Learn how you can retrieve and use Contract Events in your program.

We now know how to access the current state of the ledger with entries, let's learn how to retrieve the contract events that are emitted during the ledger transition. To do that you can use some Zephyr functions that will return an iterator over the requested events. We want these functions to be called for each new close and therefore inside the on_close() function. The method we will use to access all ledger transitions is env.reader().

env.reader().soroban_events()

This function lets you access all newly emitted events. While there is a simpler method to perform this operation, which will be explained next, we will start here to provide a clearer understanding of the events' structure (we will work now with XDR data). These functions can also be used to filter the returned events, as demonstrated below:

let events: Vec<ContractEvent> = env.reader().soroban_events().into_iter().filter(|x| {
        pools.contains(&address_from_string(&env, x.contract_id.clone()))
    }).collect();

To fully understand the code above, jump to this example, but for now, it is sufficient to know that for each event returned by the function (x), if its contract id is present in a pools vector, we're collecting it in a Vec. Note here that x.contract_id is returned as a Option<Hash>, and we need to convert it into a Soroban Address. More about the address_from_string() function in the work with data section.

Working with Events

To better understand the structure of events, how to access them, and use them in your program let's see the continuation of the example above:

for t_event in events {
        let contract_address = address_from_string(&env, t_event.contract_id);   // as before, we transform the contract id into an address
        let ContractEventBody::V0(event) = t_event.body;   // accessing the event body
        
        let action: Symbol = env.from_scval(&event.topics[0]);   // turning the first topic (ScVal) into a Soroban Symbol.
        if action == Symbol::new(env.soroban(), "supply") {
            // your_logic
        } else if {
            // other_logic
        }
}

Now you should have a clearer view of the structure of the returned events: we have the contract_id and the body with the topics and data.

Here we can also observe how we can perform some logic based on the events (in this case on the first topic). This is only an example of all that can be done in Zephyr with the retrieved data, but the framework for accessing events will be similar to this one.

env.reader().pretty().soroban_events()

There is a similar way of retrieving events with Zephyr which is more straightforward and is suggested for ease of use unless you don't want to work directly with XDR structures, as in the previous case. This method is env.reader().pretty().soroban_events(), and allows you to perform the same tasks but with less work on the user side.

for event in env.reader().pretty().soroban_events() {
        let action: Symbol = env.from_scval(&event.topics[0]);
        // easily perform comparisons with SDK types.
        if action == Symbol::new(&env.soroban(), "glyph_mint") {
                // do something
        }
}

We are performing the same task as before, but directly accessing the vent fields. See the complete example here.

env.reader().ledger_sequence()

This method returns the latest ledger number, which is the one that results from the last transition:

let sequence: u32 = env.reader().ledger_sequence();

Last updated