---
title: Concepts
description: Endpoint, PeerId, PeerAddr, PeerReady, Connection attempts, and where events go.
---

An `Endpoint` is the main application API. Five ideas explain most code built on it.

## Endpoint

`Endpoint` owns an Ed25519 identity, the transports you bound, and the connection state. It is the app-facing entrypoint for listening, connecting, Ping, Identify, and application streams. QUIC is on by default; add the `tcp` feature to use QUIC and TCP together, or listen only on TCP.

An endpoint does not spawn a runtime. `Endpoint::wait` is its one blocking wait over the ordered Endpoint event stream and returns an event, the deadline, or an interruption; it also delivers NAT, pubsub, discovery, and relay-server events. `poll()` is the non-blocking step for an existing reactor. Both use transport readiness when supported. Each call drives only its own endpoint, so blocking on one endpoint can delay others sharing the same thread.

```mermaid
flowchart TB
  A["Your caller-driven loop"] <-->|"connect / wait / poll · events"| B["Endpoint"]
  B <-->|"QUIC over UDP · Noise + Yamux over TCP"| C["Network"]
```

> **Note**
>
> Protocol and orchestration logic underneath `Endpoint` is Sans-I/O. The library supplies sockets and clocks so application code can stay small, while custom runtimes can use the deterministic lower layers directly.

## PeerId

A `PeerId` identifies a node's authenticated public key. QUIC proves it with mutual libp2p TLS; TCP uses Noise XX, then Yamux. Either way, connection events carry the verified remote identity, not a label you passed in.

`Endpoint::peer_id()` returns the local identity.

## PeerAddr and Multiaddr

A `Multiaddr` is a sequence of transport components:

```text
/ip4/127.0.0.1/udp/4001/quic-v1
```

A `PeerAddr` pairs that transport with a terminal peer identity:

```text
/ip4/127.0.0.1/udp/4001/quic-v1/p2p/12D3KooW…
```

Use `Multiaddr` when choosing where to listen. Use `PeerAddr` when connecting to a specific authenticated peer. See [Identity](/docs/rust/identity) for circuit addresses and wildcards.

## Connection readiness

`ConnectionEstablished` means the peer went from disconnected to connected: the transport finished connecting and authenticated it. `PeerReady` comes later, after the connection's first Identify exchange has supplied the peer's supported protocols and advertised addresses.

An endpoint keeps one connection per peer. When a newer connection takes over (a punched direct path replacing a relay circuit, or a late duplicate dial), the endpoint reports one `ConnectionReplaced { peer_id, old, new }` instead of a close and a second establishment; the peer stays connected throughout. Streams on `old` end with it without their own close events. Readiness belongs to a connection, so `PeerReady { conn_id, .. }` fires again for `new`; a `PeerReady` whose `conn_id` is not the peer's current connection is stale. `ConnectionClosed { reason, .. }` means the peer's last connection closed; its `ConnectionCloseReason` says why: `Local`, `Remote { code }` (the peer's QUIC close or Yamux GoAway code), `TimedOut`, `Stalled` (the peer stopped reading), or `Error(message)`. A failure that ended the connection is that reason, not a separate `EndpointEvent::Error`.

`open_stream` is allowed once the peer is connected. The remote-protocol advertisement check applies only after `PeerReady`; before that, negotiation proceeds without an early `RemoteDoesNotSupport` reject. Waiting for Identify is application policy when you want the advertised protocol list first, not a stack requirement for opening a known protocol.

## Connection attempts

`connect` starts one Connection attempt and returns its `ConnectId` immediately. The target is a `PeerId`, one complete `PeerAddr`, or a non-empty list of complete addresses naming the same peer. The attempt may run several Transport dials at once (one per candidate and IP family) and, with NAT traversal, a relay leg, but it ends with exactly one `EndpointEvent::ConnectSettled` carrying that ID: `Connected`, `Failed`, or `Cancelled`. `cancel_connect` is the only way to stop it; abandoning a wait never cancels.

Each address picks its transport (`/udp/…/quic-v1` or `/tcp`) and IP family (`/ip4`, `/ip6`, or `/dns*` resolution). Without NAT configuration, `connect` dials only direct candidates. With the `relay` feature enabled and a relay configured, it can race direct candidates against a relay leg. Every established connection has a `Path`:

- `Path::Direct`: a direct connection we dialed or accepted;
- `Path::Punched`: DCUtR established a direct connection;
- `Path::Relayed { relay }`: an end-to-end protected circuit is usable.

A relayed path can later emit `NatEvent::PathUpgraded` when hole punching lands. The upgrade is a new connection with its own path; nothing carries over from the circuit.

> **Warning**
>
> A Cargo feature makes a capability available. Enable it on `EndpointBuilder`; see [Install](/docs/rust/install#add-an-optional-capability).

## Where events go

Every event leaves through the Endpoint event stream, read with `Endpoint::wait` or `poll`. One loop sees every event exactly once. Applications wait for a particular outcome by matching its ID (a `ConnectId`, `ConnectionId`, or `StreamId`) in that loop while dispatching everything else; see [Drive events](/docs/rust/drive-events). Connection, Identify, Ping, and application stream events arrive as ordinary `EndpointEvent` variants. Enabled capabilities add variants to the same `#[non_exhaustive]` enum:

- `EndpointEvent::Nat(NatEvent)` (`autonat`): path transitions, reachability, public addresses, and relay reservations. The attempt outcome is `ConnectSettled`, never a `NatEvent`;
- `EndpointEvent::Gossipsub(GossipsubEvent)` (`pubsub`);
- `EndpointEvent::Discovery(DiscoveryEvent)` (`discovery` / `mdns`);
- `EndpointEvent::RelayServer(RelayServerEvent)` (`relay-server`);
- `EndpointEvent::MdnsFailed { error }` and `EndpointEvent::MdnsRecovered` (`mdns`): an mDNS socket failure and its recovery. mDNS keeps retrying meanwhile, and nothing else on the endpoint stops.

Durable state is available through State snapshot getters (`connected_peers`, `is_peer_ready`, `peer_info`, `connection_id`, `connection_remote_addr`, `connection_info`, `path`, `bound_addresses`, and with capabilities `reachability`, `active_reservation`, `known_peers`). `connection_info(conn_id)` returns an established connection's peer, remote address, `Path`, and Identify protocols (`ready_protocols` is `None` until it is identified); it is `None` for a pending, replaced, or closed connection. `bound_addresses` reports transport-bound local addresses; `bind` already accepts on each. Those getters do not drive the endpoint, are not one cross-getter atomic snapshot, and may be ahead of the event stream, never behind their own emitted transition.

Continue with [Listen and connect](/docs/rust/listen-and-dial) or the [glossary](/docs/reference/glossary).
