# How to Use Memory Address Lookup in iroh

> Learn how to use memory address lookup in iroh to manually store and resolve EndpointId to EndpointAddr mappings. Attach MemoryLookup to your Endpoint builder for runtime control.

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

---

**Use the `MemoryLookup` struct from [`iroh/src/address_lookup/memory.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/memory.rs) to manually store and resolve `EndpointId` to `EndpointAddr` mappings at runtime, attaching it to your `Endpoint` builder via the `address_lookup` method.**

When automatic discovery via DNS or pkarr is unavailable, iroh provides a **memory address lookup** mechanism for out-of-band address resolution. The `MemoryLookup` implementation allows you to programmatically register relay URLs and direct transport addresses, making it ideal for applications that receive addressing data through private channels like `EndpointTicket` exchanges.

## What Is Memory Address Lookup in iroh?

The `AddressLookup` trait in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs) defines how iroh resolves an `EndpointId` into a network-specific `EndpointAddr`. `MemoryLookup` provides a concrete, in-memory implementation of this trait, storing address data in a thread-safe `BTreeMap` wrapped in `Arc<RwLock<...>>`. This design allows multiple threads to query and update address information concurrently without blocking the main endpoint loop.

Each lookup entry carries a provenance string—defaulting to `"memory_lookup"`—that appears in resolution results, which helps distinguish manually injected addresses from those discovered via other mechanisms.

## Creating and Configuring MemoryLookup

You can instantiate `MemoryLookup` using several constructors depending on your initialization requirements.

### Basic Initialization

Create an empty registry or customize the provenance metadata for debugging purposes:

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

// Empty lookup with default provenance
let lookup = MemoryLookup::new();

// Or specify a custom provenance string
let lookup = MemoryLookup::with_provenance("my_app_config");

```

### Bulk Loading from EndpointInfo

When bootstrapping from a stored list of known peers, use `MemoryLookup::from_endpoint_info()` to populate the registry in a single call:

```rust
use iroh::address_lookup::memory::MemoryLookup;
use iroh_base::{EndpointInfo, SecretKey};

let infos = vec![
    EndpointInfo::new(SecretKey::generate().public())
        .with_relay("https://relay1.example.com".parse().unwrap()),
    EndpointInfo::new(SecretKey::generate().public())
        .with_relay("https://relay2.example.com".parse().unwrap()),
];

let memory_lookup = MemoryLookup::from_endpoint_info(infos);

```

## Managing Endpoint Data at Runtime

`MemoryLookup` provides distinct methods for updating stored addressing information depending on whether you want to merge or replace existing data.

### Adding New Endpoint Information

The `add_endpoint_info()` method merges new address data with any existing entry for the same `EndpointId`, accumulating multiple transport addresses:

```rust
use iroh_base::{EndpointAddr, SecretKey, TransportAddr};

let id = SecretKey::generate().public();
let addr = EndpointAddr {
    id,
    addrs: [TransportAddr::Relay("https://relay.example.com".parse().unwrap())]
        .into_iter()
        .collect(),
};

memory_lookup.add_endpoint_info(addr);

```

### Replacing Existing Records

Use `set_endpoint_info()` to completely overwrite an entry, which is critical when handling credential rotations or updated relay URLs:

```rust
// Overwrites any previous entry for this EndpointId
memory_lookup.set_endpoint_info(new_endpoint_info);

```

### Thread Safety Considerations

Because `MemoryLookup` uses an internal `Arc<RwLock<BTreeMap<EndpointId, StoredEndpointInfo>>>`, you can safely clone the lookup handle and share it between your endpoint initialization code and the runtime components that receive address updates via network messages or file watchers.

## Attaching Memory Lookup to Your Endpoint

Register your `MemoryLookup` instance with the `Endpoint` builder before binding. When `Endpoint::connect(id)` is called, iroh queries the lookup; if the `EndpointId` exists, the lookup yields a `Stream` of `Item` structs containing the stored `EndpointData` and provenance.

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

// Clone the lookup so you can update it later
let address_lookup = MemoryLookup::new();

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

// Later, inject out-of-band address data
let id = SecretKey::generate().public();
address_lookup.add_endpoint_info(
    iroh_base::EndpointAddr {
        id,
        addrs: [TransportAddr::Relay("https://relay.example.com".parse()?)]
            .into_iter()
            .collect(),
    },
);

// Connect using only the EndpointId—the lookup supplies the address
let conn = ep.connect(id).await?;

```

## Summary

- **`MemoryLookup`** in [`iroh/src/address_lookup/memory.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/memory.rs) implements the `AddressLookup` trait for manual address resolution.
- **Thread-safe updates** are enabled via `Arc<RwLock<BTreeMap<...>>>` internals, allowing concurrent access across your application.
- **Flexible construction** via `new()`, `with_provenance()`, or `from_endpoint_info()` supports both incremental and bulk initialization.
- **Additive or replacement updates** use `add_endpoint_info()` (merge) or `set_endpoint_info()` (overwrite) depending on your consistency requirements.
- **Integration** requires passing the lookup to `Endpoint::builder(presets::N0).address_lookup(...)` before binding.

## Frequently Asked Questions

### How does memory address lookup differ from DNS-based discovery in iroh?

DNS and pkarr provide automatic public discovery, whereas `MemoryLookup` is a private, application-controlled registry. According to the iroh source code in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs), the `AddressLookup` trait abstracts over bothautomatic and manual sources; `MemoryLookup` specifically handles cases where you receive addressing data through out-of-band channels like `EndpointTicket` exchanges or local configuration files.

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

Yes. The implementation in [`iroh/src/address_lookup/memory.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup/memory.rs) wraps the internal `BTreeMap` in `Arc<RwLock<...>>`, allowing you to clone the `MemoryLookup` handle and share it across threads. One thread can update addresses via `add_endpoint_info()` while another calls `Endpoint::connect()`, with both operations executing safely without data races.

### What happens if an EndpointId is not found in the MemoryLookup?

If the `EndpointId` is not present in the lookup’s internal map, the `MemoryLookup` returns an empty stream of `Item`s. As implemented in the iroh connection logic, the endpoint will then fall back to other registered address lookup implementations or eventually fail to establish a connection if no addresses can be resolved.

### Can I use MemoryLookup alongside other address lookup implementations?

Yes. While the `Endpoint` builder accepts a single `AddressLookup` implementation, iroh supports composition through wrapper types that implement the trait. You can chain `MemoryLookup` with other lookups by creating a custom implementation that queries your in-memory registry first, then delegates to secondary sources if the initial lookup returns no results.