---
title: Concepts
description: Endpoint, PeerId, PeerAddr, PeerReady, dial versus connect, 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,
dialing, Ping, Identify, and application streams. QUIC is on by default; add
the `tcp` feature to use QUIC and TCP together, or bind only TCP.

An endpoint does not spawn a runtime. Your code makes progress by calling:

- `poll()` for one non-blocking driving step;
- `next_event(deadline)` to drive until an application event or deadline;
- a focused wait such as `wait_peer_ready` or `wait_path`.

```mermaid
flowchart TB
  A["Your caller-driven loop"] <-->|"poll / next_event · 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 bind. Use `PeerAddr` when dialing a
specific authenticated peer. See [Identity](/rust/identity) for circuit
addresses and wildcards.

## Connection readiness

`ConnectionEstablished` means the transport finished connecting and authenticated
the peer. `PeerReady` comes later, after the first Identify exchange has
supplied the peer's supported protocols and advertised addresses.

Wait for `PeerReady` before opening an application stream. This avoids racing
protocol-support discovery.

## Dial versus connect

These are separate paths with different policy:

| Goal | Start with | Wait for |
| --- | --- | --- |
| Direct QUIC or TCP to a known address | `dial`, `dial_ip4`, or `dial_ip6` | `PeerReady` |
| NAT-aware attempt with optional relay | `connect`, `connect_addr`, or `connect_with_addrs` | `wait_path` or `NatEvent` |

The base `dial*` methods only start direct dials. The address picks QUIC or
TCP. They do not reserve on a relay or perform DCUtR.

With the `nat` capability enabled, `connect*` can race direct
candidates against a relay leg. The first usable result is:

- `Path::DirectDialed`: a supplied direct address connected;
- `Path::DirectPunched`: 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.

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

## Where events go

Ordinary connection, Identify, Ping, and application stream events come from
`poll` or `next_event`. Enabled capabilities expose their own event queues:

- `take_nat_events` / `next_nat_event`;
- `take_pubsub_events` / `next_pubsub_event`;
- `take_discovery_events` / `next_discovery_event`.

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