multiversx-cross-contract-storage

Read another contract's storage directly without async calls using storage_mapper_from_address. Use when building aggregators, controllers, or any contract that needs to read state from other same-shard contracts without proxy call overhead.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "multiversx-cross-contract-storage" with this command: npx skills add multiversx/mx-ai-skills/multiversx-mx-ai-skills-multiversx-cross-contract-storage

MultiversX Cross-Contract Storage Reads

Note: This skill uses TokenIdentifier (ESDT-only alias). For unified EGLD+ESDT identifiers, use TokenId.

Read another contract's storage mappers directly — zero gas overhead from proxy calls, no async complexity.

What Problem Does This Solve?

When your contract needs to read state from another same-shard contract, you have two options:

  1. Proxy call — executes a view on the target, costs execution gas, requires ABI knowledge
  2. Direct storage read — reads the raw storage key, costs only a storage read, requires knowing the key name

storage_mapper_from_address gives you option 2.

When to Use

Criteriastorage_mapper_from_addressProxy Call
Same shard onlyYes (required)Works cross-shard
Read-onlyYesRead + Write
Needs computation on targetNoYes
Gas cost~storage read cost~execution + storage
Requires knowing storage keysYesNo (uses ABI)
Data freshnessCurrent block stateCurrent block state

Core Pattern

#[multiversx_sc::module]
pub trait ExternalStorageModule {
    // Read a simple value from another contract
    #[storage_mapper_from_address("total_supply")]
    fn external_total_supply(
        &self,
        contract_address: ManagedAddress,
    ) -> SingleValueMapper<BigUint, ManagedAddress>;

    // Read a value with a composite key (token-specific balance)
    #[storage_mapper_from_address("balance")]
    fn external_balance(
        &self,
        contract_address: ManagedAddress,
        token_id: &TokenIdentifier,
    ) -> SingleValueMapper<BigUint, ManagedAddress>;

    // Read a boolean flag (module-prefixed key)
    #[storage_mapper_from_address("pause_module:paused")]
    fn external_paused(
        &self,
        contract_address: ManagedAddress,
    ) -> SingleValueMapper<bool, ManagedAddress>;
}

Key Syntax Rules

  1. The string in #[storage_mapper_from_address("key")] must EXACTLY match the storage key in the target contract
  2. First parameter must be ManagedAddress — the target contract address
  3. Return type's second generic must be ManagedAddress (e.g., SingleValueMapper<BigUint, ManagedAddress>)
  4. Additional parameters after the address become part of the composite storage key (same as normal storage mappers with parameters)

Generic Examples

Example 1: Reading a Token Balance

#[storage_mapper_from_address("balance")]
fn external_token_balance(
    &self,
    contract_address: ManagedAddress,
    token_id: &TokenIdentifier,
) -> SingleValueMapper<BigUint, ManagedAddress>;

fn get_external_balance(&self, addr: &ManagedAddress, token: &TokenIdentifier) -> BigUint {
    let mapper = self.external_token_balance(addr.clone(), token);
    if mapper.is_empty() { BigUint::zero() } else { mapper.get() }
}

Example 2: Reading a Config Flag

#[storage_mapper_from_address("is_active")]
fn external_is_active(
    &self,
    contract_address: ManagedAddress,
) -> SingleValueMapper<bool, ManagedAddress>;

Example 3: Reading a Composite Key (Multi-Parameter)

// Target contract has: #[storage_mapper("rate")] fn rate(&self, asset: &TokenIdentifier, tier: u32) -> ...
#[storage_mapper_from_address("rate")]
fn external_rate(
    &self,
    contract_address: ManagedAddress,
    asset: &TokenIdentifier,
    tier: u32,
) -> SingleValueMapper<BigUint, ManagedAddress>;

How to Discover Storage Keys

  1. Read the source code: Look for #[storage_mapper("key")] in the target contract
  2. Check the ABI: The .abi.json file lists all storage keys
  3. Composite keys: Storage mappers with parameters encode the key as key + nested-encoded parameters
  4. Module keys: Some modules use prefixed keys like pause_module:paused

Security Considerations

1. Same-Shard Only

storage_mapper_from_address only works when both contracts are on the SAME shard. Cross-shard reads return empty/default values silently — no error!

// DANGER: If target_address is on a different shard, this returns 0 silently
let value = self.external_total_supply(target_address).get();

2. Stale Data Awareness

The data is as fresh as the current block. But if the target contract hasn't been called this block, its state reflects the last block it was active.

3. Storage Key Collisions

If the target contract changes its storage key names in an upgrade, your reads will break silently (return defaults).

4. No Write Access

This pattern is READ-ONLY. You cannot write to another contract's storage.

Anti-Patterns

1. Not Validating Empty Results

// WRONG — if the key doesn't exist, you get a default (0, false, empty)
let value = self.external_balance(addr, &token).get();

// CORRECT — check if the mapper has a value
let mapper = self.external_balance(addr, &token);
require!(!mapper.is_empty(), "External value not set");
let value = mapper.get();

2. Assuming Cross-Shard Works

// WRONG — no validation
let balance = self.external_balance(unknown_address, &token).get();

// CORRECT — validate shard first
let my_shard = self.blockchain().get_shard_of_address(&self.blockchain().get_sc_address());
let target_shard = self.blockchain().get_shard_of_address(&target_address);
require!(my_shard == target_shard, "Cross-shard read not supported");

Template

multiversx_sc::imports!();

#[multiversx_sc::module]
pub trait ExternalStorageModule {
    // Define one mapper per external storage key you need
    #[storage_mapper_from_address("storage_key_name")]
    fn external_value(
        &self,
        contract_address: ManagedAddress,
    ) -> SingleValueMapper<YourType, ManagedAddress>;

    // Helper to read with validation
    fn read_external_value(&self, addr: &ManagedAddress) -> YourType {
        let mapper = self.external_value(addr.clone());
        require!(!mapper.is_empty(), "External value not set");
        mapper.get()
    }
}

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

multiversx-clarification-expert

No summary provided by upstream source.

Repository SourceNeeds Review
General

multiversx-protocol-experts

No summary provided by upstream source.

Repository SourceNeeds Review
General

multiversx-smart-contracts

No summary provided by upstream source.

Repository SourceNeeds Review
General

multiversx-spec-compliance

No summary provided by upstream source.

Repository SourceNeeds Review