# How to Use MemoryLookup in Iroh for In-Memory Peer Addressing

> Learn to use MemoryLookup in Iroh for in-memory peer addressing. This guide explains how to add and resolve peer addresses manually with Iroh's thread-safe solution using the AddressLookup trait.

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

---

**MemoryLookup in Iroh provides a thread-safe, in-memory store for endpoint addressing data, enabling applications to manually insert peer addresses via `add_endpoint_info()` and resolve them during connection attempts through the `AddressLookup` trait.**

MemoryLookup is the in-memory implementation of Iroh's address resolution system. According to the n0-computer/iroh source code, this struct maintains a shared `BTreeMap` wrapped in `Arc<RwLock<>>` that maps `EndpointId` values to `StoredEndpointInfo`, allowing you to programmatically manage addressing data received out-of-band—such as from `EndpointTicket` objects, configuration files, or custom discovery protocols.

## What Is MemoryLookup in Iroh?

The `MemoryLookup` struct serves as the **in-memory address-lookup implementation** used by Iroh `Endpoint` instances to discover how to reach remote peers. Unlike DNS-based or DHT-based discovery, this store exists only in process memory and is populated explicitly by your application.

In `iroh/src/address_lookup/memory.rs:75-78`, the struct is defined as:

- A `BTreeMap<EndpointId, StoredEndpointInfo>` containing the actual addressing data
- Wrapped in `Arc<RwLock<>>` for thread-safe concurrent access across multiple tasks

The `StoredEndpointInfo` struct (lines 89-94) holds the `EndpointData`—including direct addresses and relay URLs—alongside a `last_updated` timestamp. When the `Endpoint` queries the lookup, this timestamp is emitted as a microsecond tag in the returned `Item`, helping your application track address freshness.

## Creating and Configuring a MemoryLookup

### Basic Initialization

Create a new lookup using the default provenance string `"memory_lookup"`:

```rust
use iroh::address_lookup::memory::MemoryLookup;

let address_lookup = MemoryLookup::new();

```

As implemented in `iroh/src/address_lookup/memory.rs:104-107`, this constructor initializes the internal `BTreeMap` and sets the provenance identifier used for debugging address origins.

### Custom Provenance for Debugging

To trace which component provided an address, use `with_provenance()`:

```rust
let lookup = MemoryLookup::with_provenance("my_app_config");

```

This method (lines 117-122) sets a custom string that appears in every `Item` returned by `resolve()`, allowing you to distinguish between addresses loaded from different sources during connection diagnostics.

## Managing Endpoint Address Data

### Adding and Updating Addresses

**MemoryLookup** provides two methods for inserting data, depending on whether you want to merge or overwrite:

**`add_endpoint_info`** merges new addressing data with existing entries. It adds new direct addresses to the set while overwriting the relay URL. Use this when receiving incremental updates from a discovery protocol:

```rust
use iroh::{EndpointAddr, TransportAddr};

address_lookup.add_endpoint_info(EndpointAddr {
    id: remote_id,
    addrs: [TransportAddr::Relay("https://relay.example.com".parse()?)]
        .into_iter()
        .collect(),
});

```

This corresponds to the implementation in `iroh/src/address_lookup/memory.rs:84-99`.

**`set_endpoint_info`** replaces the entire entry for an endpoint, returning the previous `StoredEndpointInfo` if present. Use this when you have complete, authoritative addressing data:

```rust
use iroh::{EndpointInfo, TransportAddr};

let new_info = EndpointInfo::from_parts(
    remote_id,
    [TransportAddr::Direct("127.0.0.1:4000".parse()?)]
        .into_iter()
        .collect(),
);
let previous = address_lookup.set_endpoint_info(new_info);

```

See lines 68-77 for the implementation details.

### Retrieving and Removing Entries

Query the current state without modifying it using `get_endpoint_info()`:

```rust
if let Some(info) = address_lookup.get_endpoint_info(remote_id) {
    println!("Addresses: {:?}", info.addrs());
}

```

To delete an entry and return the removed data, use `remove_endpoint_info()`:

```rust
let removed = address_lookup.remove_endpoint_info(remote_id);

```

These operations are found at lines 101-106 and 108-115 respectively.

## Integrating MemoryLookup with an Endpoint

The primary integration point occurs when constructing your `Endpoint`. Pass the lookup to the builder via `address_lookup()`:

```rust
use iroh::{Endpoint, endpoint::presets};

let ep = Endpoint::builder(presets::N0)
    .address_lookup(address_lookup.clone())
    .bind()
    .await?;

```

When the `Endpoint` attempts to connect to a peer, it calls `resolve()` on the lookup. As implemented in `iroh/src/address_lookup/memory.rs:218-240`, this method:

1. Acquires a read lock on the internal map
2. Constructs an `Item` containing the `EndpointInfo`, the provenance string, and the `last_updated` timestamp
3. Returns it as a one-item async stream

The `publish()` method is a no-op because data persists only in memory and is never broadcast.

## Loading Data from Existing Sources

Populate a lookup from a vector of `EndpointInfo` objects—such as those deserialized from tickets or configuration files—using `from_endpoint_info()`:

```rust
let infos: Vec<EndpointInfo> = load_from_config();
let lookup = MemoryLookup::from_endpoint_info(infos);

```

This constructor (lines 154-160) efficiently inserts all entries into a new lookup instance, preserving the `last_updated` timestamps from the source data.

## Summary

- **MemoryLookup** maintains a thread-safe `BTreeMap<EndpointId, StoredEndpointInfo>` in [`iroh/src/address_lookup/memory.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/memory.rs) for runtime address management.
- Use **`add_endpoint_info()`** to merge updates and **`set_endpoint_info()`** to replace entire entries when managing addressing data.
- Integrate with **`Endpoint::builder().address_lookup()`** to enable the endpoint to resolve peers using your in-memory store.
- The **`resolve()`** method returns a single-item stream containing the `EndpointInfo`, provenance tag, and timestamp for consumption by the QUIC connection logic.
- All operations are **thread-safe** via `Arc<RwLock<>>` and operate entirely in memory without network side effects.

## Frequently Asked Questions

### How does MemoryLookup differ from other address lookup implementations in Iroh?

**MemoryLookup** stores data only in process memory and requires manual population via `add_endpoint_info()`, whereas other implementations might query DNS, a DHT, or a central discovery service automatically. The `publish()` method is intentionally a no-op because the lookup does not propagate data to the network—it merely serves as a local cache for out-of-band addressing information.

### Is MemoryLookup thread-safe for concurrent access?

Yes. The struct wraps its internal `BTreeMap` in `Arc<RwLock<>>` as shown in `iroh/src/address_lookup/memory.rs:75-78`. This allows multiple `Endpoint` instances or async tasks to share the same lookup via `clone()`, with read operations occurring concurrently and write operations serialized through the write lock.

### What happens when I call resolve() on a MemoryLookup?

The `resolve()` method—implemented in `iroh/src/address_lookup/memory.rs:218-240`—looks up the `EndpointId` in the internal map and returns a one-item async stream. If the ID exists, the stream yields an `Item` containing the `EndpointInfo`, the provenance string (e.g., `"memory_lookup"`), and a microsecond timestamp indicating when the data was last updated. If the ID is missing, the stream resolves to empty.

### Should I use add_endpoint_info or set_endpoint_info for updating addresses?

Use **`add_endpoint_info`** when receiving incremental updates from discovery protocols, as it merges new direct addresses with existing ones while overwriting only the relay URL. Use **`set_endpoint_info`** when you have complete, authoritative addressing data and want to replace any existing entry entirely, which returns the previous value for comparison or logging purposes.