UDX: Go, Dart, JS
This page shows how to build a libp2p host that reaches a go-ricochet server in Go, Dart and JavaScript. The server listens on UDX only, so this is the one step every client has to get right.
The address
Section titled “The address”A server’s address is a UDX multiaddr followed by its peer ID:
/ip4/203.0.113.10/udp/55223/udx/p2p/12D3KooW...| Part | Meaning |
|---|---|
/ip4/203.0.113.10 or /ip6/... |
Server IP; the UDX transports dial IP addresses, not DNS names |
/udp/55223 |
UDP port; 55223 unless the operator changed server.port |
/udx |
UDX on top of UDP (multiaddr protocol code 0x0300) |
/p2p/12D3KooW... |
Server peer ID, printed at startup; see First run & your peer ID |
What sits on top of UDX
Section titled “What sits on top of UDX”Each libp2p connection is one UDX connection. Its first UDX stream is upgraded like a TCP socket: multistream-select, then Noise, then Yamux. All three implementations below do this, so a host in any of them needs:
| Layer | Value |
|---|---|
| Transport | UDX, wire version 3 |
| Security | Noise (/noise) |
| Muxer | Yamux (/yamux/1.0.0) |
UDX wire version 3 does not interoperate with version 2: a mismatched peer looks unreachable. go-udx v0.1.3, dart_udx 3.0 and later, and js-udx all speak version 3.
Use go-libp2p-udx-transport v0.1.8, the version go-ricochet 1.1.0 itself uses. This host is the one in go-ricochet’s examples/mailbox and ricochet-bench.
go get github.com/stephanfeb/go-libp2p-udx-transport@v0.1.8// Sketch: a UDX-only host, then a connection to the server.h, err := libp2p.New( libp2p.Identity(priv), libp2p.NoTransports, libp2p.Transport(udxtransport.NewTransport), libp2p.ListenAddrStrings("/ip4/0.0.0.0/udp/0/udx"), libp2p.Security(noise.ID, noise.New), libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport),)if err != nil { return err}
info, err := peer.AddrInfoFromString("/ip4/203.0.113.10/udp/55223/udx/p2p/12D3KooW...")if err != nil { return err}if err := h.Connect(ctx, *info); err != nil { return err}Imports: github.com/libp2p/go-libp2p, .../core/peer, .../p2p/muxer/yamux, .../p2p/security/noise and udxtransport "github.com/stephanfeb/go-libp2p-udx-transport". Pass h to client.New; see Go: your own libp2p host to add UDX to a host that keeps other transports. The UDX protocol itself is documented at cryptopeer.net/udx/go.
dart_libp2p ships a UDXTransport built on dart_udx. Use dart_libp2p 3.0.0 or later, whose dart_udx speaks wire version 3; the current release is 4.7.1, on dart_udx 4.1. The host below is the one the ricochet package’s interop harness (bin/store_interop_test.dart) runs against go-ricochet.
// Sketch, after bin/store_interop_test.dart in ricochet-dart-client 0.2.4.Future<BasicHost> createHost(KeyPair keyPair) async { final connMgr = ConnectionManager(idleTimeout: const Duration(seconds: 60)); final udxTransport = UDXTransport(connManager: connMgr);
final yamuxConfig = MultiplexerConfig( keepAliveInterval: const Duration(seconds: 60), maxStreamWindowSize: 1024 * 1024, initialStreamWindowSize: 256 * 1024, streamWriteTimeout: const Duration(seconds: 30), maxStreams: 256, );
final options = <p2p_config.Option>[ await p2p_config.Libp2p.identity(keyPair), await p2p_config.Libp2p.connManager(connMgr), await p2p_config.Libp2p.transport(udxTransport), await p2p_config.Libp2p.security(await NoiseSecurity.create(keyPair)), await p2p_config.Libp2p.muxer('/yamux/1.0.0', (Conn secureConn, bool isClient) { return YamuxSession(secureConn as TransportConn, yamuxConfig, isClient, null); }), await p2p_config.Libp2p.listenAddrs([MultiAddr('/ip4/0.0.0.0/udp/0/udx')]), ]; return await p2p_config.Libp2p.new_(options) as BasicHost;}
// Connect to the server.final host = await createHost(await crypto_ed25519.generateEd25519KeyPair());await host.start();final target = MultiAddr('/ip4/203.0.113.10/udp/55223/udx/p2p/12D3KooW...');final serverId = PeerId.fromString(target.valueForProtocol(Protocols.p2p.name)!);await host.connect( AddrInfo(serverId, [target.decapsulate(Protocols.p2p.name)!]), context: core_context.Context(),);The imports are listed at the top of the harness file. Hand host to SFClient; see Dart client.
JavaScript
Section titled “JavaScript”js-udx provides @stephanfeb/libp2p-udx, a js-libp2p v3 transport. It runs on Node.js 22 or later, not in browsers, and is not on npm yet: install it from source as its Getting started guide describes.
// From the js-udx README: a js-libp2p node that speaks UDX, Noise and Yamux.import { createLibp2p } from 'libp2p'import { noise } from '@chainsafe/libp2p-noise'import { yamux } from '@chainsafe/libp2p-yamux'import { udx } from '@stephanfeb/libp2p-udx'
const node = await createLibp2p({ addresses: { listen: ['/ip4/0.0.0.0/udp/0/udx'] }, transports: [udx()], connectionEncrypters: [noise()], streamMuxers: [yamux()]})There is no Ricochet client for JavaScript, so you open protocol streams and write the frames yourself. A capacity query on the admin protocol looks like this:
// Sketch: not tested against go-ricochet. One length-prefixed JSON frame each way.import { multiaddr } from '@multiformats/multiaddr'
const server = multiaddr('/ip4/203.0.113.10/udp/55223/udx/p2p/12D3KooW...')const stream = await node.dialProtocol(server, '/sf-network/admin/1.0.0')
const body = new TextEncoder().encode(JSON.stringify({ operationType: 'queryCapacity' }))const frame = new Uint8Array(4 + body.length)new DataView(frame.buffer).setUint32(0, body.length) // big-endian lengthframe.set(body, 4)stream.send(frame)await stream.close()
const chunks: Uint8Array[] = []for await (const chunk of stream) chunks.push(chunk.subarray())const reply = Buffer.concat(chunks)console.log(JSON.parse(reply.subarray(4, 4 + reply.readUInt32BE(0)).toString()))js-udx’s interop tests run js-libp2p against go-libp2p-udx-transport and dart-libp2p in both directions. The frame format and every request shape are on Writing your own client.