# How to Use MemoryLookup for Peer Address Management in iroh

> Learn to use MemoryLookup for peer address management in iroh. Easily populate and inject in-memory stores for thread-safe endpoint resolution of peer connection details.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-06-21

---

**`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`](https://github.com/n0-computer/iroh/blob/main/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`.

```rust
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:

```rust
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)](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/memory.rs#L184-L198).

```rust
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:

```rust
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.

```rust
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`:

```rust
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)](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)](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.

```rust
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`](https://github.com/n0-computer/iroh/blob/main/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.