Accessing the Ledger

Learn how you can directly access the Stellar ledger from a Zephyr program.

Zephyr's host environment also provides functions to access the ledger maintained by the Stellar nodes that live on our server. This means that Zephyr programs have fast, first-class access to the current state of the ledger, allowing them to be aware of everything happening on-chain, not just during each ledger close.

There is currently only a limited set of data structures that can be retrieved from the Stellar ledger. All contract data and some Stellar classic entries are accessible, we are still developing the others.

Reading contract data entries

You can read contract data entries from the ledger using the following EnvClient methods:

/// Returns the instance object of a certain contract from
/// the host's ledger.
pub fn read_contract_instance(&self, contract: [u8; 32]) -> Result<Option<ContractDataEntry>, SdkError> {}

/// Returns the requested entry object of a certain contract 
/// from the host's ledger.
pub fn read_contract_entry_by_key(&self, contract: [u8; 32], key: ScVal) -> Result<Option<ContractDataEntry>, SdkError> {}

/// Returns all the entry objects of a certain contract 
/// from the host's ledger.
pub fn read_contract_entries(&self, contract: [u8; 32]) -> Result<Vec<ContractDataEntry>, SdkError> {}

/// Returns all the entry objects of a certain contract from the host's
/// ledger. This function returns an iterator over Soroban host objects,
/// and should be used along with the Soroban SDK. Use this function 
/// where you prefer to work with host objects rather than ScVals.
pub fn read_contract_entries_to_env(&self, env: &soroban_sdk::Env, contract: [u8; 32]) -> Result<Map<Val, Val>, SdkError> {}

These functions are quite straightforward and are a good tool for Zephyr ingestion programs to fetch directly from the ledger's state, but are even more important when it comes to serverless functions that grant powerful on-request access to the ledger.

Understanding return types

Being the requested data taken directly from the ledger, the returned contract data entries will be ScVals. Learn more on how to deal with returned ScVals in the examples.

Examples

read_contract_entries()

In this example taken from the Blend dashboard repository, we can see how to access the key of a specific entry (Contracts) for this factory contract, and then push the resulting addresses in a vector. The entry.key object is and ScVal, while the resulting objects inside the vector have been converted to Address environment objects.

Here PoolFactoryDataKey::Contracts is the only variable of the enum, so "address" can be defined as we did here:

let pools = {
        let mut pools = Vec::new();
        let entries = env.read_contract_entries(FACTORY_CONTRACT_ADDRESS).unwrap();
        for entry in entries {
            if let Ok(entry) = env.try_from_scval(&entry.key) {
                let PoolFactoryDataKey::Contracts(address) = entry;
                pools.push(address)
            }
        }

        pools
    };

But when the enum that holds the entry has many variants, we have to use a match arm for each variant when accessing it. Here PoolDataKey::Positions is one of the many variants of the enum:

fn get_all_entries(env: &EnvClient, contract: &str) -> Vec<ContractDataEntry> {
    env.read_contract_entries(stellar_strkey::Contract::from_string(contract).unwrap().0).unwrap()
}

fn get_from_ledger(env: &EnvClient, contract: &str) -> i64 {
    let mut total_positions = 0_i64;
    let entries = get_all_entries(env, contract);

    for entry in entries {
        let LedgerEntryData::ContractData(data) = entry.entry.data else {
            env.log().debug(format!("not contract data {:?}", entry.entry.data), None);
            panic!()};
        if let Ok(entry_key) = env.try_from_scval::<PoolDataKey>(&data.key) {
            match entry_key {
                PoolDataKey::Positions(_) => {
                    total_positions += 1
                },
                _ => ()
            }
        }
    }

    total_positions
}

See the complete code of this example here.

read_contract_instance

This example allows us to explore the structure of the contract instance we retrieve from the ledger. In this case, we are extracting (as a ScVal) the value of a specific entry in the instance by passing its key to the function str_. Remember that here we're returning a ScVal. Learn more on how to deal with ScVals in the "work with data" section.

pub fn get_from_instance(env: &EnvClient, contract: &str, str_: &str) -> ScVal {
    let instance = env.read_contract_instance(stellar_strkey::Contract::from_string(&contract).unwrap().0).unwrap().unwrap();
    let LedgerEntryData::ContractData(data) = instance.entry.data else {
        panic!()
    };
    let ScVal::ContractInstance(instance) = data.val else {panic!()};
    let val = instance.storage.as_ref().unwrap().0.iter().find(|entry| entry.key == env.to_scval(Symbol::new(&env.soroban(), str_)));

    val.unwrap().val.clone()
}

read_contract_entry_by_key()

Here we are accessing a specific value for an entry key. The returned value will technically be a ScVal, but in this case, can be used as a regular integer. You can view the complete example here.

let res: i128 = env.read_contract_entry_by_key([0;32], DataKey::Balance(source_addr)).unwrap().unwrap();

Last updated