Skip to content

Creating a client

This page shows how to create a *client.Client, what its configuration does, how it picks a server for each request, and what Close does and does not close. Full signatures are on pkg.go.dev.

client.New takes a libp2p host you have already built, and a client.Config:

func New(h host.Host, cfg client.Config) *client.Client

The host decides how the client reaches servers. A Ricochet 1.1.0 server listens on UDX only, a reliable transport over UDP; it has no TCP or QUIC listener. A host from a plain libp2p.New() carries only the default transports and cannot reach it. Build the host with the UDX transport, Noise and Yamux, as examples/mailbox does:

h, err := libp2p.New(
libp2p.Identity(priv), // an Ed25519 crypto.PrivKey
libp2p.NoTransports,
libp2p.Transport(udxtransport.NewTransport), // github.com/stephanfeb/go-libp2p-udx-transport
libp2p.ListenAddrStrings("/ip4/0.0.0.0/udp/0/udx"),
libp2p.Security(noise.ID, noise.New),
libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport),
)

The client reads the host’s private key from its peerstore when you call New. Encryption and decryption need that key to be Ed25519; with any other key, WithEncryption fails and encrypted messages cannot be read.

The client opens a new stream to a server for each request but never looks up addresses. Either connect the host to each server first, or put the addresses in the peerstore:

info, _ := peer.AddrInfoFromString("/ip4/203.0.113.7/udp/55223/udx/p2p/12D3KooW...")
if err := h.Connect(ctx, *info); err != nil { /* ... */ }
// or, without dialling yet:
h.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.PermanentAddrTTL)

UDX: Go, Dart, JS explains the transport, and Go: your own libp2p host covers adding it to a host you already use for other protocols.

cl := client.New(h, client.Config{
PreferredServers: []client.ServerPreference{
{PeerID: primaryID, Priority: 1},
{PeerID: backupID, Priority: 2},
},
MessageTimeout: 30 * time.Second,
})
defer cl.Close()
Field Default Meaning
PreferredServers none The servers to use, as client.ServerPreference values. A request with no server to try fails with no preferred servers configured.
MessageTimeout 30s Deadline for each request’s stream: opening, writing and reading the reply. If the ctx you pass has a deadline, that deadline is used instead.
ConnectionTimeout 10s Stored and defaulted, but no request in v1.1.0 uses it. Bound dialling with a deadline on ctx.

ServerPreference has three fields:

  • PeerID is the server’s peer ID.
  • Priority orders the servers. Lower is tried first.
  • Weight orders servers of equal priority. Higher is tried first. The order is deterministic; the client does not spread load by weight.

cl.PeerID() returns the host’s peer ID, which is your own mailbox address and the owner ID for your documents.

For every request without an explicit server, the client walks PreferredServers in order:

  1. It tries to open a stream to the server. If that fails (unreachable, no address, protocol not supported), it moves on to the next server.
  2. The first server that accepts the stream handles the request. Whatever it answers, success or refusal, is the result. The client does not retry a refusal elsewhere.
  3. If no server accepts, the error is no preferred server reachable: followed by every attempt’s error. If ctx ends during the walk, the client stops trying.

SendResult.StoredAtServer tells you which server took a submitted message. Servers do not replicate, so the recipient must retrieve from that server. Pick the same preferred servers for peers that write to each other.

The store calls take an option that names one server for one request. With it the client makes exactly one attempt and does not fall back:

Option Used by
WithDocServer(id) Document calls, JoinDirectory, LeaveDirectory, GetDirectoryEntry
WithDirectoryServer(id) BrowseDirectory
WithFeedServer(id) Feed calls that take FeedOption
WithFeedEntryServer(id) GetFeedEntries
WithCollectionServer(id) Collection calls that take CollectionOption
WithQueryServer(id) QueryCollection, ListCollectionKeys, ListCollectionKeysPage
doc, err := cl.GetDocument(ctx, ownerID, "profile", client.WithDocServer(archiveID))

Messaging calls (SendMessage, RetrieveMessages, the flag calls) and mailbox management calls always use the preferred list. To pin those to one server, create a second client on the same host with a one-entry PreferredServers. Register a notification handler on only one client per host: the handler is a stream handler on the host, and a second registration replaces the first.

Every call takes a context.Context. Cancelling it aborts the request. A deadline on it replaces MessageTimeout for that request’s stream, whether it is shorter or longer, so a bulk upload can set a long deadline without changing the config.

ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
results, err := cl.PutDocuments(ctx, cl.PeerID(), batch)

Close stops the client:

  • It removes the notification handler the client registered on the host, if any.
  • Every later request fails with client.ErrClosed.
  • It does not close the host. The host was yours before New, may serve other protocols, and stays open until you call h.Close().
defer h.Close()
defer cl.Close() // runs first

Calling Close twice is safe.