Open-source project
libp2p/py-libp2p avatar
libp2p/py-libp2p

py-libp2p tracks the standard libp2p modules closely, and the WebRTC dial version is where it is still moving

The Python implementation of the libp2p networking stack 🐍 [under development]

626 stars257 forksPythonNOASSERTION

At a glance

What is it?
libp2p/py-libp2p is the Python implementation of the libp2p networking stack, with a status table for every standard module: TCP, QUIC, WebSocket, circuit relay v2, AutoNAT, hole punching, Noise, TLS, bootstrap, random walk, mDNS and rendezvous are marked done, while WebTransport and both WebRTC paths are prototypes. The WebRTC dialer still defaults to version 1 while a spec change is unmerged.
Who is it for?
Use py-libp2p if you are writing a Python node that needs to speak the libp2p protocols with the other implementations, and you can live with a Beta classifier and a project that states it is still working toward production maturity. Do not choose it for a deployment where the WebRTC path matters, because the dial version default is expected to change and the SDP HTTP harness is explicitly not interoperable.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A status table for every standard module, and where the project admits it is unfinished

py-libp2p is the Python implementation of the libp2p networking stack, published on PyPI as libp2p, with the project homepage at libp2p.io and the documentation on ReadTheDocs.

The unusual thing about the README is that it is organised as a conformance table rather than a pitch. The project states that it aims for conformity with the standard libp2p modules, and then breaks the modules down by area with a status for each one. The legend is explicit: done, in progress or usable, prototype or unstable, and missing.

That is a more honest document than a feature list, because it lets a reader find the one module they care about and see exactly where it stands. It also means the gaps are visible rather than implied.

The maturity statement is more nuanced than the repository description, which still says under development. The README says the project has moved beyond its experimental roots and is steadily progressing toward production readiness, that the core features are stable, and that the focus is on refining performance, expanding protocol support and ensuring smooth interop with other libp2p implementations. It asks for contributions and real world usage feedback to reach full production maturity.

So the honest summary is a stable core with unfinished edges, maintained by three named people, pacrob, seetadev and dhuseby, with a Discord channel and a discussions board for questions. The package metadata still classifies it as Development Status 4 - Beta, and it requires Python 3.10 or newer while excluding Python 4.

TCP, QUIC and WebSocket are done; WebTransport and WebRTC are not

The transport table is the fastest way to see what this implementation can actually dial today.

Three transports are marked done: libp2p-tcp, libp2p-quic and libp2p-websocket. Three are marked prototype or unstable: libp2p-webtransport, libp2p-webrtc-browser-to-server and libp2p-webrtc-private-to-private. Every row links to the source directory in the repository, so the status is checkable rather than asserted.

The QUIC transport is worth noting for a specific reason visible in the dependency list: aioquic, a pure Python QUIC implementation, is a direct dependency. That is what makes a Python implementation of a QUIC transport practical at all, and it also means the transport inherits the performance characteristics of a Python QUIC stack rather than a native one.

The WebSocket transport matters for a different reason. A WebSocket is a browser reachable transport, and a browser cannot open a raw TCP socket, so the websocket module is what lets a page talk to a Python node without a relay in the middle. The examples directory reflects that, with a chat_websocket example alongside the plain chat one.

The WebTransport prototype is the one to watch, because it is the transport browsers are moving toward for peer to peer data, and a prototype status here means interop with other implementations is not guaranteed.

The WebRTC dial version is the sharpest edge in the codebase

The README spends more words on WebRTC than on any other module, and that is itself a signal about where the difficulty is.

The WebRTC direct path, meaning browser to server, uses the spec STUN path by default. Inbound dials hit a shared UDP port and the listener infers the offer from the first STUN packet. Outbound dials synthesise an ICE-Lite answer from the multiaddr, with no default public STUN servers. The documentation states plainly that this is the only signalling path that interoperates with go-libp2p, js-libp2p and browsers.

There is also an experimental HTTP POST /sdp harness, enabled with WebRTCTransportConfig(enable_sdp_http_harness=True), and the documentation is blunt about it: it is for py to py debugging only, it is not interoperable, and you should leave it off in production.

The dial version is the part that will change under you. The listener always accepts both v1 and v2, but the dialer picks with WebRTCTransportConfig(webrtc_direct_dial_version=1|2). Version 1 is the current default and is described as the migration path involving SDP ufrag and password munging. It stays the default while the spec change at libp2p/specs#715 is unmerged, so that existing v1 peers keep working. Version 2 implements that flow with no munging, and is recommended for new deployments, with browser dialling needing v2 once NoSdpMangleUfrag support is widespread. The default flips to v2 once the spec lands.

That is a well documented deprecation, but it is still a deprecation. Code that pins v1 today is code that has to change when the spec merges, and code that uses v2 today is code that only works against peers that support it.

NAT traversal, Noise, TLS and discovery are the settled parts

The modules that make a node reachable in practice are the ones marked done across the board.

For NAT traversal, libp2p-circuit-relay-v2, libp2p-autonat and libp2p-hole-punching are all marked done. Relay v2 is the current standard relay protocol, AutoNAT is how a node asks a peer whether it is publicly reachable, and hole punching is what lets two peers behind NAT establish a direct connection once the relay has told them about each other. Having all three means the Python node can participate in the same topology as the other implementations, which is the point of a standard.

For secure communication, libp2p-noise and libp2p-tls are both done. The dependency list shows what that costs: noiseprotocol for Noise, pynacl and pycryptodome for the cryptography underneath, and certifi style certificate handling is not needed because TLS is handled by the standard library.

For discovery, all four are done: bootstrap, random-walk, mdns-discovery and rendezvous. The first two are the standard peer discovery mechanisms, mDNS finds peers on a local network, and rendezvous is the server based meeting point. The dependencies behind them are visible too, with zeroconf for mDNS and the miniupnpc binding for UPnP port mapping.

The peer routing table is cut off in the documentation that is visible here, so the state of the DHT and Kademlia style routing modules cannot be read directly. The examples directory does contain a dht_chat example and a kademlia example, which suggests both have working code, but an example is not the same as a done status and the table is the authority.

Building and installing: the Makefile is the manual

There is no install section in the visible documentation, so the practical path is the Makefile, which documents itself through a help target.

The help target lists what each target does, and the useful ones are dist, which builds the package and lists the contents of the dist directory, package-test, which builds the package and installs it in a virtual environment for manual testing, and test, which runs the tests quickly with the default Python.

The relevant commands are these:

code
dist: clean
	python -m build
	ls -l dist

test: python -m pytest tests -n auto -m "not serial_only" --durations=40 --durations-min=1.0 python -m pytest \ tests/core/identity/identify/test [...truncated] ```

The test target runs pytest in parallel with the auto worker count, deselecting the tests marked serial only, and prints the slowest durations. The typecheck target runs two separate pre-commit hooks, mypy-local and pyrefly-local, so the project checks types with two tools.

The dist target is the packaging path, and it is worth noting that the project uses towncrier: the help text describes a notes target that consumes towncrier newsfragments and updates the release notes in the docs, and a release target that packages and uploads a release and does not run the notes target, requiring a bump to be set.

The newsfragments/ directory at the repository root is where those fragments live, which means every user visible change is expected to arrive with a fragment rather than only a commit message.

The examples directory is the other half of the onboarding story. It holds scripts for bitswap, chat, chat over websocket, circuit relay, connection management, DHT chat, echo, Filecoin, health monitoring, identify, auto TLS, Kademlia, mDNS, metrics, multiple transports and NAT traversal, which is a working catalogue of what the stack can do.

The dependency list explains the architecture better than the prose

Reading the dependency list in pyproject.toml tells you what kind of software this is.

The async story is the first thing. The project depends on trio and anyio, and the package metadata includes trio-typing. Trio is the async runtime, and anyio is the compatibility layer that lets the same code run under asyncio. Depending on both means the library is not tied to a single event loop, which matters for a networking stack that has to interoperate with applications built on either.

The protocol story is the second. grpcio and protobuf carry the gRPC based control and protocol machinery, mypy-protobuf generates typed protocol code, rpcudp is there for UDP based protocols, and cbor2 handles CBOR encoding. HTTP is covered by httpx and requests, and websocket support comes from trio-websocket rather than from an asyncio library, which is consistent with the trio dependency.

The identity and addressing story is the third. multiaddr, py-multibase, py-multihash, py-multicodec and py-cid are all present, so multiaddresses, base encoding, multihashes, multicodec tables and CIDs are first class rather than bolted on. pynacl and pycryptodome provide the cryptography, and coincurve is pinned exactly, because a pinned elliptic curve library is a security decision rather than a dependency refresh.

Two dependencies deserve a note for operational reasons. miniupnpc is the UPnP binding used to open ports on a router, and prometheus-client means the stack can export metrics about itself. The examples directory includes a metrics example, so metric emission is a supported path rather than an accident.

One caveat: fastecdsa is pinned to 2.3.2 and only installs when the platform is not win32, which means Windows users get a different cryptographic backend path than everyone else.

Dual licensed, Beta classified, and a release history with a gap

The licence is MIT AND Apache-2.0, and the repository carries both LICENSE-MIT and LICENSE-APACHE files along with a COPYRIGHT file. That is the same dual licensing the Rust and Java ecosystems use: you take the terms that suit you.

The package metadata lists the version as 0.8.0, matching the v0.8.0 tag published on 2026-09-26, with v0.7.0 before it on 2026-07-09. Then the history jumps back to v0.1.5 in March 2020, whose release note reads three's a crowd.

That gap is the real story about the project's timeline. The early versions sat for years while the libp2p specification itself was moving, and the recent versions come at roughly two month intervals, which is consistent with a project that has found its rhythm after a long period of specification churn.

The repository is not archived and the last push was on 2026-10-02, so the work is current as of this writing.

Two directories show what the project considers part of its job. The interop/ directory exists to check behaviour against other implementations, which is the only way a multi implementation protocol stays coherent, and the Filecoin_Retro_PGF.md file at the root suggests a funding and development history tied to a retroactive public goods programme for Filecoin.

The documentation also mentions that contributions and real world usage feedback are wanted to reach production maturity. For a networking library, the practical version of that request is a specific one: run it against the other implementations in the interop suite, because the module tables say done and the interop suite is what would show a gap.

Editorial conclusion

Use py-libp2p if you are writing a Python node that needs to speak the libp2p protocols with the other implementations, and you can live with a Beta classifier and a project that states it is still working toward production maturity. Do not choose it for a deployment where the WebRTC path matters, because the dial version default is expected to change and the SDP HTTP harness is explicitly not interoperable. Before depending on it, pin a release, check that the module you need is marked done rather than a prototype, and run the interop suite in the interop/ directory against the other implementations you must talk to.

Frequently asked questions

What is py-libp2p?

It is the Python implementation of the libp2p networking stack, published on PyPI as libp2p, requiring Python 3.10 or newer and licensed under MIT AND Apache-2.0. The project states that its core features are stable while it works toward production maturity.

Which libp2p modules does py-libp2p support today?

TCP, QUIC and WebSocket transports are marked done, along with circuit relay v2, AutoNAT, hole punching, Noise, TLS, bootstrap, random walk, mDNS discovery and rendezvous. WebTransport and both WebRTC direct paths are marked prototype or unstable.

Why does py-libp2p let me choose a WebRTC dial version?

The listener accepts both v1 and v2, but the dialer picks with WebRTCTransportConfig(webrtc_direct_dial_version=1|2). Version 1 is the default while the spec change at libp2p/specs#715 is unmerged, and the documentation says the default will flip to v2 once it lands.

How do I build and test py-libp2p from source?

The Makefile is the interface: make dist builds the package with python -m build, make package-test installs it in a virtual environment for manual testing, and make test runs pytest in parallel while deselecting the serial only tests. Type checking runs through the typecheck target with mypy and pyrefly.

Is the HTTP POST /sdp WebRTC harness safe to use in production?

No. The documentation states that the experimental HTTP POST /sdp harness, enabled with enable_sdp_http_harness=True, is for py to py debugging only, is not interoperable, and should be left off in production.

What licence is py-libp2p released under?

It is dual licensed as MIT AND Apache-2.0, with both LICENSE-MIT and LICENSE-APACHE files in the repository. The package metadata classifies the project as Development Status 4 - Beta.

Official sources

  1. Issues
  2. libp2p/py-libp2p on GitHub
  3. Project website
  4. README
  5. Releases
Community

Where developers are discussing it

Posts on Hacker News, dev.to and Lobsters that link to this repository, found by our scan of those communities. 1 in the last 30 days; newest first.

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/libp2p-py-libp2p.svg)](https://hysenlabs.com/projects/libp2p-py-libp2p)

Was this useful?