# Tour of the Rust SDK import { Callout } from 'nextra/components' A quick walkthrough of the most important things you can do with `nym-sdk`. Each section shows working code and links to the module that covers it in depth. **The Mixnet is not like regular internet networking** — there are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network: you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination. The raw [message API](./mixnet) therefore works differently from what most developers expect. The [Stream module](./stream) bridges this gap by providing `AsyncRead + AsyncWrite` byte streams on top of the Mixnet. If you are coming from socket-based networking, start with streams. ## Send a raw message payload The message API gives you direct access to the Mixnet's native communication model: individually addressed payloads with no connections and no ordering guarantees. This is useful when you want full control, but it's not how most networking code works: ```rust use nym_sdk::mixnet::{self, MixnetMessageSender}; #[tokio::main] async fn main() { let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); let addr = *client.nym_address(); println!("Connected: {addr}"); // Send a message to ourselves client .send_plain_message(addr, "hello mixnet!") .await .unwrap(); // Receive it (filter empty SURB management messages) if let Some(msgs) = client.wait_for_messages().await { for msg in msgs.iter().filter(|m| !m.message.is_empty()) { println!("Got: {}", String::from_utf8_lossy(&msg.message)); } } // Always disconnect for clean shutdown client.disconnect().await; } ``` The message is Sphinx-encrypted, mixed across 5 nodes, and reconstructed on arrival. The whole round trip takes a few seconds. Next: [Mixnet module](./mixnet) | [Tutorial: Send Your First Private Message](./mixnet/tutorial) ## Reply anonymously with SURBs Every received message carries a `sender_tag`, an opaque token that lets you reply **without knowing the sender's Nym address**. Replies travel back through pre-built Single Use Reply Blocks (SURBs): ```rust // After receiving a message... let tag = received_msg.sender_tag.expect("message includes sender tag"); client.send_reply(tag, "anonymous reply!").await.unwrap(); ``` The replying side never learns where the reply is going, enabling anonymous communication without mutual identity disclosure. ## Open a bidirectional stream If you're used to working with TCP sockets, this is where you'll feel at home. The [Stream module](./stream) provides persistent, bidirectional byte channels that implement tokio's `AsyncRead + AsyncWrite`, so any code that works with sockets works with `MixnetStream`: ```rust use nym_sdk::mixnet; use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[tokio::main] async fn main() { let mut sender = mixnet::MixnetClient::connect_new().await.unwrap(); let mut receiver = mixnet::MixnetClient::connect_new().await.unwrap(); let recv_addr = *receiver.nym_address(); // Receiver creates a listener (activates stream mode) let mut listener = receiver.listener().unwrap(); // Sender opens a stream to the receiver let mut out = sender.open_stream(recv_addr, None).await.unwrap(); // Receiver accepts it let mut inc = listener.accept().await.unwrap(); // Standard tokio I/O — write, flush, read out.write_all(b"hello stream").await.unwrap(); out.flush().await.unwrap(); let mut buf = vec![0u8; 1024]; let n = inc.read(&mut buf).await.unwrap(); println!("{}", String::from_utf8_lossy(&buf[..n])); drop(out); drop(inc); sender.disconnect().await; receiver.disconnect().await; } ``` Activating stream mode (by calling `listener()` or `open_stream()`) disables message-based methods like `send_plain_message()` and `wait_for_messages()`. A single client operates in one mode at a time. Next: [Stream module](./stream) | [Tutorial: Build a Private Echo Server](./stream/tutorial) ## Use a client pool for bursty traffic Creating a `MixnetClient` takes several seconds (gateway handshake, key generation, topology fetch). The [Client Pool](./client-pool) pre-creates clients in the background so they're ready when you need them: ```rust use nym_sdk::client_pool::ClientPool; #[tokio::main] async fn main() { let pool = ClientPool::new(3); // maintain 3 clients in reserve let bg = pool.clone(); tokio::spawn(async move { bg.start().await }); // Wait for pool to fill, then grab a ready client tokio::time::sleep(std::time::Duration::from_secs(15)).await; if let Some(client) = pool.get_mixnet_client().await { println!("Got client: {}", client.nym_address()); client.disconnect().await; } pool.disconnect_pool().await; } ``` Clients are consumed, not returned; the pool creates replacements automatically. Next: [Client Pool module](./client-pool) | [Tutorial: Handle Bursty Traffic](./client-pool/tutorial) ## Persist your identity By default, `connect_new()` creates ephemeral keys that are discarded on disconnect. To keep the same Nym address across restarts, use the builder with on-disk storage: ```rust use nym_sdk::mixnet::{MixnetClientBuilder, StoragePaths}; use std::path::PathBuf; let storage = StoragePaths::new_from_dir( &PathBuf::from("/tmp/my-nym-client") ).unwrap(); let client = MixnetClientBuilder::new_with_default_storage(storage) .await .unwrap() .build() .unwrap() .connect_to_mixnet() .await .unwrap(); // This address is the same every time you run with the same path println!("Persistent address: {}", client.nym_address()); ``` ## Where to go next - [Installation](./importing): add `nym-sdk` to your project - [Mixnet Tutorial](./mixnet/tutorial): send, receive, and reply with SURBs - [Stream Tutorial](./stream/tutorial): build a private echo server - [Client Pool Tutorial](./client-pool/tutorial): handle bursty traffic - [API Reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/): type details, method signatures, architecture docs