Introduction

Welcome to the Nym Developer Portal, containing quickstart resources, user manuals, integration information, and tutorials outlining to start building privacy enhanced apps.

For more in-depth information about nodes, network traffic flows, clients, coconut etc check out the docs.

If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the Operators Guides book.

If you’re looking for TypeScript/JavaScript related information such as SDKs to build your own tools, step-by-step tutorials, live playgrounds and more, make sure to check out the TS SDK Handbook.

Clients Overview

A large proportion of the Nym mixnet’s functionality is implemented client-side.

Clients perform the following actions on behalf of users:

  • determine network topology - what mixnodes exist, what their keys are, etc.
  • register with a gateway
  • authenticate with a gateway
  • receive and decrypt messages from the gateway
  • create layer-encrypted Sphinx packets
  • send Sphinx packets with real messages
  • send Sphinx packet cover traffic when no real messages are being sent
  • retransmit un-acknowledged packet sends - if a client sends 100 packets to a gateway, but only receives an acknowledgement (‘ack’) for 95 of them, it will resend those 5 packets to the gateway again, to make sure that all packets are received.

As a developer, you’ll want to use a Nym client to send your application network traffic through the mixnet; whether that is an RPC call, a TCP connection request, or treating it like a UDP pipe, you need to send whatever bytes your app needs to send through it. However, unlike (e.g.) a TCP Socket, Nym client communication is message-based, so you cannot (yet) simply plug-and-play using the mixnet as a seamless drop-in replacement. We are currently working on stream-like abstractions for ease of integration with the Rust SDK.

Types of Nym clients

At present, there are three Nym clients:

  • the websocket (native) client
  • the SOCKS5 client
  • the wasm (webassembly) client

You need to choose which one you want incorporate into your app. Which one you use will depend largely on your preferred programming style and the purpose of your app.

The websocket client

Your first option is the native websocket client (nym-client). This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It can be run as a persistent process on a desktop or server machine. You can connect to it with any language that supports websockets.

Rust developers can import websocket client functionality into their code via the Rust SDK.

The webassembly client

If you’re working in JavaScript or Typescript in the browser, or building an edge computing app, you’ll likely want to choose the webassembly client.

It’s packaged and available on the npm registry, so you can npm install it into your JavaScript or TypeScript application.

The webassembly client is most easily used via the Typescript SDK. Typescript developers who wish to send API requests through the mixnet can can also check the mixfetch package.

The SOCKS5 client

The nym-socks5-client is useful for allowing existing applications to use the Nym mixnet without any code changes. All that’s necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc).

When used as a standalone client, it’s less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes.

Rust developers can import socks client functionality into their code via the Rust SDK.

Commonalities between clients

All Nym client packages present basically the same capabilities to the privacy application developer. They need to run as a persistent process in order to stay connected and ready to receive any incoming messages from their gateway nodes. They register and authenticate to gateways, and encrypt Sphinx packets.

Rust SDK

The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a separate process on their machine. This makes both developing and running applications much easier, reducing complexity in the development process (not having to restart another client in a separate console window/tab) and being able to have a single binary for other people to use.

Currently developers can use the Rust SDK to import either websocket client (nym-client) or socks-client functionality into their Rust code.

In the future the SDK will be made up of several components, each of which will allow developers to interact with different parts of Nym infrastructure.

ComponentFunctionalityReleased
MixnetCreate / load clients & keypairs, subscribe to Mixnet events, send & receive messages✔️
CoconutCreate & verify Coconut credentials🛠️
ValidatorSign & broadcast Nyx blockchain transactions, query the blockchain

The mixnet component currently exposes the logic of two clients: the websocket client, and the socks client.

The coconut component is currently being worked on. Right now it exposes logic allowing for the creation of coconut credentials on the Sandbox testnet.

Development status

The SDK is still somewhat a work in progress: interfaces are fairly stable but still may change in subsequent releases.

Installation

The nym-sdk crate is not yet available via crates.io. As such, in order to import the crate you must specify the Nym monorepo in your Cargo.toml file:

nym-sdk = { git = "https://github.com/nymtech/nym" }

By default the above command will import the current HEAD of the default branch, which in our case is develop. Assuming instead you wish to pull in another branch (e.g. master or a particular release) you can specify this like so:

# importing HEAD of master branch 
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" }
# importing HEAD of the third release of 2023, codename 'kinder' 
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "release/2023.3-kinder" }

You can also define a particular git commit to use as your import like so:

nym-sdk = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }

Since the HEAD of master is always the most recent release, we recommend developers use that for their imports, unless they have a reason to pull in a specific historic version of the code.

Generate Crate Docs

In order to generate the crate docs run cargo doc --open from nym/sdk/rust/nym-sdk/

Message Types

There are two methods for sending messages through the mixnet using your client:

  • send_plain_message() is the most simple: pass the recipient address and the message you wish to send as a string (this was previously send_str()). This is a nicer-to-use wrapper around send_message().
  • send_message() allows you to also define the amount of SURBs to send along with your message (which is sent as bytes).

Message Helpers

Handling incoming messages

As seen in the Chain querier tutorial when listening out for a response to a sent message (e.g. if you have sent a request to a service, and are awaiting the response) you will want to await non-empty messages (if you don’t know why, read the info on this here). This can be done with something like the helper functions here:

#![allow(unused)]
fn main() {
use nym_sdk::mixnet::ReconstructedMessage; 

pub async fn wait_for_non_empty_message(
    client: &mut MixnetClient,
) -> anyhow::Result<ReconstructedMessage> {
    while let Some(mut new_message) = client.wait_for_messages().await {
        if !new_message.is_empty() {
            return Ok(new_message.pop().unwrap());
        }
    }
    
    bail!("did not receive any non-empty message")
}

pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result<ResponseTypes> {
    ResponseTypes::try_deserialize(message.message)
}

// Note here that the only difference between handling a request and a response
// is that a request will have a sender_tag to parse. 
// 
// This is used for anonymous replies with SURBs.  
pub fn handle_request(
    message: ReconstructedMessage,
) -> anyhow::Result<(RequestTypes, Option<AnonymousSenderTag>)> {
    let request = RequestTypes::try_deserialize(message.message)?;
    Ok((request, message.sender_tag))
}
}

The above helper functions are used as such by the client in tutorial example: it sends a message to the service (what the message is isn’t important - just that your client has sent a message somewhere and you are awaiting a response), waits for a non_empty message, then handles it (then logs it - but you can do whatever you want, parse it, etc):

#![allow(unused)]
fn main() {
// [snip]

// Send serialised request to service via mixnet what is await-ed here is 
// placing the message in the client's message queue, NOT the sending itself.
let _ = client
    .send_message(sp_address, message.serialize(), Default::default())
    .await;

// Await a non-empty message 
let received = wait_for_non_empty_message(client).await?;

// Handle the response received (the non-empty message awaited above) 
let sp_response = handle_response(received)?;

// Match JSON -> ResponseType
let res = match sp_response {
    crate::ResponseTypes::Balance(response) => {
        println!("{:#?}", response);
        response.balance
    }
};

// [snip]
}

(repo code on Github here)

Iterating over incoming messages

It is recommended to use nym_client.next().await over nym_client.wait_for_messages().await as the latter will return one message at a time which will probably be easier to deal with. See the parallel send and receive example for an example.

Remember to disconnect your client

You should always manually disconnect your client with client.disconnect().await as seen in the code examples. This is important as your client is writing to a local DB and dealing with SURB storage.

Troubleshooting

Below are several common issues or questions you may have.

If you come across something that isn’t explained here, PRs are welcome.

Verbose task client is being dropped logging

On client shutdown (expected)

If this is happening at the end of your code when disconnecting your client, this is fine; we just have a verbose client! When calling client.disconnect().await this is simply informing you that the client is shutting down.

On client shutdown / disconnect this is to be expected - this can be seen in many of the code examples as well. We use the nym_bin_common::logging import to set logging in our example code. This defaults to INFO level.

If you wish to quickly lower the verbosity of your client process logs when developing you can prepend your command with RUST_LOG=<LOGGING_LEVEL>.

If you want to run the builder.rs example with only WARN level logging and below:

cargo run --example builder 

Becomes:

RUST_LOG=warn cargo run --example builder 

You can also make the logging more verbose with:

RUST_LOG=debug cargo run --example builder

Not on client shutdown (unexpected)

If this is happening unexpectedly then you might be shutting your client process down too early. See the accidentally killing your client process below for possible explanations and how to fix this issue.

Accidentally killing your client process too early

If you are seeing either of the following errors when trying to run a client, specifically sending a message, then you may be accidentally killing your client process.

 2023-11-02T10:31:03.930Z INFO  TaskClient-BaseNymClient-real_traffic_controller-ack_control-action_controller                           > the task client is getting dropped
 2023-11-02T10:31:04.625Z INFO  TaskClient-BaseNymClient-received_messages_buffer-request_receiver                                       > the task client is getting dropped
 2023-11-02T10:31:04.626Z DEBUG nym_client_core::client::real_messages_control::acknowledgement_control::input_message_listener          > InputMessageListener: Exiting
 2023-11-02T10:31:04.626Z INFO  TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener                      > the task client is getting dropped
 2023-11-02T10:31:04.626Z INFO  TaskClient-BaseNymClient-real_traffic_controller-reply_control                                           > the task client is getting dropped
 2023-11-02T10:31:04.626Z DEBUG nym_client_core::client::real_messages_control                                                           > The reply controller has finished execution!
 2023-11-02T10:31:04.626Z DEBUG nym_client_core::client::real_messages_control::acknowledgement_control                                  > The input listener has finished execution!
 2023-11-02T10:31:04.626Z INFO  nym_task::manager                                                                                        > All registered tasks succesfully shutdown
 2023-11-02T11:22:08.408Z ERROR TaskClient-BaseNymClient-topology_refresher                                                  > Assuming this means we should shutdown...
 2023-11-02T11:22:08.408Z ERROR TaskClient-BaseNymClient-mix_traffic_controller                                              > Polling shutdown failed: channel closed
 2023-11-02T11:22:08.408Z INFO  TaskClient-BaseNymClient-gateway_transceiver-child                                           > the task client is getting dropped
 2023-11-02T11:22:08.408Z ERROR TaskClient-BaseNymClient-mix_traffic_controller                                              > Assuming this means we should shutdown...
thread 'tokio-runtime-worker' panicked at 'action control task has died: TrySendError { kind: Disconnected }', /home/.local/share/cargo/git/checkouts/nym-fbd2f6ea2e760da9/a800cba/common/client-core/src/client/real_messages_control/message_handler.rs:634:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
 2023-11-02T11:22:08.477Z INFO  TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener          > the task client is getting dropped
 2023-11-02T11:22:08.477Z ERROR TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener          > Polling shutdown failed: channel closed
 2023-11-02T11:22:08.477Z ERROR TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener          > Assuming this means we should shutdown...

Using the following piece of code as an example:

use nym_sdk::mixnet::{MixnetClient, MixnetMessageSender, Recipient};
use clap::Parser;

#[derive(Debug, Clone, Parser)]
enum Opts {
    Client {
        recipient: Recipient
    }
}

#[tokio::main]
async fn main() {
    let opts: Opts = Parser::parse();
    nym_bin_common::logging::setup_logging();

    let mut nym_client = MixnetClient::connect_new().await.expect("Could not build Nym client");

    match opts {
        Opts::Client { recipient } => {
            nym_client.send_plain_message(recipient, "some message string").await.expect("send failed");
        }
    }
}

This is a simplified snippet of code for sending a simple hardcoded message with the following command:

cargo run client <RECIPIENT_NYM_ADDRESS>

You might assume that send-ing your message would just work as nym_client.send_plain_message() is an async function; you might expect that the client will block until the message is actually sent into the mixnet, then shutdown.

However, this is not true.

This will only block until the message is put into client’s internal queue. Therefore in the above example, the client is being shut down before the message is actually sent to the mixnet; after being placed in the client’s internal queue, there is still work to be done under the hood, such as route encrypting the message and placing it amongst the stream of cover traffic.

The simple solution? Make sure the program/client stays active, either by calling sleep, or listening out for new messages. As sending a one-shot message without listening out for a response is likely not what you’ll be doing, then you will be then awaiting a response (see the message helpers page for an example of this).

Furthermore, you should always manually disconnect your client with client.disconnect().await as seen in the code examples. This is important as your client is writing to a local DB and dealing with SURB storage.

Client receives empty messages when listening for response

If you are sending out a message, it makes sense for your client to then listen out for incoming messages; this would probably be the reply you get from the service you’ve sent a message to.

You might however be receiving messages without data attached to them / empty payloads. This is most likely because your client is receiving a message containing a SURB request - a SURB requesting more SURB packets to be sent to the service, in order for them to have enough packets (with a big enough overall payload) to split the entire response to your initial request across.

Whether the data of a SURB request being empty is a feature or a bug is to be decided - there is some discussion surrounding whether we can use SURB requests to send additional data to streamline the process of sending large replies across the mixnet.

You can find a few helper functions here to help deal with this issue in the meantime.

If you can think of a more succinct or different way of handling this do reach out - we’re happy to hear other opinions

Examples

All the following examples can be found in the nym-sdk examples directory in the monorepo. Just navigate to nym/sdk/rust/nym-sdk/examples/ and run the files from there with:

cargo run --example <NAME_OF_FILE>

If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the sdk, tokio, and nym_bin_common crates.

An example Cargo.toml file can be found here.

Simple Send

Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code (examples/simple.rs).

Simply importing the nym_sdk crate into your project allows you to create a client and send traffic through the mixnet.

use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;

#[tokio::main]
async fn main() {
    nym_bin_common::logging::setup_logging();

    // Passing no config makes the client fire up an ephemeral session and figure shit out on its own
    let mut client = mixnet::MixnetClient::connect_new().await.unwrap();

    // Be able to get our client address
    let our_address = client.nym_address();
    println!("Our client nym address is: {our_address}");

    // Send a message through the mixnet to ourselves
    client
        .send_plain_message(*our_address, "hello there")
        .await
        .unwrap();

    println!("Waiting for message (ctrl-c to exit)");
    client
        .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message)))
        .await;
}

Key Creation and Use

The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys (examples/builder_with_storage):

use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;
use std::path::PathBuf;

#[tokio::main]
async fn main() {
    nym_bin_common::logging::setup_logging();

    // Specify some config options
    let config_dir = PathBuf::from("/tmp/mixnet-client");
    let storage_paths = mixnet::StoragePaths::new_from_dir(&config_dir).unwrap();

    // Create the client with a storage backend, and enable it by giving it some paths. If keys
    // exists at these paths, they will be loaded, otherwise they will be generated.
    let client = mixnet::MixnetClientBuilder::new_with_default_storage(storage_paths)
        .await
        .unwrap()
        .build()
        .unwrap();

    // Now we connect to the mixnet, using keys now stored in the paths provided.
    let mut client = client.connect_to_mixnet().await.unwrap();

    // Be able to get our client address
    let our_address = client.nym_address();
    println!("Our client nym address is: {our_address}");

    // Send a message throught the mixnet to ourselves
    client
        .send_plain_message(*our_address, "hello there")
        .await
        .unwrap();

    println!("Waiting for message");
    if let Some(received) = client.wait_for_messages().await {
        for r in received {
            println!("Received: {}", String::from_utf8_lossy(&r.message));
        }
    }

    client.disconnect().await;
}

As seen in the example above, the mixnet::MixnetClientBuilder::new() function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple.

Assuming our client config is stored in /tmp/mixnet-client, the following files are generated:

$ tree /tmp/mixnet-client

mixnet-client
├── ack_key.pem
├── db.sqlite
├── db.sqlite-shm
├── db.sqlite-wal
├── gateway_details.json
├── gateway_shared.pem
├── persistent_reply_store.sqlite
├── private_encryption.pem
├── private_identity.pem
├── public_encryption.pem
└── public_identity.pem

1 directory, 11 files

Manually Handled Storage

If you’re integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (examples/manually_handle_keys_and_config.rs)

use nym_sdk::mixnet::{
    self, ActiveGateway, BadGateway, ClientKeys, EmptyReplyStorage, EphemeralCredentialStorage,
    GatewayRegistration, GatewaysDetailsStore, KeyStore, MixnetClientStorage, MixnetMessageSender,
};
use nym_topology::provider_trait::async_trait;

#[tokio::main]
async fn main() {
    nym_bin_common::logging::setup_logging();

    // Just some plain data to pretend we have some external storage that the application
    // implementer is using.
    let mock_storage = MockClientStorage::empty();
    let mut client = mixnet::MixnetClientBuilder::new_with_storage(mock_storage)
        .build()
        .unwrap()
        .connect_to_mixnet()
        .await
        .unwrap();

    // Be able to get our client address
    let our_address = client.nym_address();
    println!("Our client nym address is: {our_address}");

    // Send important info up the pipe to a buddy
    client
        .send_plain_message(*our_address, "hello there")
        .await
        .unwrap();

    println!("Waiting for message");
    if let Some(received) = client.wait_for_messages().await {
        for r in received {
            println!("Received: {}", String::from_utf8_lossy(&r.message));
        }
    }

    client.disconnect().await;
}

#[allow(unused)]
struct MockClientStorage {
    pub key_store: MockKeyStore,
    pub gateway_details_store: MockGatewayDetailsStore,
    pub reply_store: EmptyReplyStorage,
    pub credential_store: EphemeralCredentialStorage,
}

impl MockClientStorage {
    fn empty() -> Self {
        Self {
            key_store: MockKeyStore,
            gateway_details_store: MockGatewayDetailsStore,
            reply_store: EmptyReplyStorage::default(),
            credential_store: EphemeralCredentialStorage::default(),
        }
    }
}

impl MixnetClientStorage for MockClientStorage {
    type KeyStore = MockKeyStore;
    type ReplyStore = EmptyReplyStorage;
    type CredentialStore = EphemeralCredentialStorage;
    type GatewaysDetailsStore = MockGatewayDetailsStore;

    fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
        (self.reply_store, self.credential_store)
    }

    fn key_store(&self) -> &Self::KeyStore {
        &self.key_store
    }

    fn reply_store(&self) -> &Self::ReplyStore {
        &self.reply_store
    }

    fn credential_store(&self) -> &Self::CredentialStore {
        &self.credential_store
    }

    fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore {
        &self.gateway_details_store
    }
}

struct MockKeyStore;

#[async_trait]
impl KeyStore for MockKeyStore {
    type StorageError = MyError;

    async fn load_keys(&self) -> Result<ClientKeys, Self::StorageError> {
        println!("loading stored keys");

        Err(MyError)
    }

    async fn store_keys(&self, _keys: &ClientKeys) -> Result<(), Self::StorageError> {
        println!("storing keys");

        Ok(())
    }
}

struct MockGatewayDetailsStore;

#[async_trait]
impl GatewaysDetailsStore for MockGatewayDetailsStore {
    type StorageError = MyError;

    async fn active_gateway(&self) -> Result<ActiveGateway, Self::StorageError> {
        println!("getting active gateway");

        Err(MyError)
    }

    async fn set_active_gateway(&self, _gateway_id: &str) -> Result<(), Self::StorageError> {
        println!("setting active gateway");

        Ok(())
    }

    async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError> {
        println!("getting all registered gateways");

        Err(MyError)
    }

    async fn has_gateway_details(&self, _gateway_id: &str) -> Result<bool, Self::StorageError> {
        println!("checking for gateway details");

        Err(MyError)
    }

    async fn load_gateway_details(
        &self,
        _gateway_id: &str,
    ) -> Result<GatewayRegistration, Self::StorageError> {
        println!("loading gateway details");

        Err(MyError)
    }

    async fn store_gateway_details(
        &self,
        _details: &GatewayRegistration,
    ) -> Result<(), Self::StorageError> {
        println!("storing gateway details");

        Ok(())
    }

    async fn remove_gateway_details(&self, _gateway_id: &str) -> Result<(), Self::StorageError> {
        println!("removing gateway details");

        Ok(())
    }
}

//
// struct MockReplyStore;
//
// #[async_trait]
// impl ReplyStorageBackend for MockReplyStore {
//     type StorageError = MyError;
//
//     async fn flush_surb_storage(
//         &mut self,
//         _storage: &CombinedReplyStorage,
//     ) -> Result<(), Self::StorageError> {
//         todo!()
//     }
//
//     async fn init_fresh(&mut self, _fresh: &CombinedReplyStorage) -> Result<(), Self::StorageError> {
//         todo!()
//     }
//
//     async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
//         todo!()
//     }
// }
//
// struct MockCredentialStore;
//
// #[async_trait]
// impl CredentialStorage for MockCredentialStore {
//     type StorageError = MyError;
//
//     async fn insert_coconut_credential(
//         &self,
//         _voucher_value: String,
//         _voucher_info: String,
//         _serial_number: String,
//        _binding_number: String,
//         _signature: String,
//         _epoch_id: String,
//     ) -> Result<(), Self::StorageError> {
//         todo!()
//     }
//
//     async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, Self::StorageError> {
//         todo!()
//     }
//
//     async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError> {
//         todo!()
//     }
// }

#[derive(thiserror::Error, Debug)]
#[error("foobar")]
struct MyError;

impl From<BadGateway> for MyError {
    fn from(_: BadGateway) -> Self {
        MyError
    }
}

Anonymous Replies with SURBs (Single Use Reply Blocks)

Both functions used to send messages through the mixnet (send_message and send_plain_message) send a pre-determined number of SURBs along with their messages by default.

You can read more about how SURBs function under the hood here.

In order to reply to an incoming message using SURBs, you can construct a recipient from the sender_tag sent along with the message you wish to reply to:

use nym_sdk::mixnet::{
    AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage,
    StoragePaths,
};
use std::path::PathBuf;

#[tokio::main]
async fn main() {
    nym_bin_common::logging::setup_logging();

    // Specify some config options
    let config_dir = PathBuf::from("/tmp/surb-example");
    let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap();

    // Create the client with a storage backend, and enable it by giving it some paths. If keys
    // exists at these paths, they will be loaded, otherwise they will be generated.
    let client = MixnetClientBuilder::new_with_default_storage(storage_paths)
        .await
        .unwrap()
        .build()
        .unwrap();

    // Now we connect to the mixnet, using keys now stored in the paths provided.
    let mut client = client.connect_to_mixnet().await.unwrap();

    // Be able to get our client address
    let our_address = client.nym_address();
    println!("\nOur client nym address is: {our_address}");

    // Send a message through the mixnet to ourselves using our nym address
    client
        .send_plain_message(*our_address, "hello there")
        .await
        .unwrap();

    // we're going to parse the sender_tag (AnonymousSenderTag) from the incoming message and use it to 'reply' to ourselves instead of our Nym address.
    // we know there will be a sender_tag since the sdk sends SURBs along with messages by default.
    println!("Waiting for message\n");

    // get the actual message - discard the empty vec sent along with a potential SURB topup request
    let mut message: Vec<ReconstructedMessage> = Vec::new();
    while let Some(new_message) = client.wait_for_messages().await {
        if new_message.is_empty() {
            continue;
        }
        message = new_message;
        break;
    }

    let mut parsed = String::new();
    if let Some(r) = message.first() {
        parsed = String::from_utf8(r.message.clone()).unwrap();
    }
    // parse sender_tag: we will use this to reply to sender without needing their Nym address
    let return_recipient: AnonymousSenderTag = message[0].sender_tag.unwrap();
    println!(
        "\nReceived the following message: {} \nfrom sender with surb bucket {}",
        parsed, return_recipient
    );

    // reply to self with it: note we use `send_str_reply` instead of `send_str`
    println!("Replying with using SURBs");
    client
        .send_reply(return_recipient, "hi an0n!")
        .await
        .unwrap();

    println!("Waiting for message (once you see it, ctrl-c to exit)\n");
    client
        .on_messages(|msg| println!("\nReceived: {}", String::from_utf8_lossy(&msg.message)))
        .await;
}

Importing and using a custom network topology

If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the Mainnet Nym-API (examples/custom_topology_provider.rs).

There are two ways to do this:

Import a custom Nym API endpoint

If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood):

// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0

use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;
use nym_topology::provider_trait::{async_trait, TopologyProvider};
use nym_topology::{nym_topology_from_detailed, NymTopology};
use url::Url;

struct MyTopologyProvider {
    validator_client: nym_validator_client::client::NymApiClient,
}

impl MyTopologyProvider {
    fn new(nym_api_url: Url) -> MyTopologyProvider {
        MyTopologyProvider {
            validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
        }
    }

    async fn get_topology(&self) -> NymTopology {
        let mixnodes = self
            .validator_client
            .get_cached_active_mixnodes()
            .await
            .unwrap();

        // in our topology provider only use mixnodes that have mix_id divisible by 3
        // and have more than 100k nym (i.e. 100'000'000'000 unym) in stake
        // why? because this is just an example to showcase arbitrary uses and capabilities of this trait
        let filtered_mixnodes = mixnodes
            .into_iter()
            .filter(|mix| {
                mix.mix_id() % 3 == 0 && mix.total_stake() > "100000000000".parse().unwrap()
            })
            .collect::<Vec<_>>();

        let gateways = self.validator_client.get_cached_gateways().await.unwrap();

        nym_topology_from_detailed(filtered_mixnodes, gateways)
    }
}

#[async_trait]
impl TopologyProvider for MyTopologyProvider {
    // this will be manually refreshed on a timer specified inside mixnet client config
    async fn get_new_topology(&mut self) -> Option<NymTopology> {
        Some(self.get_topology().await)
    }
}

#[tokio::main]
async fn main() {
    nym_bin_common::logging::setup_logging();

    let nym_api = "https://validator.nymtech.net/api/".parse().unwrap();
    let my_topology_provider = MyTopologyProvider::new(nym_api);

    // Passing no config makes the client fire up an ephemeral session and figure things out on its own
    let mut client = mixnet::MixnetClientBuilder::new_ephemeral()
        .custom_topology_provider(Box::new(my_topology_provider))
        .build()
        .unwrap()
        .connect_to_mixnet()
        .await
        .unwrap();

    let our_address = client.nym_address();
    println!("Our client nym address is: {our_address}");

    // Send a message through the mixnet to ourselves
    client
        .send_plain_message(*our_address, "hello there")
        .await
        .unwrap();

    println!("Waiting for message (ctrl-c to exit)");
    client
        .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message)))
        .await;
}

Import a specific topology manually

If you aren’t running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually:

// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0

use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;
use nym_topology::mix::Layer;
use nym_topology::{mix, NymTopology};
use std::collections::BTreeMap;

#[tokio::main]
async fn main() {
    nym_bin_common::logging::setup_logging();

    // Passing no config makes the client fire up an ephemeral session and figure shit out on its own
    let mut client = mixnet::MixnetClient::connect_new().await.unwrap();
    let starting_topology = client.read_current_topology().await.unwrap();

    // but we don't like our default topology, we want to use only those very specific, hardcoded, nodes:
    let mut mixnodes = BTreeMap::new();
    mixnodes.insert(
        1,
        vec![mix::Node {
            mix_id: 63,
            owner: None,
            host: "172.105.92.48".parse().unwrap(),
            mix_host: "172.105.92.48:1789".parse().unwrap(),
            identity_key: "GLdR2NRVZBiCoCbv4fNqt9wUJZAnNjGXHkx3TjVAUzrK"
                .parse()
                .unwrap(),
            sphinx_key: "CBmYewWf43iarBq349KhbfYMc9ys2ebXWd4Vp4CLQ5Rq"
                .parse()
                .unwrap(),
            layer: Layer::One,
            version: "1.1.0".into(),
        }],
    );
    mixnodes.insert(
        2,
        vec![mix::Node {
            mix_id: 23,
            owner: None,
            host: "178.79.143.65".parse().unwrap(),
            mix_host: "178.79.143.65:1789".parse().unwrap(),
            identity_key: "4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz"
                .parse()
                .unwrap(),
            sphinx_key: "8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG"
                .parse()
                .unwrap(),
            layer: Layer::Two,
            version: "1.1.0".into(),
        }],
    );
    mixnodes.insert(
        3,
        vec![mix::Node {
            mix_id: 66,
            owner: None,
            host: "139.162.247.97".parse().unwrap(),
            mix_host: "139.162.247.97:1789".parse().unwrap(),
            identity_key: "66UngapebhJRni3Nj52EW1qcNsWYiuonjkWJzHFsmyYY"
                .parse()
                .unwrap(),
            sphinx_key: "7KyZh8Z8KxuVunqytAJ2eXFuZkCS7BLTZSzujHJZsGa2"
                .parse()
                .unwrap(),
            layer: Layer::Three,
            version: "1.1.0".into(),
        }],
    );

    // but we like the available gateways, so keep using them!
    // (we like them because the author of this example is too lazy to use the same hardcoded gateway
    // during client initialisation to make sure we are able to send to ourselves : )  )
    let custom_topology = NymTopology::new(mixnodes, starting_topology.gateways().to_vec());

    client.manually_overwrite_topology(custom_topology).await;

    // and everything we send now should only ever go via those nodes

    let our_address = client.nym_address();
    println!("Our client nym address is: {our_address}");

    // Send a message through the mixnet to ourselves
    client
        .send_plain_message(*our_address, "hello there")
        .await
        .unwrap();

    println!("Waiting for message (ctrl-c to exit)");
    client
        .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message)))
        .await;
}

Socks Proxy

There is also the option to embed the socks5-client into your app code (examples/socks5.rs):

Info

If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start if they can speak SOCKS5, 4a, or 4.

use nym_sdk::mixnet;

#[tokio::main]
async fn main() {
    nym_bin_common::logging::setup_logging();

    println!("Connecting receiver");
    let mut receiving_client = mixnet::MixnetClient::connect_new().await.unwrap();

    let socks5_config = mixnet::Socks5::new(receiving_client.nym_address().to_string());
    let sending_client = mixnet::MixnetClientBuilder::new_ephemeral()
        .socks5_config(socks5_config)
        .build()
        .unwrap();

    println!("Connecting sender");
    let sending_client = sending_client.connect_to_mixnet_via_socks5().await.unwrap();

    let proxy = reqwest::Proxy::all(sending_client.socks5_url()).unwrap();
    let reqwest_client = reqwest::Client::builder().proxy(proxy).build().unwrap();
    tokio::spawn(async move {
        println!("Sending socks5-wrapped http request");
        // Message should be sent through the mixnet, via socks5
        // We don't expect to get anything, as there is no network requester on the other end
        reqwest_client.get("https://nymtech.net").send().await.ok()
    });

    println!("Waiting for message");
    if let Some(received) = receiving_client.wait_for_messages().await {
        for r in received {
            println!(
                "Received socks5 message requesting for endpoint: {}",
                String::from_utf8_lossy(&r.message[10..27])
            );
        }
    }

    receiving_client.disconnect().await;
    sending_client.disconnect().await;
}

Send and Receive in Different Tasks

If you need to split the different actions of your client across different tasks, you can do so like this:

// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0

use futures::StreamExt;
use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;

#[tokio::main]
async fn main() {
    nym_bin_common::logging::setup_logging();

    // Passing no config makes the client fire up an ephemeral session and figure stuff out on its own
    let mut client = mixnet::MixnetClient::connect_new().await.unwrap();

    // Be able to get our client address
    let our_address = *client.nym_address();
    println!("Our client nym address is: {our_address}");

    let sender = client.split_sender();

    // receiving task
    let receiving_task_handle = tokio::spawn(async move {
        if let Some(received) = client.next().await {
            println!("Received: {}", String::from_utf8_lossy(&received.message));
        }

        client.disconnect().await;
    });

    // sending task
    let sending_task_handle = tokio::spawn(async move {
        sender
            .send_plain_message(our_address, "hello from a different task!")
            .await
            .unwrap();
    });

    // wait for both tasks to be done
    println!("waiting for shutdown");
    sending_task_handle.await.unwrap();
    receiving_task_handle.await.unwrap();
}

Coconut credential generation

The following code shows how you can use the SDK to create and use a credential representing paid bandwidth on the Sandbox testnet.

use futures::StreamExt;
use nym_network_defaults::setup_env;
use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    nym_bin_common::logging::setup_logging();
    // right now, only sandbox has coconut setup
    // this should be run from the `sdk/rust/nym-sdk` directory
    setup_env(Some("../../../envs/sandbox.env"));

    let sandbox_network = mixnet::NymNetworkDetails::new_from_env();
    let mnemonic = String::from("my super secret mnemonic");

    let mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral()
        .network_details(sandbox_network)
        .enable_credentials_mode()
        .build()?;

    let bandwidth_client = mixnet_client.create_bandwidth_client(mnemonic)?;

    // Get a bandwidth credential worth 1000000 unym for the mixnet_client
    bandwidth_client.acquire(1000000).await?;

    // Connect using paid bandwidth credential
    let mut client = mixnet_client.connect_to_mixnet().await?;

    let our_address = client.nym_address();

    // Send a message throughout the mixnet to ourselves
    client
        .send_plain_message(*our_address, "hello there")
        .await?;

    println!("Waiting for message");
    let received = client.next().await.unwrap();
    println!("Received: {}", String::from_utf8_lossy(&received.message));

    client.disconnect().await;
    Ok(())
}

You can read more about Coconut credentials (also referred to as zk-Nym) here.

Example Cargo File

This file imports the basic requirements for running these pieces of example code, and can be used as the basis for your own cargo project.

[package]
name = "your_app"
version = "x.y.z"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
# Async runtime
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
# Used for (de)serialising incoming and outgoing messages 
serde = "1.0.152"
serde_json = "1.0.91"
# Nym clients, addressing, etc 
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-bin-common = { git = "https://github.com/nymtech/nym", branch = "master" }
nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", branch = "master" }
# Additional dependencies if you're interacting with Nyx or another Cosmos SDK blockchain
cosmrs = "=0.14.0"
nym-validator-client = { git = "https://github.com/nymtech/nym", branch = "master" }

# If you're building an app with a client and server / serivce this might be a useful structure for your repo 
[[bin]]
name = "client"
path = "bin/client.rs"

[[bin]]
name = "service"
path = "bin/service.rs"

Typescript SDK

The Typescript SDK allows developers to start building browser-based mixnet applications quickly, by simply importing the SDK into their code via NPM as they would any other Typescript library.

If you’d like to learn more, build apps or integrate Nym components using the TS SDK, visit the dedicated TS SDK Handbook.

Pre-built Binaries

The Github releases page has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere.

If the pre-built binaries don’t work or are unavailable for your system, you will need to build the platform yourself.

Building from Source

Nym runs on Mac OS X, Linux, and Windows. All nodes except the Desktop Wallet and NymConnect on Windows should be considered experimental - it works fine if you’re an app developer but isn’t recommended for running nodes.

Building Nym

Nym has two main codebases:

This page details how to build the main Nym platform code.

Prerequisites

  • Debian/Ubuntu: pkg-config, build-essential, libssl-dev, curl, jq, git
apt install pkg-config build-essential libssl-dev curl jq git
  • Arch/Manjaro: base-devel
pacman -S base-devel
  • Mac OS X: pkg-config , brew, openss1, protobuf, curl, git Running the following the script installs Homebrew and the above dependencies:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  • Rust & cargo >= 1.66

We recommend using the Rust shell script installer. Installing cargo from your package manager (e.g. apt) is not recommended as the packaged versions are usually too old.

If you really don’t want to use the shell script installer, the Rust installation docs contain instructions for many platforms.

Download and build Nym binaries

The following commands will compile binaries into the nym/target/release directory:

rustup update
git clone https://github.com/nymtech/nym.git
cd nym

git reset --hard # in case you made any changes on your branch
git pull # in case you've checked it out before

git checkout master # master branch has the latest release version: `develop` will most likely be incompatible with deployed public networks

cargo build --release # build your binaries with **mainnet** configuration

Quite a bit of stuff gets built. The key working parts for devs are the Client binaries and the CLI tool:

You cannot build from GitHub’s .zip or .tar.gz archive files on the releases page - the Nym build scripts automatically include the current git commit hash in the built binary during compilation, so the build will fail if you use the archive code (which isn’t a Git repository). Check the code out from github using git clone instead.

Websocket Client

The Nym Websocket Client was built in the building nym section. If you haven’t yet built Nym and want to run the code on this page, go there first.

Current version

1.1.38

You can run this client as a standalone process and pipe traffic into it to be sent through the mixnet. This is useful if you’re building an application in a language other than Typescript or Rust and cannot utilise one of the SDKs.

You can find the code for this client here.

Setup & Run

Viewing command help

You can check that your binaries are properly compiled with:

./nym-client --help

Console output

Implementation of the Nym Client

Usage: nym-client [OPTIONS] <COMMAND>

Commands:
  init               Initialise a Nym client. Do this first!
  run                Run the Nym client with provided configuration client optionally overriding set parameters
  import-credential  Import a pre-generated credential
  list-gateways      List all registered with gateways
  add-gateway        Add new gateway to this client
  switch-gateway     Change the currently active gateway. Note that you must have already registered with the new gateway!
  build-info         Show build information of this binary
  completions        Generate shell completions
  generate-fig-spec  Generate Fig specification
  help               Print this message or the help of the given subcommand(s)

Options:
  -c, --config-env-file <CONFIG_ENV_FILE>  Path pointing to an env file that configures the client
      --no-banner                          Flag used for disabling the printed banner in tty
  -h, --help                               Print help
  -V, --version                            Print version

The two most important commands you will issue to the client are:

  • init - initalise a new client instance.
  • run - run a mixnet client process.

You can check the necessary parameters for the available commands by running:

./nym-client <command> --help

Initialising your client

Before you can use the client, you need to initalise a new instance of it. Each instance of the client has its own public/private keypair, and connects to its own gateway node. Taken together, these 3 things (public/private keypair + gateway node identity key) make up an app’s identity.

Initialising a new client instance can be done with the following command:

./nym-client init --id example-client

Console output

 Version: 1.1.38
ID: example-client
Identity key: BbitjMqc7F4qqAkFrbo3vmYtDn1QzwHSZfBEgyndeYrt
Encryption: 3LPDasjaTwrjt8yUHjdfBif3t2zja6FgWQdLh4LPGgs5
Gateway ID: Cf96a8TdEVerg55Enn1DabJgZfhnwAFcyuPqp9xEFHF6
Gateway: ws://148.113.16.39:9000/
Registered at: 2024-07-24 12:47:47.008832832 +00:00:00
Client listening port: 1977
Address of this client: BbitjMqc7F4qqAkFrbo3vmYtDn1QzwHSZfBEgyndeYrt.3LPDasjaTwrjt8yUHjdfBif3t2zja6FgWQdLh4LPGgs5@Cf96a8TdEVerg55Enn1DabJgZfhnwAFcyuPqp9xEFHF6

The --id in the example above is a local identifier so that you can name your clients; it is never transmitted over the network.

There is an optional --gateway flag that you can use if you want to use a specific gateway. The supplied argument is the Identity Key of the gateway you wish to use, which can be found on the mainnet Network Explorer or Sandbox Testnet Explorer depending on which network you are on.

Not passing this argument will randomly select a gateway for your client.

Running your client

You can run the initalised client by doing this:

./nym-client run --id example-client

When you run the client, it immediately starts generating (fake) cover traffic and sending it to the mixnet.

When the client is first started, it will reach out to the Nym network’s validators, and get a list of available Nym nodes (gateways, mixnodes, and validators). We call this list of nodes the network topology. The client does this so that it knows how to connect, register itself with the network, and know which mixnodes it can route Sphinx packets through.

Configuration

Default listening port

The Nym native client exposes a websocket interface that your code connects to. To program your app, choose a websocket library for whatever language you’re using. The default websocket port is 1977, you can override that in the client config if you want.

You can either set this via the --port flag at init or run, or you can manually edit ~/.nym/clients/<CLIENT-ID>/config/config.toml.

Remember to restart your client if you change your listening port via editing your config file.

Choosing a Gateway

By default your client will choose a random gateway to connect to.

However, there are several options for choosing a gateway, if you do not want one that is randomly assigned to your client:

  • If you wish to connect to a specific gateway, you can specify this with the --gateway flag when running init.
  • You can also choose a gateway based on its location relative to your client. This can be done by appending the --latency-based-routing flag to your init command. This command means that to select a gateway, your client will:
    • fetch a list of all available gateways
    • send few ping messages to all of them, and measure response times.
    • create a weighted distribution to randomly choose one, favouring ones with lower latency.

Note this doesn’t mean that your client will pick the closest gateway to you, but it will be far more likely to connect to gateway with a 20ms ping rather than 200ms

Configuring your client

When you initalise a client instance, a configuration directory will be generated and stored in $HOME_DIR/.nym/clients/<client-name>/.

tree $HOME/<user>/.nym/clients/example-client
├── config
│   └── config.toml
└── data
    ├── ack_key.pem
    ├── gateway_shared.pem
    ├── private_encryption.pem
    ├── private_identity.pem
    ├── public_encryption.pem
    └── public_identity.pem

The config.toml file contains client configuration options, while the two pem files contain client key information.

The generated files contain the client name, public/private keypairs, and gateway address. The name <client_id> in the example above is just a local identifier so that you can name your clients.

Configuring your client for Docker

By default, the native client listens to host 127.0.0.1. However this can be an issue if you wish to run a client in a Dockerized environment, where it can be convenenient to listen on a different host such as 0.0.0.0.

You can set this via the --host flag during either the init or run commands.

Alternatively, a custom host can be set in the config.toml file under the socket section. If you do this, remember to restart your client process.

Using Your Client

The Nym native client exposes a websocket interface that your code connects to. The default websocket port is 1977, you can override that in the client config if you want.

Once you have a websocket connection, interacting with the client involves piping messages down the socket and listening for incoming messages.

Message Requests

There are a number of message types that you can send up the websocket as defined here:

#[derive(Debug)]
pub enum ClientRequest {
    /// The simplest message variant where no additional information is attached.
    /// You're simply sending your `data` to specified `recipient` without any tagging.
    ///
    /// Ends up with `NymMessage::Plain` variant
    Send {
        recipient: Recipient,
        message: Vec<u8>,
        connection_id: Option<u64>,
    },

    /// Create a message used for a duplex anonymous communication where the recipient
    /// will never learn of our true identity. This is achieved by carefully sending `reply_surbs`.
    ///
    /// Note that if reply_surbs is set to zero then
    /// this variant requires the client having sent some reply_surbs in the past
    /// (and thus the recipient also knowing our sender tag).
    ///
    /// Ends up with `NymMessage::Repliable` variant
    SendAnonymous {
        recipient: Recipient,
        message: Vec<u8>,
        reply_surbs: u32,
        connection_id: Option<u64>,
    },

    /// Attempt to use our internally received and stored `ReplySurb` to send the message back
    /// to specified recipient whilst not knowing its full identity (or even gateway).
    ///
    /// Ends up with `NymMessage::Reply` variant
    Reply {
        sender_tag: AnonymousSenderTag,
        message: Vec<u8>,
        connection_id: Option<u64>,
    },

    SelfAddress,

    ClosedConnection(u64),

    GetLaneQueueLength(u64),
}

Getting your own address

When you start your app, it is best practice to ask the native client to tell you what your own address is (from the generated configuration files - see here for more on Nym’s addressing scheme). If you are running a service, you need to do this in order to know what address to give others. In a client-side piece of code you can also use this as a test to make sure your websocket connection is running smoothly. To do this, send:

{
  "type": "selfAddress"
}

You’ll receive a response of the format:

{
  "type": "selfAddress",
  "address": "your address" // e.g. "71od3ZAupdCdxeFNg8sdonqfZTnZZy1E86WYKEjxD4kj@FWYoUrnKuXryysptnCZgUYRTauHq4FnEFu2QGn5LZWbm"
}

See here for an example of this being used.

Note that all the pieces of native client example code begin with printing the selfAddress. Examples exist for Rust, Go, Javascript, and Python.

Sending text

If you want to send text information through the mixnet, format a message like this one and poke it into the websocket:

{
  "type": "send",
  "message": "the message",
  "recipient": "71od3ZAupdCdxeFNg8sdonqfZTnZZy1E86WYKEjxD4kj@FWYoUrnKuXryysptnCZgUYRTauHq4FnEFu2QGn5LZWbm"
}

In some applications, e.g. where people are chatting with friends who they know, you might want to include unencrypted reply information in the message field. This provides an easy way for the receiving chat to then turn around and send a reply message:

{
  "type": "send",
  "message": {
    "sender": "198427b63ZAupdCdxeFNg8sdonqfZTnZZy1E86WYKEjxD4kj@FWYoUrnKuXryysptnCZgUYRTauHq4FnEFu2QGn5LZWbm",
    "chatMessage": "hi julia!"
  },
  "recipient": "71od3ZAupdCdxeFNg8sdonqfZTnZZy1E86WYKEjxD4kj@FWYoUrnKuXryysptnCZgUYRTauHq4FnEFu2QGn5LZWbm"
}

If that fits your security model, good. However, will probably be the case that you want to send anonymous replies using Single Use Reply Blocks (SURBs).

You can read more about SURBs here but in short they are ways for the receiver of this message to anonymously reply to you - the sender - without them having to know your client address.

Your client will send along a number of replySurbs to the recipient of the message. These are pre-addressed Sphinx packets that the recipient can write to the payload of (i.e. write response data to), but not view the final destination of. If the recipient is unable to fit the response data into the bucket of SURBs sent to it, it will use a SURB to request more SURBs be sent to it from your client.

{
    "type": "sendAnonymous",
    "message": "something you want to keep secret", 
    "recipient": "71od3ZAupdCdxeFNg8sdonqfZTnZZy1E86WYKEjxD4kj@FWYoUrnKuXryysptnCZgUYRTauHq4FnEFu2QGn5LZWbm", 
    "replySurbs": 20 // however many reply SURBs to send along with your message
}

See ‘Replying to SURB Messages’ below for an example of how to deal with incoming messages that have SURBs attached.

Deciding on the amount of SURBs to generate and send along with outgoing messages depends on the expected size of the reply. You might want to send a lot of SURBs in order to make sure you get your response as quickly as possible (but accept the minor additional latency when sending, as your client has to generate and encrypt the packets), or you might just send a few (e.g. 20) and then if your response requires more SURBs, send them along, accepting the additional latency in getting your response.

Sending binary data

You can also send bytes instead of JSON. For that you have to send a binary websocket frame containing a binary encoded Nym ClientRequest containing the same information.

As a response the native-client will send a ServerResponse to be decoded. See Message Responses below for more.

You can find examples of sending and receiving binary data in the code examples, and an example project from the Nym community BTC-BC: Bitcoin transaction transmission via Nym, a client and service provider written in Rust.

Replying to SURB messages

Each bucket of replySURBs, when received as part of an incoming message, has a unique session identifier, which only identifies the bucket of pre-addressed packets. This is necessary to make sure that your app is replying to the correct people with the information meant for them in a situation where multiple clients are sending requests to a single service.

Constructing a reply with SURBs looks something like this (where senderTag was parsed from the incoming message)

{
    "type": "reply",
    "message": "reply you also want to keep secret",
    "senderTag": "the sender tag you parsed from the incoming message"
}

Error messages

Errors from the app’s client, or from the gateway, will be sent down the websocket to your code in the following format:

{
  "type": "error",
  "message": "string message"
}

LaneQueueLength

This is currently only used in the Socks Client to keep track of the number of Sphinx packets waiting to be sent to the mixnet via being slotted amongst cover traffic. As this value becomes larger, the client signals to the application it should slow down the speed with which it writes to the proxy. This is to stop situations arising whereby an app connected to the client appears as if it has sent (e.g.) a bunch of messages and is awaiting a reply, when they in fact have not been sent through the mixnet yet.

Message Responses

Responses to your messages are defined here:

pub enum ServerResponse {
    Received(ReconstructedMessage),
    SelfAddress(Box<Recipient>),
    LaneQueueLength { lane: u64, queue_length: usize },
    Error(error::Error),
}

Examples

The Nym monorepo includes websocket client example code for Rust, Go, Javacript, and Python, all of which can be found here.

Rust users can run the examples with cargo run --example <rust_file>.rs, as the examples are not organised in the same way as the other examples, due to already being inside a Cargo project.

All of these code examples will do the following:

  • connect to a running websocket client on port 1977
  • format a message to send in either JSON or Binary format. Nym messages have defined JSON formats.
  • send the message into the websocket. The native client packages the message into a Sphinx packet and sends it to the mixnet
  • wait for confirmation that the message hit the native client
  • wait to receive messages from other Nym apps

By varying the message content, you can easily build sophisticated service provider apps. For example, instead of printing the response received from the mixnet, your service provider might take some action on behalf of the user - perhaps initiating a network request, a blockchain transaction, or writing to a local data store.

You can find an example of building both frontend and service provider code with the websocket client in the Simple Service Provider Tutorial in the Developer Portal.

Socks5 Client

The Nym Socks5 Client was built in the building nym section. If you haven’t yet built Nym and want to run the code on this page, go there first.

Current version

1.1.38

What is this client for?

Many existing applications are able to use either the SOCKS4, SOCKS4A, or SOCKS5 proxy protocols. If you want to send such an application’s traffic through the mixnet, you can use the nym-socks5-client to bounce network traffic through the Nym network, like this:

                                                                              External Systems:
                                                                                     +--------------------+
                                                                             |------>| Monero blockchain  |
                                                                             |       +--------------------+
                                                                             |       +--------------------+
                                                                             |------>|    Email server    |
                                                                             |       +--------------------+
                                                                             |       +--------------------+
                                                                             |------>|    RPC endpoint    |
                                                                             |       +--------------------+
                                                                             |       +--------------------+
                                                                             |------>|       Website      |
                                                                             |       +--------------------+
                                                                             |       +--------------------+
  +----------------------------------+                                       |------>|       etc...       |
  | Mixnet:                          |                                       |       +--------------------+
  |       * Gateway your client is   |                                       |
  |       connected to               |          +--------------------+       |
  |       * Mix nodes 1 -> 3         |<-------->| Network requester  |<------+
  |       * Gateway that network     |          +--------------------+
  |       requester is connected to  |
  +----------------------------------+
           ^
           |
           |
           |
           |
           v
 +-------------------+
 | +---------------+ |
 | |  Nym client   | |
 | +---------------+ |
 |         ^         |
 |         |         |
 |         |         |
 |         |         |
 |         v         |
 | +---------------+ |
 | | Your app code | |
 | +---------------+ |
 +-------------------+
  Your Local Machine

There are 2 pieces of software that work together to send SOCKS traffic through the mixnet: the nym-socks5-client, and the nym-network-requester.

The nym-socks5-client allows you to do the following from your local machine:

  • Take a TCP data stream from a application that can send traffic via SOCKS5.
  • Chop up the TCP stream into multiple Sphinx packets, assigning sequence numbers to them, while leaving the TCP connection open for more data
  • Send the Sphinx packets through the mixnet to a network requester. Packets are shuffled and mixed as they transit the mixnet.

The nym-network-requester then reassembles the original TCP stream using the packets’ sequence numbers, and make the intended request. It will then chop up the response into Sphinx packets and send them back through the mixnet to your nym-socks5-client. The application will then receive its data, without even noticing that it wasn’t talking to a “normal” SOCKS5 proxy!

Client setup

Viewing command help

You can check that your binaries are properly compiled with:

./nym-socks5-client --help

Console output

A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address

Usage: nym-socks5-client [OPTIONS] <COMMAND>

Commands:
  init               Initialise a Nym client. Do this first!
  run                Run the Nym client with provided configuration client optionally overriding set parameters
  import-credential  Import a pre-generated credential
  list-gateways      List all registered with gateways
  add-gateway        Add new gateway to this client
  switch-gateway     Change the currently active gateway. Note that you must have already registered with the new gateway!
  build-info         Show build information of this binary
  completions        Generate shell completions
  generate-fig-spec  Generate Fig specification
  help               Print this message or the help of the given subcommand(s)

Options:
  -c, --config-env-file <CONFIG_ENV_FILE>  Path pointing to an env file that configures the client
      --no-banner                          Flag used for disabling the printed banner in tty
  -h, --help                               Print help
  -V, --version                            Print version

You can check the necessary parameters for the available commands by running:

./nym-client <COMMAND> --help

Initialising a new client instance

Before you can use the client, you need to initalise a new instance of it, which can be done with the following command:

./nym-socks5-client init --id docs-example --use-reply-surbs true --provider Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf

Console output

An error occurred: this client (id: 'docs-example') has already been initialised before. If you want to add additional gateway, use `add-gateway` command

The --id in the example above is a local identifier so that you can name your clients and keep track of them on your local system; it is never transmitted over the network.

The --use-reply-surbs field denotes whether you wish to send SURBs along with your request. It defaults to false, we are explicitly setting it as true. It defaults to false for compatibility with older versions of the Network Requester.

The --provider field needs to be filled with the Nym address of a Network Requester that can make network requests on your behalf. If you don’t want to run your own you can select one from the mixnet explorer by copying its Client ID and using this as the value of the --provider flag. Alternatively, you could use this list.

Since the nodes on this list are the infrastructure for Nymconnect they will support all apps on the default whitelist: Keybase, Telegram, Electrum, Blockstream Green, and Helios.

Choosing a Gateway

By default - as in the example above - your client will choose a random gateway to connect to.

However, there are several options for choosing a gateway, if you do not want one that is randomly assigned to your client:

  • If you wish to connect to a specific gateway, you can specify this with the --gateway flag when running init.
  • You can also choose a gateway based on its location relative to your client. This can be done by appending the --latency-based-selection flag to your init command. This command means that to select a gateway, your client will:
    • fetch a list of all availiable gateways
    • send few ping messages to all of them, and measure response times.
    • create a weighted distribution to randomly choose one, favouring ones with lower latency.

Note this doesn’t mean that your client will pick the closest gateway to you, but it will be far more likely to connect to gateway with a 20ms ping rather than 200ms

Configuring your client

When you initalise a client instance, a configuration directory will be generated and stored in $HOME_DIR/.nym/socks5-clients/<client-name>/.

tree $HOME/<user>/.nym/socks5-clients/docs-example
├── config
│   └── config.toml
└── data
    ├── ack_key.pem
    ├── credentials_database.db
    ├── gateway_shared.pem
    ├── persistent_reply_store.sqlite
    ├── private_encryption.pem
    ├── private_identity.pem
    ├── public_encryption.pem
    └── public_identity.pem

The config.toml file contains client configuration options, while the two pem files contain client key information.

The generated files contain the client name, public/private keypairs, and gateway address. The name <client_id> in the example above is just a local identifier so that you can name your clients.

Configuring your client for Docker

By default, the native client listens to host 127.0.0.1. However this can be an issue if you wish to run a client in a Dockerized environment, where it can be convenenient to listen on a different host such as 0.0.0.0.

You can set this via the --host flag during either the init or run commands.

Alternatively, a custom host can be set in the config.toml file under the socket section. If you do this, remember to restart your client process.

Running the socks5 client

You can run the initialised client by doing this:

./nym-socks5-client run --id docs-example

Automating your socks5 client with systemd

Create a service file for the socks5 client at /etc/systemd/system/nym-socks5-client.service:

[Unit]
Description=Nym Socks5 Client
StartLimitInterval=350
StartLimitBurst=10

[Service]
User=nym # replace this with whatever user you wish
LimitNOFILE=65536
ExecStart=/home/nym/nym-socks5-client run --id <your_id>
KillSignal=SIGINT
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target

Now enable and start your socks5 client:

systemctl enable nym-socks5-client.service
systemctl start nym-socks5-client.service
# you can always check your socks5 client has succesfully started with:
systemctl status nym-socks5-client.service

Using your Socks5 Client

After completing the steps above, your local Socks5 Client will be listening on localhost:1080 ready to proxy traffic to the Network Requester set as the --provider when initialising.

When trying to connect your app, generally the proxy settings are found in settings->advanced or settings->connection.

Here is an example of setting the proxy connecting in Blockstream Green:

Blockstream Green settings

Most wallets and other applications will work basically the same way: find the network proxy settings, enter the proxy url (host: localhost, port: 1080).

In some other applications, this might be written as localhost:1080 if there’s only one proxy entry field.

Useful Commands

no-banner

Adding --no-banner startup flag will prevent Nym banner being printed even if run in tty environment.

build-info

A build-info command prints the build information like commit hash, rust version, binary version just like what command --version does. However, you can also specify an --output=json flag that will format the whole output as a json, making it an order of magnitude easier to parse.

Setup

nym-socks5-client now also supports SOCKS4 and SOCKS4A protocols as well as SOCKS5.

The Nym socks5 client allows you to proxy traffic from a desktop application through the mixnet, meaning you can send and receive information from remote application servers without leaking metadata which can be used to deanonymise you, even if you’re using an encrypted application such as Signal.

Setup and Run

Download or compile socks5 client

If you are using OSX or a Debian-based operating system, you can download the nym-socks5-client binary from our Github releases page.

If you are using a different operating system, head over to the Building from Source page for instructions on how to build the repository from source.

Initialise your socks5 client

To initialise your nym-socks5-client you need to have an address of a Network Requester (NR). Nowadays NR is part of every Exit Gateway (nym-node --mode exit-gateway). The easiest way to get a NR address is to visit harbourmaster.nymtech.net and scroll down to SOCKS5 Network Requesters table. There you can filter the NR by Gateways identity address, and other options.

Use the following command to initialise nym-socs5-client where <ID> can be anything you want (it’s only for local config file storage) and <PROVIDER> is suplemented with a NR address:

./nym-socks5-client init --id <ID> --provider <PROVIDER>

Tip

Another option to find a NR address associated with a Gateway is to query nodes Self Described API endpoint where the NR address is noted like in this example:

"network_requester": {
    "address": "CyuN49nkyeuiLohSpV5A1MbSqcugHLJQ95B5HooCpjv8.CguTh45Vp99QuGWZRBKpBjZDQbsJaHaXqAMGyc4Qhkzp@2w5RduXRqxKgHt1wtp4qGA4AfXaBj8TuUj1LvcPe2Ea1",
    "uses_exit_policy": true
}

Start your socks5 client

Now your client is initialised, start it with the following:

./nym-socks5-client run --id <ID>

Using Your Client

Proxying traffic

After completing the steps above, your local nym-socks5-client will be listening on localhost:1080 ready to proxy traffic to the Network Requester set as the --provider when initialising.

When trying to connect your app, generally the proxy settings are found in settings->advanced or settings->connection.

Here is an example of setting the proxy connecting in Blockstream Green:

Blockstream Green settings

Most wallets and other applications will work basically the same way: find the network proxy settings, enter the proxy url (host: localhost, port: 1080).

In some other applications, this might be written as localhost:1080 if there’s only one proxy entry field.

Supported Applications

Any application which can be redirected over Socks5 proxy should work. Nym community has been successfully running over Nym Mixnet these applications:

  • Bitcoin Electrum wallet
  • Monero wallet (GUI and CLI with monerod)
  • Telegram chat
  • Element/Matrix chat
  • Firo wallet
  • Blockstream Green

DarkFi’s ircd chat was previously supported: they have moved to DarkIrc: whether the existing integration work is still operational needs to be tested.

Keep in mind that Nym has been developing a new client NymVPN (GUI and CLI) routing all users traffic through the Mixnet.

Further reading

If you want to dig more into the architecture and use of the socks5 client check out its documentation here.

Webassembly Client

The Nym webassembly client allows any webassembly-capable runtime to build and send Sphinx packets to the Nym network, for uses in edge computing and browser-based applications.

This is currently packaged and distributed for ease of use via the Nym Typescript SDK library. We imagine most developers will use this client via the SDK for ease.

The webassembly client allows for the easy creation of Sphinx packets from within mobile apps and browser-based client-side apps (including Electron or similar).

Building apps with Webassembly Client

Check out the Typescript SDK docs for examples of usage.

There are also example applications located in the clients/webassembly directory in the main Nym platform codebase.

Think about what you’re sending!

Caution

Think about what information your app sends. That goes for whatever you put into your Sphinx packet messages as well as what your app’s environment may leak.

Whenever you write client PEAPPs using HTML/JavaScript, we recommend that you do not load external resources from CDNs. Webapp developers do this all the time, to save load time for common resources, or just for convenience. But when you’re writing privacy apps it’s better not to make these kinds of requests. Pack everything locally.

If you use only local resources within your Electron app or your browser extensions, explicitly encoding request data in a Sphinx packet does protect you from the normal leakage that gets sent in a browser HTTP request. There’s a lot of stuff that leaks when you make an HTTP request from a browser window. Luckily, all that metadata and request leakage doesn’t happen in Nym, because you’re choosing very explicitly what to encode into Sphinx packets, instead of sending a whole browser environment by default.

Stub: Updates Coming Soon!

There is a lot of development work ongoing with our clients; as such the old tutorials that were here got quite out of date.

However, you can still access the old tutorial codebases as well as the markdown files in the tutorial-archives/ directory in the developer portal docs repo if you want.

More up to date tutorials will be coming soon for using RPC and gRPC, mixfetch, as well as using the FFI libraries for interacting with the Mixnet via C++ and Go.

Developers who are searching for example code can use the following list as the current ‘best practices’:

  • Generic traffic transport: the zcash-rpc-demo repo, although here used to only pipe RPC traffic, is a proof of concept ‘generic’ mixnet piping example which exposes a TPC Socket on the client side for incoming traffic, pipes this through the mixnet, and then streams TCP packets ‘out’ the other side.
  • In-browser usage: see the browser examples page for examples using mixFetch.

Custom Services

Custom services involve two pieces of code that communicate via the mixnet: a client, and a custom server/service. This custom service will most likely interact with the wider internet / a clearnet service on your behalf, with the mixnet between you and the service, acting as a privacy shield.

The current model of relying on a Service Provider has some issues, such as additional complexity in deployment and maintenance, as well as creating potential chokepoints for app traffic. Work is going on (in the open in our monorepo ofc) to start removing this requirement as much as possible, by allowing for the creation of packet-contents in such a way that the existing Network Requester/Exit Gateway infrastructure can support network requests in a similar way to mixFetch. More on this as and when it is released.

  • Nym Zcash RPC demo, although used to only pipe RPC traffic, is a proof of concept ‘generic’ mixnet piping example which exposes a TPC Socket on the client side for incoming traffic, pipes this through the mixnet, and then streams TCP packets ‘out’ the other side. A good example of non-app-specific traffic transport which developers could also quite easily use as a template for their own app-specific work.

  • PasteNym is a private pastebin alternative. It involves a browser-based frontend utilising the Typescript SDK and a Python-based backend service communicating with a standalone Nym Websocket Client. If you’re a Python developer, start here!.

  • Nostr-Nym is another application written by NoTrustVerify, standing between mixnet users and a Nostr server in order to protect their metadata from being revealed when gossiping. Useful for Go and Python developers.

  • Spook and Nym-Ethtx are both examples of Ethereum transaction broadcasters utilising the mixnet, written in Rust. Since they were written before the release of the Rust SDK, they utilise standalone clients to communicate with the mixnet.

  • NymDrive is an early proof of concept application for privacy-enhanced file storage on IPFS. JS and CSS, and a good example of packaging as an Electrum app.

Apps Using Network Requesters

These applications utilise custom app logic in the user-facing apps in order to communicate using the mixnet as a transport layer, without having to rely on custom server-side logic. Instead, they utilise existing Nym infrastructure - Network Requesters - with a custom whitelist addition.

If you are sending ‘normal’ application traffic, and/or don’t require and custom logic to be happening on the ‘other side’ of the mixnet, this is most likely the best option to take as a developer who wishes to privacy-enhance their application.

Nym will soon be switching from a whitelist-based approach to a blocklist-based approach to filtering traffic. As such, it will soon be even easier for developers to utilise the mixnet, as they will not have to run their own NRs or have to add their domains to the whitelist

  • DarkFi over Nym leverages Nym’s mixnet as a pluggable transport for DarkIRC, their p2p IRC variant. Users can anonymously connect to peers over the network, ensuring secure and private communication within the DarkFi ecosystem. Written in Rust.

  • MiniBolt is a complete guide to building a Bitcoin & Lightning full node on a personal computer. It has the capacity to run network traffic (transactions and syncing) over the mixnet, so you can privately sync your node and not expose your home IP to the wider world when interacting with the rest of the network!

  • Email over Nym is a set of configuration options to set up a Network Requester to send and recieve emails over Nym, using something like Thunderbird.

Browser only

With the Typescript SDK you can run a Nym client in a webworker - meaning you can connect to the mixnet through the browser without having to worry about any other code than your web framework.

  • Oreowallet have integrated mixFetch into their browser-extension wallet to run transactions through the mixnet.

  • NoTrustVerify have set up an example application using mixFetch to fetch crypto prices from CoinGecko over the mixnet.

  • There is a coconut-scheme based Credential Library playground here. This is a WASM implementation of our Coconut libraries which generate raw Coconut credentials. Test it to create and re-randomize your own credentials. For more information on what is happening here check out the Coconut docs.

  • You can find a browser-based ‘hello world’ chat app here. Either open in two browser windows and send messages to yourself, or share with a friend and send messages to each other through the mixnet.

Monorepo examples

As well as these examples, there are a bunch of examples for each SDK in the Nym monorepo.

Integration Options

If you’ve already gone through the different options and had a look at the tutorials, you have seen the possibilities available to you for quickly connecting existing application code to another Nym process.

Below are a resources that will be useful for either beginning to integrate mixnet functionality into existing application code or build a new app using Nym.

  • We suggest you begin with this integration decision tree. This will give you a better idea of what pieces of software (SDKs, standalone clients, service providers) your integration might involve, and what is currently possible to do with as little custom code as possible.

  • The integrations FAQ has a list of common questions regarding integrating with Nym and Nyx, as well as commonly required links.

If you wish to integrate with the Nyx blockchain to use NYM for payments, start with the payment integration page.

Integrating with Nyx for payments

If you want to integrate with Nym in order to send NYM tokens (for instance, if running a NYM <-> BTC swap application, or using NYM for payments), then you will need to interact with the Nyx blockchain.

Nyx is the blockchain supporting the Nym network, hosting both the NYM and NYX cryptocurrencies, the CosmWasm smart contracts keeping track of the network, and (coming soon) facilitating zk-Nym credential generation. It is built with the Cosmos SDK.

Interacting with the Nyx blockchain

Check out the integration options in the Integration FAQ.

Chain information and RPC endpoints

You can find most information required for integration in the Cosmos Chain Registry and Keplr Chain Registry repositories.

We recommend that users wanting to integrate with Nyx for cryptocurrency payments set up their own RPC Node, in order to be able to reliably query the blockchain and send transactions without having to worry about relying on 3rd party validators.

The guide to setting up an RPC node can be found here.

Integrations FAQ

On this page, you’ll find links and frequently asked questions on how to get started on integrating your project with Nym’s Mixnet and its blockchain, Nyx.

General Info

Codebase Info

Documentation Info

  • Documentation
  • Developer Portal - you are currently viewing the Developer Portal

Wallet Installation

The Nym wallet can be downloaded here.

You can find all the instructions related to setting up your wallet in the docs, as well as instructions on how to build the wallet if there is not a downloadable version built for your operating system.

What are the machine hardware requirements for Nym Wallet?

About 16GB of RAM is recommended for the wallet. However you can expect an average memory usage of ~100MB.

Interacting with the Nyx blockchain

Where can I find information on the blockchain, such as RPC endpoints?

You can find most information required for integration in the Cosmos Chain Registry and Keplr Chain Registry repositories.

How can I use JSON-RPC methods to interact with the Nyx blockchain?

There are multiple ways to use JSON-RPC methods to interact with the Nyx blockchain. Which method you use will depend on the type of application you are integrating Nyx interactions into.

  1. The standalone nyxd binary can be used for CLI wallets, interacting with smart contracts via the CLI, setting up RPC nodes, and even running validators. This is a version of the Cosmos Hub’s gaiad binary compiled with Nyx chain configuration, and is written in Go. Instructions on setting up the nyxd binary can be found here. This is recommended for more complex commands. For full documentation check the gaiad documentation.

  2. CosmJS is a Typescript library allowing for developers to interact with CosmosSDK blockchains from a Javascript or Typescript project. You can find it on Github here and an explainer of its functionality in the Cosmos Developer Portal. You can find a list of example apps which use CosmJS here.

  3. The Nym-CLI tool, a standalone rust binary which can be built and used according to the docs can be used in much the same way as nyxd. It is a bit simpler to use than the nyxd binary, but is not recommended for complex queries, and not all commands are currently implemented. A list of Nym CLI commands and example usage can be found here

How do I generate an address/mnemonic for users to interact with?

Nyxd

Use the following command, replacing your_id with the ID you want to use for your keypair:

./nyxd keys add your_id --chain-id=nyx --gas=auto --gas-adjustment=1.4 --fees=7000unym

Nym-CLI

./nym-cli account create

Both methods will generate a keypair and log the mnemonic in the console.

CosmJS

You can find example code for keypair generation here.

How to get block information like block height, block hash, block time as so on?

Nyxd

You would use one of the subcommands returned by this command:

./nyxd query tx --chain-id=nyx --gas=auto --gas-adjustment=1.4 --fees=7000unym

Nym-CLI

./nym-cli block current-height

CosmJS

CosmJS documentation can be found here. We will be working on example code blocks soon.

How to get account/address balance to check there is enough coins to withdraw?

Nyxd

./nyxd query bank balances <address> --chain-id=nyx --gas=auto --gas-adjustment=1.4 --fees=7000unym

Nym-CLI

./nym-cli account balance

CosmJS

CosmJS documentation can be found here. We will be working on example code blocks soon.

How do I transfer tokens to another address?

Nyxd

./nyxd tx bank send [from_key_or_address] [to_address] [amount] --chain-id=nyx --gas=auto --gas-adjustment=1.4 --fees=7000unym

Nym-CLI

./nym-cli account send TARGET_ADDRESS AMOUNT

CosmJS

CosmJS documentation can be found here. We will be working on example code blocks soon.

Does the address support the inclusion of a memo or destinationTag when doing the transfer?

Yes, it is supported.

Can I use my Ledger hardware wallet to interact with the Nyx blockchain?

Yes. Follow the instructions in the Ledger support for Nyx documentation.

Where can I find network details such as deployed smart contract addresses?

In the network defaults file.

NYM Token

The token used to reward mixnet infrastructure operators - NYM - is one of the native tokens of the Nyx blockchain. The other token is NYX.

NYM is used to incentivise the mixnet, whereas NYX is used to secure the Nyx blockchain via Validator staking.

Integration with Nym’s technology stack will most likely involve using NYM if you do need to interact with the Nyx blockchain and transfer tokens.

I’ve seen an ERC20 representation of NYM on Ethereum - what’s this and how do I use it?

We use the Gravity Bridge blockchain to bridge an ERC20 representation of NYM between the Cosmos ecosystem of IBC-enabled chains and Ethereum mainnet. Gravity Bridge is its own IBC-enabled CosmosSDK chain, which interacts with a smart contract deployed on Ethereum mainnet.

The ERC20 representation of NYM cannot be used with the mixnet; only the native Cosmos representation is usable for staking or bonding nodes.

If you need to transfer tokens across the bridge, we recommend users use Cosmostation’s spacestation.zone dApp with Metamask and Keplr.

What is Circulating Supply and how to find out the distribution amount?

Circulating supply is the total number of available NYM. NYM is currently present on the IBC-enabled Nyx blockchain, as well as in ERC20 form on Ethereum Mainnet.

The Validator API endpoints can be found via the Swagger Documentation. The following endpoints can be called to retrieve the correct distribution amount and circulating supply within Nym.

Using this API endpoint returns information about the circulating supply of Nym tokens:

/circulating-supply

Query Response:

{
    "total_supply": {
        "denom": "unym",
        "amount": "1000000000000000"
    },
    "mixmining_reserve": {
        "denom": "unym",
        "amount": "241105338883248"
    },
    "vesting_tokens": {
        "denom": "unym",
        "amount": "390255200928865"
    },
    "circulating_supply": {
        "denom": "unym",
        "amount": "368639460187887"
    }
}
  • total_supply- The total number of NYM tokens that have been created and can exist, including those that are currently in circulation and those that are reserved for various purposes.

  • mixmining_reserved- The number of NYM tokens that are reserved for the mixnet miners who help to power the Nym network.

  • vesting_tokens- The number of NYM tokens that are subject to vesting, meaning they are gradually released over time to certain stakeholders such as the team, advisors, and early investors.

  • circulating_supply- The number of NYM tokens that are currently in circulation and available to be traded on the open market, which is calculated by subtracting the mixmining_reserved and vesting_tokens from the total_supply.

Using this API endpoint returns the current value of the total supply of NYM tokens:

/circulating-supply/total-supply-value

Query Response:

1000000000.0

The maximum number of NYM tokens that can ever be created is 1 billion.

Using this API endpoint returns the current value of the circulating supply of NYM tokens:

/circulating-supply/circulating-supply-value

Query Response:

368639460.187887

This refers to the present quantity of NYM tokens that are actively in circulation.

Sending traffic through the Nym mixnet

Is the mixnet free to use?

For the moment then yes, the mixnet is free to use. There are no limits on the amount of traffic that an app can send through the mixnet.

Do I need to run my own gateway to send application traffic through the mixnet?

No, although we do recommend that apps that wish to integrate look into running some of their own infrastructure such as gateways in order to assure uptime.

NymVPN alpha

NymVPN alpha is a client that uses Nym Mixnet to anonymise all of a user’s internet traffic through either a 5-hop mixnet (for a full network privacy) or the faster 2-hop decentralised VPN (with some extra features).

You are invited to take part in the alpha testing of this new application. Register for private testing round at nymvpn.com, that will grant you access to the download page. Visit NymVPN Support & FAQ or join the NymVPN matrix channel if you have any questions, comments or blockers.

Checkout the release page for available binaries.

NOTE: NymVPN alpha is experimental software for testing purposes only.

NymVPN Overview

To understand what’s under the hood of NymVPN and the mixnet, we recommend interested developers to begin with Nym network overview and the Mixnet traffic flow pages.

The default setup of NymVPN is to run in 5-hop mode (mixnet):

                      ┌─►mix──┐  mix     mix
                      │       │
            Entry     │       │                   Exit
client ───► Gateway ──┘  mix  │  mix  ┌─►mix ───► Gateway ───► internet
                              │       │
                              │       │
                         mix  └─►mix──┘  mix

Users can switch to 2-hop only mode, which is a faster but less private option. In this mode traffic is only sent between the two Gateways, and is not passed between Mix Nodes. It uses Mixnet Sphinx packets with shorter, fixed routes, which improve latency, but doesn’t offer the same level of protection as the 5 hop mode.

NymVPN CLI Guide

Info

To download NymVPN desktop version, visit nymvpn.com/en/download.

NymVPN is an experimental software and it’s for testing purposes only. Anyone can submit a registration to the private alpha round on nymvpn.com.

Overview

The core binaries consist of:

  • nym-vpn-cli: Basic commandline client for running the vpn. This runs in the foreground.

  • nym-vpnd: Daemon implementation of the vpn client that can run in the background and interacted with using nym-vpnc.

  • nym-vpnc: The commandline client used to interact with nym-vpnd.

Installation

Any syntax in <> brackets is a user’s/version unique variable. Exchange with a corresponding name without the <> brackets.

  1. Open Github releases page and download the CLI latest binary for your system (labelled as nym-vpn-core)

  2. Verify sha hash of your downloaded binary with the one listed on the releases page. You can use a simple shasum command and compare strings (ie with Python) or run in the same directory the following command, exchanging <SHA_STRING> with the one of your binary, like in the example:

echo "<SHA_STRING>" | shasum -a 256 -c

# choose a correct one according to your binary, this is just an example
# echo "0e4abb461e86b2c168577e0294112a3bacd3a24bf8565b49783bfebd9b530e23  nym-vpn-cli__ubuntu-22.04_amd64.tar.gz" | shasum -a 256 -c
  1. Extract files:
tar -xvf <BINARY>.tar.gz
# for example
# tar -xvf nym-vpn-cli__ubuntu-22.04_x86_64.tar.gz

Running

If you are running Debian/Ubuntu/PopOS or any other distributio supporting debian packages and systemd, see the relevant section below.

Daemon

Start the daemon with

sudo -E ./nym-vpnd

Then run

./nym-vpnc status
./nym-vpnc connect
./nym-vpnc disconnect

CLI

An alternative to the daemon is to run the nym-vpn-cli commandline client that runs in the foreground.

./nym-vpn-cli run

Credentials

NymVPN uses zkNym bandwidth credentials. Those can be imported as a file or base58 encoded string.

sudo -E ./nym-vpn-cli import-credential --credential-path </PATH/TO/freepass.nym>
sudo -E ./nym-vpn-cli import-credential --credential-data "<STRING>"

Debian package for Debian/Ubuntu/PopOS

For linux platforms using deb packages and systemd, there are also debian packages.

sudo apt install ./nym-vpnd_-1_amd64.deb ./nym-vpnc_-1_amd64.deb

# In case of error please substitute the correct version

Installing the nym-vpnd deb package starts a nym-vpnd.service. Check that the daemon is running with

systemctl status nym-vpnd.service

and check its logs with

sudo journalctl -u nym-vpnd.service -f

To stop the background service

systemctl stop nym-vpnd.service

It will start again on startup, so disable with

systemctl disable nym-vpnd.service

Interact with it with nym-vpnc

nym-vpnc status
nym-vpnc connect
nym-vpnc disconnect

Commands & Options

Note

Nym Exit Gateway functionality was implemented just recently and not all the Gateways are upgraded and ready to handle the VPN connections. If you want to make sure you are connecting to a Gateway with an embedded Network Requester, IP Packet Router and applied Nym exit policy, visit harbourmaster.nymtech.net and search Gateways with all the functionalities enabled.

The basic syntax of nym-vpn-cli is:

# choose only one conditional --argument listed in {brackets}
sudo ./nym-vpn-cli { --exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY> }

To see all the possibilities run with --help flag:

./nym-vpn-cli --help

Console output

Usage: nym-vpn-cli [OPTIONS] <COMMAND>

Commands:
  run                Run the client
  import-credential  Import credential
  help               Print this message or the help of the given subcommand(s)

Options:
  -c, --config-env-file <CONFIG_ENV_FILE>  Path pointing to an env file describing the network
      --data-path <DATA_PATH>              Path to the data directory of the mixnet client
  -h, --help                               Print help
  -V, --version                            Print version

Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to be adjusted by users.

Fundamental commands and arguments

  • --entry-gateway-id: paste one of the values labeled with a key "identityKey" (without " ")
  • --exit-gateway-id: paste one of the values labeled with a key "identityKey" (without " ")
  • --exit-router-address: paste one of the values labeled with a key "address" (without " ")
  • --enable-wireguard: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping wireguard-go and to setup local routing rules to route all traffic to the TUN virtual network device
  • --wg-ip: The address of the wireguard interface, you can get it here
  • --private-key: get your private key for testing purposes here
  • --enable-two-hop is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops)

Advanced options

  • -c is a path to an enviroment config, like sandbox.env
  • --enable-poisson: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it’s on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
  • --ip is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
  • --mtu: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
  • --disable-routing: Disable routing all traffic through the VPN TUN device.

Testnet environment

If you want to run NymVPN CLI in Nym Sandbox environment, there are a few adjustments to be done:

  1. Create Sandbox environment config file by saving this as sandbox.env in the same directory as your NymVPN binaries:
curl -o sandbox.env -L https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env
  1. Check available Gateways at nymvpn.com/en/alpha/api/gateways

  2. Run with a flag -c

sudo ./nym-vpn-cli -c <PATH_TO>/sandbox.env <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>

Code of Conduct

We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.

Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all.

Please be kind and courteous. There’s no need to be mean or rude.

Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.

Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.

We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term “harassment” as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behaviour that excludes people in socially marginalized groups.

Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the Rust moderation team immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back.

Likewise any spamming, trolling, flaming, baiting or other attention-stealing behaviour is not welcome.

Licensing

As a general approach, licensing is as follows this pattern:

For accurate information, please check individual files.