---
name: dart-libp2p-host
description: Use when creating or configuring a dart_libp2p host in Dart (package:dart_libp2p, Libp2p.new_, Config): identity keys, UDX/TCP transports, Noise, Yamux, listen addresses, connect, peerstore, identify, ping, event bus, resource limits, shutdown, tests and go-libp2p interop.
triggers: dart_libp2p, Libp2p.new_, BasicHost, UDXTransport, TCPTransport, NoiseSecurity, YamuxSession, MultiplexerConfig, ConnectionManager, generateEd25519KeyPair, AddrInfo, PingService, EvtPeerConnectednessChanged, ConfigurableLimiter, libp2p host
---

# dart_libp2p host

## What it is

`dart_libp2p` is a Dart port of the libp2p stack: a `Host` with a peer ID,
transports (UDX over UDP, TCP), Noise security, Yamux multiplexing, Identify,
Ping, a peerstore, an event bus and a resource manager. It interoperates with
go-libp2p over TCP (and over UDX with peers that carry go-udx). This skill
covers building, running and closing a host.

Use another skill for: your own wire protocol on top of the host
(`dart-libp2p-custom-protocol`), relays, NAT, hole punching and mDNS
(`dart-libp2p-nat-relay`), GossipSub (`dart-libp2p-gossipsub`), the DHT
(`dart-libp2p-kad-dht`), WebRTC or BLE transports (`dart-libp2p-webrtc`,
`dart-libp2p-ble`).

## Install

```yaml
dependencies:
  dart_libp2p: ^4.12.0
  # Only if you import package:dart_udx yourself (e.g. to share a UDX instance):
  # dart_udx: ^4.3.0
```

Dart SDK `^3.5.0`. Imports used below:

```dart
import 'package:dart_libp2p/dart_libp2p.dart' hide Option; // Host, MultiAddr, AddrInfo, PeerId, Context, keys, events
import 'package:dart_libp2p/config/config.dart';            // Libp2p, Config, Option
import 'package:dart_libp2p/p2p/transport/udx_transport.dart';
import 'package:dart_libp2p/p2p/transport/tcp_transport.dart';
import 'package:dart_libp2p/p2p/transport/connection_manager.dart';
import 'package:dart_libp2p/p2p/security/noise/noise_protocol.dart'; // NoiseSecurity (optional: default)
import 'package:dart_libp2p/p2p/protocol/ping/ping.dart';             // PingService
import 'package:dart_libp2p/p2p/host/basic/basic_host.dart';          // BasicHost extras
```

The barrel exports a routing `Option` typedef that clashes with the config
`Option`; hide it (as above) or import `config.dart` with a prefix.

## Core workflow

### Minimal host

```dart
Future<Host> createHost({required KeyPair identity, int port = 0}) async {
  final connManager = ConnectionManager();            // share it with the transports
  final host = await Libp2p.new_([
    Libp2p.identity(identity),
    Libp2p.connManager(connManager),
    Libp2p.transport(UDXTransport(connManager: connManager)),
    Libp2p.transport(TCPTransport(connManager: connManager)), // no resourceManager: uses the host's
    Libp2p.listenAddrs([
      MultiAddr('/ip4/0.0.0.0/udp/$port/udx'),
      MultiAddr('/ip4/0.0.0.0/tcp/$port'),
    ]),
    Libp2p.userAgent('my-app/1.0.0'),
    Libp2p.identifyUserAgent('my-app/1.0.0'),
    // Noise and Yamux (/yamux/1.0.0) are added by default.
  ]);
  await host.start();   // Libp2p.new_ does NOT listen; start() does.
  return host;
}

void main() async {
  final a = await createHost(identity: await generateEd25519KeyPair());
  final b = await createHost(identity: await generateEd25519KeyPair());

  // Dial b by its real listen addresses (see Gotchas: host.addrs drops loopback).
  await a.connect(AddrInfo(b.id, b.network.listenAddresses));

  final rtt = await PingService(a).ping(b.id).first;
  print('rtt ${rtt.rtt} error ${rtt.error}');

  await a.close();
  await b.close();   // the process exits once every host is closed
}
```

### Persist the identity

The peer ID comes from the key. A node that others dial by `/p2p/<id>` (a relay,
a bootstrap node) must keep its key.

```dart
// Save: protobuf bytes, the format go-libp2p's crypto.MarshalPrivateKey writes.
final bytes = keyPair.privateKey.marshal();
// Load:
final priv = await Ed25519PrivateKey.unmarshal(bytes);
final loaded = KeyPair(priv.publicKey, priv);

// Or keep the 32-byte seed (what the internet_chat demo does):
final seed = keyPair.privateKey.raw;                  // Ed25519 seed
final again = await generateEd25519KeyPairFromSeed(seed);
```

Store the file with mode 600. Other key types: `generateRsaKeyPair()`,
`generateEcdsaKeyPair()` (P-256); Ed25519 is the default and the cheapest.

### Addresses to hand out

```dart
// What other peers should dial (filtered by the AddrsFactory, see Gotchas):
for (final a in host.addrs) print('$a/p2p/${host.id.toBase58()}');

// Parse one back:
final info = AddrInfo.fromMultiaddr(MultiAddr('/ip4/203.0.113.7/udp/4001/udx/p2p/12D3KooW...'));
await host.connect(info);
```

A server whose interface address is private (cloud VM) must announce its
public address itself:

```dart
Libp2p.addrsFactory((addrs) => [
  ...addrs.where((a) => !a.isLoopback() &&
      a.valueForProtocol('ip4') != '0.0.0.0' && a.valueForProtocol('ip6') != '::'),
  MultiAddr('/ip4/203.0.113.7/udp/4001/udx'),
  MultiAddr('/ip4/203.0.113.7/tcp/4001'),
]),
```

### Peerstore and Identify

Identify runs on every new connection, in both directions, and fills the
peerstore. `connect()` returns after Identify.

```dart
final protos = await host.peerStore.protoBook.getProtocols(peer);
final addrs  = await host.peerStore.addrBook.addrs(peer);
final key    = await host.peerStore.keyBook.pubKey(peer);
final first  = await host.peerStore.protoBook.firstSupportedProtocol(peer, ['/app/2.0.0', '/app/1.0.0']);

// Bootstrap address that never expires:
await host.peerStore.addrBook.addAddrs(peer, addrs, AddressTTL.permanentAddrTTL);
```

`connect()` stores the given addresses with a 5-minute TTL. `newStream()`
dials from the peerstore, so it works only for a connected peer or one whose
addresses are stored.

### Connections

```dart
host.network.connectedness(peer) == Connectedness.connected;
host.network.peers;                       // connected peer IDs
host.network.connsToPeer(peer);           // List<Conn>
conn.remoteMultiaddr.hasProtocol('p2p-circuit');   // relayed?
await host.network.closePeer(peer);
```

### Event bus

```dart
final sub = host.eventBus.subscribe([EvtPeerConnectednessChanged, EvtPeerIdentificationCompleted]);
sub.stream.listen((dynamic e) {
  if (e is EvtPeerConnectednessChanged) print('${e.peer} ${e.connectedness}');
  if (e is EvtPeerIdentificationCompleted) print('${e.peer} speaks ${e.protocols}');
});
// ... later
await sub.close();
```

`subscribe` is synchronous and returns a `Subscription`; `emitter(Type)` is
async and its `Emitter` must be closed. Other events: `EvtLocalReachabilityChanged`,
`EvtLocalAddressesUpdated`, `EvtLocalProtocolsUpdated`, `EvtPeerProtocolsUpdated`,
`EvtPeerIdentificationFailed`, `EvtNATDeviceTypeChanged`.
`EvtPeerConnectednessChanged` fires on a peer's first connection and when its
last one closes. For per-connection callbacks use `host.network.notify(NotifyBundle(...))`.

### Resource limits

A host from `Libp2p.new_` has go-libp2p's default limits (1 GiB / 512 FD
budget: 256 connections total, 8 per peer, 68 inbound streams per peer and
protocol). Over a limit you get `ResourceLimitExceededException`.

```dart
import 'package:dart_libp2p/p2p/host/resource_manager/limiter.dart'; // ConfigurableLimiter, LimiterConfig, FixedLimiter
import 'package:dart_libp2p/p2p/host/resource_manager/limit.dart';   // BaseLimit

Libp2p.resourceLimiter(ConfigurableLimiter(
    LimiterConfig.scaled(memory: 2 * 1024 * 1024 * 1024, fds: 4096))),
// or custom: LimiterConfig(peer: BaseLimit(conns: 4), protocolOverrides: {...})
// no limits:  Libp2p.resourceLimiter(FixedLimiter())
// no tracking: Libp2p.resourceManager(NullResourceManager())

await host.network.resourceManager.viewSystem((scope) async => print(scope.stat));
```

### Tuning Yamux

```dart
Libp2p.muxer('/yamux/1.0.0', (conn, isClient) => YamuxSession(
    conn as TransportConn,
    const MultiplexerConfig(maxStreams: 256, keepAliveInterval: Duration(seconds: 15)),
    isClient)),
```

(`YamuxSession`, `MultiplexerConfig`: `p2p/transport/multiplexing/yamux/session.dart`,
`p2p/transport/multiplexing/multiplexer.dart`.) Defaults: 256 KiB frames,
16 MiB max window, keep-alive 10 s, no pong in 30 s closes the session, stream
open timeout 10 s.

### Other options worth knowing

`Libp2p.dialTimeout` (15 s), `Libp2p.relayDialTimeout` (30 s),
`Libp2p.negotiationTimeout` (10 s, multistream on `newStream`),
`Libp2p.ping(false)`, `Libp2p.autoNAT(false)`, `Libp2p.autoNATv2Server(false)`
(phones), `Libp2p.holePunching(bool)`, `Libp2p.connectionGater(BasicConnGater()..blockPeer(id))`
(`p2p/network/conn_gater.dart`), `Libp2p.observedAddrActivationThreshold(n)`.
Relay and NAT options: see `dart-libp2p-nat-relay`.

### Shutdown

```dart
await host.close();   // stops services, timers, connections, listeners, peerstore
```

Close everything you started on top first (pubsub, DHT, your own timers), then
the host. A host cannot be restarted; build a new one.

## Key concepts / API map

- `Libp2p.new_(List<Option>)`: config + defaults + `newNode()`; returns an unstarted `Host`.
- `Config` / `Libp2p.newConfig()`, `config.apply(opts)`, `applyDefaults(config)` (`config/defaults.dart`), `config.newNode()`: the manual path; a `Config` is consumed by `newNode`.
- `Host`: `id`, `addrs`, `peerStore`, `network`, `mux`, `eventBus`, `connManager`, `connect`, `newStream`, `setStreamHandler`, `setStreamHandlerMatch`, `removeStreamHandler`, `start`, `close`, `holePunchService`.
- `BasicHost` (the concrete host): `pingService`, `circuitV2Client`, `autoNATService`, `publicAddrs`, `udpNatDeviceType`, `allAddrs`.
- `Network` (the `Swarm`): `listenAddresses`, `peers`, `conns`, `connsToPeer`, `connectedness`, `closePeer`, `notify`, `resourceManager`.
- `UDXTransport({required connManager, udxInstance, config})`: `/ip4|ip6/.../udp/<port>/udx`.
- `TCPTransport({resourceManager, connManager, config})`: `/ip4|ip6/.../tcp/<port>`.
- `NoiseSecurity.create(keyPair)`: `/noise` (XX). Default when none is given.
- `YamuxSession`, `MultiplexerConfig`: `/yamux/1.0.0`. Default.
- `AddrInfo(id, addrs)`, `AddrInfo.fromMultiaddr(ma)`; `PeerId.fromString`, `toBase58()`.
- `PingService(host).ping(peer)`: `Stream<PingResult>` (`rtt`, `error`, `hasError`), `/ipfs/ping/1.0.0`.
- Identify: `/ipfs/id/1.0.0`, push `/ipfs/id/push/1.0.0`, 30 s timeout, `IdentifyTimeoutException`.
- `Context`: `withDialPeerTimeout`, `withForceDirectDial`, `withForceFreshDial`.
- Errors (`core/network/errors.dart`): `ResourceLimitExceededException`, `PeerIdMismatchException`, `GaterDisallowedConnectionException`.

## Gotchas

- **`host.addrs` drops loopback and unspecified addresses.** The default
  `AddrsFactory` filters `127.0.0.1`, `::1`, `0.0.0.0` and `::`. A host that
  listens only on `127.0.0.1` has empty `addrs`, so `connect(AddrInfo(id, host.addrs))`
  fails. In tests use `host.network.listenAddresses`, or pass
  `Libp2p.addrsFactory((a) => a)`.
- **`Libp2p.new_` does not listen.** Call `await host.start()`; it binds the
  listen addresses and starts Identify, AutoNAT, hole punching and relay
  services. Calling `network.listen` yourself as well listens twice.
- **No DNS dialing.** TCP and UDX dial only `/ip4` and `/ip6`. A `/dns4/...`
  address is skipped. Resolve the name with `InternetAddress.lookup` and build
  an `/ip4` address. WebSocket addresses (`/ws`, `/wss`, `/tls/ws`) parse but are
  not dialable: there is no WebSocket, QUIC or WebTransport transport.
- **Pass the same `ConnectionManager` to `Libp2p.connManager` and to every
  transport.** `UDXTransport` requires one; the examples and tests all share one.
- **Make `TCPTransport` without `resourceManager`.** Then it uses the host's
  manager and a connection is counted once from socket open. Older code passes
  `NullResourceManager()`, which counts sockets nowhere (4.6.0).
- **`Libp2p.autoNAT` is on by default** and starts an AutoNAT v2 server with a
  second, internal dial-back host (own key). On a phone or in unit tests use
  `Libp2p.autoNATv2Server(false)` or `Libp2p.autoNAT(false)`. Before 4.3.0
  `autoNAT(false)` was ignored.
- **Key files written before 4.1.3 are lost.** `Ed25519PrivateKey.marshal()`
  wrote zeros; `unmarshal()` rejects such a key with a `FormatException`.
  Generate a new identity. From 4.1.3 marshal/unmarshal match go-libp2p.
- **RSA/ECDSA peer IDs changed in 3.0.0 and 4.0.0** to match go-libp2p.
  Re-derive stored non-Ed25519 IDs from the key.
- **A dial fails if a different peer answers** (`PeerIdMismatchException`,
  4.11.0). Do not "fix" it by dropping the peer ID.
- **Handlers registered after `start()` reach peers through an Identify push.**
  Register handlers (and start protocols such as the DHT) before `host.start()`
  so the first Identify already lists them.
- **`connect()` to a connected peer returns at once**; it does not re-dial or
  re-check the gater. Concurrent dials to one peer join one dial.
- **Dead connections**: a Yamux session closes when keep-alive gets no pong in
  30 s; the swarm then removes it and the next `newStream` dials again. Before
  4.7.0 a dead UDX path could hang new streams for minutes; stay on >= 4.7.0.
- **Android / network change**: from 4.8.0 a UDX listener whose socket the OS
  closed is bound again (same port if free). Older versions stopped dialling.
- **UDX wire version.** dart_libp2p >= 2.0.0 uses dart_udx wire v3, which does
  not talk to 1.x nodes; a mismatch looks like an unreachable peer. Upgrade both
  ends together.
- **Logging.** Routine traffic is logged at FINE. Run at `Level.WARNING` in
  production; turn on one logger (for example `p2p-holepunch`) to debug.
- **Docs drift.** Some pages show `TcpTransport`, `UdxTransport`,
  `host.services[...]`, `Multiaddr.fromString` or `newStream` without a
  `Context`. The real names are `TCPTransport`, `UDXTransport`,
  `PingService(host)`/`BasicHost.pingService`, `MultiAddr('...')` and
  `newStream(peer, pids, Context())`.

## Testing

Two real hosts on loopback, as `test/protocol/ping_test.dart` does:

```dart
Future<Host> testHost() async {
  final kp = await generateEd25519KeyPair();
  final cm = ConnectionManager();
  final h = await Libp2p.new_([
    Libp2p.identity(kp),
    Libp2p.connManager(cm),
    Libp2p.transport(UDXTransport(connManager: cm)),
    Libp2p.listenAddrs([MultiAddr('/ip4/127.0.0.1/udp/0/udx')]),
    Libp2p.autoNAT(false),          // no dial-back host, no probes
    Libp2p.holePunching(false),
  ]);
  await h.start();
  return h;
}

late Host a, b;
setUp(() async {
  a = await testHost();
  b = await testHost();
  await a.connect(AddrInfo(b.id, b.network.listenAddresses));
});
tearDown(() async {
  await a.close();
  await b.close();
});
test('ping', () async {
  final r = await PingService(a).ping(b.id).first;
  expect(r.hasError, isFalse, reason: '${r.error}');
}, timeout: const Timeout(Duration(seconds: 30)));
```

- Use TCP (`/ip4/127.0.0.1/tcp/0`) instead of UDX when you need to talk to a
  go-libp2p test peer; vanilla go-libp2p has no UDX.
- Always close hosts in `tearDown`: an open host keeps timers alive and the
  test process does not exit. A handler that throws resets only its stream and
  is logged; it does not fail the test zone (4.0.1).
- The repo's `test/real_net_stack.dart` and `test/mocks/` are not part of the
  published package; copy patterns, do not import them.

## Red lines

- Never use `Libp2p.noSecurity()` outside a test: connections are then
  unauthenticated and unencrypted, and no other libp2p peer will accept them.
- Never log, commit or send a private key or seed; keep key files mode 600.
- Never reuse a `Config` after `newNode()`, and never use a host after `close()`.
- Do not strip the `/p2p/<id>` check or catch-and-ignore `PeerIdMismatchException`.
- Do not change the Identify, Ping, Noise or Yamux protocol IDs; go-libp2p
  peers will not negotiate them.
- Always `close()` hosts, subscriptions and emitters you create, or timers and
  sockets leak and the process never exits.

## Reference

- Getting started: https://cryptopeer.net/libp2p/core/getting-started/
- Architecture: https://cryptopeer.net/libp2p/core/architecture/
- Configuration: https://cryptopeer.net/libp2p/core/configuration/
- Host: https://cryptopeer.net/libp2p/core/host/
- Transports: https://cryptopeer.net/libp2p/core/transports/
- Security (Noise, connection gating): https://cryptopeer.net/libp2p/core/security/
- Multiplexing: https://cryptopeer.net/libp2p/core/multiplexing/
- Peerstore: https://cryptopeer.net/libp2p/core/peerstore/
- Event bus: https://cryptopeer.net/libp2p/core/event-bus/
- Resource manager: https://cryptopeer.net/libp2p/core/resource-manager/
- Cookbook: https://cryptopeer.net/libp2p/core/cookbook/
- End-to-end demo: https://cryptopeer.net/demos/internet-chat/
- Source and CHANGELOG: https://github.com/stephanfeb/dart_libp2p
