# 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