# How to Dial Another Endpoint by Its Public Key in Iroh

> Learn how to dial another Iroh endpoint using its public key. Construct an EndpointAddr and connect directly, establishing a secure P2P connection. Get started now.

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

---

**To dial another Iroh endpoint by its public key, construct an `EndpointAddr` containing the remote node's `EndpointId` (public key) and optional `RelayUrl`, then call `endpoint.connect(addr).await` to initiate the cryptographic handshake and connection.**

Iroh provides a peer-to-peer networking stack where every node is cryptographically identified by its public key. In the `n0-computer/iroh` codebase, this identifier is called the **EndpointId**, and dialing another endpoint by its public key requires packaging this ID into an `EndpointAddr` before invoking the connection logic. This guide walks through the source code implementation to show exactly how to open connections using public key identification.

## Understanding Iroh Endpoint Identification

Every node in an Iroh network is identified by an **EndpointId**, which represents the cryptographic public key derived from the node's long-term secret key. This design eliminates the need for centralized address registries—knowing a node's public key is sufficient to initiate a connection.

To actually dial a remote node, you combine the `EndpointId` with an optional **RelayUrl** to create an `EndpointAddr`. According to [`iroh-base/src/endpoint_addr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/endpoint_addr.rs), this struct bundles the identity and reachability information into a single address type that the networking stack can resolve.

## Prerequisites: Binding a Local Endpoint

Before dialing remote nodes, your application must bind a local `Endpoint`. This initializes the QUIC sockets, timers, and cryptographic credentials needed to participate in the network.

```rust
use iroh::Endpoint;

// Bind a local endpoint using default configuration
let local_endpoint = Endpoint::bind(Default::default()).await?;

```

The `Endpoint::bind` method in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) returns an `Endpoint` instance that serves as your main entry point for both accepting incoming connections and initiating outbound dials.

## Constructing the Remote Address

To dial another endpoint by its public key, you must construct an `EndpointAddr` that encapsulates the target's identity and optional relay information.

### Parsing the Public Key (EndpointId)

The `EndpointId` type in [`iroh-base/src/key.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/key.rs) provides methods to parse public keys from various encodings. The most common format is base32 (z32):

```rust
use iroh_base::EndpointId;

// Parse a base32-encoded public key (e.g., from a QR code or configuration file)
let remote_id = EndpointId::from_z32("v4pdu5k2j...")?;

```

This `remote_id` represents the cryptographic identity of the peer you want to reach.

### Adding a Relay URL (Optional)

While direct connections are possible, providing a **RelayUrl** significantly improves connectivity when peers are behind NATs. The relay server helps coordinate the initial connection before potentially upgrading to a direct path.

```rust
use iroh_base::RelayUrl;
use iroh::EndpointAddr;

// Create a relay URL (WebSocket secure is recommended)
let relay = RelayUrl::try_from("wss://relay.example.com")?;

// Build the address with both public key and relay
let remote_addr = EndpointAddr::new(remote_id).with_relay(relay);

```

If you omit the relay, the library will attempt direct hole-punching using IPv6/IPv4 as implemented in [`iroh-relay/src/client/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/client/tls.rs).

## The Connection Process

When you call `endpoint.connect(remote_addr).await`, the Iroh runtime executes a multi-stage dialing process defined across several core files.

### Address Resolution and Relay Discovery

If your `EndpointAddr` contains a `RelayUrl`, the `Endpoint` contacts the relay server to obtain a list of reachable socket addresses for the remote node. The `ActiveRelayActor` in [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs) handles this relay dialing logic with exponential backoff for resilience.

### Transport Establishment and NAT Traversal

For direct connectivity, the library employs the `dial_happy_eyeballs` helper function found in [`iroh-relay/src/client/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/client/tls.rs). This implementation attempts IPv6 connections first, then falls back to IPv4, implementing the "happy eyeballs" algorithm to minimize connection latency while traversing NATs.

### Cryptographic Handshake

Once the transport layer is established, [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) manages the cryptographic handshake. This process authenticates both peers using their static keys (the `EndpointId` values), ensuring you are connected to the correct remote node and establishing encrypted channels for data transfer.

## Complete Example: Dialing by Public Key

Here is a complete, runnable example demonstrating how to dial another Iroh endpoint using only its public key:

```rust
use iroh::Endpoint;
use iroh_base::{EndpointId, RelayUrl};
use iroh::EndpointAddr;

async fn connect_by_public_key() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Bind local endpoint
    let local = Endpoint::bind(Default::default()).await?;
    
    // 2. Parse the remote public key (EndpointId)
    let remote_id = EndpointId::from_z32("v4pdu5k2j...")?;
    
    // 3. Optional: Add a relay for NAT traversal
    let relay = RelayUrl::try_from("wss://relay.example.com")?;
    let remote_addr = EndpointAddr::new(remote_id).with_relay(relay);
    
    // 4. Connect - this performs relay lookup, NAT traversal, and handshake
    let conn = local.connect(remote_addr).await?;
    
    // 5. Use the connection
    println!("Connected to remote endpoint: {}", conn.remote_id());
    
    Ok(())
}

```

## Summary

- **Every Iroh node** is identified by an `EndpointId`, which is the cryptographic public key of the node's long-term secret key.
- **Dialing requires** constructing an `EndpointAddr` containing the target's `EndpointId` and optionally a `RelayUrl` for NAT traversal.
- **The `Endpoint::connect` method** in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) orchestrates address resolution, relay discovery, transport establishment, and cryptographic handshake.
- **Source files** including [`iroh-base/src/endpoint_addr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/endpoint_addr.rs), [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs), and [`iroh-relay/src/client/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/client/tls.rs) implement the underlying dialing logic.
- **Direct connections** are possible without relays when both peers are on the same LAN or have public IPv6 addresses, though relays improve reliability.

## Frequently Asked Questions

### What is an EndpointId in Iroh?

An `EndpointId` is the cryptographic public key that uniquely identifies an Iroh node. Defined in [`iroh-base/src/key.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/key.rs), this 32-byte value is derived from the node's long-term secret key and serves as the primary addressing mechanism for the peer-to-peer network. When you dial another endpoint by its public key, you are essentially specifying its `EndpointId`.

### Do I need a relay URL to dial another Iroh node?

No, a relay URL is optional but highly recommended. If you provide only the `EndpointId` without a `RelayUrl`, the library attempts direct hole-punching using IPv6 and IPv4 as implemented in [`iroh-relay/src/client/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/client/tls.rs). However, without a relay, connections may fail if both peers are behind restrictive NATs or firewalls that prevent direct UDP communication.

### How does Iroh handle NAT traversal when dialing?

Iroh implements several NAT traversal strategies. When a `RelayUrl` is provided, the `ActiveRelayActor` in [`iroh/src/socket/transports/relay/actor.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs) coordinates with the relay server to facilitate the initial connection. For direct paths, the `dial_happy_eyeballs` function attempts IPv6 first, then IPv4, using QUIC and WebSocket transports to maximize connectivity chances.

### What happens if the remote node is offline?

If the remote node is unreachable, the `endpoint.connect` call will return an error after exhausting retry attempts. The connection logic includes exponential backoff when attempting relay connections, and will eventually timeout if the peer cannot be contacted through either direct paths or relay infrastructure. Your application should handle these errors appropriately, as the connection attempt is asynchronous and may take several seconds to fail completely.