How to Use MemoryLookup for Peer Address Management in iroh

MemoryLookup provides a thread-safe, in-memory store for peer addressing information that you can populate at runtime and inject into iroh endpoints to resolve EndpointId values to relay URLs and direct transport addresses.

MemoryLookup serves as the in-memory implementation of iroh’s address-lookup abstraction, providing a mechanism to manage EndpointId-to-address mappings without external dependencies. Located in iroh/src/address_lookup/memory.rs, this struct enables applications to inject relay URLs, direct transport addresses, and user data obtained from tickets, configuration files, or discovery services directly into the connection establishment flow.

Initializing MemoryLookup and Attaching to Endpoints

Create a MemoryLookup using the new constructor, or use with_provenance to attach a custom source identifier for debugging. Attach the lookup to an endpoint through the Builder::address_lookup method before calling bind.

use iroh::{
    address_lookup::memory::MemoryLookup,
    endpoint::{presets, Builder},
};
use iroh_base::{SecretKey, TransportAddr};

let lookup = MemoryLookup::new();                     // ← empty lookup
let ep = Builder::new(presets::Minimal)               // ← pick a preset
    .address_lookup(lookup.clone())                    // ← inject the lookup
    .bind()
    .await?;                                          // ← creates the endpoint

You can customize the provenance string—useful for tracing which component supplied an address—via the with_provenance method:

let lookup = MemoryLookup::with_provenance("my_app");
let mut stream = lookup.resolve(some_endpoint_id).unwrap();
let item = stream.next().await.unwrap()?;
assert_eq!(item.provenance(), "my_app");

Adding and Updating Peer Addresses

MemoryLookup provides distinct methods for different update semantics. Use add_endpoint_info to merge new addressing data with existing entries, or set_endpoint_info to perform a complete replacement and retrieve the previous value.

Augmenting Existing Entries

The add_endpoint_info method merges new transport addresses with any existing entry while overwriting the relay URL. This corresponds to the implementation in lines 184-198 of [iroh/src/address_lookup/memory.rs](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/memory.rs#L184-L198).

use iroh_base::{EndpointAddr, TransportAddr};

let relay = TransportAddr::Relay("https://my.relay.example".parse()?);
let ep_id = SecretKey::generate().public(); // any endpoint id you own
let info = EndpointAddr::from_parts(ep_id, [relay].into_iter().collect());

lookup.add_endpoint_info(info);   // merges with existing entry if present

Adding Direct Transport Addresses

Populate the lookup with direct socket addresses for peer-to-peer connectivity:

use iroh_base::{TransportAddr, SocketAddr};

let direct = TransportAddr::Direct(SocketAddr::from(([127,0,0,1], 8080)));
let info = EndpointAddr::from_parts(ep_id, [direct].into_iter().collect());

lookup.add_endpoint_info(info);   // augments the stored entry

Replacing Entire Entries

When you need to overwrite the complete stored information for an endpoint, use set_endpoint_info. This method replaces the entire entry and returns the previous EndpointData, as implemented in lines 62-77 of the source file.

let previous_data = lookup.set_endpoint_info(ep_id, new_info).await?;

Querying and Removing Address Information

Retrieve current mappings with get_endpoint_info, which returns a cloned EndpointInfo for the given EndpointId (lines 200-206). To delete stale information, call remove_endpoint_info:

lookup.remove_endpoint_info(ep_id);   // returns the removed EndpointInfo (or None)

Resolving Addresses for Connection Establishment

MemoryLookup implements the AddressLookup trait, providing the resolve method that iroh endpoints invoke internally. The lookup stores EndpointData together with a timestamp (last_updated) in a thread-safe Arc<RwLock<BTreeMap>> according to lines 75-80 of [iroh/src/address_lookup/memory.rs](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/memory.rs#L75-L80).

When resolve is called (lines 220-237), the lookup searches for the EndpointId, builds an Item containing the EndpointInfo, the provenance string, and the update time as microseconds since the epoch, then yields it as a stream. If the lookup returns None, the endpoint falls back to other resolution services such as DNS or PKARR.

Sharing Address Data Between Endpoints

A single MemoryLookup instance can be shared between multiple endpoints to maintain a synchronized view of the network. The test helper endpoint_pair in [iroh/src/socket.rs](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs#L2337-L2659) demonstrates this pattern by having both endpoints write their addressing information to the same shared lookup.

use iroh::{address_lookup::memory::MemoryLookup, endpoint::presets, Endpoint};

let shared_lookup = MemoryLookup::new();

let ep1 = Endpoint::builder(presets::Minimal)
    .address_lookup(shared_lookup.clone())
    .bind()
    .await?;

let ep2 = Endpoint::builder(presets::Minimal)
    .address_lookup(shared_lookup.clone())
    .bind()
    .await?;

// Keep the lookup up‑to‑date with the endpoints' own address streams:
tokio::spawn(async move {
    let mut stream = ep1.watch_addr().stream().merge(ep2.watch_addr().stream());
    while let Some(info) = stream.next().await {
        shared_lookup.add_endpoint_info(info);
    }
});

Summary

  • MemoryLookup stores EndpointId-to-address mappings in an Arc<RwLock<BTreeMap>> located in iroh/src/address_lookup/memory.rs, enabling thread-safe concurrent access.
  • Initialization occurs via MemoryLookup::new() or with_provenance(), with integration through Builder::address_lookup before binding the endpoint.
  • Updates use add_endpoint_info for merging addresses or set_endpoint_info for complete replacement, while remove_endpoint_info deletes stale entries and returns the previous data.
  • Resolution happens through the AddressLookup::resolve trait implementation, yielding Item streams containing provenance metadata and timestamps for the endpoint's connection logic.
  • Sharing a single lookup instance across multiple endpoints allows dynamic, synchronized peer address management at runtime.

Frequently Asked Questions

How does MemoryLookup handle concurrent updates from multiple tasks?

MemoryLookup wraps its internal BTreeMap in an Arc<RwLock<>>, allowing multiple concurrent readers or a single writer. This design prevents blocking scenarios during high-frequency address updates while ensuring that the endpoint always receives consistent EndpointInfo snapshots during resolution.

What is the difference between add_endpoint_info and set_endpoint_info?

add_endpoint_info merges new transport addresses with existing entries and overwrites only the relay URL, preserving other stored metadata. set_endpoint_info completely replaces the entire stored entry for an EndpointId and returns the previous EndpointData, making it suitable for full synchronization scenarios where you want to replace stale information entirely.

Can I use MemoryLookup with direct peer-to-peer addresses?

Yes. MemoryLookup accepts any TransportAddr variant, including TransportAddr::Direct(SocketAddr) for direct connections and TransportAddr::Relay(url) for relay-based connectivity. You can populate the lookup with any combination of addressing types that your application discovers through out-of-band mechanisms.

How do I debug which source provided a resolved address?

Each Item produced by MemoryLookup::resolve carries a provenance string (defaulting to "memory_lookup"). When creating the lookup with MemoryLookup::with_provenance("custom_name"), this identifier attaches to every resolved item and appears in logs, allowing you to trace whether addresses came from memory, DNS, or other discovery mechanisms.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →