Max/smolmix docs (#6716)

* Smolmix documentation

* Add smolmix docs: landing page, tutorials, and developer page links

* Add Exit Gateway services page (NR vs IPR) and link from existing docs

* Update auto-generated command and API outputs

* Reorg of tutorials and architecture pages

* License information + remove TODO from docs.rs visibile comment + reorg
readme

* Add versions file for doc-wide versioning

* Relative -> absolute links

* Relative -> absolute links

* Update license + add old tutorial code as examples

* Streamline smolmix docs

* Clippy

* Clean up doc comments

* Last pass

* Add larger file download to list

* set new versions

* Clippy

* Remove blake pin from docs + add version range to root Cargo.toml

* Format example logging

* Remove crate blocked component

* Loose whitespace

* Add doc verification script for inline mdx

* Formatting

* Components regen

* Reorg + tighten text

* Voicing cohesion pass + remove bloated examples

* Voicing cont.

* Reduce max download size

* Small suggested clarifications

* Max/docs voicing consistency (#6769)

* Reduce max download size

* voicing consistency across docs

* New landing order w smolmix

* Tweaks

* Final tweaks
This commit is contained in:
mfahampshire
2026-05-13 11:19:44 +00:00
committed by GitHub
parent fdebed7c38
commit a70e68c7bd
120 changed files with 3043 additions and 2346 deletions
+3
View File
@@ -37,6 +37,9 @@ jobs:
command: build
args: --workspace --release
- name: Verify doc versions
run: ${{ github.workspace }}/documentation/scripts/verify-doc-versions.sh
working-directory: ${{ github.workspace }}
- name: Install project dependencies
run: pnpm i
- name: Generate llms-full.txt
+3
View File
@@ -61,6 +61,9 @@ jobs:
cd ${{ github.workspace }}/sdk/typescript/packages/sdk && typedoc --skipErrorChecking
cd ${{ github.workspace }}/sdk/typescript/packages/mix-fetch && typedoc --skipErrorChecking
- name: Verify doc versions
run: ${{ github.workspace }}/documentation/scripts/verify-doc-versions.sh
working-directory: ${{ github.workspace }}
- name: Install project dependencies
run: pnpm i
- name: Generate llms-full.txt
+1
View File
@@ -78,3 +78,4 @@ CLAUDE.md
/notes
/target-otel
test-tutorials/
Generated
+1 -1
View File
@@ -11301,7 +11301,7 @@ dependencies = [
[[package]]
name = "smolmix"
version = "0.0.1"
version = "1.20.4"
dependencies = [
"futures",
"hickory-proto",
+1 -1
View File
@@ -229,7 +229,7 @@ base85rs = "0.1.3"
bincode = "1.3.3"
bip39 = { version = "2.0.0", features = ["zeroize"] }
bitvec = "1.0.0"
blake3 = "1.7.0"
blake3 = ">=1.7, <1.8.4" # blake3 1.8.4+ requires digest 0.11; workspace is on 0.10
bloomfilter = "3.0.1"
bs58 = "0.5.1"
bytecodec = "0.4.15"
+1
View File
@@ -32,6 +32,7 @@ mod tests {
fn stream_transport_threshold_is_consistent() {
assert_eq!(MAX_NON_STREAM_VERSION, 8);
assert_eq!(SPHINX_STREAM_VERSION_THRESHOLD, 9);
const _: () = assert!(SPHINX_STREAM_VERSION_THRESHOLD > MAX_NON_STREAM_VERSION);
}
}
+1
View File
@@ -1,2 +1,3 @@
todo.md
scripts/generate-api
RELEASE_TASKS.md
+22 -11
View File
@@ -1,22 +1,33 @@
import { Callout } from "nextra/components";
import { NYM_SDK_VERSION, SMOLMIX_VERSION } from "./versions";
const COMMIT_SHORT = "97068b2";
const COMMIT_FULL = "97068b2aa";
const EXAMPLES_URL =
"https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples";
const VERSIONS: Record<string, string> = {
"nym-sdk": NYM_SDK_VERSION,
smolmix: SMOLMIX_VERSION,
};
export const CodeVerified = () => (
const EXAMPLES_URLS: Record<string, string> = {
"nym-sdk":
"https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples",
smolmix:
"https://github.com/nymtech/nym/tree/develop/smolmix/core/examples",
};
interface CodeVerifiedProps {
/** Crate name to display. Defaults to "nym-sdk". */
crate?: keyof typeof VERSIONS;
}
export const CodeVerified = ({ crate: crateName = "nym-sdk" }: CodeVerifiedProps) => (
<Callout type="info">
Code verified against commit{" "}
Code verified against{" "}
<code>{crateName}</code> v{VERSIONS[crateName]}. If the API has changed
since then, check the{" "}
<a
href={`https://github.com/nymtech/nym/commit/${COMMIT_FULL}`}
href={EXAMPLES_URLS[crateName]}
target="_blank"
rel="noopener noreferrer"
>
<code>{COMMIT_SHORT}</code>
</a>
. If the API has changed since then, check the{" "}
<a href={EXAMPLES_URL} target="_blank" rel="noopener noreferrer">
examples in the repo
</a>{" "}
for the latest usage.
@@ -1,13 +0,0 @@
import { Callout } from "nextra/components";
const CRATES_VERSION = "1.20.4";
const INSTALL_PATH = "/developers/rust/importing";
export const CratesPaused = () => (
<Callout type="warning">
<strong>Crate publication is paused.</strong> The crates.io release (v
{CRATES_VERSION}) doesn't include the Stream module or other recent work.
Publication resumes with the Lewes Protocol. Import from Git for now see{" "}
<a href={INSTALL_PATH}>Installation</a>.
</Callout>
);
+18 -14
View File
@@ -11,8 +11,6 @@ const asciiStyle: React.CSSProperties = {
margin: 0,
};
// ── Animation components ──
const randomRow = () => Math.floor(Math.random() * 3);
const randomPath = () => [randomRow(), randomRow(), randomRow()];
@@ -243,8 +241,6 @@ const ApiAnimation = () => {
);
};
// ── Section data ──
const sections = [
{
title: "Network",
@@ -291,17 +287,29 @@ const AnimationBlock = ({ type }: { type: string }) => {
const sdks = [
{
name: "Rust",
name: "Rust SDK",
description:
"Native SDK with async Mixnet client, streams, and TcpProxy modules.",
"Async Mixnet client with AsyncRead/AsyncWrite streams over the Mixnet.",
href: "/developers/rust",
},
{
name: "TypeScript",
name: "smolmix",
description:
"Browser-based SDK with fetch API replacement and message-based WebSocket transport.",
"TCP/UDP tunnel over the Mixnet. Userspace smoltcp stack exposing AsyncRead/AsyncWrite TcpStream and UdpSocket types.",
href: "/developers/smolmix",
},
{
name: "TypeScript SDK",
description:
"Browser-side Mixnet Client for raw messaging via WebSocket, plus Nyx smart contract bindings.",
href: "/developers/typescript",
},
{
name: "mix-fetch",
description:
"fetch()-compatible API that routes HTTP(S) requests through the Mixnet. Browsers and Node.js.",
href: "/developers/mix-fetch",
},
];
export const LandingPage = () => {
@@ -309,7 +317,6 @@ export const LandingPage = () => {
<div
style={{ maxWidth: "64rem", margin: "0 auto", padding: "3rem 1.5rem" }}
>
{/* ── Section cards ── */}
<div
className="landing-grid"
style={{
@@ -385,7 +392,6 @@ export const LandingPage = () => {
))}
</div>
{/* ── SDKs ── */}
<div
className="landing-sdk-grid"
style={{
@@ -407,7 +413,7 @@ export const LandingPage = () => {
padding: 0,
}}
>
SDKs
Libraries
</h2>
<p
style={{
@@ -416,8 +422,7 @@ export const LandingPage = () => {
lineHeight: 1.6,
}}
>
Integrate Mixnet privacy into your application with our Rust and
TypeScript SDKs.
Rust and TypeScript libraries for Mixnet integration.
</p>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: "0" }}>
@@ -466,7 +471,6 @@ export const LandingPage = () => {
</div>
</div>
{/* ── Links ── */}
<div
style={{
borderTop: "1px solid var(--border)",
@@ -0,0 +1,57 @@
import { Callout } from "nextra/components";
import type { ReactNode } from "react";
/**
* Centralised "Lewes Protocol release is coming" notice.
*
* When the Lewes Protocol ships, update the variant strings below in this one
* file rather than searching the docs tree for individual callouts.
*
* <LewesPending variant="latency" />
* <LewesPending variant="cryptography" />
* <LewesPending variant="acks" />
*/
type Variant = "latency" | "cryptography" | "acks";
interface LewesPendingProps {
variant: Variant;
}
interface VariantEntry {
type: "info" | "warning";
body: ReactNode;
}
const VARIANTS: Record<Variant, VariantEntry> = {
latency: {
type: "info",
body: "Updated latency measurements will be published after the Lewes Protocol release.",
},
cryptography: {
type: "info",
body: (
<>
Cryptographic details on this page will be updated for the Lewes
Protocol release. For the current algorithm overview, see the{" "}
<a
href="https://nym.com/trust-center/cryptography"
target="_blank"
rel="noopener noreferrer"
>
Nym Trust Center: Cryptography
</a>
.
</>
),
},
acks: {
type: "warning",
body: "The upcoming Lewes Protocol release will introduce changes to how acknowledgements are handled. The current hop-by-hop ACK mechanism described above may be revised as part of broader protocol improvements. Details will be documented here once the changes are finalised.",
},
};
export const LewesPending = ({ variant }: LewesPendingProps) => {
const { type, body } = VARIANTS[variant];
return <Callout type={type}>{body}</Callout>;
};
+12 -11
View File
@@ -153,16 +153,17 @@ export const MixFetch = () => {
error: `Error: ${errorMsg}`,
};
const statusColor: Record<typeof status, string> = {
idle: "gray",
idle: "#9e9e9e",
starting: "orange",
ready: "green",
error: "red",
ready: "#85E89D",
error: "#ff6b6b",
};
return (
<div style={{ marginTop: "1rem" }}>
<Box sx={{ mt: 2 }}>
<Paper sx={{ p: 3 }}>
<Stack spacing={3}>
{/* --- Start MixFetch Section --- */}
<Paper sx={{ p: 2, mb: 2 }} variant="outlined">
<Stack direction="row" alignItems="center" spacing={2}>
<Button
variant="contained"
@@ -180,7 +181,6 @@ export const MixFetch = () => {
{statusText[status]}
</Typography>
</Stack>
</Paper>
{/* --- Fetch Controls (disabled until ready) --- */}
<Box
@@ -190,7 +190,7 @@ export const MixFetch = () => {
}}
>
{/* Single fetch */}
<Stack direction="row">
<Stack direction="row" spacing={2}>
<TextField
disabled={busy}
fullWidth
@@ -199,11 +199,11 @@ export const MixFetch = () => {
variant="outlined"
defaultValue={defaultUrl}
onChange={(e) => setUrl(e.target.value)}
size="small"
/>
<Button
variant="outlined"
disabled={busy}
sx={{ marginLeft: "1rem" }}
onClick={handleFetch}
>
Fetch
@@ -255,13 +255,14 @@ export const MixFetch = () => {
</Paper>
)}
</Box>
</Stack>
</Paper>
{/* --- Log Panel --- */}
{logs.length > 0 && (
<Paper
ref={logContainerRef}
sx={{ p: 2, mt: 3, maxHeight: 200, overflow: "auto" }}
variant="outlined"
sx={{ p: 2, mt: 2, maxHeight: 200, overflow: "auto" }}
>
<strong>Log</strong>
{logs.map((entry, i) => (
@@ -276,6 +277,6 @@ export const MixFetch = () => {
))}
</Paper>
)}
</div>
</Box>
);
};
@@ -1,6 +1,6 @@
{
"nodes": 745,
"nodes": 743,
"locations": 77,
"mixnodes": 264,
"exit_gateways": 474
"mixnodes": 258,
"exit_gateways": 477
}
@@ -1 +1 @@
Wednesday, May 6th 2026, 11:11:33 UTC
Wednesday, May 13th 2026, 10:37:10 UTC
@@ -11,7 +11,7 @@ options:
--no_routing_history Display node stats without routing history
--no_verloc_metrics Display node stats without verloc metrics
-m, --markdown Display results in markdown format
-o [OUTPUT], --output [OUTPUT]
-o, --output [OUTPUT]
Save results to file (in current dir or supply with
path without filename)
```
@@ -8,8 +8,10 @@ Commands:
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 Nym API [env: NYMAPI_CONFIG_ENV_FILE_ARG=]
--no-banner A no-op flag included for consistency with other binaries (and compatibility with nymvisor, oops) [env: NYMAPI_NO_BANNER_ARG=]
-c, --config-env-file <CONFIG_ENV_FILE> Path pointing to an env file that configures the Nym API [env:
NYMAPI_CONFIG_ENV_FILE_ARG=]
--no-banner A no-op flag included for consistency with other binaries (and compatibility with
nymvisor, oops) [env: NYMAPI_NO_BANNER_ARG=]
-h, --help Print help
-V, --version Print version
```
@@ -13,8 +13,7 @@ usage: nym-node-cli install [-h] [-V] [-d BRANCH] [-v]
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
-d BRANCH, --dev BRANCH
Define github branch (default: develop)
-d, --dev BRANCH Define github branch (default: develop)
-v, --verbose Show full error tracebacks
--mode {mixnode,entry-gateway,exit-gateway}
Node mode: 'mixnode', 'entry-gateway', or 'exit-
@@ -8,11 +8,13 @@ Commands:
migrate Attempt to migrate an existing mixnode or gateway into a nym-node
run Start this nym-node
sign Use identity key of this node to sign provided message
unsafe-reset-sphinx-keys UNSAFE: reset existing sphinx keys and attempt to generate fresh one for the current network state
unsafe-reset-sphinx-keys UNSAFE: reset existing sphinx keys and attempt to generate fresh one for the current network
state
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 nym-node and overrides any preconfigured values [env: NYMNODE_CONFIG_ENV_FILE_ARG=]
-c, --config-env-file <CONFIG_ENV_FILE> Path pointing to an env file that configures the nym-node and overrides any
preconfigured values [env: NYMNODE_CONFIG_ENV_FILE_ARG=]
--no-banner Flag used for disabling the printed banner in tty [env: NYMNODE_NO_BANNER=]
-h, --help Print help
-V, --version Print version
@@ -4,81 +4,138 @@ Start this nym-node
Usage: nym-node run [OPTIONS]
Options:
--id <ID> Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node]
--config-file <CONFIG_FILE> Path to a configuration file of this node [env: NYMNODE_CONFIG=]
--accept-operator-terms-and-conditions Explicitly specify whether you agree with the terms and conditions of a nym node operator as defined at
--id <ID>
Id of the nym-node to use [env: NYMNODE_ID=] [default: default-nym-node]
--config-file <CONFIG_FILE>
Path to a configuration file of this node [env: NYMNODE_CONFIG=]
--accept-operator-terms-and-conditions
Explicitly specify whether you agree with the terms and conditions of a nym node operator as defined at
<https://nymtech.net/terms-and-conditions/operators/v1.0.0> [env: NYMNODE_ACCEPT_OPERATOR_TERMS=]
--deny-init Forbid a new node from being initialised if configuration file for the provided specification doesn't already exist [env:
NYMNODE_DENY_INIT=]
--init-only If this is a brand new nym-node, specify whether it should only be initialised without actually running the subprocesses [env:
NYMNODE_INIT_ONLY=]
--local Flag specifying this node will be running in a local setting [env: NYMNODE_LOCAL=]
--mode [<MODE>...] Specifies the current mode(s) of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway,
exit-providers-only]
--modes <MODES> Specifies the current mode(s) of this nym-node as a single flag [env: NYMNODE_MODES=] [possible values: mixnode, entry-gateway,
--deny-init
Forbid a new node from being initialised if configuration file for the provided specification doesn't already
exist [env: NYMNODE_DENY_INIT=]
--init-only
If this is a brand new nym-node, specify whether it should only be initialised without actually running the
subprocesses [env: NYMNODE_INIT_ONLY=]
--local
Flag specifying this node will be running in a local setting [env: NYMNODE_LOCAL=]
--mode [<MODE>...]
Specifies the current mode(s) of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway,
exit-gateway, exit-providers-only]
-w, --write-changes If this node has been initialised before, specify whether to write any new changes to the config file [env:
--modes <MODES>
Specifies the current mode(s) of this nym-node as a single flag [env: NYMNODE_MODES=] [possible values: mixnode,
entry-gateway, exit-gateway, exit-providers-only]
-w, --write-changes
If this node has been initialised before, specify whether to write any new changes to the config file [env:
NYMNODE_WRITE_CONFIG_CHANGES=]
--bonding-information-output <BONDING_INFORMATION_OUTPUT> Specify output file for bonding information of this nym-node, i.e. its encoded keys. NOTE: the required bonding information is still a
subject to change and this argument should be treated only as a preview of future features [env: NYMNODE_BONDING_INFORMATION_OUTPUT=]
-o, --output <OUTPUT> Specify the output format of the bonding information (`text` or `json`) [env: NYMNODE_OUTPUT=] [default: text] [possible values: text,
json]
--public-ips <PUBLIC_IPS> Comma separated list of public ip addresses that will be announced to the nym-api and subsequently to the clients. In nearly all
circumstances, it's going to be identical to the address you're going to use for bonding [env: NYMNODE_PUBLIC_IPS=]
--hostname <HOSTNAME> Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the clients [env:
NYMNODE_HOSTNAME=]
--location <LOCATION> Optional **physical** location of this node's server. Either full country name (e.g. 'Poland'), two-letter alpha2 (e.g. 'PL'),
three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided [env: NYMNODE_LOCATION=]
--http-bind-address <HTTP_BIND_ADDRESS> Socket address this node will use for binding its http API. default: `[::]:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=]
--landing-page-assets-path <LANDING_PAGE_ASSETS_PATH> Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=]
--http-access-token <HTTP_ACCESS_TOKEN> An optional bearer token for accessing certain http endpoints. Currently only used for prometheus metrics [env:
--bonding-information-output <BONDING_INFORMATION_OUTPUT>
Specify output file for bonding information of this nym-node, i.e. its encoded keys. NOTE: the required bonding
information is still a subject to change and this argument should be treated only as a preview of future features
[env: NYMNODE_BONDING_INFORMATION_OUTPUT=]
-o, --output <OUTPUT>
Specify the output format of the bonding information (`text` or `json`) [env: NYMNODE_OUTPUT=] [default: text]
[possible values: text, json]
--public-ips <PUBLIC_IPS>
Comma separated list of public ip addresses that will be announced to the nym-api and subsequently to the clients.
In nearly all circumstances, it's going to be identical to the address you're going to use for bonding [env:
NYMNODE_PUBLIC_IPS=]
--hostname <HOSTNAME>
Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the
clients [env: NYMNODE_HOSTNAME=]
--location <LOCATION>
Optional **physical** location of this node's server. Either full country name (e.g. 'Poland'), two-letter alpha2
(e.g. 'PL'), three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided [env:
NYMNODE_LOCATION=]
--http-bind-address <HTTP_BIND_ADDRESS>
Socket address this node will use for binding its http API. default: `[::]:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=]
--landing-page-assets-path <LANDING_PAGE_ASSETS_PATH>
Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=]
--http-access-token <HTTP_ACCESS_TOKEN>
An optional bearer token for accessing certain http endpoints. Currently only used for prometheus metrics [env:
NYMNODE_HTTP_ACCESS_TOKEN=]
--expose-system-info <EXPOSE_SYSTEM_INFO> Specify whether basic system information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_INFO=] [possible values:
true, false]
--expose-system-hardware <EXPOSE_SYSTEM_HARDWARE> Specify whether basic system hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE=]
--expose-system-info <EXPOSE_SYSTEM_INFO>
Specify whether basic system information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_INFO=]
[possible values: true, false]
--expose-crypto-hardware <EXPOSE_CRYPTO_HARDWARE> Specify whether detailed system crypto hardware information should be exposed. default: true [env:
--expose-system-hardware <EXPOSE_SYSTEM_HARDWARE>
Specify whether basic system hardware information should be exposed. default: true [env:
NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE=] [possible values: true, false]
--expose-crypto-hardware <EXPOSE_CRYPTO_HARDWARE>
Specify whether detailed system crypto hardware information should be exposed. default: true [env:
NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false]
--mixnet-bind-address <MIXNET_BIND_ADDRESS> Address this node will bind to for listening for mixnet packets default: `[::]:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=]
--mixnet-announce-port <MIXNET_ANNOUNCE_PORT> If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a
proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=]
--nym-api-urls <NYM_API_URLS> Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=]
--nyxd-urls <NYXD_URLS> Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=]
--enable-console-logging <ENABLE_CONSOLE_LOGGING> Specify whether running statistics of this node should be logged to the console [env: NYMNODE_ENABLE_CONSOLE_LOGGING=] [possible
values: true, false]
--wireguard-enabled <WIREGUARD_ENABLED> Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false]
--wireguard-bind-address <WIREGUARD_BIND_ADDRESS> Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=]
--wireguard-tunnel-announced-port <WIREGUARD_TUNNEL_ANNOUNCED_PORT> Tunnel port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is
behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
--wireguard-private-network-prefix <WIREGUARD_PRIVATE_NETWORK_PREFIX> The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for
IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=]
--wireguard-userspace <WIREGUARD_USERSPACE> Use userspace implementation of WireGuard (wireguard-go) instead of kernel module. Useful in containerized environments without kernel
WireGuard support [env: NYMNODE_WG_USERSPACE=] [possible values: true, false]
--verloc-bind-address <VERLOC_BIND_ADDRESS> Socket address this node will use for binding its verloc API. default: `[::]:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=]
--verloc-announce-port <VERLOC_ANNOUNCE_PORT> If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a
proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=]
--entry-bind-address <ENTRY_BIND_ADDRESS> Socket address this node will use for binding its client websocket API. default: `[::]:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=]
--announce-ws-port <ANNOUNCE_WS_PORT> Custom announced port for listening for websocket client traffic. If unspecified, the value from the `bind_address` will be used
instead [env: NYMNODE_ENTRY_ANNOUNCE_WS_PORT=]
--announce-wss-port <ANNOUNCE_WSS_PORT> If applicable, announced port for listening for secure websocket client traffic [env: NYMNODE_ENTRY_ANNOUNCE_WSS_PORT=]
--enforce-zk-nyms <ENFORCE_ZK_NYMS> Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also accepts non-paying clients
[env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false]
--mnemonic <MNEMONIC> Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be generated [env:
NYMNODE_MNEMONIC=]
--upgrade-mode-attestation-url <UPGRADE_MODE_ATTESTATION_URL> Endpoint to query to retrieve current upgrade mode attestation. This argument should never be set outside testnets and local networks
[env: NYMNODE_UPGRADE_MODE_ATTESTATION_URL=]
--upgrade-mode-attester-public-key <UPGRADE_MODE_ATTESTER_PUBLIC_KEY> Expected public key of the entity signing the published attestation. This argument should never be set outside testnets and local
networks [env: NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY=]
--upstream-exit-policy-url <UPSTREAM_EXIT_POLICY_URL> Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=]
--open-proxy <OPEN_PROXY> Specifies whether this exit node should run in 'open-proxy' mode and thus would attempt to resolve **ANY** request it receives [env:
NYMNODE_OPEN_PROXY=] [possible values: true, false]
--lp-control-bind-address <LP_CONTROL_BIND_ADDRESS> Bind address for the TCP LP control traffic. default: `[::]:41264` [env: NYMNODE_LP_CONTROL_BIND_ADDRESS=]
--lp-control-announce-port <LP_CONTROL_ANNOUNCE_PORT> Custom announced port for listening for the TCP LP control traffic. If unspecified, the value from the `lp_control_bind_address` will
be used instead [env: NYMNODE_LP_CONTROL_ANNOUNCE_PORT=]
--lp-data-bind-address <LP_DATA_BIND_ADDRESS> Bind address for the UDP LP data traffic. default: `[::]:51264` [env: NYMNODE_LP_DATA_BIND_ADDRESS=]
--lp-data-announce-port <LP_DATA_ANNOUNCE_PORT> Custom announced port for listening for the UDP LP data traffic. If unspecified, the value from the `lp_data_bind_address` will be
used instead [env: NYMNODE_LP_DATA_ANNOUNCE_PORT=]
--lp-use-mock-ecash <LP_USE_MOCK_ECASH> Use mock ecash manager for LP testing. WARNING: Only use this for local testing! Never enable in production. When enabled, the LP
listener will accept any credential without blockchain verification [env: NYMNODE_LP_USE_MOCK_ECASH=] [possible values: true, false]
-h, --help Print help
--mixnet-bind-address <MIXNET_BIND_ADDRESS>
Address this node will bind to for listening for mixnet packets default: `[::]:1789` [env:
NYMNODE_MIXNET_BIND_ADDRESS=]
--mixnet-announce-port <MIXNET_ANNOUNCE_PORT>
If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when
the node is behind a proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=]
--nym-api-urls <NYM_API_URLS>
Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=]
--nyxd-urls <NYXD_URLS>
Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=]
--enable-console-logging <ENABLE_CONSOLE_LOGGING>
Specify whether running statistics of this node should be logged to the console [env:
NYMNODE_ENABLE_CONSOLE_LOGGING=] [possible values: true, false]
--wireguard-enabled <WIREGUARD_ENABLED>
Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true,
false]
--wireguard-bind-address <WIREGUARD_BIND_ADDRESS>
Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env:
NYMNODE_WG_BIND_ADDRESS=]
--wireguard-tunnel-announced-port <WIREGUARD_TUNNEL_ANNOUNCED_PORT>
Tunnel port announced to external clients wishing to connect to the wireguard interface. Useful in the instances
where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
--wireguard-private-network-prefix <WIREGUARD_PRIVATE_NETWORK_PREFIX>
The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for
IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=]
--wireguard-userspace <WIREGUARD_USERSPACE>
Use userspace implementation of WireGuard (wireguard-go) instead of kernel module. Useful in containerized
environments without kernel WireGuard support [env: NYMNODE_WG_USERSPACE=] [possible values: true, false]
--verloc-bind-address <VERLOC_BIND_ADDRESS>
Socket address this node will use for binding its verloc API. default: `[::]:1790` [env:
NYMNODE_VERLOC_BIND_ADDRESS=]
--verloc-announce-port <VERLOC_ANNOUNCE_PORT>
If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when
the node is behind a proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=]
--entry-bind-address <ENTRY_BIND_ADDRESS>
Socket address this node will use for binding its client websocket API. default: `[::]:9000` [env:
NYMNODE_ENTRY_BIND_ADDRESS=]
--announce-ws-port <ANNOUNCE_WS_PORT>
Custom announced port for listening for websocket client traffic. If unspecified, the value from the
`bind_address` will be used instead [env: NYMNODE_ENTRY_ANNOUNCE_WS_PORT=]
--announce-wss-port <ANNOUNCE_WSS_PORT>
If applicable, announced port for listening for secure websocket client traffic [env:
NYMNODE_ENTRY_ANNOUNCE_WSS_PORT=]
--enforce-zk-nyms <ENFORCE_ZK_NYMS>
Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet or if it also
accepts non-paying clients [env: NYMNODE_ENFORCE_ZK_NYMS=] [possible values: true, false]
--mnemonic <MNEMONIC>
Custom cosmos wallet mnemonic used for zk-nym redemption. If no value is provided, a fresh mnemonic is going to be
generated [env: NYMNODE_MNEMONIC=]
--upgrade-mode-attestation-url <UPGRADE_MODE_ATTESTATION_URL>
Endpoint to query to retrieve current upgrade mode attestation. This argument should never be set outside testnets
and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTATION_URL=]
--upgrade-mode-attester-public-key <UPGRADE_MODE_ATTESTER_PUBLIC_KEY>
Expected public key of the entity signing the published attestation. This argument should never be set outside
testnets and local networks [env: NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY=]
--upstream-exit-policy-url <UPSTREAM_EXIT_POLICY_URL>
Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=]
--open-proxy <OPEN_PROXY>
Specifies whether this exit node should run in 'open-proxy' mode and thus would attempt to resolve **ANY** request
it receives [env: NYMNODE_OPEN_PROXY=] [possible values: true, false]
--lp-control-bind-address <LP_CONTROL_BIND_ADDRESS>
Bind address for the TCP LP control traffic. default: `[::]:41264` [env: NYMNODE_LP_CONTROL_BIND_ADDRESS=]
--lp-control-announce-port <LP_CONTROL_ANNOUNCE_PORT>
Custom announced port for listening for the TCP LP control traffic. If unspecified, the value from the
`lp_control_bind_address` will be used instead [env: NYMNODE_LP_CONTROL_ANNOUNCE_PORT=]
--lp-data-bind-address <LP_DATA_BIND_ADDRESS>
Bind address for the UDP LP data traffic. default: `[::]:51264` [env: NYMNODE_LP_DATA_BIND_ADDRESS=]
--lp-data-announce-port <LP_DATA_ANNOUNCE_PORT>
Custom announced port for listening for the UDP LP data traffic. If unspecified, the value from the
`lp_data_bind_address` will be used instead [env: NYMNODE_LP_DATA_ANNOUNCE_PORT=]
--lp-use-mock-ecash <LP_USE_MOCK_ECASH>
Use mock ecash manager for LP testing. WARNING: Only use this for local testing! Never enable in production. When
enabled, the LP listener will accept any credential without blockchain verification [env:
NYMNODE_LP_USE_MOCK_ECASH=] [possible values: true, false]
-h, --help
Print help
```
@@ -11,7 +11,8 @@ Commands:
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 nymvisor and overrides any preconfigured values
-c, --config-env-file <CONFIG_ENV_FILE> Path pointing to an env file that configures the nymvisor and overrides any
preconfigured values
-h, --help Print help
-V, --version Print version
```
@@ -0,0 +1,15 @@
import { Callout } from "nextra/components";
import { NYM_SDK_VERSION } from "./versions";
const INSTALL_PATH = "/developers/rust/importing";
export const VersionBanner = () => (
<Callout type="info">
Code examples target <strong>v{NYM_SDK_VERSION}</strong> of the Nym crates
on{" "}
<a href="https://crates.io/crates/nym-sdk" target="_blank" rel="noopener noreferrer">
crates.io
</a>
. See <a href={INSTALL_PATH}>Installation</a> for setup instructions.
</Callout>
);
+31
View File
@@ -0,0 +1,31 @@
/**
* Centralised version constants for documentation.
*
* Components (VersionBanner, CodeVerified, etc.) import from here so there is a
* single place to update on each release.
*
* Fenced code blocks still hardcode versions for copy-paste friendliness.
* When bumping versions here, also update the Cargo.toml snippets in:
*
* pages/developers/rust/importing.mdx
* pages/developers/rust/tcpproxy.mdx
* pages/developers/rust/mixnet/tutorial.mdx
* pages/developers/rust/stream/tutorial.mdx
* pages/developers/rust/client-pool/tutorial.mdx
* public/llms.txt
*
* RUST_MSRV is imported directly by all pages that display the Rust version,
* so no manual file edits are needed for MSRV bumps:
*
* pages/developers/smolmix.mdx
* pages/developers/rust/importing.mdx
*/
// nym-sdk / nym-bin-common / nym-network-defaults (Rust SDK crates)
export const NYM_SDK_VERSION = "1.21.0";
// smolmix standalone crate
export const SMOLMIX_VERSION = "1.21.0";
// Minimum supported Rust version (matches workspace rust-version in root Cargo.toml)
export const RUST_MSRV = "1.87";
+35 -1
View File
@@ -1178,13 +1178,46 @@ const config = {
basePath: false,
},
// Docs reorg: language-based sidebar
// Deleted routing pages → merged into integrations
{
source: "/docs/developers/native",
destination: "/docs/developers/integrations",
permanent: true,
basePath: false,
},
{
source: "/docs/developers/browsers",
destination: "/docs/developers/integrations",
permanent: true,
basePath: false,
},
// --- Directory index redirects (directories without index pages) ---
{
source: "/docs/developers/concepts",
destination: "/docs/developers/concepts/message-queue",
permanent: false,
basePath: false,
},
{
source: "/docs/developers/typescript/bundling",
destination: "/docs/developers/typescript/bundling/bundling",
permanent: false,
basePath: false,
},
{
source: "/docs/developers/typescript/api/mix-fetch",
destination: "/docs/developers/typescript/api/mix-fetch/globals",
permanent: false,
basePath: false,
},
{
source: "/docs/developers/typescript/api/sdk",
destination: "/docs/developers/typescript/api/sdk/globals",
permanent: false,
basePath: false,
},
{
source: "/docs/developers/typescript/examples",
destination: "/docs/developers/typescript/examples/mix-fetch",
@@ -1231,7 +1264,8 @@ const config = {
},
{
source: "/docs/apis/ns-api/ns-api-run-deploy",
destination: "/docs/operators/performance-and-testing/ns-api-deployment",
destination:
"/docs/operators/performance-and-testing/ns-api-deployment",
permanent: true,
basePath: false,
},
+1 -15
View File
@@ -8,21 +8,7 @@
},
"developers": {
"title": "Developers",
"type": "menu",
"items": {
"developers": {
"title": "Integrations",
"href": "/developers/integrations"
},
"rust": {
"title": "Rust SDK",
"href": "/developers/rust"
},
"typescript": {
"title": "Typescript SDK",
"href": "/developers/typescript"
}
}
"type": "page"
},
"operators": {
"title": "Operators",
@@ -1,6 +1,6 @@
---
title: "Explorer API (Deprecated)"
description: "Legacy Explorer API reference for the Nym Mixnet Explorer. Deprecated in favor of the Node Status API."
description: "Legacy Explorer API reference for the Nym Mixnet Explorer. Deprecated in favour of the Node Status API."
schemaType: "TechArticle"
section: "APIs"
lastUpdated: "2026-03-15"
+1 -1
View File
@@ -19,7 +19,7 @@ The Node Status API serves information about individual `nym-node` instances in
- **Mixnet summaries**: active set composition, role distribution, network health
<Callout type="info">
If you're building a service that makes heavy use of this API, consider [running your own instance](/operators/performance-and-testing/ns-api-deployment) to distribute load and promote a robust network of downstream services.
If you're building a service that makes heavy use of this API, consider [running your own instance](/operators/performance-and-testing/ns-api-deployment) to distribute load across the network.
</Callout>
## Quick examples
+12 -9
View File
@@ -1,19 +1,22 @@
{
"index": "Introduction",
"index": "Overview",
"integrations": "Choosing an Approach",
"concepts": "Key Concepts",
"--": {
"type": "separator",
"title": "Mixnet Integrations"
"title": "Rust"
},
"integrations": "Overview - Start Here!",
"native": "Native Apps",
"browsers": "Browser Apps",
"concepts": "Nym-Specific Concepts for Integrations",
"smolmix": "smolmix",
"rust": "nym-sdk",
"-": {
"type": "separator",
"title": "SDKs"
"title": "TypeScript"
},
"rust": "Rust SDK",
"typescript": "Typescript SDK",
"mix-fetch": "mix-fetch",
"typescript": "TypeScript SDK",
"---": {
"type": "separator",
"title": "Extras"
@@ -4,7 +4,7 @@ import { Callout } from 'nextra/components'
<Callout type="warning">
NymConnect is no longer maintained as of early 2024. Nym is developing a new client called [NymVPN](https://nymvpn.com), an application routing all user traffic through the Mixnet.
If you want to route traffic through SOCKS5, use the maintained [Nym Socks5 Client](../clients/socks5/setup).
If you want to route traffic through SOCKS5, use the maintained [Nym Socks5 Client](/developers/clients/socks5/setup).
</Callout>
In case you want to run deprecated NymConnect, follow these steps:
@@ -29,7 +29,7 @@ Here are some examples of applications which will work behind Socks5 proxy (`nym
To download Electrum visit the [official webpage](https://electrum.org/#download). To connect to the Mixnet follow these steps:
1. Start and connect NymConnect (or [`nym-socks5-client`](../clients/socks5/setup))
1. Start and connect NymConnect (or [`nym-socks5-client`](/developers/clients/socks5/setup))
2. Start your Electrum Bitcoin wallet
3. Go to: *Tools* -> *Network* -> *Proxy*
4. Set *Use proxy* to ✅, choose `SOCKS5` from the drop-down and add (copy-paste) the values from your NymConnect application
@@ -41,7 +41,7 @@ To download Electrum visit the [official webpage](https://electrum.org/#download
To download Monero wallet visit [getmonero.org](https://www.getmonero.org/downloads/). To connect to the Mixnet follow these steps:
1. Start and connect NymConnect (or [`nym-socks5-client`](../clients/socks5/setup))
1. Start and connect NymConnect (or [`nym-socks5-client`](/developers/clients/socks5/setup))
2. Start your Monero wallet
3. Go to: *Settings* -> *Interface* -> *Socks5 proxy* -> Add values: IP address `127.0.0.1`, Port `1080` (the values copied from NymConnect)
5. Now your Monero wallet runs through the Mixnet and it will be connected only if your NymConnect or `nym-socks5-client` are connected.
@@ -51,7 +51,7 @@ To download Monero wallet visit [getmonero.org](https://www.getmonero.org/downlo
To download Element (chat client for Matrix) visit [element.io](https://element.io/download). To connect to the Mixnet follow these steps:
1. Start and connect NymConnect (or [`nym-socks5-client`](../clients/socks5/setup))
1. Start and connect NymConnect (or [`nym-socks5-client`](/developers/clients/socks5/setup))
2. Start `element-desktop` with `--proxy-server` argument:
**Linux**
@@ -70,7 +70,7 @@ To make the start of Element over NymConnect simplier, you can add this command
### Telegram via NymConnect
1. Start and connect NymConnect (or [`nym-socks5-client`](../clients/socks5/setup))
1. Start and connect NymConnect (or [`nym-socks5-client`](/developers/clients/socks5/setup))
2. Start your Telegram chat application
3. Open the Telegram proxy settings.
- Linux: *Settings* -> *Advanced* -> *Connection type* -> *Use custom proxy*
@@ -1,43 +0,0 @@
---
title: "Browser-Based App Integration"
description: "Build privacy-preserving browser apps with mixFetch and the Nym WASM SDK. Route HTTP requests and messages through the mixnet from the browser."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-04-07"
---
import { Callout } from 'nextra/components';
# Browser-Based Apps
Browsers are a restricted environment: communication is limited to WebSockets, Web Transport, and WebRTC; mixed content policies enforce HTTPS-only; and there is no access to the filesystem or system calls. The main obstacle for routing traffic through the Mixnet is the lack of access to browser TLS negotiation or the CA certificate store from JavaScript.
Two integration options are available, both delivered as packages bundled into your web application.
![](/images/developers/nym-browser-arch.png)
## mixFetch
A drop-in replacement for the browser `fetch` API that makes HTTP(S) requests via Exit Gateways using the SOCKS Network Requester. It ships with an embedded CA certificate store to establish a TLS session between `mixFetch` and the remote host, creating a secure channel from the browser to the destination over the Mixnet.
Internally, `mixFetch` uses the WASM client.
- [Docs](./typescript#mixfetch)
- [Example](./typescript/playground/mixfetch)
<Callout type="info">
`mixFetch` currently supports a maximum of 10 concurrent in-flight requests. `mixFetchv2`, which will function as a general-purpose userspace IP stack, is in development.
</Callout>
## WASM Client
Constructs Sphinx packets and cover traffic in WASM, sent over a WebSocket to the Entry Gateway. Responses arrive the same way.
This operates in messaging mode only (text or binary payloads) and does not currently support IP packet routing via the Exit Gateway IPR or any stream-like API. For HTTP(S) requests, use `mixFetch`.
Standard browser CSP and mixed content restrictions (HTTPS only) apply to the WebSocket connection, including in embedded WebViews.
The client runs in a web worker to keep the UI thread free.
- [Docs](./typescript#mixnet-client)
- [Example](./typescript/playground/traffic)
+4 -4
View File
@@ -13,11 +13,11 @@ There are two options for interacting with the blockchain to send tokens or inte
* `nyxd` binary
## Nym-CLI tool (recommended in most cases)
The `nym-cli` tool is a binary offering a simple interface for interacting with deployed smart contract (for instance, bonding and unbonding a Mix Node from the CLI), as well as creating and managing accounts and keypairs, sending tokens, and querying the blockchain.
The `nym-cli` tool is a binary offering an interface for interacting with deployed smart contracts (e.g. bonding and unbonding a Mix Node from the CLI), creating and managing accounts and keypairs, sending tokens, and querying the blockchain.
Instructions on how to do so can be found on the [`nym-cli` docs page](./tools/nym-cli)
See the [`nym-cli` docs page](./tools/nym-cli) for instructions.
## Nyxd binary
The `nyxd` binary, although more complex to compile and use, offers the full range of commands availiable to users of CosmosSDK chains. Use this if you are (e.g.) wanting to perform more granular queries about transactions from the CLI.
The `nyxd` binary, although harder to compile and use, offers the full range of commands available to users of CosmosSDK chains. Use this when you need more granular queries about transactions from the CLI.
You can use the instructions on how to do this on from the [`gaiad` docs page](https://hub.cosmos.network/main/delegators/delegator-guide-cli.html#querying-the-state).
The [`gaiad` docs page](https://hub.cosmos.network/main/delegators/delegator-guide-cli.html#querying-the-state) covers how to do this.
@@ -1,6 +1,6 @@
# CLI Wallet
If you have already read our [validator setup and maintenance documentation](../../operators/nodes/validator-setup) you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can however be used for many other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions.
If you have read our [validator setup and maintenance documentation](../../operators/nodes/validator-setup), you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can also be used for other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions.
### Using `nyxd` binary as a CLI wallet
You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](../../operators/nodes/validator-setup#building-your-validator) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain.
You can use `nyxd` as a minimal CLI wallet if you want to set up one or more accounts. Compile the binary as per the documentation, stopping after the [building your validator](../../operators/nodes/validator-setup#building-your-validator) step is complete. Then run `nyxd keys --help` to see how to set up and store keypairs for interacting with the Nyx blockchain.
@@ -1,6 +1,6 @@
# Ledger Live Support
Use the following instructions to interact with the Nyx blockchain - either with deployed smart contracts, or just to send tokens - using your Ledger device to sign transactions.
Use the following instructions to interact with the Nyx blockchain (either with deployed smart contracts, or to send tokens) using your Ledger device to sign transactions.
## Prerequisites
* Download and install [Ledger Live](https://www.ledger.com/ledger-live).
@@ -8,7 +8,7 @@ Use the following instructions to interact with the Nyx blockchain - either with
## Prepare your Ledger App
* Plug in your Ledger device
* Install the `Cosmos (ATOM)` app by following the instructions [here](https://hub.cosmos.network/main/resources/ledger.html). This app allows you to interact with **any** Cosmos SDK chain - you can manage your ATOM, OSMOSIS, NYM tokens, etc.
* Install the `Cosmos (ATOM)` app by following the instructions [here](https://hub.cosmos.network/main/resources/ledger.html). This app works with any Cosmos SDK chain, so you can manage your ATOM, OSMOSIS, NYM tokens, etc.
* On the device, navigate to the Cosmos app and open it
## Create a keypair
@@ -8,10 +8,10 @@ lastUpdated: "2026-02-01"
# RPC Nodes
RPC Nodes (which might otherwise be referred to as 'Lite Nodes' or just 'Full Nodes') differ from Validators in that they hold a copy of the Nyx blockchain, but do **not** participate in consensus / block-production.
RPC Nodes (sometimes called 'Lite Nodes' or 'Full Nodes') differ from Validators in that they hold a copy of the Nyx blockchain but do **not** participate in consensus / block-production.
You may want to set up an RPC Node for querying the blockchain, or in order to have an endpoint that your app can use to send transactions.
You may want to set up an RPC Node for querying the blockchain, or to provide an endpoint that your app can use to send transactions.
In order to set up an RPC Node, simply follow the instructions to set up a [Validator](../../operators/nodes/validator-setup), but **exclude the `nyxd tx staking create-validator` command**.
To set up an RPC Node, follow the instructions to set up a [Validator](../../operators/nodes/validator-setup), but **exclude the `nyxd tx staking create-validator` command**.
If you want to fast-sync your node, check out the Polkachu snapshot and their other [resources](https://polkachu.com/seeds/nym).
@@ -1,6 +1,6 @@
# Socks5 Client (Standalone)
> This client can also be utilised via the [Rust SDK](../rust).
> This client can also be utilised via the [Rust SDK](/developers/rust).
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:
@@ -47,4 +47,4 @@ The `nym-socks5-client` allows you to do the following from your local machine:
* 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 Nym Network. Packets are shuffled and mixed as they transit the mixnet.
The `nym-node` 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!
The `nym-node` then reassembles the original TCP stream using the packets' sequence numbers, and makes the intended request. It chops up the response into Sphinx packets and sends them back through the mixnet to your `nym-socks5-client`. The application receives its data without noticing that it wasn't talking to a "normal" SOCKS5 proxy.
@@ -21,7 +21,7 @@ tree $HOME/<user>/.nym/socks5-clients/docs-example
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.
The generated files contain the client name, public/private keypairs, and gateway address. The name `<client_id>` in the example above is 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`.
@@ -4,7 +4,7 @@
If you are using OSX or a Debian-based operating system, you can download the `nym-socks5-client` binary from our [Github releases page](https://github.com/nymtech/nym/releases).
If you are using a different operating system, or want to build from source, simply use `cargo build --release` from the root of the Nym monorepo.
If you are using a different operating system, or want to build from source, run `cargo build --release` from the root of the Nym monorepo.
## Viewing command help
You can check that your binaries are properly compiled with:
@@ -57,4 +57,4 @@ Adding `--no-banner` startup flag will prevent Nym banner being printed even if
**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.
A `build-info` command prints the build information (commit hash, rust version, binary version), the same as `--version`. You can also specify an `--output=json` flag that formats the whole output as JSON, which is much easier to parse.
@@ -1,22 +1,23 @@
# Webassembly Client
## Overview
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.
The Nym webassembly client lets any webassembly-capable runtime build and send Sphinx packets to the Nym network, for use in edge computing and browser-based applications.
This is currently packaged and distributed for ease of use via the [Nym Typescript SDK library](../typescript). **We imagine most developers will use this client via the SDK for ease.**
It is packaged and distributed via the [Nym Typescript SDK library](/developers/typescript). Most developers consume it through the SDK.
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).
The client supports building Sphinx packets from mobile apps and browser-based client-side apps (including Electron or similar).
## Building apps with Webassembly Client
Check out the [Typescript SDK docs](../typescript) for examples of usage.
See the [Typescript SDK docs](/developers/typescript) for examples of usage.
## Think about what you're sending
## Think about what you're sending!
import { Callout } from 'nextra/components'
<Callout type="warning" emoji="⚠️">
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.
<Callout type="warning">
Think about what information your app sends. That covers whatever you put into your Sphinx packet messages as well as what your app's environment may leak.
</Callout>
Whenever you write client apps 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.
When writing client apps in HTML/JavaScript, we recommend you **do not load external resources from CDNs**. Webapp developers often do this to save load time for common resources, or for convenience. For 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](https://panopticlick.eff.org/). 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.
If you use only local resources within your Electron app or your browser extensions, encoding request data in a Sphinx packet protects you from the normal leakage that gets sent in a browser HTTP request. [A lot of metadata leaks when you make an HTTP request from a browser window](https://panopticlick.eff.org/). That leakage doesn't happen in Nym, because you control what gets encoded into Sphinx packets, rather than sending a whole browser environment by default.
@@ -20,7 +20,7 @@ cargo build --release -p nym-client
The binary will be at `target/release/nym-client`.
## Initialize and run
## Initialise and run
```bash
# Create a new client identity
@@ -37,7 +37,7 @@ tree $HOME/<user>/.nym/clients/example-client
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.
The generated files contain the client name, public/private keypairs, and gateway address. The name `<client_id>` in the example above is 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`.
@@ -10,8 +10,4 @@ All of these code examples will do the following:
* 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.
<!-- THIS PAGE IS NOT WORKING AT THE MOMENT:
> You can find an example of building both frontend and service provider code with the websocket client in the [Simple Service Provider Tutorial](https://nym.com/developers/tutorials/simple-service-provider/simple-service-provider.html) in the Developer Portal.
-->
By varying the message content, you can build 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 (initiating a network request, a blockchain transaction, or writing to a local data store).
@@ -4,7 +4,7 @@ The Nym native client exposes a websocket interface that your code connects to.
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](https://github.com/nymtech/nym/blob/master/clients/native/websocket-requests/src/responses.rs#L48).
The message types you can send up the websocket are defined [here](https://github.com/nymtech/nym/blob/master/clients/native/websocket-requests/src/responses.rs#L48).
### 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 <!--add link -->. 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:
@@ -26,7 +26,7 @@ You'll receive a response of the format:
See [here](https://github.com/nymtech/nym/blob/93cc281abc2cc951023b51746fa6f2ead1f56c46/clients/native/examples/python-examples/websocket/textsend.py#L16C9-L16C9) 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.
> All native client example code begins 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:
@@ -52,11 +52,11 @@ In some applications, e.g. where people are chatting with friends who they know,
}
```
**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)**.
If that fits your security model, good. Otherwise, send anonymous replies using Single Use Reply Blocks (SURBs).
You can read more about SURBs [here](/network/mixnet-mode/anonymous-replies) 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**.
More on SURBs in the [anonymous replies docs](/network/mixnet-mode/anonymous-replies). In short, they let the receiver of a message reply to you without needing 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.
Your client will send some `replySurbs` along to the recipient of the message. These are pre-addressed Sphinx packets that the recipient can write response data into, but cannot see the final destination of. If the recipient cannot fit its response into the bucket of SURBs sent to it, it will use a SURB to request more SURBs from your client.
```json
{
@@ -69,7 +69,7 @@ Your client will send along a number of `replySurbs` to the recipient of the mes
See ['Replying to SURB Messages'](#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.
How many SURBs to generate and send with outgoing messages depends on the expected size of the reply. Send many SURBs to get the response as quickly as possible (accepting minor additional latency at send time, since your client has to generate and encrypt the packets), or send a few (e.g. 20) and let the recipient request more if needed (accepting additional latency in receiving the 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
@@ -11,7 +11,7 @@ import { Callout } from 'nextra/components'
# Message Queue
<Callout type="info">
Although useful for understanding how the Nym Client works internally, this information is only of practical use if you are using the [`Mixnet`](../rust/mixnet) module of the Rust SDK and interacting with the client at a low level. Most of this is abstracted away by the [`Stream`](../rust/stream) module (`AsyncRead + AsyncWrite` channels) and the [`TcpProxy`](../rust/tcpproxy) module (TCP tunnelling with message ordering).
Useful for understanding how the Nym Client works internally, but only of practical interest if you are using the [`Mixnet`](/developers/rust/mixnet) module of the Rust SDK and interacting with the client at a low level. The [`Stream`](/developers/rust/stream) module (`AsyncRead + AsyncWrite` channels) abstracts most of this away.
</Callout>
## Sphinx Packet Streams
@@ -85,6 +85,6 @@ sequenceDiagram
When passing a message to a client (however you do it, either piping messages from an app to a standalone client or via one of the `send` functions exposed by the SDKs), you are **putting that message into the queue** to be source encrypted and sent in the future, in order to ensure that traffic leaving the client does so in a manner that to an external observer is uniform / does not create any 'burst' or change in traffic timings that could aid traffic analysis.
## Note on Client Shutdown
Accidentally dropping a client before your message has been sent is something that is possible and should be avoided (see the [troubleshooting guide](../rust/mixnet/troubleshooting) for more on this) but is easy to avoid simply by remembering to:
Accidentally dropping a client before your message has been sent is possible and should be avoided (see the [troubleshooting guide](/developers/rust/mixnet/troubleshooting) for more on this). To avoid it:
- keep your client process alive, even if you are not expecting a reply to your message
- (in the case of the SDKs) properly disconnecting your client in order to make sure that the message queue is flushed of Sphinx packets with actual payloads.
- (with the SDKs) disconnect your client properly so that the message queue is flushed of Sphinx packets with real payloads.
+18 -15
View File
@@ -1,27 +1,30 @@
---
title: "Nym Developer Portal: SDKs & Tools"
description: "Developer documentation for building privacy-enhanced applications on the Nym mixnet. Covers Rust SDK, TypeScript SDK, blockchain interaction & CLI tools."
title: "Overview"
description: "Developer documentation index for the Nym mixnet: Rust and TypeScript SDKs, smolmix, mix-fetch, chain interaction, and CLI tools."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-02-01"
lastUpdated: "2026-05-12"
---
# Developer Documentation
# Overview
Build applications that protect user metadata using the Nym Mixnet. This section covers SDK integration, blockchain interaction, and developer tools.
This section covers the SDKs, standalone crates, blockchain interaction, and developer tools for building on the Nym mixnet.
## Where to start
## Start here
**Choosing an integration approach:** read [Integrations](/developers/integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path:
If you're new, read **[Choosing an Approach](/developers/integrations)** first. It maps your runtime (native vs browser vs mobile) and your architecture (end-to-end vs proxy) onto the right crate/library.
- **[Rust SDK](/developers/rust):** full-featured SDK with message passing, `AsyncRead`/`AsyncWrite` streams, and client pooling. Start with the [Tour](/developers/rust/tour).
- **[TypeScript SDK](/developers/typescript):** browser and Node.js SDK for mixFetch, Mixnet client, and smart contract interaction.
- **[Standalone Clients](/developers/clients):** language-agnostic SOCKS5 and WebSocket proxies for piping traffic through the Mixnet without an SDK.
## Crates/Libraries
## Blockchain interaction
| Crate/library | Language | Use it for |
|---|---|---|
| [`nym-sdk`](/developers/rust) | Rust | E2E messaging, `AsyncRead`/`AsyncWrite` streams, client pooling. Start with the [Tour](/developers/rust/tour). |
| [`smolmix`](/developers/smolmix) | Rust | `TcpStream` and `UdpSocket` over the Mixnet via a userspace IP stack. Compatible with `tokio-rustls`, `hyper`, `tungstenite`. |
| [`mix-fetch`](/developers/mix-fetch) | TypeScript | `fetch()`-compatible API for browser HTTP(S) requests over the Mixnet. |
| [TypeScript SDK](/developers/typescript) | TypeScript | Browser-side Mixnet Client (raw messaging) and Nyx Smart Contracts. |
| [Standalone Clients](/developers/clients) | Language-agnostic | SOCKS5 and WebSocket binaries for piping traffic through the Mixnet without an SDK. |
The Nym Network is coordinated by the [Nyx blockchain](/network/infrastructure/nyx). To query chain state, submit transactions, or interact with smart contracts, see [Chain Interaction](/developers/chain).
## Other sections
## API reference
Auto-generated API specs for Nym infrastructure endpoints are in the [APIs section](/apis/introduction).
- **[Chain interaction](/developers/chain)**: query Nyx state, submit transactions, and call Nym smart contracts.
- **[APIs](/apis/introduction)**: auto-generated reference for Nym infrastructure HTTP endpoints.
@@ -1,23 +1,42 @@
---
title: "Integrating With Nym"
description: "Choose an integration path for sending application traffic through the Nym mixnet, depending on your runtime environment and architecture."
title: "Choosing an Approach"
description: "Decide which Nym integration path fits your project. Compare nym-sdk, smolmix, mix-fetch, and the TypeScript SDK by runtime environment and architecture."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-04-07"
lastUpdated: "2026-05-12"
---
import { Callout } from 'nextra/components';
# Integrating With Nym
# Choosing an Approach
Any application that integrates with Nym sends its traffic through the Mixnet via a Nym client. The right integration path depends on two factors: **environment** and **architecture**.
Any application that integrates with Nym sends its traffic through the Mixnet via a Nym client. The right product depends on two factors: your **environment** (where your code runs) and your **architecture** (whether you control both sides of the communication).
## At a glance
| | **End-to-end** (both sides run Nym) | **Proxy mode** (Nym → clearnet exit) |
|---|---|---|
| **Rust** (native / desktop / CLI) | [`nym-sdk`](/developers/rust) (Stream, Mixnet, Client Pool) | [`smolmix`](/developers/smolmix) (TCP / UDP) · [`nym-sdk`](/developers/rust) SOCKS client |
| **TypeScript** (browser) | [TypeScript SDK](/developers/typescript) (WASM Mixnet Client, messaging only) | [`mix-fetch`](/developers/mix-fetch) (HTTP) |
| **Mobile** (iOS / Android) | _untested; see [Mobile note](#mobile-untested-ground) below_ | _untested; see [Mobile note](#mobile-untested-ground) below_ |
## Environment
Different runtimes have different transport constraints: a browser cannot open raw sockets or access the filesystem, while a desktop app can.
- **Native / Desktop**: full access to system networking and persistent storage. Use the [Rust SDK](./rust).
- **Browser**: restricted to WebSockets, Web Transport, and `fetch`, with HTTPS-only mixed content rules and no filesystem access. Use the [TypeScript SDK](./typescript).
- **Native / Desktop / CLI**: full access to system networking and persistent storage. Use [`nym-sdk`](/developers/rust) (the Rust SDK) for E2E messaging or byte streams, or [`smolmix`](/developers/smolmix) for TCP/UDP socket-shaped access in proxy mode.
- **Browser**: restricted to WebSockets, Web Transport, and `fetch`; HTTPS-only mixed-content rules; no filesystem access. Use [`mix-fetch`](/developers/mix-fetch) for HTTP(S) requests, or the [TypeScript SDK](/developers/typescript)'s WASM Mixnet Client for raw message passing.
### Mobile (untested ground)
There is no first-party mobile SDK, and we have not yet shipped tested mobile builds. The Rust crates *should* cross-compile to mobile targets. This is the path we'd take if starting today:
- Build [`nym-sdk`](/developers/rust) or [`smolmix`](/developers/smolmix) for the target triple (`aarch64-apple-ios`, `aarch64-linux-android`, etc.).
- Generate FFI bindings with [`uniffi`](https://mozilla.github.io/uniffi-rs/) and call them from Swift or Kotlin. The existing [Rust SDK FFI](/developers/rust/ffi) (Go, C/C++) is a useful reference for the binding shape.
- Run the tokio reactor on a dedicated background thread. iOS in particular has strong opinions about backgrounding and `libdispatch` interaction.
- Plan for TLS / cert-store integration up front. On mobile you'll typically want to call out to platform TLS (`SecTrust` on iOS, Android `NetworkSecurityConfig`) rather than embed a CA bundle.
If you try this and hit (or solve) blockers, we'd like to hear about it. Drop a note in the [Nym chat](https://nymtech.net/community) or open an issue on [GitHub](https://github.com/nymtech/nym).
## Architecture
@@ -25,10 +44,24 @@ The second factor is whether you control both sides of the communication.
**End-to-end (E2E)**: both sides run Nym clients. All traffic stays Sphinx-encrypted the entire way. Appropriate for peer-to-peer setups or any case where you control both endpoints.
**Proxy**: only the client side runs Nym. Traffic exits the Mixnet at an Exit Gateway and continues to the destination as normal internet traffic. Appropriate when connecting to third-party services (blockchain RPCs, external APIs) that you do not control.
![](/images/developers/nym-arch-client-to-client.png)
**Proxy**: only the client side runs Nym. Traffic exits the Mixnet at an Exit Gateway and continues to the destination over the public internet. The mixnet anonymises the sender; payload protection (TLS, Noise, etc.) is your application's job, as on a direct connection. Appropriate when connecting to third-party services such as blockchain RPCs or external APIs.
![](/images/developers/nym-arch-ip-routing.png)
<Callout type="warning">
In proxy mode, the last hop from Exit Gateway to the remote host travels as standard internet traffic. This is weaker than E2E against a global passive adversary, but still provides timing obfuscation and sender-receiver unlinkability.
Once traffic leaves the Exit Gateway, it travels over the public internet to the destination, exactly like any other server-initiated connection. The mixnet anonymises the sender but does not encrypt the payload past the gateway. Use TLS or another application-layer cipher as you would on a direct connection. See [Exit Gateway Services](/network/infrastructure/exit-services) for what the exit can and cannot observe.
</Callout>
See the [Native / Desktop](./native) and [Browser](./browsers) pages for the specific modules available in each environment.
**Browser apps**: both proxy and E2E modes work slightly differently in a browser setting. The Nym client runs as a WASM blob inside a Web Worker, and your application communicates with it via JS bindings rather than direct function calls. The mixnet behaviour is identical; the integration shape differs.
![](/images/developers/nym-browser-arch.png)
## Where to go next
- **Rust, E2E messaging or byte streams**: [`nym-sdk`](/developers/rust)
- **Rust, TCP/UDP socket replacements**: [`smolmix`](/developers/smolmix)
- **Browser, HTTP(S) requests**: [`mix-fetch`](/developers/mix-fetch)
- **Browser, raw mixnet messaging or Nyx smart contracts**: [TypeScript SDK](/developers/typescript)
- **Background on Sphinx, gateways, and the mixnet itself**: [Key Concepts](/developers/concepts)
@@ -0,0 +1,130 @@
---
title: "mix-fetch: fetch() Over the Nym Mixnet"
description: "Package providing a fetch()-compatible API that routes HTTP(S) requests through the Nym mixnet via a Network Requester. Available for browsers and Node.js."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-05-12"
---
import { Callout } from 'nextra/components'
# mix-fetch
`mix-fetch` is a replacement for [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) that routes HTTP(S) requests through the Nym mixnet. The call signature is identical; underneath, the request is tunnelled through a WASM Nym client to a Network Requester (a Nym service provider, typically operated by an Exit Gateway), which decodes a SOCKS5-shaped connect request and opens a TCP connection to the destination. TLS runs end-to-end between the WASM bundle and the destination server.
Available for browsers and Node.js, with the WASM core shared between both.
```text
┌────────────────────────────────────────────────────────────────────┐
│ Your app (browser or Node.js) │
│ └─ mixFetch('https://...') (fetch() replacement) │
│ └─ Go-WASM HTTP/TLS client (embedded Mozilla CA bundle) │
│ └─ Rust-WASM SOCKS5 framing + Nym mixnet transport │
│ └─ WebSocket → entry gateway │
│ └─ mixnet (Sphinx, 3 mix hops by default) │
│ └─ Network Requester decodes SOCKS5 │
│ request, opens TCP to dest │
│ └─ destination server │
│ (TLS handshake here) │
└────────────────────────────────────────────────────────────────────┘
```
Two WASM modules sit in the bundle: a Go module that handles HTTP and TLS (Go's `crypto/tls` compiled to WASM, with an embedded Mozilla root CA list), and a Rust module that handles SOCKS5 request framing and the Nym mixnet client itself. They communicate via JS bindings.
Because TLS terminates at the destination (not at the Network Requester or any node before it), every hop from the entry gateway onwards only sees TLS ciphertext for HTTPS targets. This is the same trust model as a normal HTTPS request through a SOCKS proxy.
<Callout type="info">
The "SOCKS5" framing here is Nym's binary `Socks5Request` format wrapped in Sphinx packets, not RFC 1928 SOCKS5 over a TCP socket. The Network Requester decodes it on the mixnet side and proxies onwards as regular TCP.
</Callout>
## Runtime and platform support
### Browser
The WASM core runs in a Web Worker and needs:
- WebSocket support, for the entry-gateway connection
- WebAssembly
- A CSP that permits `wss://` connections and `worker-src 'self'` (or `blob:` for the `*-full-fat` variants, which load workers as inline blobs)
Mixed-content rules apply: target URLs must be HTTPS.
### Node.js
The same WASM core runs in a `worker_threads` worker. The `ws` package polyfills `WebSocket`, and a Node-flavoured `comlink` adapter (`mix-fetch-node/src/node-adapter.ts`) bridges `worker_threads` to the same Worker-like API surface.
## Installation
### Browser variants
| Variant | Package | When to use |
|---|---|---|
| ESM | `@nymproject/mix-fetch` | Modern project, you can configure your bundler |
| ESM full-fat | `@nymproject/mix-fetch-full-fat` | Modern project, can't configure your bundler |
| CommonJS | `@nymproject/mix-fetch-commonjs` | Legacy project, you can configure your bundler |
| CommonJS full-fat | `@nymproject/mix-fetch-commonjs-full-fat` | Legacy project, can't configure your bundler |
### Node.js variant
| Variant | Package | When to use |
|---|---|---|
| CommonJS | `@nymproject/mix-fetch-node-commonjs` | Node.js (currently the only published Node variant) |
The standard browser variants need your bundler to handle WASM and web workers (see [Bundling](/developers/typescript/bundling)). The `*-full-fat` variants inline both as Base64 so no bundler configuration is needed.
<Callout type="warning">
The `*-full-fat` variants are large (~18 MB), since they inline ~10 MB of WASM (Go runtime + Rust core) and the web-worker source as Base64. Prefer a standard variant if bundle size matters.
</Callout>
```bash
# Browser
npm install @nymproject/mix-fetch-full-fat
# Node.js
npm install @nymproject/mix-fetch-node-commonjs
```
<Callout type="info">
`mixFetch` caps concurrent connections at **10 per destination host** (Go `http.Transport`'s `MaxConnsPerHost`, see `wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go:214`). Keep-alive is disabled, so each request opens a fresh TCP connection through the mixnet; extra concurrent requests to the same host queue until a slot frees. Different hosts are independent.
</Callout>
## Playground and examples
See the [interactive playground](/developers/typescript/playground/mixfetch) for a working `mixFetch` example you can run in the browser.
The first call bootstraps the WASM Nym client (gateway handshake, key generation, cover traffic). Subsequent calls reuse the active client; the Rust side holds it in a `OnceLock` singleton, so there is one client per page (or per Node process).
## When to use mix-fetch
| | mix-fetch | WASM Mixnet Client | smolmix | Plain fetch (no mixnet) |
|---|---|---|---|---|
| **Runtime** | Browser, Node.js | Browser | Native (Rust) | Anywhere |
| **Architecture** | Proxy (Network Requester → destination) | E2E (both sides Nym) | Proxy | Direct |
| **API shape** | `fetch()` replacement | Send/recv text or binary messages | `TcpStream` / `UdpSocket` | `fetch()` |
| **HTTP support** | Yes | No | Yes (via `hyper` over `TcpStream`) | Yes |
| **Sender unlinkability** | Strong (mixnet) | Strong (mixnet) | Strong (mixnet) | None |
| **Concurrency** | 10 per host | Unlimited | Unlimited | Unlimited |
## Security model
<Callout type="warning">
Use HTTPS targets. Plaintext HTTP requests are visible to the Network Requester and to any router between it and the destination.
</Callout>
### What's protected
| Segment | Mixnet encryption | What's visible |
|---|---|---|
| App → entry gateway | Sphinx (layered) over a WebSocket | Entry gateway sees your IP, not the destination |
| Inside the mixnet | Sphinx (layered) | Each node only knows previous / next hop |
| Network Requester | Sphinx removed; SOCKS5 connect request decoded | The Requester sees destination hostname + port; payload is application-layer TLS |
| Network Requester → destination | None (regular TCP) | TLS handshake + ciphertext (with HTTPS targets); cleartext (with HTTP targets) |
### Why mix-fetch ships its own CA store
The browser's TLS stack and CA store aren't accessible from JavaScript or from a WASM SOCKS client; on Node, the TLS stack lives outside the Web Worker that hosts the mixnet client. `mix-fetch` therefore performs TLS itself, inside the WASM bundle, against the destination server. The bundle ships with an embedded Mozilla root CA list (refreshed from [curl.se's bundle](https://curl.se/docs/caextract.html), verified by SHA-256 in `wasm/mix-fetch/go-mix-conn/scripts/update-root-certs.sh`) and an in-WASM TLS implementation (Go's `crypto/tls`, configured at `wasm/mix-fetch/go-mix-conn/internal/sslhelpers/ssl_helper.go`). The mixnet path sees encrypted TLS ciphertext, not plaintext.
`mix-fetch` handles the TLS layer for you: HTTPS targets are protected end-to-end between the WASM bundle and the destination, as if a browser had initiated the TLS handshake directly. Plaintext HTTP targets remain visible to the Network Requester and to any router beyond it. See [Exit Gateway Services](/network/infrastructure/exit-services) for what the exit can and cannot observe.
## API reference
Generated reference: [typedoc output](/developers/typescript/api/mix-fetch).
@@ -1,63 +0,0 @@
---
title: "Native and Desktop App Integration"
description: "Integrate privacy into native desktop apps and CLIs using the Nym Rust SDK. Choose between end-to-end mixnet messaging or TCP proxy approaches."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-03-15"
---
import { Callout } from 'nextra/components';
# Native / Desktop Apps
Desktop apps and CLIs integrate via the [Rust SDK](./rust), with two broad approaches: embedding Nym clients on both sides of the communication (E2E), or using the Mixnet as a proxy to reach external services.
## Option 1: Mixnet End-To-End
Both sides of your app run Nym clients. All traffic stays Sphinx-encrypted the entire way. Works for peer-to-peer setups or any case where you control both ends.
![](/images/developers/nym-arch-client-to-client.png)
### Stream Module
The [Stream module](./rust/stream) provides `AsyncRead + AsyncWrite` byte streams multiplexed over the mixnet, the closest analogue to TCP sockets.
- [docs](./rust/stream)
- [tutorial](./rust/stream/tutorial)
### Mixnet & Client Pool Modules
The [Mixnet module](./rust/mixnet) exposes the raw message API and `MixnetClient`. The [Client Pool](./rust/client-pool) maintains pre-connected clients for bursty workloads. These are appropriate when you need full control over the communication model.
- [docs](./rust/mixnet)
- [tutorial](./rust/mixnet/tutorial)
### TcpProxy Module (Unmaintained)
<Callout type="error">
**This module is unmaintained.** Use the [Stream module](./rust/stream) for new projects. Existing users should plan to migrate when possible.
</Callout>
Exposes localhost TCP sockets that proxy traffic through the mixnet.
- [docs](./rust/tcpproxy)
## Option 2: Mixnet-As-Proxy
For cases where you only control the client side and need to reach a third-party service such as a blockchain RPC or remote API.
![](/images/developers/nym-arch-ip-routing.png)
<Callout type="warning">
### Security Considerations
Traffic is Sphinx-encrypted until the Exit Gateway, where it's unwrapped into HTTPS (Network Requester) or raw IP (IP Packet Router). The last hop to the remote host **travels as normal internet traffic**.
Weaker than E2E against a global passive adversary, but you still get timing obfuscation and sender-receiver unlinkability between your client and the remote service.
</Callout>
### SOCKS Client
Applications that support SOCKS4, 4a, or 5 can use the Socks Client exposed by the Mixnet module. Traffic is routed through the Exit Gateway's Network Requester, which uses SURBs to reply to the sender anonymously.
- [docs](./rust/mixnet)
<Callout type="info">
Development is in progress to allow for this proxy method from native Rust, C, and Go without requiring a separate SOCKS client. Stay tuned.
</Callout>
@@ -74,7 +74,7 @@ yay -S gcc make protobuf base-devel clang
apt install gcc make protobuf-compiler pkconfig libdbus-1-dev build-essential clang
```
<Callout type="warning" emoji="⚠️">
<Callout type="warning">
Older Debian/Ubuntu versions need to manually install `protobuf-compiler` >= v3.21.12
</Callout>
@@ -169,7 +169,7 @@ nym-vpnc device get
You can fund your VPN usage directly from your own wallet instead of going through the NymVPN account system. You deposit `$NYM` into the ticketbook smart contract and receive zk-nym ticketbooks that authenticate you on the network.
<Callout type="warning" emoji="⚠️">
<Callout type="warning">
If you already have an account stored in `nym-vpnc`, you must remove it first with `nym-vpnc account forget` before setting a new mnemonic.
</Callout>
+32 -13
View File
@@ -1,34 +1,53 @@
---
title: "Nym Rust SDK: Privacy Apps for the Mixnet"
description: "Rust SDK reference for building privacy applications on the Nym mixnet. Covers the Mixnet client, Stream multiplexing, Client Pool, and code examples."
title: "nym-sdk: Rust SDK for the Nym Mixnet"
description: "Rust SDK reference for building privacy applications on the Nym mixnet. Covers the Mixnet client, Stream multiplexing, Client Pool, FFI bindings, and code examples."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-03-13"
lastUpdated: "2026-05-12"
---
# Rust SDK
# nym-sdk
import { Callout } from 'nextra/components'
import { CratesPaused } from '../../components/crates-paused'
All modules share a common `MixnetClient` that manages gateway connections, Sphinx packet encryption, routing, and cover traffic.
`nym-sdk` is the Rust SDK for the Nym mixnet. All modules share a common `MixnetClient` that manages gateway connections, Sphinx encryption, and cover traffic.
```text
┌──────────────────────────────────────────────────────────────┐
│ Your Rust app (alice) │
│ └─ MixnetClient (Sphinx layering, cover traffic) │
│ └─ WebSocket to entry gateway │
│ └─ Nym mixnet (entry → 3 mix layers → exit) │
│ └─ MixnetClient (bob) │
│ └─ Your Rust app (bob) │
└──────────────────────────────────────────────────────────────┘
```
Both sides run a `MixnetClient`. Sphinx encryption protects every hop end-to-end; neither gateway nor any mix node can link sender to receiver.
<Callout type="info">
Full API reference: [**docs.rs/nym-sdk**](https://docs.rs/nym-sdk/latest/nym_sdk/)
</Callout>
<CratesPaused />
For an overview of what the SDK can do, see the **[Tour](./rust/tour)**. For setup instructions, see [Installation](./rust/importing).
## Modules
- **[Stream](./rust/stream)**: multiplexed `AsyncRead + AsyncWrite` byte streams over the Mixnet. **If you're used to TCP sockets, start here.**
| Module | What it does | Status |
|---|---|---|
| [**Stream**](./rust/stream) | Multiplexed `AsyncRead + AsyncWrite` byte streams over the Mixnet, the closest analogue to TCP sockets. | Recommended |
| [**Mixnet**](./rust/mixnet) | Raw message payloads, independently routed, no connections or ordering. Full control over the communication model. | Stable |
| [**Client Pool**](./rust/client-pool) | Keeps ready-to-use `MixnetClient` instances warm for bursty workloads. | Stable |
| [**TcpProxy**](./rust/tcpproxy) | TCP socket proxying with session management and message ordering. | Deprecated |
| [**FFI**](./rust/ffi) | Go and C/C++ bindings. | Stable |
- **[Mixnet](./rust/mixnet)**: raw message payloads, independently routed, no connections or ordering. Use this when you want full control over the communication model.
<Callout type="error">
**TcpProxy is deprecated.** Use the [Stream module](./rust/stream) for new projects.
</Callout>
- **[Client Pool](./rust/client-pool)**: keeps ready-to-use `MixnetClient` instances warm for bursty workloads.
## Proxy-mode crates
- **[TcpProxy](./rust/tcpproxy)** *(deprecated)*: TCP socket proxying with session management and message ordering. Use Stream for new projects.
For proxy-mode integrations (reaching third-party services through an Exit Gateway), see also:
- **[FFI](./rust/ffi)**: Go and C/C++ bindings.
- [**`smolmix`**](/developers/smolmix): `TcpStream` and `UdpSocket` over the Mixnet via a userspace IP stack. Compatible with `tokio-rustls`, `hyper`, `tokio-tungstenite`, and the rest of the async Rust ecosystem.
- [**SOCKS Client**](./rust/mixnet): SOCKS4/4a/5 proxy via the Exit Gateway's Network Requester. Works with any SOCKS-capable application without code changes.
@@ -10,7 +10,7 @@ lastUpdated: "2026-03-15"
import { Callout } from 'nextra/components'
The `ClientPool` maintains a configurable number of connected ephemeral `MixnetClient` instances, ready for immediate use. This eliminates the connection latency that comes with creating a new client on each request: the gateway handshake, key generation, and topology fetch all happen ahead of time.
The `ClientPool` keeps a configurable number of `MixnetClient` instances pre-connected in a background loop, so callers don't pay the gateway handshake, key generation, and topology fetch cost on the hot path.
## How it works
@@ -26,11 +26,11 @@ flowchart LR
BG -->|"pool < reserve? create another"| P
```
1. **Create** the pool with a target reserve size: `ClientPool::new(5)`
2. **Start** the background loop: `pool.start()`. It immediately begins connecting clients
3. **Pop** a client when needed: `pool.get_mixnet_client()` returns `Some(client)` or `None` if the pool is empty
4. **Use** the client normally: send messages, open streams, etc.
5. **Disconnect** the client when done. The background loop notices the pool is below reserve and creates a replacement
1. Create the pool with a target reserve size: `ClientPool::new(5)`.
2. Start the background loop: `pool.start()`. It immediately begins connecting clients.
3. Pop a client when needed: `pool.get_mixnet_client()` returns `Some(client)` or `None` if the pool is empty.
4. Use the client normally: send messages, open streams.
5. Disconnect the client when done. The background loop notices the pool is below reserve and creates a replacement.
Clients are **consumed, not returned**. The pool creates new ones to maintain the reserve. If the pool is empty, you can fall back to `MixnetClient::connect_new()` (slower, but keeps things working).
@@ -45,7 +45,7 @@ use nym_sdk::client_pool::ClientPool;
use nym_network_defaults::setup_env;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
nym_bin_common::logging::setup_tracing_logger();
// Load mainnet network defaults into env vars (required by ClientPool)
setup_env(None::<String>);
@@ -3,15 +3,16 @@ title: "Client Pool Tutorial: Handle Bursty Traffic"
description: "Step-by-step Rust tutorial to use Nym ClientPool for handling bursts of concurrent mixnet operations without blocking on client creation."
schemaType: "HowTo"
section: "Developers"
lastUpdated: "2026-03-26"
lastUpdated: "2026-04-17"
---
# Tutorial: Handle Bursty Traffic with Client Pool
import { Callout } from 'nextra/components'
import { CodeVerified } from '../../../../components/code-verified'
import { RUST_MSRV } from '../../../../components/versions'
In this tutorial you'll build a program that uses `ClientPool` to handle bursts of concurrent Mixnet operations without blocking on client creation. You'll see how the pool pre-creates clients in the background, how to pop them under load, and what happens when demand exceeds supply.
A program that uses `ClientPool` to absorb bursts of concurrent Mixnet operations without paying client-creation latency on the hot path. The pool pre-creates clients in the background; tasks pop them under load; the tutorial also walks through what happens when demand outruns supply.
## What you'll learn
@@ -25,7 +26,7 @@ In this tutorial you'll build a program that uses `ClientPool` to handle bursts
## Prerequisites
- Rust toolchain (1.70+)
- Rust toolchain ({RUST_MSRV}+)
- A working internet connection
## Step 1: Set up the project
@@ -39,11 +40,10 @@ Add dependencies to `Cargo.toml`:
```toml
[dependencies]
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" }
nym-network-defaults = { git = "https://github.com/nymtech/nym", rev = "97068b2" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "97068b2", features = ["basic_tracing"] }
nym-sdk = "1.21.0"
nym-network-defaults = "1.21.0"
nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] }
tokio = { version = "1", features = ["full"] }
blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing
```
## Step 2: Create and start the pool
@@ -75,7 +75,7 @@ async fn main() {
pool_bg.start().await.unwrap();
});
println!("Pool started waiting for clients to connect...");
println!("Pool started, waiting for clients to connect...");
tokio::time::sleep(Duration::from_secs(15)).await;
// Check how many are ready
@@ -106,14 +106,14 @@ When you call `get_mixnet_client()`, the pool removes a client and returns it. T
c
}
None => {
// Pool is empty fall back to creating one on the fly.
// Pool is empty; fall back to creating one on the fly.
// This is slower but keeps things working.
println!("Task {i}: pool empty, creating client on the fly...");
nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap()
}
};
// Do something with the client — here, send a message to ourselves
// Do something with the client. Here, send a message to ourselves.
let addr = *client.nym_address();
client
.send_plain_message(addr, format!("hello from task {i}"))
@@ -132,7 +132,7 @@ When you call `get_mixnet_client()`, the pool removes a client and returns it. T
}
}
// Disconnect when done the pool will create a replacement
// Disconnect when done; the pool will create a replacement.
client.disconnect().await;
println!("Task {i}: done");
});
@@ -177,7 +177,7 @@ RUST_LOG=info cargo run
You'll see output like:
```
Pool started waiting for clients to connect...
Pool started, waiting for clients to connect...
Pool has 3 clients ready
Task 1: got client 8gk4Y...@2xU4d... from pool
Task 2: got client F3qR7...@9nK2m... from pool
@@ -231,20 +231,33 @@ async fn main() {
setup_env(None::<String>);
let pool = ClientPool::new(3);
let pool_bg = pool.clone();
tokio::spawn(async move { pool_bg.start().await.unwrap() });
println!("Waiting for pool to fill...");
let pool_bg = pool.clone();
tokio::spawn(async move {
pool_bg.start().await.unwrap();
});
println!("Pool started, waiting for clients to connect...");
tokio::time::sleep(Duration::from_secs(15)).await;
println!("Pool has {} clients", pool.get_client_count().await);
let count = pool.get_client_count().await;
println!("Pool has {count} clients ready");
let mut handles = vec![];
for i in 1..=3 {
let pool = pool.clone();
handles.push(tokio::spawn(async move {
let handle = tokio::spawn(async move {
let mut client = match pool.get_mixnet_client().await {
Some(c) => c,
None => nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap(),
Some(c) => {
println!("Task {i}: got client {} from pool", c.nym_address());
c
}
None => {
println!("Task {i}: pool empty, creating client on the fly...");
nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap()
}
};
let addr = *client.nym_address();
@@ -254,24 +267,34 @@ async fn main() {
.unwrap();
if let Some(msgs) = client.wait_for_messages().await {
for msg in msgs.iter().filter(|m| !m.message.is_empty()) {
println!("Task {i}: {}", String::from_utf8_lossy(&msg.message));
for msg in msgs {
if !msg.message.is_empty() {
println!(
"Task {i}: received {:?}",
String::from_utf8_lossy(&msg.message)
);
}
}
}
client.disconnect().await;
}));
println!("Task {i}: done");
});
handles.push(handle);
}
for h in handles {
h.await.unwrap();
}
println!("Waiting for replenishment...");
println!("\nWaiting for pool to replenish...");
tokio::time::sleep(Duration::from_secs(15)).await;
println!("Pool has {} clients", pool.get_client_count().await);
let count = pool.get_client_count().await;
println!("Pool has {count} clients ready again");
pool.disconnect_pool().await;
println!("Done");
println!("Pool shut down");
}
```
@@ -19,7 +19,7 @@ ffi
└── shared # Shared Rust implementation
```
Core logic lives in `shared/` and is imported into language-specific wrappers. The shared layer handles thread safety and ensures client operations run on blocking threads on the Rust side of the FFI boundary.
Core logic lives in `shared/` and is imported into language-specific wrappers. The shared layer handles thread safety and runs client operations on blocking threads on the Rust side of the FFI boundary.
## What's exposed
@@ -36,7 +36,7 @@ The TcpProxy module is deprecated. For new projects, use the [Stream module](./s
## Quick example (Go)
```go
// Initialize an ephemeral client
// Initialise an ephemeral client
bindings.InitEphemeral()
// Get our Nym address
@@ -3,27 +3,24 @@ title: "Install the Nym Rust SDK"
description: "Add nym-sdk to your Rust project from Git or crates.io. Covers version requirements, minimum Rust version, and current feature gate status."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-03-27"
lastUpdated: "2026-04-17"
---
# Installation
import { Callout } from 'nextra/components';
import { CratesPaused } from '../../../components/crates-paused'
<CratesPaused />
import { RUST_MSRV } from '../../../components/versions'
```toml
[dependencies]
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" }
blake3 = "=1.7.0" # pin to avoid a transitive dependency conflict — see note below
nym-sdk = "1.21.0"
```
<Callout type="warning">
**Temporary pin required.** You must pin `blake3 = "=1.7.0"` in your `Cargo.toml` to avoid a build failure caused by a transitive `digest` version conflict. This will be resolved in a future SDK release.
</Callout>
**Minimum Rust version:** {RUST_MSRV}+
You can also track a branch if you want the latest changes:
### From Git
You can also import directly from Git if you want unreleased changes:
```toml
# development branch (latest changes, may be unstable)
@@ -33,19 +30,6 @@ nym-sdk = { git = "https://github.com/nymtech/nym", branch = "develop" }
nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" }
```
**Minimum Rust version:** 1.70+
### crates.io (older API only)
If you don't need the Stream module or other recent additions, you can still use the published crate:
```toml
[dependencies]
nym-sdk = "1.20.4"
```
This version includes the Mixnet message API, Client Pool, and TcpProxy modules.
<Callout type="warning">
**No feature gates yet.** Importing `nym-sdk` pulls in everything (mixnet, tcp_proxy, client_pool, etc.) and their full dependency trees. Cargo feature flags are planned.
</Callout>
@@ -10,7 +10,7 @@ lastUpdated: "2026-03-13"
import { Callout } from 'nextra/components';
The `mixnet` module is the core of the Nym SDK. It provides [`MixnetClient`](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetClient.html) for connecting to the Nym Mixnet, sending messages through Sphinx packet encryption and 5-hop routing, and receiving reconstructed messages on the other side.
The `mixnet` module provides [`MixnetClient`](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetClient.html) for connecting to the Nym Mixnet, sending messages through Sphinx packet encryption and 5-hop routing, and receiving reconstructed messages on the other side.
<Callout type="warning">
Messages are individually routed through the Mixnet with no guaranteed ordering or persistent connections. If you want familiar socket-like I/O (`read`/`write`), use the [Stream module](./stream) instead. See the [Tour](./tour) for how the two approaches compare.
@@ -23,7 +23,7 @@ cargo run --example <NAME>
| Parallel Send/Receive | [`parallel_sending_and_receiving.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs) | Using `split_sender()` for concurrent tasks |
| Sandbox Testnet | [`sandbox.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/sandbox.rs) | Connecting to the Sandbox testnet instead of mainnet |
| Bandwidth Credential | [`bandwidth.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/bandwidth.rs) | Acquiring a bandwidth credential for paid mixnet access |
| Custom Topology | [`custom_topology_provider.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/custom_topology_provider.rs) | Implementing the `TopologyProvider` trait to filter or customize node selection |
| Custom Topology | [`custom_topology_provider.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/custom_topology_provider.rs) | Implementing the `TopologyProvider` trait to filter or customise node selection |
| Overwrite Topology | [`manually_overwrite_topology.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs) | Manually constructing a topology with hardcoded nodes |
| Control Requests | [`control_requests.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/control_requests.rs) | Sending service provider control requests (health, version, binary info) |
| Custom Storage | [`manually_handle_storage.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/manually_handle_storage.rs) | Implementing custom storage backends for keys, gateways, and credentials |
@@ -3,17 +3,18 @@ title: "Mixnet Tutorial: Send Your First Private Message"
description: "Step-by-step Rust tutorial to connect to the Nym mixnet, send and receive messages, reply anonymously with SURBs, and persist client identity."
schemaType: "HowTo"
section: "Developers"
lastUpdated: "2026-03-26"
lastUpdated: "2026-04-17"
---
# Tutorial: Send Your First Private Message
import { Callout } from 'nextra/components'
import { CodeVerified } from '../../../../components/code-verified'
import { RUST_MSRV } from '../../../../components/versions'
By the end of this tutorial you'll have a working program that sends a Sphinx-encrypted message to itself through the Nym Mixnet, receives it, and replies anonymously using SURBs. The later sections cover persistent identity and concurrent send/receive.
A program that sends a Sphinx-encrypted message to itself through the Nym Mixnet, receives it, and replies anonymously using SURBs. Later sections cover persistent identity and concurrent send/receive.
**You'll need:** Rust 1.70+ and an internet connection (clients connect to the live Mixnet).
Requires Rust {RUST_MSRV}+ and an internet connection (clients connect to the live Mixnet).
<CodeVerified />
@@ -28,10 +29,9 @@ Add dependencies to `Cargo.toml`:
```toml
[dependencies]
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "97068b2", features = ["basic_tracing"] }
nym-sdk = "1.21.0"
nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] }
tokio = { version = "1", features = ["full"] }
blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing
```
## Step 2: Connect and send
@@ -45,21 +45,21 @@ use nym_sdk::mixnet::{self, MixnetMessageSender};
async fn main() {
nym_bin_common::logging::setup_tracing_logger();
// connect_new() creates an ephemeral client keys are generated in
// connect_new() creates an ephemeral client; keys are generated in
// memory and discarded on disconnect.
let mut client = mixnet::MixnetClient::connect_new().await.unwrap();
let our_address = client.nym_address();
println!("Connected: {our_address}");
// The message is Sphinx-encrypted and mixed across 5 nodes.
// send_plain_message only blocks until the message is queued
// send_plain_message only blocks until the message is queued;
// encryption and mixing happen in background tasks.
client
.send_plain_message(*our_address, "hello from the mixnet!")
.await
.unwrap();
println!("Sent waiting for arrival...");
println!("Sent, waiting for arrival...");
```
<Callout type="info">
@@ -70,7 +70,7 @@ async fn main() {
```rust
// wait_for_messages() returns the next batch of incoming messages.
// Filter empty messages these are SURB replenishment requests.
// Filter empty messages: these are SURB replenishment requests.
let message = loop {
if let Some(msgs) = client.wait_for_messages().await {
if let Some(msg) = msgs.into_iter().find(|m| !m.message.is_empty()) {
@@ -89,7 +89,7 @@ Every message includes a `sender_tag`, an opaque `AnonymousSenderTag` that lets
```rust
let sender_tag = message.sender_tag.expect("should have sender tag");
// send_reply uses the SURB the sender's address is never revealed.
// send_reply uses the SURB; the sender's address is never revealed.
client.send_reply(sender_tag, "hello back, anonymously!").await.unwrap();
let reply = loop {
@@ -114,7 +114,7 @@ RUST_LOG=info cargo run
```
Connected: 8gk4Y...@2xU4d...
Sent waiting for arrival...
Sent, waiting for arrival...
Received: hello from the mixnet!
Reply: hello back, anonymously!
```
@@ -140,21 +140,38 @@ async fn main() {
.await
.unwrap();
println!("Address: {}", client.nym_address());
let our_address = client.nym_address();
println!("Address: {our_address}");
// Same API as before send, receive, reply.
// Same API as before: send, receive, SURB reply.
client
.send_plain_message(*client.nym_address(), "persistent identity!")
.send_plain_message(*our_address, "hello from persistent client!")
.await
.unwrap();
println!("Sent, waiting for arrival...");
let message = loop {
if let Some(msgs) = client.wait_for_messages().await {
for m in msgs.into_iter().filter(|m| !m.message.is_empty()) {
println!("Received: {}", String::from_utf8_lossy(&m.message));
if let Some(msg) = msgs.into_iter().find(|m| !m.message.is_empty()) {
break msg;
}
}
};
println!("Received: {}", String::from_utf8_lossy(&message.message));
// Always disconnect for clean shutdown — background tasks need to be
let sender_tag = message.sender_tag.expect("should have sender tag");
client.send_reply(sender_tag, "anonymous reply!").await.unwrap();
let reply = loop {
if let Some(msgs) = client.wait_for_messages().await {
if let Some(msg) = msgs.into_iter().find(|m| !m.message.is_empty()) {
break msg;
}
}
};
println!("Reply: {}", String::from_utf8_lossy(&reply.message));
// Always disconnect for clean shutdown; background tasks need to be
// stopped and state files flushed.
client.disconnect().await;
}
@@ -184,7 +201,7 @@ async fn main() {
// split_sender() returns a clone-able MixnetClientSender.
let sender = client.split_sender();
// Spawn a receiver the original client implements futures::Stream.
// Spawn a receiver: the original client implements futures::Stream.
let rx = tokio::spawn(async move {
if let Some(msg) = client.next().await {
println!("Received: {}", String::from_utf8_lossy(&msg.message));
@@ -231,7 +248,7 @@ async fn main() {
.send_plain_message(*our_address, "hello from the mixnet!")
.await
.unwrap();
println!("Sent waiting for arrival...");
println!("Sent, waiting for arrival...");
let message = loop {
if let Some(msgs) = client.wait_for_messages().await {
@@ -270,6 +287,7 @@ async fn main() {
nym_bin_common::logging::setup_tracing_logger();
let paths = StoragePaths::new_from_dir("./my-client-data").unwrap();
let mut client = mixnet::MixnetClientBuilder::new_with_default_storage(paths)
.await
.unwrap()
@@ -280,13 +298,13 @@ async fn main() {
.unwrap();
let our_address = client.nym_address();
println!("Connected: {our_address}");
println!("Address: {our_address}");
client
.send_plain_message(*our_address, "hello from the mixnet!")
.send_plain_message(*our_address, "hello from persistent client!")
.await
.unwrap();
println!("Sent waiting for arrival...");
println!("Sent, waiting for arrival...");
let message = loop {
if let Some(msgs) = client.wait_for_messages().await {
@@ -298,7 +316,7 @@ async fn main() {
println!("Received: {}", String::from_utf8_lossy(&message.message));
let sender_tag = message.sender_tag.expect("should have sender tag");
client.send_reply(sender_tag, "hello back, anonymously!").await.unwrap();
client.send_reply(sender_tag, "anonymous reply!").await.unwrap();
let reply = loop {
if let Some(msgs) = client.wait_for_messages().await {
@@ -9,13 +9,10 @@ lastUpdated: "2026-03-15"
# Stream Module
import { Callout } from 'nextra/components'
import { CratesPaused } from '../../../components/crates-paused'
<CratesPaused />
The Mixnet is fundamentally message-based: no persistent connections, no guaranteed ordering, no TCP. The default [message API](./mixnet) works at this level, sending individual payloads independently through Mix Nodes. This is effective for privacy but unlike how most networking code is structured.
The **Stream module** bridges the gap by providing persistent, bidirectional byte channels that behave like TCP sockets. Each `MixnetStream` implements [`AsyncRead`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html) and [`AsyncWrite`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html), so `tokio::io::copy`, codecs, `BufReader`/`BufWriter`, and any other async I/O consumer work without modification. **If you're coming from socket-based networking, start here.**
The Stream module bridges the gap by providing persistent, bidirectional byte channels that behave like TCP sockets. Each `MixnetStream` implements [`AsyncRead`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html) and [`AsyncWrite`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html), so `tokio::io::copy`, codecs, `BufReader`/`BufWriter`, and any other async I/O consumer work without modification.
All streams are multiplexed over a single `MixnetClient`. A background router task reads a small header on each incoming message and dispatches the payload to the correct stream by ID, so multiple concurrent streams require no additional connections or gateways.
@@ -23,10 +20,10 @@ All streams are multiplexed over a single `MixnetClient`. A background router ta
The two sides of a stream connection follow a client/server pattern:
1. **Opener** calls `client.open_stream(recipient, surbs)`. This generates a random `StreamId`, registers the stream locally, and sends an `Open` message through the Mixnet.
2. **Listener** calls `listener.accept()`, which blocks until an `Open` arrives, registers the new stream, and returns a `MixnetStream` ready for reading and writing.
1. The opener calls `client.open_stream(recipient, surbs)`. This generates a random `StreamId`, registers the stream locally, and sends an `Open` message through the Mixnet.
2. The listener calls `listener.accept()`, which blocks until an `Open` arrives, registers the new stream, and returns a `MixnetStream` ready for reading and writing.
3. Both sides read and write using standard `AsyncRead`/`AsyncWrite`. Bytes are wrapped in a 16-byte LP frame header (stream ID, message type, sequence number), routed through the Mixnet, and demultiplexed on arrival.
4. **Cleanup** happens on `drop`. The stream deregisters from the local router. No close message is sent over the wire, since a close could race ahead of in-flight data.
4. On drop, the stream deregisters from the local router. No close message is sent over the wire, since a close could race ahead of in-flight data.
```text
┌─────────────────────────────────────────────────────────┐
@@ -94,7 +91,7 @@ async fn main() {
.expect("timed out")
.expect("listener closed");
// Send data and read it back just like a TCP socket
// Send data and read it back, just like a TCP socket
outbound.write_all(b"hello from sender").await.unwrap();
outbound.flush().await.unwrap();
@@ -139,7 +136,7 @@ The receiver replies via **reply SURBs** (Single Use Reply Blocks) and never lea
| **Status** | Stable | New | Deprecated |
<Callout type="warning">
**Streams and messages are mutually exclusive.** Once you call `open_stream()` or `listener()`, the message-based API (`send_plain_message`, `wait_for_messages`) is permanently disabled on that client. This is a one-way transition: there is no switching back without disconnecting and reconnecting. See the [`stream_mode_guard.rs` example](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_mode_guard.rs) for details.
Streams and messages are mutually exclusive. Once you call `open_stream()` or `listener()`, the message-based API (`send_plain_message`, `wait_for_messages`) is permanently disabled on that client. This is a one-way transition: no switching back without disconnecting and reconnecting. See the [`stream_mode_guard.rs` example](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_mode_guard.rs) for details.
</Callout>
## Next steps
@@ -80,7 +80,7 @@ There is no switching back without disconnecting and creating a new client.
## Known limitations
<Callout type="info">
**Sequence-based reordering.** The Mixnet does not guarantee message ordering at the transport level, but each stream write includes a `sequence_num` in the LP frame header. The receiver maintains a per-stream reorder buffer (BTreeMap keyed by sequence number) that buffers out-of-order messages and drains them in sequence. This means protocols that depend on byte ordering (HTTP, TLS, protobuf) work correctly over streams.
The Mixnet does not guarantee message ordering at the transport level, but each stream write includes a `sequence_num` in the LP frame header. The receiver maintains a per-stream reorder buffer (BTreeMap keyed by sequence number) that buffers out-of-order messages and drains them in sequence, so protocols that depend on byte ordering (HTTP, TLS, protobuf) work correctly over streams.
- **Buffer cap:** 256 messages per stream. If the buffer fills (e.g. a large gap in sequence numbers), the receiver skips ahead to the lowest buffered sequence.
- **Duplicates:** messages with a sequence number below the next expected are dropped.
@@ -89,4 +89,4 @@ There is no switching back without disconnecting and creating a new client.
## Internal details
For the full implementation details (router task, `StreamMap`, `PollSender` usage, base-client type rationale), see the `ARCHITECTURE.md` file next to the module source code. This will also be available on [docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/) once crate publication resumes with the Lewes Protocol.
For the full implementation details (router task, `StreamMap`, `PollSender` usage, base-client type rationale), see the `ARCHITECTURE.md` file next to the module source code, or the [docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/) API reference.
@@ -3,15 +3,16 @@ title: "Stream Tutorial: Build a Private Echo Server"
description: "Step-by-step Rust tutorial to build an echo server and client communicating through the Nym mixnet using AsyncRead and AsyncWrite streams."
schemaType: "HowTo"
section: "Developers"
lastUpdated: "2026-03-26"
lastUpdated: "2026-04-17"
---
# Tutorial: Build a Private Echo Server
import { Callout } from 'nextra/components'
import { CodeVerified } from '../../../../components/code-verified'
import { RUST_MSRV } from '../../../../components/versions'
In this tutorial you'll build two programs: a server that listens for incoming streams and echoes back whatever it receives, and a client that opens a stream, sends data, and reads the echo. Both communicate through the Nym Mixnet using `AsyncRead` and `AsyncWrite`, just like TCP sockets.
Two programs: a server that listens for incoming streams and echoes back what it receives, and a client that opens a stream, writes data, and reads the echo. Both communicate through the Nym Mixnet using `AsyncRead` and `AsyncWrite`, like a TCP socket pair.
## What you'll learn
@@ -25,7 +26,7 @@ In this tutorial you'll build two programs: a server that listens for incoming s
## Prerequisites
- Rust toolchain (1.70+)
- Rust toolchain ({RUST_MSRV}+)
- A working internet connection (clients connect to the live Nym Mixnet)
## Step 1: Set up the project
@@ -40,11 +41,10 @@ Add dependencies to `Cargo.toml`:
```toml
[dependencies]
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "97068b2", features = ["basic_tracing"] }
nym-sdk = "1.21.0"
nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] }
tokio = { version = "1", features = ["full"] }
rand = "0.8"
blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing
```
## Step 2: Build the echo server
@@ -65,7 +65,7 @@ async fn main() {
let mut client = mixnet::MixnetClient::connect_new().await.unwrap();
println!("Echo server listening at: {}", client.nym_address());
// Create a listener this activates stream mode.
// Create a listener; this activates stream mode.
// From this point, message-based methods are disabled.
let mut listener = client.listener().unwrap();
@@ -88,7 +88,7 @@ async fn main() {
loop {
let n = match stream.read(&mut buf).await {
Ok(0) => break, // EOF stream closed
Ok(0) => break, // EOF, stream closed
Ok(n) => n,
Err(e) => {
eprintln!("Stream {stream_id} read error: {e}");
@@ -114,7 +114,7 @@ async fn main() {
```
<Callout type="info">
**`listener()` can only be called once per client.** It takes exclusive ownership of the inbound message channel. A second call returns `Error::ListenerAlreadyTaken`.
`listener()` can only be called once per client. It takes exclusive ownership of the inbound message channel; a second call returns `Error::ListenerAlreadyTaken`.
</Callout>
## Step 3: Build the client
@@ -151,13 +151,13 @@ async fn main() {
println!("Stream opened: {}", stream.id());
// Give the Open message time to traverse the mixnet and reach the server.
// open_stream() returns immediately after sending it doesn't wait for
// open_stream() returns immediately after sending; it doesn't wait for
// the server to accept. Writing too soon risks the data arriving before
// the Open, which the server would drop.
tokio::time::sleep(Duration::from_secs(5)).await;
// Send three payloads of different sizes and verify the echo.
// Random bytes show that streams are binary-safe not just text.
// Random bytes show that streams are binary-safe, not just text.
let sizes = [320, 25_000, 1280];
for (i, &size) in sizes.iter().enumerate() {
@@ -243,7 +243,7 @@ Stream 12345678 closed
5. On arrival, the router reads the `LpFrameKind` to identify it as stream traffic, decodes the header, finds the matching stream by ID, and delivers the raw payload to `read()`.
6. The inbound stream replies via **reply SURBs**, the same anonymous reply mechanism as the message API, applied transparently. The server never learns the client's Nym address.
6. The inbound stream replies via reply SURBs, the same anonymous reply mechanism as the message API. The server never learns the client's Nym address.
7. When a stream is dropped, it deregisters from the local router. No close message is sent over the wire, since a close could race ahead of in-flight data.
@@ -279,7 +279,10 @@ async fn main() {
loop {
let mut stream = match listener.accept().await {
Some(s) => s,
None => break,
None => {
println!("Listener closed");
break;
}
};
let stream_id = stream.id();
@@ -287,16 +290,27 @@ async fn main() {
tokio::spawn(async move {
let mut buf = vec![0u8; 32_000];
loop {
let n = match stream.read(&mut buf).await {
Ok(0) | Err(_) => break,
Ok(0) => break,
Ok(n) => n,
Err(e) => {
eprintln!("Stream {stream_id} read error: {e}");
break;
}
};
if let Err(_) = stream.write_all(&buf[..n]).await {
let data = &buf[..n];
println!("Stream {stream_id} received {n} bytes");
if let Err(e) = stream.write_all(data).await {
eprintln!("Stream {stream_id} write error: {e}");
break;
}
stream.flush().await.unwrap();
}
println!("Stream {stream_id} closed");
});
}
@@ -1,26 +1,22 @@
---
title: "Nym TcpProxy: Route TCP via the Mixnet"
description: "Route TCP traffic through the Nym mixnet using the TcpProxy Rust module. Covers architecture, single and multi-connection patterns, and troubleshooting."
title: "Nym TcpProxy: Route TCP via the Mixnet (Deprecated)"
description: "Route TCP traffic through the Nym mixnet using the TcpProxy Rust module. Deprecated in favour of the Stream module."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-03-27"
lastUpdated: "2026-04-17"
---
# TcpProxy Module
import { Callout } from 'nextra/components';
import { CodeVerified } from '../../../components/code-verified'
<Callout type="error">
**This module is unmaintained.** The TcpProxy is no longer actively developed in favour of the [Stream module](./stream), which provides `AsyncRead + AsyncWrite` streams directly over the Mixnet without the TCP socket overhead. Existing users should plan to migrate to streams when possible. The TcpProxy will continue to work but will not receive new features or bug fixes.
This module is unmaintained. The TcpProxy is no longer actively developed in favour of the [Stream module](/developers/rust/stream), which provides `AsyncRead + AsyncWrite` channels directly over the Mixnet without the localhost TCP socket layer. Existing users should plan to migrate. The module will continue to work but will not receive new features or bug fixes.
</Callout>
The Stream module offers the same key benefit (familiar I/O patterns on top of the Mixnet) with a simpler API. Streams multiplex connections on a single client, eliminate the localhost socket overhead, and now include sequence-based message reordering. There is no remaining reason to choose TcpProxy over Streams for new projects.
`NymProxyClient` and `NymProxyServer` proxy TCP traffic through the Mixnet. Both run in a background thread and expose a configurable `localhost` socket that callers read and write to like any other TCP connection. The Stream module replaces this pattern with multiplexed channels on a single client and no localhost socket layer.
---
`NymProxyClient` and `NymProxyServer` proxy TCP traffic through the Mixnet. Both run in a background thread and expose a configurable `localhost` socket that you read and write to like any other TCP connection.
> Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](../tools/standalone-tcpproxy).
> Non-Rust/Go developers can use the [standalone binaries](/developers/tools/standalone-tcpproxy) instead.
## Examples
@@ -36,149 +32,13 @@ cargo run --example tcp_proxy_multistream
## API reference
- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/tcp_proxy/): architecture overview, client/server examples, and type documentation
## Tutorial
<CodeVerified />
Set up the project:
```sh
cargo init nym-tcp-proxy
cd nym-tcp-proxy
rm src/main.rs
```
Add dependencies to `Cargo.toml`:
```toml
[dependencies]
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "97068b2" }
nym-network-defaults = { git = "https://github.com/nymtech/nym", rev = "97068b2" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "97068b2", features = ["basic_tracing"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
blake3 = "=1.7.0" # required pin — see https://nymtech.net/docs/developers/rust/importing
[[bin]]
name = "proxy_server"
path = "src/bin/proxy_server.rs"
[[bin]]
name = "proxy_client"
path = "src/bin/proxy_client.rs"
```
### Server
The server connects to the Mixnet and forwards incoming traffic to a local TCP service (e.g. a web server on port 8000).
```rust
use nym_sdk::tcp_proxy::NymProxyServer;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
nym_bin_common::logging::setup_tracing_logger();
let mut server = NymProxyServer::new(
"127.0.0.1:8000", // upstream address (host:port)
"./proxy-server-config", // config directory for persistent keys
None, // env file (None = mainnet)
None, // gateway (None = auto-select)
).await?;
println!("Proxy server address: {}", server.nym_address());
server.run_with_shutdown().await?;
Ok(())
}
```
### Client
The client opens a localhost TCP socket and tunnels all traffic through the Mixnet to the server.
```rust
use nym_sdk::tcp_proxy::NymProxyClient;
use nym_sdk::mixnet::Recipient;
use nym_network_defaults::setup_env;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
nym_bin_common::logging::setup_tracing_logger();
// Load mainnet network defaults into env vars (required by NymProxyClient's internal ClientPool)
setup_env(None::<String>);
let server_addr: Recipient = std::env::args()
.nth(1).expect("Usage: proxy_client <SERVER_NYM_ADDRESS>")
.parse()?;
let client = NymProxyClient::new(
server_addr,
"127.0.0.1", // listen host
"8070", // listen port
60, // close timeout (seconds)
None, // env file (None = mainnet)
1, // client pool size
).await?;
let proxy = tokio::spawn(async move { client.run().await });
// Wait for the pool to create a client and the proxy to be ready.
// The first startup takes ~10-15s while the client connects to the Mixnet.
println!("Waiting for proxy to be ready...");
tokio::time::sleep(std::time::Duration::from_secs(15)).await;
let mut stream = TcpStream::connect("127.0.0.1:8070").await?;
stream.write_all(b"GET / HTTP/1.0\r\nHost: localhost\r\n\r\n").await?;
let mut response = Vec::new();
stream.read_to_end(&mut response).await?;
println!("Response:\n{}", String::from_utf8_lossy(&response));
drop(stream);
proxy.abort();
Ok(())
}
```
### Run it
Start an upstream TCP service (e.g. a simple HTTP server):
```sh
python3 -m http.server 8000
```
In a second terminal, start the proxy server:
```sh
RUST_LOG=info cargo run --bin proxy_server
```
Copy the Nym address it prints, then in a third terminal:
```sh
RUST_LOG=info cargo run --bin proxy_client -- <SERVER_NYM_ADDRESS>
```
The response will take 3060 seconds to arrive as it traverses the Mixnet in both directions.
[`docs.rs/nym-sdk/tcp_proxy`](https://docs.rs/nym-sdk/latest/nym_sdk/tcp_proxy/) covers types, methods, and the full client/server walkthrough.
## Architecture
Each sub-module handles Nym clients differently:
- **`NymProxyClient`** relies on the [Client Pool](./client-pool) to create clients and keep a reserve. If incoming TCP connections outpace the pool, it creates an ephemeral client per connection. One client maps to one TCP connection.
- **`NymProxyServer`** has a single Nym client with a persistent identity.
`NymProxyClient` uses a [Client Pool](/developers/rust/client-pool) with one client per incoming TCP connection; if the pool runs dry it falls back to creating clients on demand. `NymProxyServer` runs a single Nym client with a persistent identity.
### Sessions & message ordering
Messages are wrapped in a session ID per connection, with individual messages given an incrementing message ID. Once all messages are sent, the client sends a `Close` message to notify the server that there are no more outbound messages for this session.
> Session management and message IDs are necessary since *the Mixnet guarantees message delivery but not message ordering*: in the case of trying to e.g. send gRPC protobuf through the Mixnet, ordering is required so that a buffer is not split across Sphinx packet payloads, and that the 2nd half of the frame is not passed upstream to the parser before the 1st half.
The key data structure:
Each TCP connection is wrapped in a session ID; messages within a session carry an incrementing message ID, and a final `Close` message signals that no more outbound bytes are coming. Session and message IDs are necessary because the Mixnet guarantees delivery but not ordering, and ordering matters whenever a parser cares about frame boundaries (gRPC over protobuf, HTTP, TLS).
```rust
pub struct ProxiedMessage {
@@ -188,68 +48,4 @@ pub struct ProxiedMessage {
}
```
### Full request/response flow
```mermaid
---
config:
theme: neo-dark
layout: elk
---
sequenceDiagram
box Local Machine
participant Client Process
participant NymProxyClient
end
Client Process->>NymProxyClient: Request bytes
NymProxyClient->>NymProxyClient: New session
NymProxyClient->>Entry Gateway: Sphinx Packets: Message 1
Entry Gateway-->>NymProxyClient: Acks
NymProxyClient->>Entry Gateway: Sphinx Packets: Message 2
Entry Gateway-->>NymProxyClient: Acks
NymProxyClient->>Entry Gateway: Sphinx Packets: Close Message
Entry Gateway-->>NymProxyClient: Acks
Entry Gateway-->>Mix Nodes: All Packets, Acks, etc
Note right of Mix Nodes: We are omitting the 3 hops etc for brevity here
Mix Nodes-->> Exit Gateway: All Packets, Acks, etc
Exit Gateway->>NymProxyServer: Sphinx Packets: Message 2
NymProxyServer-->>Exit Gateway: Acks
loop Message Buffer
NymProxyServer->>NymProxyServer: Wait for Message 1
Exit Gateway->>NymProxyServer: Sphinx Packets: Message 1
NymProxyServer-->>Exit Gateway: Acks
NymProxyServer->>NymProxyServer: Message Received: trigger upstream send
end
Note right of NymProxyServer: Note this happens **per session**
NymProxyServer->>Upstream Process: Reconstructed request bytes
Upstream Process->>Upstream Process: Do something with request
Exit Gateway->>NymProxyServer: Sphinx Packets: Close Message
NymProxyServer-->>Exit Gateway: Acks
NymProxyServer->>NymProxyServer: Trigger Client timeout start for session
Upstream Process->>NymProxyServer: Response bytes
NymProxyServer->>NymProxyServer: Write to provided SURB payloads
NymProxyServer->>Exit Gateway: Anonymous replies
box Remote Host
participant NymProxyServer
participant Upstream Process
end
Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 2
NymProxyClient-->Entry Gateway: Ack
Loop Message Buffer:
NymProxyClient->>NymProxyClient: Wait for Message 1
Entry Gateway->>NymProxyClient: Sphinx Packets: Message 1
NymProxyClient-->>Entry Gateway: Acks
NymProxyClient->>NymProxyClient: Message Received: trigger send
NymProxyClient->>Client Process: Response bytes
end
Note right of NymProxyClient: Note this happens **per session**
```
## Troubleshooting
### Lots of `duplicate fragment received` messages
`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission, where both the original and the retransmitted copy arrive at the destination. This is expected behaviour, not a bug in the client or TcpProxy module.
For the full request/response sequence diagram, see the [module source](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/src/tcp_proxy) or the [docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/tcp_proxy/) entry.
@@ -5,7 +5,7 @@ import { Callout } from 'nextra/components'
A quick walkthrough of the most important things you can do with `nym-sdk`. Each section shows working code and links to the module that covers it in depth.
<Callout type="warning">
**The Mixnet is not like regular internet networking** — there are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network: you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination.
The Mixnet is not like regular internet networking. There are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network: you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination.
The raw [message API](./mixnet) therefore works differently from what most developers expect. The [Stream module](./stream) bridges this gap by providing `AsyncRead + AsyncWrite` byte streams on top of the Mixnet. If you are coming from socket-based networking, start with streams.
</Callout>
@@ -80,7 +80,7 @@ async fn main() {
// Receiver accepts it
let mut inc = listener.accept().await.unwrap();
// Standard tokio I/O write, flush, read
// Standard tokio I/O: write, flush, read
out.write_all(b"hello stream").await.unwrap();
out.flush().await.unwrap();
@@ -0,0 +1,130 @@
---
title: "smolmix: TCP/UDP Over the Nym Mixnet"
description: "A userspace IP tunnel that provides standard TcpStream and UdpSocket types over the Nym mixnet. Compatible with the async tokio Rust ecosystem."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-04-29"
---
# smolmix
import { Callout } from 'nextra/components'
import { RUST_MSRV } from '../../components/versions'
`smolmix` is a TCP/UDP tunnel over the Nym mixnet. It uses a userspace network stack [`smoltcp`](https://docs.rs/smoltcp/latest/smoltcp/) to provide `TcpStream` and `UdpSocket` types that work with the async [`tokio`](https://docs.rs/tokio) Rust ecosystem e.g. [`tokio-rustls`](https://docs.rs/tokio-rustls), [`hyper`](https://docs.rs/hyper), [`tokio-tungstenite`](https://docs.rs/tokio-tungstenite), etc.
The `TcpStream` type implements tokio's `AsyncRead`/`AsyncWrite` traits and `UdpSocket` provides `send_to`/`recv_from` for datagrams.
```text
┌──────────────────────────────────────────────────────────────┐
│ Your application (TLS, HTTP, WebSocket, DNS, etc.) │
│ └─ smolmix::TcpStream / UdpSocket │
│ └─ smoltcp (userspace TCP/IP) │
│ └─ Nym mixnet → IPR exit gateway → internet │
└──────────────────────────────────────────────────────────────┘
```
Traffic exits the mixnet at an [IPR (Internet Packet Router)](/network/infrastructure/exit-services#ip-packet-router) exit gateway. The exit IP is the gateway's, not yours.
## Runtime and platform support
### Other runtimes
`smolmix` currently requires `tokio`. The internal pipeline is tokio-based: the bridge task that shuttles packets to the mixnet, the Nym SDK's `MixnetClient`, and the [`tokio-smoltcp`](https://docs.rs/tokio-smoltcp) reactor that drives the userspace TCP/IP stack all run on the tokio runtime.
This means `smolmix` is not directly compatible with alternative async runtimes like [`smol`](https://docs.rs/smol) or [`async-std`](https://docs.rs/async-std). If you need to use `smolmix` from another runtime, the [`async-compat`](https://docs.rs/async-compat) crate can bridge the gap.
### Non-native `smolmix`
A WASM version of `smolmix` is planned for a future release.
## Installation
Add `smolmix` to your `Cargo.toml`:
```toml
[dependencies]
smolmix = "1.21.0"
nym-bin-common = { version = "1.21.0", features = ["basic_tracing"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```
`tokio` is a transitive dependency of smolmix, but you need to enable `rt-multi-thread` (the default runtime spun up by `#[tokio::main]`) and `macros` (for the `#[tokio::main]` attribute itself).
`nym-bin-common` is optional but recommended: it sets up [`tracing`](https://docs.rs/tracing) logging so you can see mixnet connection progress.
**Minimum Rust version:** {RUST_MSRV}+
### From Git
For unreleased changes, import directly from the repository:
```toml
smolmix = { git = "https://github.com/nymtech/nym", branch = "develop" }
```
## When to use smolmix
| | smolmix | Stream module | mixFetch | SOCKS client |
|---|---|---|---|---|
| **Layer** | Transport (TCP/UDP) | Message (multiplexed streams) | HTTP | TCP (SOCKS proxy) |
| **Controls both sides?** | No (proxy mode) | Yes (E2E) | No (proxy mode) | No (proxy mode) |
| **API** | `TcpStream`, `UdpSocket` | `AsyncRead + AsyncWrite` | `fetch()` replacement | SOCKS4/5 protocol |
| **Composability** | Full: TLS, HTTP, WebSocket, DNS, etc. stack on top | Byte streams only | HTTP(S) only | Application-dependent |
| **Best for** | Reaching external services from Rust with standard networking | Peer-to-peer / E2E protocols between Nym clients | Browser HTTP requests | Legacy apps with SOCKS support |
## Security model
<Callout type="warning">
Traffic is Sphinx-encrypted inside the mixnet. Past the Exit Gateway, it travels over the public internet to the destination, the same as any other server-initiated connection. Protect the payload at the application layer with TLS ([`rustls`](https://docs.rs/rustls)), Noise Protocol ([`snow`](https://docs.rs/snow)), or equivalent, as you would on a direct connection.
</Callout>
### What's protected
| Segment | Mixnet encryption | What's visible |
|---|---|---|
| Your machine → mixnet entry | Sphinx (layered) | Entry gateway sees your IP but not the destination |
| Inside the mixnet (entry + 3 mix layers + exit) | Sphinx (layered) | Each node only knows prev/next hop |
| Exit gateway (IPR) | Sphinx removed, raw IP packet exposed | IPR sees destination IP + port. Payload depends on your application layer (see below). |
| IPR → remote host | None (Sphinx is mixnet-only) | Remote host sees IPR's IP, not yours |
The Sphinx encryption is the **mixnet transport layer**: it protects packets as they traverse the mix nodes. At the exit gateway, the Sphinx layers are stripped and the original IP packet is forwarded to the destination. This is analogous to how a Tor exit node or VPN endpoint unwraps its tunnel.
What's inside that IP packet is up to you. If you connect with TLS (as in the [TCP example](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp.rs)), the IPR sees encrypted TLS ciphertext going to a destination IP: it knows where but not what. If you send plaintext HTTP, the IPR can read the full request and response.
### Trust boundaries
- You trust the mixnet to provide unlinkability between sender and receiver. This is enforced cryptographically by the Sphinx packet format and mixing.
- You trust the IPR exit gateway in the same way you trust a VPN exit or Tor exit node: it can inspect your raw IP packets. The difference is that the IPR doesn't know who is sending the traffic (the mixnet hides your identity).
- **Application-layer encryption closes the gap.** TLS, Noise Protocol, or any authenticated encryption keeps the payload as ciphertext to the IPR. It can see destination IP and port, but not payload content.
### Comparison with other privacy tools
| | smolmix | Tor | VPN |
|---|---|---|---|
| **Exit node sees traffic?** | Yes (encrypt it) | Yes (encrypt it) | Yes (encrypt it) |
| **Exit node knows sender?** | No (mixnet hides identity) | No (onion routing) | Yes (VPN provider knows) |
| **Timing analysis resistance** | Strong (mixing, cover traffic) | Weak (low-latency) | None |
| **UDP support** | Yes | No (TCP only) | Yes |
## Examples
Runnable examples in [`smolmix/core/examples/`](https://github.com/nymtech/nym/tree/develop/smolmix/core/examples). Each is self-contained; read the `//!` doc comments at the top of each file for a walkthrough.
```sh
cargo run -p smolmix --example <name>
```
All examples accept `--ipr <ADDRESS>` to target a specific exit node (pass a `Recipient` address to `Tunnel::builder().ipr_address()`).
| Example | Source | What it demonstrates |
|---|---|---|
| UDP | [`udp.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/udp.rs) | DNS lookup via [`hickory-proto`](https://docs.rs/hickory-proto), sending a raw UDP query to `1.1.1.1:53` through the mixnet. Runs a clearnet [`hickory-resolver`](https://docs.rs/hickory-resolver) lookup alongside it to compare resolved IPs and latency |
| TCP | [`tcp.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp.rs) | HTTPS request via [`hyper`](https://docs.rs/hyper) + [`tokio-rustls`](https://docs.rs/tokio-rustls). Fetches Cloudflare's `/cdn-cgi/trace` to show that the exit IP differs from clearnet |
| WebSocket | [`websocket.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/websocket.rs) | WebSocket echo against `ws.postman-echo.com` via [`tokio-tungstenite`](https://docs.rs/tokio-tungstenite) + [`tokio-rustls`](https://docs.rs/tokio-rustls). Runs the same stack over clearnet first, so the only thing that changes between runs is the underlying TCP stream |
| TCP download | [`tcp_download.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp_download.rs) | DNS-over-mixnet + multi-request HTTP/1.1 download over a single keep-alive connection, the full real-world pattern |
## Architecture
The internal stack (smoltcp reactor, device adapter, bridge task, packet flow) is documented in [`ARCHITECTURE.md`](https://github.com/nymtech/nym/blob/develop/smolmix/core/src/ARCHITECTURE.md). This is also the crate-level documentation on docs.rs.
## API reference
Full API documentation is available on [docs.rs/smolmix](https://docs.rs/smolmix).
@@ -8,10 +8,10 @@ lastUpdated: "2026-02-01"
# Tools
Standalone binaries for development and testing that don't require an SDK — download or compile them and use them directly.
Standalone binaries for development and testing that don't require an SDK. Download or compile them and use them directly.
| Tool | Use case |
|---|---|
| [nym-cli](./tools/nym-cli) | Command-line interface for interacting with the Nyx blockchain: querying state, submitting transactions, managing keys. An easier-to-use wrapper around `nyxd`. |
| [Diagnostic Tool](./tools/diagnostic-tool) | Network diagnostic utility for troubleshooting connectivity issues. |
| [Standalone TcpProxy](./tools/standalone-tcpproxy) | Pre-built binaries of the TcpProxy client and server for proxying TCP traffic through the Mixnet. Note: the TcpProxy module is unmaintained; use the [Stream module](./rust/stream) for new projects. |
| [Standalone TcpProxy](./tools/standalone-tcpproxy) | Pre-built binaries of the TcpProxy client and server for proxying TCP traffic through the Mixnet. The TcpProxy module is unmaintained; use the [Stream module](./rust/stream) for new projects. |
@@ -2,9 +2,9 @@ import { Steps } from 'nextra/components';
# Diagnostic Tool
The Diagnostic Tool is a standalone binary designed to perform various network tests, including DNS, HTTP, and gateway connectivity tests. This tool helps diagnose connectivity issues and provides insights into network performance.
The Diagnostic Tool is a standalone binary that runs network tests (DNS, HTTP, gateway connectivity) to diagnose connectivity issues and report on network performance.
Its also possible to run it within the daemon with the same CLI interface.
It can also be run from within the daemon with the same CLI interface.
## Download Binary
@@ -36,7 +36,7 @@ chmod +x ./*
## CLI Usage
The Diagnostic Tool can be executed from the command line interface (CLI). Below are the usage instructions and options available. Read in the chapter [*Tests Performed*](#tests-performed) about the purpose and outcome of these commands.
The Diagnostic Tool runs from the command line. The usage instructions and options follow. See [Tests Performed](#tests-performed) for the purpose and outcome of each command.
### Command Syntax
@@ -99,36 +99,36 @@ The Diagnostic Tool runs the following tests:
### 1. DNS Test
- **Purpose**: To check the resolution DNS availability.
- **Process**: We try to resolve all the domain names present in a given nym network environment with different DNS configurations
- **Output**: Displays the resolved IP address and the time taken for the resolution.
- **Purpose**: Check DNS resolution availability.
- **Process**: Resolve all domain names present in the given Nym network environment with different DNS configurations.
- **Output**: Resolved IP address and time taken for resolution.
### 2. HTTP Test
- **Purpose**: To verify the accessibility of the NymVPN API.
- **Process**: The tool query the `health` endpoint as well as the `nodes/described` endpoint.
- **Output**: Displays the response of the `health` endpoint, the time skew and the number of nodes in the network (sanity check)
- **Purpose**: Verify accessibility of the NymVPN API.
- **Process**: Query the `health` and `nodes/described` endpoints.
- **Output**: Response from the `health` endpoint, time skew, and number of nodes in the network (sanity check).
### 3. Gateway Test
- **Purpose**: To check the connectivity to a given gateway.
- **Process**: The tool fetches information about the gateway, then establishes a TCP connection, upgrades it to WS and sends a request
- **Output**: Display the gateway reported information, the status of the connections and the WS response.
- **Purpose**: Check connectivity to a given gateway.
- **Process**: Fetch information about the gateway, establish a TCP connection, upgrade it to WS, and send a request.
- **Output**: Gateway-reported information, connection status, and WS response.
### 4. Registration Test
- **Purpose:** To check the correctness of the registration process.
- **Process:** The tool tries to build a mixnet client to the provided gateway and then tries to register to the entry authenticator
- **Output:** Display the status of the different steps
- **Caveat:** This test requires a credential to be spent, which is why it is available as a separate command only
- **Purpose:** Check the registration process.
- **Process:** Build a mixnet client to the provided gateway and register with the entry authenticator.
- **Output:** Status of each step.
- **Caveat:** This test spends a credential, which is why it lives in a separate command.
### 5. Wireguard Test
- **Purpose:** To check the soundness of a wireguard connection
- **Process:** The tool uses the registration data from the previous step to establish a wireguard connection and ping an IP.
- **Output:** Display the ping RTTs and any error that might have happened
- **Purpose:** Check the soundness of a wireguard connection.
- **Process:** Use the registration data from the previous step to establish a wireguard connection and ping an IP.
- **Output:** Ping RTTs and any errors.
## Reports
Reports are logged in a JSON format and also returned by the commands for a future use
Reports are logged in JSON format and also returned by the commands for later use.
@@ -4,11 +4,11 @@
The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory.
## Usage
See the [commands](commands.mdx) page for an overview of all command options.
See the [commands](/developers/tools/nym-cli/commands) page for an overview of all command options.
## Staking on someone's behalf (for custodians)
There is a limitation the staking address can only perform the following actions (and are visible via the Nym Wallet:
The staking address can only perform the following actions (visible via the Nym Wallet):
- Bond on the gateway's or Mix Node's behalf.
- Delegate or Un-delegate (to a Mix Node in order to begin receiving rewards)
@@ -6,9 +6,9 @@ import { Callout } from 'nextra/components'
**Deprecated.** The TcpProxy module is no longer actively developed. The [Stream module](/developers/rust/stream) provides the same functionality (familiar `AsyncRead`/`AsyncWrite` I/O over the Mixnet) with a simpler API, multiplexed connections, and sequence-based message reordering. Use Streams for new projects.
</Callout>
Standalone versions of the `TcpProxyClient` and `TcpProxyServer` [sdk module](../rust/tcpproxy) can be found [here](https://github.com/nymtech/standalone-tcp-proxies/tree/main).
Standalone versions of the `TcpProxyClient` and `TcpProxyServer` [sdk module](/developers/rust/tcpproxy) can be found [here](https://github.com/nymtech/standalone-tcp-proxies/tree/main).
These might be an easy way for developers to start proxying their traffic throught the mixnet and understanding the sort of latency they should expect, and whether their application can currently tolerate it. They might also prove useful for server setups where several components are being run via init scripts, and the addition of a separate process is acceptable.
These can be a quick way to start proxying traffic through the mixnet, see what latency to expect, and check whether your application can tolerate it. They are also useful for server setups where several components run via init scripts and an extra process is acceptable.
## Build
```shell
@@ -1,9 +1,9 @@
---
title: "Nym TypeScript SDK: Privacy for Web Apps"
description: "TypeScript SDK for integrating web apps with the Nym mixnet. Covers mixFetch, Mixnet Client, Smart Contracts, and Cosmos Kit with live playground examples."
title: "TypeScript SDK: Mixnet Messaging & Smart Contracts"
description: "TypeScript SDK for integrating web apps with the Nym mixnet. Covers the Mixnet Client (messaging) and Nym Smart Contracts. For HTTP requests, see mix-fetch."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-03-13"
lastUpdated: "2026-05-12"
---
import { Callout } from 'nextra/components'
@@ -12,7 +12,23 @@ import { NPMLink } from '../../components/npm';
# TypeScript SDK
The TypeScript SDK lets you build browser-based applications that communicate through the Nym mixnet. Import SDK packages via NPM as you would any other TypeScript library.
The TypeScript SDK lets you build browser-based applications that communicate through the Nym mixnet.
```text
┌──────────────────────────────────────────────────────────────┐
│ Your browser app │
│ └─ Nym Mixnet Client (WASM, runs in a Web Worker) │
│ └─ WebSocket to entry gateway │
│ └─ Nym mixnet (entry → 3 mix layers → exit) │
│ └─ Peer MixnetClient (e.g. nym-sdk) │
└──────────────────────────────────────────────────────────────┘
```
The Mixnet Client operates in messaging mode (text or binary payloads) and runs in a Web Worker to keep the UI thread free. For HTTP requests, use [`mix-fetch`](/developers/mix-fetch) instead.
<Callout type="info">
For an HTTP-over-mixnet `fetch()` replacement, see [**mix-fetch**](/developers/mix-fetch). This page covers the **Mixnet Client** (send / receive raw messages) and **Smart Contracts** (interact with the Nyx chain).
</Callout>
<Callout type="warning">
The Nym Mixnet routes traffic through multiple nodes with no persistent connections or guaranteed ordering. The SDK abstracts the complexity, but understanding the [underlying model](/developers/rust/tour) helps when debugging.
@@ -23,21 +39,6 @@ The Nym Mixnet routes traffic through multiple nodes with no persistent connecti
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell>
**mixFetch**
</TableCell>
<TableCell>
A drop-in replacement for [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
that sends HTTP requests over the Nym mixnet
</TableCell>
<TableCell>
<NPMLink packageName={'@nymproject/mix-fetch'} kind={'esm'}/><br/>
<NPMLink packageName={'@nymproject/mix-fetch-full-fat'} kind={'esm'} preBundled/><br/>
<NPMLink packageName={'@nymproject/mix-fetch-commonjs'} kind={'cjs'}/><br/>
<NPMLink packageName={'@nymproject/mix-fetch-full-fat-commonjs'} kind={'cjs'} preBundled/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
**Mixnet Client**
@@ -82,12 +83,6 @@ All `*-full-fat` variants have large bundle sizes because they include WASM and
## Installation
### mixFetch
```bash
npm install @nymproject/mix-fetch-full-fat
```
### Mixnet Client
```bash
@@ -103,30 +98,11 @@ npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto
### Install everything
```bash
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk-full-fat @nymproject/mix-fetch-full-fat
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk-full-fat
```
## Quick start
### mixFetch
Use [`mixFetch`](https://www.npmjs.com/package/@nymproject/mix-fetch) as a drop-in replacement for `fetch` to send HTTP requests over the mixnet:
```ts
import { mixFetch } from '@nymproject/mix-fetch';
// HTTP GET
const response = await mixFetch('https://nym.com');
const html = await response.text();
// HTTP POST
const apiResponse = await mixFetch('https://api.example.com', {
method: 'POST',
body: JSON.stringify({ foo: 'bar' }),
headers: { 'Content-Type': 'application/json' }
});
```
### Mixnet Client
Create a [`Mixnet Client`](https://www.npmjs.com/package/@nymproject/sdk) to send and receive messages through the mixnet:
@@ -184,4 +160,4 @@ console.log(`Tx Hash = ${result.transactionHash}`);
- **[Step-by-step examples](./typescript/examples):** Full working projects for each package
- **[Live playground](./typescript/playground):** Try the SDK in your browser
- **[Bundling](./typescript/bundling):** Configure Webpack or ESBuild for WASM and web workers
- **[TypeDoc reference](./typescript/api):** generated reference for all packages
- **[TypeDoc reference](./typescript/api):** Generated reference for all packages
@@ -2,16 +2,18 @@ import { Callout } from 'nextra/components';
# Troubleshooting bundling with ESbuild
If you've been following the steps outlined in the Examples section, your development environment should be configured as follows:
If you have followed the steps in the Examples section, your development environment should be configured as follows.
#### Environment Setup
Begin by creating a directory and configuring your application environment:
Create your directory and set-up your app environment:
Create your directory and set up your app environment:
```bash
npm create vite@latest
```
During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands:
Choose React, then Typescript. Navigate to your application directory and run:
```bash
cd < YOUR_APP >
npm i
@@ -20,12 +22,13 @@ npm run dev
##### Installation
Install the required package:
```bash
npm install @nymproject/< PACKAGE_NAME >
```
<Callout type="info" emoji="️">
Remember that the CosmosKit example will require you to make use of polyfills.
<Callout type="info">
The CosmosKit example requires polyfills.
</Callout>
By implementing the provided code for the various components in the step-by-step examples section, you should be able to set-up and run your application without encountering any bundling challenges!
With the code from the step-by-step examples section in place, the application should run without bundling errors.
@@ -19,18 +19,19 @@ For any project using Webpack, you´ll need the following rule in your `webpack.
#### General cases
If you wish to use Webpack for your app with the code provided in the step-by-step examples section, you'll need to:
To use Webpack with the code from the step-by-step examples section:
```bash
npx create-react-app nymapp --template typescript
cd nymapp
```
You'll then need to install the needed dependencies, head to your app's `App.tsx` file and paste the code provided in the step-by-step section.
Install the required dependencies, then paste the code from the step-by-step section into your app's `App.tsx`.
#### Contract client
<Callout type="info" emoji="️">
Using webpack, the `Contract client` for querying or executing might need polyfills. As create-react-app doesn´t allow you access to the Webpack config without ejecting, you'll overwrite it as follow:
<Callout type="info">
With webpack, the `Contract client` for querying or executing may need polyfills. Since create-react-app doesn't expose the Webpack config without ejecting, override it as follows:
</Callout>
##### Install contract-clients dependencies
@@ -38,7 +39,7 @@ You'll then need to install the needed dependencies, head to your app's `App.tsx
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing
```
Head to you app's `App.tsx` file and replace the code by the one provided in the step-by-step examples section.
In your app's `App.tsx`, replace the existing code with the code from the step-by-step examples section.
##### Polyfilling
@@ -2,21 +2,22 @@ import { Callout } from 'nextra/components'
# Cosmos Kit
The wonderful people of Cosmology have made some [fantastic components](https://cosmoskit.com/) that can be used with
Nym. These include:
Cosmology's [components](https://cosmoskit.com/) work with Nym. They cover:
- Using the wallets such as Keplr, Cosmostation and others from your React application;
- Using the [Ledger hardware wallet](https://docs.cosmoskit.com/integrating-wallets/ledger) from your browser;
- Any wallet that supports [Wallet Connect v2.0](https://docs.cosmoskit.com/integrating-wallets/adding-new-wallets);
- Wallets such as Keplr, Cosmostation, and others from your React application
- The [Ledger hardware wallet](https://docs.cosmoskit.com/integrating-wallets/ledger) from your browser
- Any wallet that supports [Wallet Connect v2.0](https://docs.cosmoskit.com/integrating-wallets/adding-new-wallets)
##### Environment Setup
Begin by creating a directory and configuring your application environment:
Create a new project with Vite:
```bash
npm create vite@latest
```
During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands:
Choose React, then Typescript. Navigate to your application directory and run:
```bash
cd < YOUR_APP >
npm i
@@ -1,6 +1,6 @@
---
title: "mixFetch Example: Private HTTP Requests"
description: "Replace browser fetch with mixFetch to route HTTP requests through the Nym mixnet. Covers setup, CA certificates, WSS gateways, and usage examples."
description: "Replace browser fetch with mixFetch to route HTTP requests through the Nym mixnet. Covers setup, CA certificates, TLS configuration, and usage examples."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-03-15"
@@ -14,13 +14,9 @@ An easy way to secure parts or all of your web app is to replace calls to [`fetc
Things to be aware of:
- CA certificates in `mixFetch` are periodically updated. If you get a certificate error, the root certificate you need might not be valid yet. [Send a PR](https://github.com/nymtech/nym/pulls) if you need changes to the certificates.
- If you are using `mixFetch` in a web app with HTTPS, you will need to use a gateway that has Secure Websockets (WSS) to avoid a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error.
- `mixFetch` supports concurrent requests (up to 10) to the same or different URLs.
<Callout type="info">
Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so. You need to select a Gateway that has WSS from [Harbourmaster](https://harbourmaster.nymtech.net/).
</Callout>
- **CA certificates** are bundled into the WASM binary at build time. They're updated with each SDK release, so if you hit a certificate error, update to the latest `@nymproject/mix-fetch-full-fat` version.
- **HTTPS and WSS.** When serving your app over HTTPS, the mixnet connection must also use Secure WebSockets to avoid a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error. Set `forceTls: true` in your `SetupMixFetchOps` config (see below) and the SDK will automatically select a WSS-capable gateway.
- `mixFetch` supports **concurrent requests** (up to 10) to the same or different URLs.
## Environment Setup
@@ -41,30 +37,34 @@ npm run dev
## Installation
```bash
npm install @nymproject/mix-fetch-full-fat
npm install @nymproject/mix-fetch-full-fat @mui/material @emotion/react @emotion/styled
```
The MUI packages are used by the example UI below. If you only need `mixFetch` itself, install only `@nymproject/mix-fetch-full-fat`.
## Configuration
```ts
import type { SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat';
const mixFetchOptions: SetupMixFetchOps = {
clientId: "docs-mixfetch-demo",
clientId: "my-app",
preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1",
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
forceTls: true, // force WSS
forceTls: true, // use Secure WebSockets (required when serving over HTTPS)
};
```
`preferredGateway` is optional. If omitted, the SDK auto-selects a gateway. You can pin a specific one via [Harbourmaster](https://harbourmaster.nymtech.net/).
## Full Example
This example shows explicit initialization via `createMixFetch`, single URL fetch, and concurrent requests. Results appear both in the UI and in a visible log panel.
This example shows explicit initialisation via `createMixFetch`, single URL fetch, and concurrent requests. Results appear both in the UI and in a visible log panel.
<Callout type="info">
For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](../bundling/bundling) copies the WASM and web worker files to the output bundle.
For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](/developers/typescript/bundling/bundling) copies the WASM and web worker files to the output bundle.
</Callout>
```tsx
@@ -138,7 +138,7 @@ export const MixFetch = () => {
setLogs((prev) => [...prev, { timestamp, message, level }]);
};
// Initialize MixFetch explicitly via createMixFetch
// Initialise MixFetch explicitly via createMixFetch
const handleStart = async () => {
try {
setStatus("starting");
@@ -155,7 +155,7 @@ export const MixFetch = () => {
}
};
// Single URL fetch reuses the existing MixFetch singleton
// Single URL fetch (reuses the existing MixFetch singleton)
const handleFetch = async () => {
try {
setBusy(true);
@@ -229,7 +229,7 @@ export const MixFetch = () => {
</Stack>
</Paper>
{/* Fetch controls disabled until MixFetch is ready */}
{/* Fetch controls (disabled until MixFetch is ready) */}
<Box sx={{ opacity: isReady ? 1 : 0.5, pointerEvents: isReady ? "auto" : "none" }}>
{/* Single fetch */}
<Stack direction="row">
@@ -43,7 +43,7 @@ npm install @nymproject/sdk-full-fat
This example creates a Mixnet client, connects to a gateway, and provides a UI for sending and receiving messages through the mixnet.
<Callout type="info">
For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](../bundling/bundling) copies the WASM and web worker files to the output bundle.
For this example we use the `full-fat` version of the ESM SDK. If you use the unbundled ESM variant, make sure your [bundler configuration](/developers/typescript/bundling/bundling) copies the WASM and web worker files to the output bundle.
</Callout>
```ts copy filename="App.tsx"
@@ -12,7 +12,7 @@ Sign a transaction using [CosmosKit](https://cosmoskit.com/) wallet adapters. Th
<CosmosKit />
<Callout type="info" emoji="️">
<Callout type="info">
No transactions will be broadcast. You will only be signing a transaction.
</Callout>
@@ -27,6 +27,6 @@ If you are using a Ledger hardware wallet:
## How this works
The component uses CosmosKit to manage wallet connections across different wallet providers (Keplr, Ledger). When you click Sign, it constructs a message, requests your wallet to sign it, and displays the signature hash. This demonstrates how to integrate Cosmos-compatible wallets into a web application that interacts with the Nyx blockchain.
The component uses CosmosKit to manage wallet connections across different wallet providers (Keplr, Ledger). When you click Sign, it constructs a message, requests your wallet to sign it, and displays the signature hash. The same pattern works for any web application that interacts with the Nyx blockchain.
<FormattedCosmoskitExampleCode />
@@ -17,6 +17,6 @@ Fetch a URL through the Nym Mixnet. This demo creates a mixFetch client in your
## How this works
The component calls `createMixFetch()` to initialize a Mixnet client in the browser, then uses the returned `mixFetch()` function as a drop-in replacement for `window.fetch()`. The request is routed through the Mixnet to a Network Requester, which makes the HTTP request on your behalf and returns the response anonymously.
The component calls `createMixFetch()` to initialise a Mixnet client in the browser, then uses the returned `mixFetch()` function as a replacement for `window.fetch()`. The request is routed through the Mixnet to a Network Requester, which makes the HTTP request on your behalf and returns the response anonymously.
<FormattedMixFetchExampleCode />
@@ -7,7 +7,7 @@ import FormattedTrafficExampleCode from '../../../../code-examples/sdk/typescrip
Send and receive messages through the Nym Mixnet directly in your browser. This demo creates a Mixnet client, connects to the network, and lets you send a message to yourself (or any Nym address) to see the full round-trip.
**Try it:** Click Connect, wait for the client to initialize, then send a message. You'll see it arrive back through the Mixnet after traversing 5 hops.
**Try it:** Click Connect, wait for the client to initialise, then send a message. You'll see it arrive back through the Mixnet after traversing 5 hops.
<Callout type="info">
Open your browser's console to see the connection and send/receive logging for this example.
@@ -12,6 +12,6 @@ The Nym Network relies on several cryptographic systems working together. This s
## What's covered
[Sphinx Packets](/network/cryptography/sphinx) explains the packet format that enables layered encryption and anonymous routing. Each Sphinx packet contains routing information encrypted in layers, where each hop can only decrypt its own layer.
[Sphinx Packets](/network/cryptography/sphinx) explains the packet format used for layered encryption and anonymous routing. Each Sphinx packet contains routing information encrypted in layers, where each hop can only decrypt its own layer.
[zk-nyms](/network/cryptography/zk-nym) covers the anonymous credential system that separates payment from usage. This is how you can pay for network access without that payment being linkable to your activity.
@@ -8,7 +8,7 @@ lastUpdated: "2026-03-15"
# Sphinx
Sphinx is the cryptographic packet format used for all mixnet traffic. It provides layered encryption where each hop can only decrypt its own routing information, ensuring that no single node knows both the source and destination of a packet.
Sphinx is the cryptographic packet format used for all mixnet traffic. It provides layered encryption where each hop can only decrypt its own routing information, so no single node knows both the source and destination of a packet.
## How Sphinx works
@@ -20,7 +20,7 @@ At each hop, the node uses its private key to decrypt its layer, revealing the a
All Sphinx packets have a fixed payload size of 2048 bytes. This uniformity is critical: if packets varied in size, nodes could infer their position in the route or correlate packets by size.
The packet contains a header with encrypted routing information for each hop, HMACs to verify integrity at each layer, and the encrypted payload. The header uses a clever "onion" structure where processing at each hop reveals only the next hop's information while maintaining constant size through padding.
The packet contains a header with encrypted routing information for each hop, HMACs to verify integrity at each layer, and the encrypted payload. The header uses an "onion" structure where processing at each hop reveals only the next hop's information while maintaining constant size through padding.
## Integrity verification
@@ -2,9 +2,9 @@
The zk-nym scheme enables the creation and use of unlinkable, rerandomisable anonymous access credentials that are 'spent' with Gateways in order to anonymously prove that someone has paid for Mixnet access. This implementation incorporates elements of both the [Coconut Credential](https://arxiv.org/pdf/1802.07344) and [Offline Ecash](https://arxiv.org/pdf/2303.08221) schemes.
As outlined in the [overview](./zk-nym/zk-nym-overview) on the next page, zk-nyms allow for users to pay for Mixnet access in a manner that is **unlinkable to their payment account**; even with pseudonymous cryptocurrencies or fiat. This solves one of the fundamental privacy problems with the majority of VPNs and dVPNs in production today: the linkability of a user's session with their payment information, which can in the majority of cases be easily used to deanonymise them, either at the behest of an authority or by the service operators themselves.
As outlined in the [overview](./zk-nym/zk-nym-overview) on the next page, zk-nyms allow users to pay for Mixnet access in a way that is **unlinkable to their payment account**, even with pseudonymous cryptocurrencies or fiat. This solves one of the fundamental privacy problems with most VPNs and dVPNs in production today: the linkability of a user's session with their payment information, which can in most cases be used to deanonymise them, either at the behest of an authority or by the service operators themselves.
> The current zk-nym scheme is non-generic in that it is only used for gating Mixnet access. A generic scheme based on zk-nyms is being actively researched in order to facilitate more generic and customisable anonymous credentials for other applications and services.
> The current zk-nym scheme is non-generic in that it is only used for gating Mixnet access. A generic scheme based on zk-nyms is being actively researched, to support more generic and customisable anonymous credentials for other applications and services.
## Motivations
Most of the time, when we build system security, we think of _who_ questions:
@@ -25,7 +25,7 @@ The zk-nym scheme allows for this move to take place. Credentials are generated
### Re-randomisation vs pseudonymity
We stand on the shoulders of giants. Ten years ago, Bitcoin showed the way forward by allowing people to control resource access without recourse to _who_ questions. Rather, in Bitcoin and succeeding blockchains, a private key proves a _right to use_.
But as we can now see, private keys in blockchain systems act only as a minor barrier to finding out _who_ is accessing resources. A Bitcoin or Ethereum private key is effectively a long-lived pseudonym which is easily traceable through successive transactions.
But as we can now see, private keys in blockchain systems act only as a minor barrier to finding out _who_ is accessing resources. A Bitcoin or Ethereum private key is a long-lived pseudonym that is easily traceable through successive transactions.
**zk-nyms allows us to build truly private systems rather than pseudonymous ones.**
@@ -38,8 +38,8 @@ Let's say you have a `message` with the content `This credential controls X` in
2. _Re-randomizable signatures_ - take a signature, and generate a brand new signature that is valid for the same underlying message `This credential controls X`. The new bitstring in the re-randomized signature is equivalent to the original signature but not linkable to it. So a user can generate multiple zk-nyms from a single credential source, unlinkable to any previous "shown" zk-nym. But the underlying content of the re-randomized credential is the same (including for things like double-spend protection). This once again protects the user against the signer, because the signer can't trace the signed message that they gave back to the user when it is presented. It also protects the user against the relying party that accepts the signed credential. The user can generate multiple re-randomized credentials repeatedly, and although the underlying message is the same in all cases, there's no way of tracking them by watching the user present the same credential multiple times.
3. _Selective disclosure of attributes_ - allows someone with the public key to verify some, but not all, parts of a message. So you could for instance selectively reveal parts of a signed message to some people, but not to others. This is a very powerful property of the scheme which is to be explored more in future work, potentially leading to diverse applications: voting systems, anonymous currency, privacy-friendly KYC systems, etc.
3. _Selective disclosure of attributes_ - allows someone with the public key to verify some, but not all, parts of a message. So you could for instance selectively reveal parts of a signed message to some people, but not to others. This property of the scheme is to be explored more in future work, with potential applications including voting systems, anonymous currency, and privacy-friendly KYC systems.
4. _[Threshold issuance](https://en.wikipedia.org/wiki/Threshold_cryptosystem)_ - allows signature generation to be split up across multiple nodes and decentralized, so that either all signers need to sign (_n of n_ where _n_ is the number of signers) or only a threshold number of signers need to sign a message (_t of n_ where _t_ is the threshold value).
4. _[Threshold issuance](https://en.wikipedia.org/wiki/Threshold_cryptosystem)_ - allows signature generation to be split up across multiple nodes and decentralised, so that either all signers need to sign (_n of n_ where _n_ is the number of signers) or only a threshold number of signers need to sign a message (_t of n_ where _t_ is the threshold value).
Taken together, these properties provide privacy for applications when it comes to generating and using signatures for cryptographic claims. If you compare it to existing tech, the closest analogy in conventional systems is a decentralized, privacy-preserving [JWT](https://jwt.io/).
Taken together, these properties provide privacy for applications when generating and using signatures for cryptographic claims. The closest analogy in conventional systems is a decentralised, privacy-preserving [JWT](https://jwt.io/).
@@ -10,7 +10,7 @@ Double spend protection in the context of zk-nym is a balancing act between spee
## Offline Approach: Pros & Cons
The advantages of the offline approach are manifold:
- Immediate access to the Nym network upon zk-nym submission, eliminating any delays in service provisioning until payments are deposited and verified as would occur in the online approach.
- Alleviates performance strain on ingress Gateways and Quorum members, serving as a more efficient method compared to the online counterpart. By moving computationally intense work to the Quorum, this means that Gateway nodes are able to be run on less powerful machines, meaning more operators can more easily run them (and cover their costs) and thus increase the overall number and spread of Gateways around the globe.
- Reduces load on ingress Gateways and Quorum members compared to the online approach. Moving the compute-heavy work to the Quorum means Gateway nodes can run on less capable machines, so more operators can run them (and cover their costs), increasing the overall number and spread of Gateways around the globe.
- Moreover, the offline approach can circumvent the potential issue of overwhelming the blockchain with the serial numbers of spent coins.
However, the offline approach introduces certain limitations.
@@ -18,10 +18,10 @@ However, the offline approach introduces certain limitations.
- Any potential repercussions against double spenders can only be implemented once the user requests a new credential for their zk-nym Generator (aka they have to 'top up' and buy more bandwidth allowance), assuming they haven't altered their identifier (the Bech32 address).
An exploitable scenario arises from these limitations:
- A malicious user purchases bandwidth and aggregates a valid zk-nym credential in the standard way, worth $10 of crypto/fiat. Subsequently, the malicious user proceeds to sell the credential to 100 users for $1 each, allowing each user to generate zk-nym tickets of 100MB from this **valid** credential. Under the offline approach, entry nodes forego double-spending checks; so long as the clients all used different ingress Gateways, all 100 users could access the network without obtaining a subscription. As bandwidth consumption is tracked locally between client and ingress node, and each zk-nym ticket is rerandomised, there is no way that ingress Gateways would know that the zk-credential used by the client has been shared with other parties. This loophole highlights the need for stringent measures to counter such potential abuses within the system, without creating either speed bottlenecks (in the case of the Online model) or impacting the anonymity of the system. We can, however, mitigate this problem without doing either of these things.
- A malicious user purchases bandwidth and aggregates a valid zk-nym credential in the standard way, worth $10 of crypto/fiat. The malicious user then sells the credential to 100 users for $1 each, allowing each user to generate zk-nym tickets of 100MB from this **valid** credential. Under the offline approach, entry nodes skip double-spending checks; so long as the clients all used different ingress Gateways, all 100 users could access the network without obtaining a subscription. As bandwidth consumption is tracked locally between client and ingress node, and each zk-nym ticket is rerandomised, there is no way that ingress Gateways would know that the zk-credential used by the client has been shared with other parties. This loophole calls for measures to counter such abuses without creating either speed bottlenecks (as in the Online model) or harming the anonymity of the system. We can mitigate this problem without doing either.
## Solution to Offline Double Spending
To efficiently prevent the fraudulent use of tickets within the Nym network, a two-tiered solution is in place that combines (1) the immediate detection of double-spending attempts at the level of individuals ingress Gateways and (2) subsequent identification and blacklisting of offending clients at the Quorum level.
To prevent fraudulent use of tickets within the Nym network, a two-tiered solution combines (1) immediate detection of double-spending attempts at individual ingress Gateways and (2) subsequent identification and blacklisting of offending clients at the Quorum level.
### Entry Node Implementation: Real-Time Ticket Validation
Each spent zk-nym ticket contains as an attribute a unique serial number, which is revealed in plaintext to the respective ingress Gateway. Each Gateway has a copy of a [Bloom Filter](https://www.geeksforgeeks.org/bloom-filters-introduction-and-python-implementation/) - on receiving a ticket, it will check against its copy of a local database to check whether this serial number has already been seen. If so, it rejects the ticket as being double-spent and the client's connection request is rejected. If not, it will add the serial number to its local DB.
@@ -30,7 +30,7 @@ Each spent zk-nym ticket contains as an attribute a unique serial number, which
Each Gateway will periodically share their serial numbers with the Quorum and refresh their copy of the Bloom Filters from the Quorum, in order to refresh the global list shared by all ingress Gateways and the Quorum. See the step below for more on this.
> Crucially, ingress Gateways refrain from extensive computations to identify the original ticket owner, and avoids broadcasting information about the double-spending attempt to other ingress Gateways. The entry node is also not involved in any global blacklisting process of the clients. The sole purpose of this check is to swiftly identify any attempts at double-spending and add the seen ticket's serial number to the local DB cache.
> Crucially, ingress Gateways do not perform extensive computations to identify the original ticket owner, and do not broadcast information about the double-spending attempt to other ingress Gateways. The entry node is also not involved in any global blacklisting of clients. The sole purpose of this check is to quickly identify double-spending attempts and add the seen ticket's serial number to the local DB cache.
### Nym-API Implementation: Blacklisting and Penalties for Double-Spenders
All Gateways periodically forward the collected tickets to the Quorum, enabling them to pinpoint and blacklist any clients who double spend. Upon receiving the tickets, the Quorum appends all the incoming serial numbers to the global list of spend zk-nym serial numbers and proceed with the identification process for any malicious users engaging in double-spending.
@@ -9,9 +9,9 @@ Each ticket will not be valid for the entire amount of data that the ticketbook
</Callout>
## Why a 'ticketbook', not individual 'tickets', and why not spend them all at once?
This is to account for the need for a client to change their ingress Gateway, either because the Gateway itself has gone down / is not offering the required bandwidth, or because a user might simply want to split their traffic across multiple Gateways for extra privacy.
This is to account for the need for a client to change their ingress Gateway, either because the Gateway itself has gone down or is not offering the required bandwidth, or because a user might want to split their traffic across multiple Gateways for extra privacy.
Clients are therefore not tied to a particular Gateway they have spent their entire subscription with. If an ingress Gateway goes down, or the client simply wants to use a different one, remaining tickets can be spent with any other Gateway.
Clients are therefore not tied to a particular Gateway they have spent their entire subscription with. If an ingress Gateway goes down, or the client wants to use a different one, remaining tickets can be spent with any other Gateway.
Going back to the `nym-cli` tool to illustrate this; we can generate multiple unlinkable tickets from a single ticketbook aggregated from PSCs:
@@ -2,13 +2,13 @@ import { Callout } from 'nextra/components'
# Unlinkability
Each time a credential is requested by an ingress Gateway to prove that a client has purchased data to send through the Mixnet the Requester's device will produce a ticket. This is a rerandomised value that is able to be verified as being legitimate (in that it was created by a valid root ticketbook) but **not linked to any other tickets**, either previously generated or to be generated in the future. This feature also allows for a single ticketbook to allow access to be split across multiple ingress Gateways / connections and [incrementally spent](./rerandomise) over time.
Each time a credential is requested by an ingress Gateway to prove that a client has purchased data to send through the Mixnet, the Requester's device produces a ticket. This is a rerandomised value that can be verified as legitimate (in that it was created by a valid root ticketbook) but is **not linked to any other tickets**, either previously generated or to be generated in the future. This also allows a single ticketbook to be split across multiple ingress Gateways or connections and [incrementally spent](./rerandomise) over time.
<Callout type="info" emoji="️">
The functionality included in the following code block examples were added to the [nym-cli tool](/developers/tools/nym-cli) for illustrative purposes only: this is not necessarily how credentials will be accessed in the future.
The numbers used in this high level overview are for illustration purposes only. The figures used in production will potentially vary. Note that individual zkNym sizes will be uniform across the Network.
The numbers used in this high level overview are for illustration only. The figures used in production may vary. Individual zkNym sizes are uniform across the Network.
</Callout>
@@ -23,7 +23,7 @@ PAYMENT FOR TICKET 3:
VfZAuVRRHekQYMvFevNAZmPPuwMAfEhTBY8TXatBysbrNXAg8euEGPpJvdbhNfQSznBb9nRSeBUSVoNTToSA6Uj5dXmJ7oE2rCB439DarLMWHWYfQNhw6yhWJhcg6bt7ebBYTs3vVeQgSB5kYuifzJF4QQmK6uJyTNPvpV1J6V8M32PBkGT3JpVB3GUGZiksETf7TaF9wAhMo2QAMxw5ZvaQVve5ea7Mane6cfb2Gx69SRff5zDfEQvKqKnyyZje4SGZgWUeHWVLhRjg4KMTJ3JcsHxEqj2k5qeGeyBbgzcuEtCpYvaytsz7nuZGJsT4Z87gB5Zq4NGuDmekuN977eRJvua2dASNWeHiAzVyvnS7ARN5cdUjjYKYiWgHaYrHGsv26WTDeiu4U3sdJMrLHGFY5ihX7f8sTZqD6Wx5AWjQNbEtKaVHymDogfLcwGCC42gQ2yhKfPUaWJ8H4yMB65YBDXGjATaUzcDmJcZKx8g31j2uTVNSFUesd5CRNEEcTNW7cSFFCishCD3T4eV9SuyZyEXAZ48pazPzc1BysBNHEXQNUEtEAZTKmpghC2pihhfDub6LnMJPo9DDdhCULCbcWbGAPc1vPekPaWvk7wrUTGwp5xoNUhQLW3MeJzMvrMSsqLdursCKB4h4Tk272WCStCPQwAKMYoxjWvMzxoUTTWCkhLKHruMtsehRnai4vhu13jbui6ji1F389gfazm4ctth2s4Yw3H3SaPtRETBfZNvZ7n5UV1MD6Q3qin92gT65iqXEi4zRN3woYcK6ZehiSvgUksdEFAUSxNMgNXKtHEYDS6kA37tn5JdBa2Ex2jLudFfhg6JBM226ZKyj65o6feYPgbJAR3jMCmQRHe6DSFb4aH895EowNMjfGUhwhmnbYB1djp7iFXxPP7575NAerhxEQ1WFnxTfoX7pu1Vc9YZb5priCAVbATCaDkECJsdedM45Vx96Jc6E5NWqD98RhMsPimVJkSfYJmRxH9qugica6WonFFb2YLvXYyhoBA1VHBcRqZJ5KHitS5AegYSoYprUfubMzcYo2hGVEQkGKAsFq6jZgCsbJoGLXt3No317vcowB5f3hqT9FjASHAzW2j8uJ9RRzX7XtrPhArwx4EyPgYzrvgG7xcenoSgQt8poa7aYky56eZTKHVUZgUEt6St32MjcivMvmNdWiAHHDc2ZxzTJHgeuCckX7n19vQ3XNLuXv9oGKNNCi8kHnT4tUnnGXNAWXWuyBgZKWUL8u3y41iW6dLYK3Pw5zfpKZTrq3q3bTLJRN5LnnUuFVnWsC3SNqa6VAAvhTGR9PzxLk8C6HeLP2AsYPpqeQwbaL3Ks6tvPdob3tQPWRBGL4uiKtNZ23tRYZGZLYFWZK7psRSZg5AETejKxztVzAuYovpVUiDq71o331tjqWWV1SzWT13Rd1uwz6nHtsjgao2863YaizKARcYr1j9MKtNfDs483yho6i7tbCRR9M4CPLqdiKEaRyVC1FP4F3sejA6nZTuAA35JWUzX6BBj7wgdypMLdMmmtcCZm3bRrF3GvJJs67U8JWRc6dnoGUDaD7rUu
```
Now lets generate another ticket to spend either topping up once the previous one's data allowance has been used, or with another Gateway. Notice that the `ticket-index` is the same: this is generated from the same aggregated credential as the one above!
Generating another ticket, to spend either when topping up after the previous one's data allowance has been used or with another Gateway. The `ticket-index` is the same: this is generated from the same aggregated credential as the one above.
```sh
./nym-cli ecash generate-ticket --credential-storage storage.db --provider 6qidVK21zpHD298jdDa1RRpbRozP29ENVyqcSbm6hQrG --ticket-index=3
@@ -36,4 +36,4 @@ PAYMENT FOR TICKET 3:
Vev3SmwWtH5vbnejX5Zzc1EcxXAgveqHpKNN8arxXaWLhFcEpdcZ6n7qr3NrQUNURWsK2AsUiX8aSiGSjMPEY3iDE3aDYnjYERVow8RKUmQiYSKvz7v9cEJxt97JAHBfu9WYNHXTnLFSJwWuFtBdzY5dzPdzGckFenGCysa1ZBHGADHChDVXKoPHXxpn5qyJxmi48coUQDptR64QgkCeQ8RRZ396Lxw2NKFSjqavCMMDVm3g1rW7cYyPanBhkoAUzPU9KXX1rtmhD6F9gV89mGZ8fm7ByDuKuYU28seLQ7GkVKkhNeRW9XxbjSiyscTnMUzJ24R5VbSdr141BaquUHezdUTzmA2EjAtcyyiVrCMV13cc96CRbMXENP2soUzckFnh1qPnrfKCvX4JYkztq7UgPT2mZEnSTDW4C6Z2NVCNBPNLqUSYrU4id8Jzcp1mBxqJjdYcQ7P5fWJbT5Q9NAq44PCgfXpsUkNoj35QVQvKXKLb5oNGqnua5YC1WBPcENcpS7ZPWpk2hwe8VK4gNgnwQtWH2RPmWbvBREAV97vS1vKNHJyry9sD2PiMJGSmBnb1bKsGxR9UQN3YvRsdGHzyJHzAMTzxbFJBqMPmxjSHJR4UdwzhB81Ludu1RAffTvecWFxmWH5bNymCQjw3wey7Uequcxgyy8KAWYDzvHGwCZQbHQXghsYREiqquZWaa8hX3iTNBFUtEk8PRVT78MoFNdeBWNjsLr8zyZ5EGnf4kqmw3a91g5p5vywf6e3LgMu19VHjPSNtKMNXiatkPEVjsCuCppmV4sB7FsdKKWcMUSWLsdmrDBg9PStHr7NaJRzLL5E91gvysmB36Nob9cHeHSZj3wM4NVVjFfZeRqQf4bi7ahfXjeeBetgDpqx7JcbU6tTN4JpcGUpp7fp4MhTq7MeVQMLweGUVLqewKgAGzCvEmrK6dzLd3U1P9vkAAVZ3cCAKUywnHGxoxDeEfexP1g1EqJLtKNZVKPf7hSMWqGhoQ36K7y5GnyZ5YhQ7jcDME9orm5w4StoxoDdCPcjbakKG7UaTHuhd7tU1mUffXcEvVerkXoQK9SEaKvGks21RBhW86aHUzJWVbkiDzdaqjJWbmzLV8FKvNxNyzucoH2rq8LiHRMZfV1H3SkVSa4j2Ktw7ZGoQfdj8DgekxXSR2nHPfhybzKYXTBqFo2ACisxkjR4rXr9Xo6eYywQhQ1MP6aYgYCAXFGHPoFf7kx7Jns5sWvHRBdaMF65zeFF2m5NDuMWETtLgFfsyNgR84vfSqTfzj2gsUykRei7q9N4LKmiDwBALTAEcTvZpLtXBjc8JaB9PUeBw7DoSiSK376sGrQ9F6ZGTngXACNz1TbvYhtau4bDa6KC2Qn7wmoyrphpn7TtM1jdwGBxLcaEEWZKQHvWVfTyL2itjqnrcAZkxYdCj56oQYwpWfKQk3zJEUA6SYHqyJjaLNVK6u25j7969EWjdpTsJ8qSsZgXi3T7dQqiwintZbUUUKRq7egN1SGVnA6Wup91uKrYUWEWMqVu4g8ipmRsLD9iXHHr3yA21Cka7pqk1FxR9BFTAnkk1
```
These are both generated by the _same_ underlying ticketbook and used in a way that they cannot be tied to each other. An ingress Gateway might (for instance) get 100 connection requests from 100 Nym clients, each validated with a ticket. It has no way of knowing whether these are all from the same single subscription, or 100 different ones.
These are both generated by the same underlying ticketbook and used in a way that they cannot be tied to each other. An ingress Gateway might (for instance) get 100 connection requests from 100 Nym clients, each validated with a ticket. It has no way of knowing whether these are all from the same single subscription, or 100 different ones.
@@ -3,7 +3,7 @@ import { Callout } from 'nextra/components'
# Generating and using zk-nym anonymous credentials
<Callout type="info">
zk-nyms are already used in production by [NymVPN](https://nymvpn.com) to unlink subscription payments from network activity. The entire credential lifecycle described on this page (key generation, issuance, spending) happens transparently within the NymVPN application. SDK integrations currently connect to the Mixnet without requiring credentials.
zk-nyms are already used in production by [NymVPN](https://nymvpn.com) to unlink subscription payments from network activity. The entire credential lifecycle described on this page (key generation, issuance, spending) happens inside the NymVPN application without user involvement. SDK integrations currently connect to the Mixnet without requiring credentials.
</Callout>
Generation of zk-nyms involves the following actors / pieces of infrastructure:
@@ -16,13 +16,13 @@ Generation happens in 3 distinct stages:
- Issue credential
- Generate unlinkable zk-nyms for Nym Network access
From the Requester's perspective this happens transparently, producing an unlinkable, rerandomisable anonymous proof-of-payment credential (a zk-nym) that grants Mixnet access without linking usage to payment information. A single credential can be split into multiple smaller zk-nyms, so a Requester purchases bandwidth in bulk and spends it incrementally across different ingress Gateways as needed.
From the Requester's perspective this happens without user involvement, producing an unlinkable, rerandomisable anonymous proof-of-payment credential (a zk-nym) that grants Mixnet access without linking usage to payment information. A single credential can be split into multiple smaller zk-nyms, so a Requester purchases bandwidth in bulk and spends it incrementally across different ingress Gateways as needed.
## Key Generation & Payment
- First, a Cosmos [Bech32 address](https://docs.cosmos.network/sdk/latest/guides/reference/bech32#performance-address-caching) is created for the Requester. This is used to identify themselves when interacting with the OrderAPI via signed authentication tokens. **This is the only identity that the OrderAPI is able to see, and is not able to link this to the zk-nyms that will be generated.** This identity never leaves the Requester's device and there is no email or any personal details needed for signup. If a Requester is simply 'topping up' their subscription, the creation of the address is skipped as it already exists.
- First, a Cosmos [Bech32 address](https://docs.cosmos.network/sdk/latest/guides/reference/bech32#performance-address-caching) is created for the Requester. This is used to identify themselves when interacting with the OrderAPI via signed authentication tokens. **This is the only identity the OrderAPI sees, and it cannot link this to the zk-nyms that will be generated.** This identity never leaves the Requester's device and there is no email or personal details needed for signup. If a Requester is 'topping up' their subscription, the creation of the address is skipped as it already exists.
- The Requester also generates an ed25519 keypair: this is used to identify and authenticate them in the case of using zk-nyms across several devices as an individual user. However, **this is never used in the clear**: these keys are used as private attribute values within generated credentials which are verified via zero-knowledge and not publicly exposed.
- The Requester can then interact with various payment backends to pay for their zk-nyms with crypto, fiat options, or natively with NYM tokens.
- The Requester can then interact with payment backends to pay for their zk-nyms with crypto, fiat options, or natively with NYM tokens.
- Payment options will trigger the OrderAPI. This will:
- Create a swap for `<PAYMENT_AMOUNT>` to `NYM` tokens.
- Deposit these tokens with the NymAPI Quorum via a CosmWasm smart contract deployed on the Nyx blockchain.
@@ -1,6 +1,6 @@
---
title: "dVPN Mode"
description: "How Nym's decentralized VPN mode routes traffic through two independent gateways, splitting trust so no single operator sees both your identity and destination."
description: "How Nym's decentralised VPN mode routes traffic through two independent gateways, splitting trust so no single operator sees both your identity and destination."
schemaType: "TechArticle"
section: "Network"
lastUpdated: "2026-03-15"
@@ -8,7 +8,7 @@ lastUpdated: "2026-03-15"
# dVPN Mode
dVPN mode is a 2-hop decentralized VPN available through [NymVPN](https://nymvpn.com). Traffic is routed through two independent gateways rather than a single VPN provider's server, so no single operator ever sees both who you are and what you're doing.
dVPN mode is a 2-hop decentralised VPN available through [NymVPN](https://nymvpn.com). Traffic is routed through two independent gateways rather than a single VPN provider's server, so no single operator ever sees both who you are and what you're doing.
## How it works
@@ -1,6 +1,6 @@
---
title: "dVPN Protocol Stack and Encryption"
description: "Technical details of Nym dVPN mode's protocol layers: nested WireGuard tunnels, split-knowledge architecture, and packet format tradeoffs."
description: "Technical details of Nym dVPN mode's protocol layers: nested WireGuard tunnels, split-knowledge architecture, and packet format trade-offs."
schemaType: "TechArticle"
section: "Network"
lastUpdated: "2026-04-07"
@@ -8,11 +8,9 @@ lastUpdated: "2026-04-07"
# dVPN Protocol
import { Callout } from 'nextra/components'
import { LewesPending } from '../../../components/lewes-pending'
<Callout type="info">
Cryptographic details on this page will be updated for the Lewes Protocol release. For the current algorithm overview, see the [Nym Trust Center: Cryptography](https://nym.com/trust-center/cryptography).
</Callout>
<LewesPending variant="cryptography" />
This page covers the technical details of dVPN mode's protocol stack and encryption.
@@ -40,7 +38,7 @@ Both tunnels use standard WireGuard cryptography: Curve25519 for key exchange, C
## Packet format
dVPN mode uses standard WireGuard packet framing: packets are not padded to a uniform size. Packet sizes may vary and could in principle leak information about content types (video streams have different size patterns than text messages). This is a deliberate tradeoff: uniform padding would add overhead and reduce throughput, which conflicts with dVPN mode's goal of low-latency, high-throughput connectivity. For uniform packet sizes, use [mixnet mode](/network/mixnet-mode), which wraps all traffic in fixed-size Sphinx packets.
dVPN mode uses standard WireGuard packet framing: packets are not padded to a uniform size. Packet sizes may vary and could in principle leak information about content types (video streams have different size patterns than text messages). This is a deliberate trade-off: uniform padding would add overhead and reduce throughput, which conflicts with dVPN mode's goal of low-latency, high-throughput connectivity. For uniform packet sizes, use [mixnet mode](/network/mixnet-mode), which wraps all traffic in fixed-size Sphinx packets.
## Connection lifecycle
@@ -60,4 +58,6 @@ Replay protection comes from WireGuard's counter-based mechanism and from zk-nym
dVPN mode shares infrastructure with mixnet mode. Both use the same Entry and Exit Gateways and the same credential system. The difference is in how traffic is handled: mixnet mode routes through three additional Mix Node layers with delays and cover traffic using fixed-size [Sphinx packets](/network/cryptography/sphinx), while dVPN mode routes directly between gateways using WireGuard. The two modes are distinguishable at the protocol level due to their different packet formats and traffic patterns.
In anonymous (5-hop) mode, NymVPN routes traffic through the full mixnet to the Exit Gateway's [IP Packet Router](/network/infrastructure/exit-services#ip-packet-router), which tunnels raw IP packets to the internet. See [Exit Gateway Services](/network/infrastructure/exit-services) for how the IPR and Network Requester work.
This shared infrastructure means improvements to Gateways and credentials benefit both modes.
+2 -2
View File
@@ -8,7 +8,7 @@ lastUpdated: "2026-02-11"
# The Nym Network
The Nym Network is decentralized privacy infrastructure that protects against **network-level** surveillance. Unlike tools that focus on encrypting message content, Nym protects the metadata surrounding communication: who talks to whom, when, how often, and how much. This metadata is sufficient for observers to map relationships and build behavioural profiles even without access to any message content. See [The Privacy Problem](/network/overview/privacy-problem) for a fuller treatment.
The Nym Network is decentralised privacy infrastructure that protects against **network-level** surveillance. Unlike tools that focus on encrypting message content, Nym protects the metadata surrounding communication: who talks to whom, when, how often, and how much. This metadata is sufficient for observers to map relationships and build behavioural profiles even without access to any message content. See [The Privacy Problem](/network/overview/privacy-problem) for a fuller treatment.
Nym offers two operating modes with different privacy/performance trade-offs, both available through [NymVPN](https://nymvpn.com). Developers can also integrate Mixnet mode directly via the [Nym SDKs](/developers). See [Choosing a Mode](/network/overview/choosing-a-mode) for guidance on which fits a given threat model.
@@ -26,7 +26,7 @@ The [Nym SDKs](/developers) allow developers to embed mixnet functionality direc
## Paying for privacy without losing it
A fundamental weakness of traditional VPNs is that payment records can deanonymize users, since most providers link sessions to account IDs. Nym addresses this with **zk-nyms**: zero-knowledge anonymous credentials that prove payment without revealing any other information. Each credential covers a small chunk of bandwidth and is unlinkable to any other.
A fundamental weakness of traditional VPNs is that payment records can deanonymise users, since most providers link sessions to account IDs. Nym addresses this with **zk-nyms**: zero-knowledge anonymous credentials that prove payment without revealing any other information. Each credential covers a small chunk of bandwidth and is unlinkable to any other.
When you pay for NymVPN, your payment is converted into a credential that can be split and re-randomized. Each Gateway connection uses a fresh, unlinkable proof; the Gateway verifies that you have paid without learning who you are. Your subscription cannot be linked to your network activity, even by infrastructure operators.
@@ -1,6 +1,6 @@
---
title: "Nym Network Infrastructure"
description: "Overview of the Nym Network's decentralized infrastructure: independently operated nodes coordinated by the Nyx blockchain for routing, key management, and credential issuance."
description: "Overview of the Nym Network's decentralised infrastructure: independently operated nodes coordinated by the Nyx blockchain for routing, key management, and credential issuance."
schemaType: "TechArticle"
section: "Network"
lastUpdated: "2026-03-15"
@@ -8,7 +8,7 @@ lastUpdated: "2026-03-15"
# Infrastructure
The Nym Network runs on decentralized infrastructure: a set of independently operated nodes coordinated by the Nyx blockchain, where no single party controls routing, key management, or credential issuance.
The Nym Network runs on decentralised infrastructure: a set of independently operated nodes coordinated by the Nyx blockchain, where no single party controls routing, key management, or credential issuance.
## In this section
@@ -1,4 +1,5 @@
{
"nyx": "Nyx Blockchain",
"nym-nodes": "Nym Nodes"
"nym-nodes": "Nym Nodes",
"exit-services": "Exit Gateway Services"
}
@@ -0,0 +1,84 @@
---
title: "Exit Gateway Services: Network Requester & IP Packet Router"
description: "The two proxy services running on Nym Exit Gateways: the Network Requester (SOCKS proxy) and the IP Packet Router (raw IP tunneling). How they work, what they see, and who uses them."
schemaType: "TechArticle"
section: "Network"
lastUpdated: "2026-04-15"
---
# Exit Gateway Services
import { Callout } from 'nextra/components'
Exit Gateways are where traffic leaves the Nym network and reaches the wider internet. Each Exit Gateway runs two distinct proxy services that handle different kinds of outbound traffic:
- **Network Requester (NR)**, an application-layer SOCKS proxy
- **IP Packet Router (IPR)**, a raw IP tunnel with address allocation
Both services run on every Exit Gateway. Which one handles your traffic depends on how you connect.
## Network Requester
The Network Requester is a SOCKS4/4a/5 proxy. Clients send SOCKS-formatted requests through the mixnet, and the NR makes the corresponding connection on their behalf: resolving hostnames, opening TCP connections, and relaying data.
```text
Client → Entry Gateway → Mixnodes1..3 → Exit Gateway (NR) → SOCKS connect → destination
← relay response ←
```
Because it operates at the application layer, the NR:
- Resolves DNS on behalf of the client (the client sends hostnames, not IPs)
- Opens individual TCP connections per SOCKS request
- Can enforce allow/deny lists on destination hosts and ports
- Sees the destination hostname and port, but not the contents if TLS is used
**Used by:** the [SDK's SOCKS client](/developers/rust/mixnet), [standalone SOCKS5 client](/developers/clients/socks5), and [mixFetch](/developers/mix-fetch) (which wraps SOCKS requests in a browser-friendly `fetch` API).
## IP Packet Router
The IP Packet Router operates at the IP layer. Instead of proxying individual connections, it allocates a virtual IP address to the client and routes raw IP packets between the client and the internet, functioning as a tunnel endpoint.
```text
Client → Entry Gateway → Mixnodes1..3 → Exit Gateway (IPR) → raw IP packets → destination
← raw IP packets ←
```
On connection, the IPR:
1. Allocates an IPv4/IPv6 address pair to the client
2. Accepts raw IP packets (TCP, UDP, or any IP protocol) from the client via the mixnet
3. Sends them to the internet from the gateway's own IP address
4. Routes response packets back through the mixnet to the client
Because it operates at the IP layer, the IPR:
- Does not resolve DNS; the client handles its own DNS (either via clearnet or by sending DNS queries as UDP packets through the tunnel)
- Handles any IP protocol: TCP, UDP, ICMP, etc.
- Sees raw IP packets, including destination IPs and ports
- Does not see contents if the client uses TLS or another encryption layer
<Callout type="warning">
In both services, traffic between the Exit Gateway and the destination travels over the public internet, exactly as it would from any other server. The mixnet protects sender anonymity (the destination sees the gateway's IP, not yours), but does not encrypt the payload past the gateway. Use TLS or another application-layer cipher to protect payload confidentiality, just as you would on a direct connection.
</Callout>
**Used by:** [NymVPN anonymous mode](/network/dvpn-mode/protocol) (5-hop mixnet routing to the IPR), and [`smolmix`](/developers/smolmix) (programmatic `TcpStream`/`UdpSocket` access to the IPR via the Rust SDK).
## Comparison
| | Network Requester | IP Packet Router |
|---|---|---|
| **Layer** | Application (SOCKS) | IP (raw packets) |
| **Protocols** | TCP only | TCP, UDP, any IP protocol |
| **DNS** | Resolved by the NR | Client resolves its own |
| **Client gets** | Proxied connections | An allocated IP address |
| **Connection model** | Per-request | Persistent tunnel |
| **Used by** | SDK SOCKS client, mixFetch | NymVPN (anonymous mode), smolmix |
## Trust model
Both services share the same fundamental trust property: **the Exit Gateway can see destinations but not senders.** The mixnet's layered encryption ensures that the Exit Gateway cannot determine who sent a given packet; it only knows where it's going.
Specifically, the Exit Gateway:
- **Can see:** destination IP/hostname, destination port, unencrypted payload content, traffic volume and timing at the exit hop
- **Cannot see:** the sender's IP address, the sender's Nym address, which Entry Gateway the traffic entered through
- **Cannot determine:** the linkage between different requests from the same sender (unless the payload itself contains identifying information)
The sender's identity is protected by the mixnet's 5-hop routing, Sphinx encryption, cover traffic, and packet mixing. The Exit Gateway is the last hop: it decrypts the final Sphinx layer and sees the destination, but the chain of Mix Nodes between Entry and Exit has destroyed any timing or ordering correlation.
@@ -16,11 +16,11 @@ To run a node, see the [Operator Documentation](/operators/introduction).
**Mix Nodes** form the three mixing layers that provide core privacy. They receive Sphinx packets, remove one encryption layer, verify integrity, apply a random delay, and forward to the next hop. Mix Nodes cannot determine their position in the route and cannot link incoming packets to outgoing packets.
**Exit Gateways** handle traffic leaving the mixnet. They communicate with external internet services on behalf of users and return responses through the network. Exit Gateways can see destination addresses but cannot identify the original sender.
**Exit Gateways** handle traffic leaving the mixnet. They run two proxy services: the [Network Requester](/network/infrastructure/exit-services#network-requester) (a SOCKS proxy for application-layer requests) and the [IP Packet Router](/network/infrastructure/exit-services#ip-packet-router) (a raw IP tunnel used by NymVPN and smolmix). Exit Gateways can see destination addresses but cannot identify the original sender. See [Exit Gateway Services](/network/infrastructure/exit-services) for details.
## Unified binary
The various components were originally separate binaries but have been consolidated into a single `nym-node` binary where the role is specified at runtime. This simplifies operation and makes configuration consistent across roles.
These components were originally separate binaries but have been consolidated into a single `nym-node` binary where the role is specified at runtime. This simplifies operation and makes configuration consistent across roles.
In the future, nodes will automatically switch modes based on network conditions. Operators won't need to manually set whether a node is a Gateway or Mix Node; the network will assign modes dynamically each epoch.
@@ -8,7 +8,7 @@ lastUpdated: "2026-03-15"
# Mixnet Mode
import { Callout } from 'nextra/components'
import { LewesPending } from '../../components/lewes-pending'
Mixnet mode routes traffic through 5 hops: an Entry Gateway, three layers of Mix Nodes, and an Exit Gateway. Each mixing layer adds random delays, reorders packets, and injects cover traffic. Available through [NymVPN](https://nymvpn.com) and the [Nym SDKs](/developers).
@@ -32,9 +32,7 @@ Each Mix Node strips one layer of [Sphinx](/network/cryptography/sphinx) encrypt
The three mixing layers add additional latency. This is acceptable for messaging, file transfers, and most API calls, but unsuitable for real-time applications like video calling. For those, [dVPN mode](/network/dvpn-mode) is more appropriate.
<Callout type="info">
Updated latency measurements will be published after the Lewes Protocol release.
</Callout>
<LewesPending variant="latency" />
## Further reading
@@ -28,7 +28,7 @@ SURB validity is tied to key rotation. Node keys rotate on an odd/even schedule
## SURB replenishment
If Bob's reply is larger than the available SURBs can carry, he uses one SURB to request more. Alice receives the request, generates additional SURBs, and sends them to Bob. This adds round-trip latency but ensures conversations can continue regardless of reply size.
If Bob's reply is larger than the available SURBs can carry, he uses one SURB to request more. Alice receives the request, generates additional SURBs, and sends them to Bob. This adds round-trip latency but lets conversations continue regardless of reply size.
```mermaid
---
@@ -53,7 +53,7 @@ sequenceDiagram
## Sender tags
For sessions with multiple messages, Alice includes a randomly generated sender tag with her SURBs. This helps Bob organize SURBs from multiple conversations without revealing anything about Alice's identity; the tag is random and unlinkable to her address.
For sessions with multiple messages, Alice includes a randomly generated sender tag with her SURBs. This helps Bob organise SURBs from multiple conversations without revealing anything about Alice's identity; the tag is random and unlinkable to her address.
## Security considerations
@@ -43,7 +43,7 @@ Mix nodes also generate their own cover traffic, ensuring minimum traffic levels
Traffic follows a Poisson process with a configurable rate parameter. Inter-packet times are exponentially distributed: random, but with a known average rate. This distribution provides maximum entropy (uncertainty) for a given mean rate, which translates to optimal privacy properties.
## Tradeoffs
## Trade-offs
More cover traffic provides better unobservability but uses more bandwidth and, when zk-nyms are enabled, more credential value. Less cover traffic reduces costs but may allow some inference about activity patterns.
@@ -51,4 +51,4 @@ The default parameters balance privacy and resource usage. Applications with hei
## What cover traffic defeats
Cover traffic prevents volume analysis (how much you communicate), timing analysis (when you communicate), and behavioral profiling (your communication patterns over time). Combined with packet mixing, it ensures that even an adversary watching the entire network cannot learn about your communication behavior with currently known methods.
Cover traffic prevents volume analysis (how much you communicate), timing analysis (when you communicate), and behavioural profiling (your communication patterns over time). Combined with packet mixing, this means that even an adversary watching the entire network cannot learn about your communication behaviour with currently known methods.
@@ -8,7 +8,7 @@ lastUpdated: "2026-03-15"
# Loopix Design
The Nym mixnet is based on the [Loopix](https://arxiv.org/pdf/1703.00536) design, with modifications for decentralized operation and economic incentives.
The Nym mixnet is based on the [Loopix](https://arxiv.org/pdf/1703.00536) design, with modifications for decentralised operation and economic incentives.
## The insight
@@ -38,7 +38,7 @@ Loop traffic ensures minimum anonymity even when few users are active, hides whe
## Nym's modifications
The Nym implementation extends Loopix in several ways: replacing the trusted directory server with the Nyx blockchain for decentralized topology management, incentivising node operation with NYM token rewards rather than relying on volunteers, and adding zk-nyms for privacy-preserving payment, which the original academic design did not address.
The Nym implementation extends Loopix in several ways: replacing the trusted directory server with the Nyx blockchain for decentralised topology management, incentivising node operation with NYM token rewards rather than relying on volunteers, and adding zk-nyms for privacy-preserving payment, which the original academic design did not address.
## Security guarantees
@@ -8,7 +8,7 @@ lastUpdated: "2026-03-15"
# Packet Mixing
import { Callout } from 'nextra/components'
import { LewesPending } from '../../../components/lewes-pending'
Packet mixing breaks timing correlations by adding random delays at each Mix Node. It's the core mechanism that prevents traffic analysis.
@@ -50,9 +50,7 @@ With three Mix Node layers, each applying random delays, the overall effect is t
These delays account for the additional latency of mixnet mode relative to dVPN mode.
<Callout type="info">
Updated latency measurements will be published after the Lewes Protocol release.
</Callout>
<LewesPending variant="latency" />
## Combined with cover traffic
+1 -1
View File
@@ -18,4 +18,4 @@ The Nym Network is a privacy infrastructure that protects metadata: not just mes
## Network Components
All traffic-routing infrastructure runs on [Nym Nodes](/network/infrastructure/nym-nodes), a single binary that operators configure to serve as an Entry Gateway, Mix Node, or Exit Gateway depending on their setup. Network coordination, token bonding, and the distributed credential system all live on the [Nyx blockchain](/network/infrastructure/nyx), a Cosmos SDK chain whose on-chain topology registry eliminates the need for a centralised directory server.
All traffic-routing infrastructure runs on [Nym Nodes](/network/infrastructure/nym-nodes), a single binary that operators configure to serve as an Entry Gateway, Mix Node, or Exit Gateway depending on their setup. Network coordination, token bonding, and the distributed credential system all live on the [Nyx blockchain](/network/infrastructure/nyx), a Cosmos SDK chain whose on-chain topology registry removes the need for a centralised directory server.
@@ -28,7 +28,7 @@ Both modes run on the same Nym infrastructure but defend against different threa
- Latency matters: browsing, streaming, downloads, video calls
- Your concern is ISPs, advertisers, and websites tracking you, not nation-state surveillance
- You want decentralized trust and payment privacy without the overhead of mixing
- You want decentralised trust and payment privacy without the overhead of mixing
## Use Mixnet mode when
@@ -24,7 +24,7 @@ Nym's mixnet mode goes further by adding timing obfuscation and cover traffic, w
Nym's mixnet addresses this by adding random delays at each Mix Node to break timing correlations, cover traffic so observers can't tell when real communication is occurring, per-packet routing rather than Tor's per-session circuits (so there's no long-lived path to observe), and a blockchain-based topology instead of Tor's centralised directory authority.
The tradeoff is latency: Tor is faster because it doesn't add mixing delays, so it may be a better fit for general browsing where timing protection isn't needed. Nym's mixnet is designed for threat models where the adversary can perform traffic analysis.
The trade-off is latency: Tor is faster because it doesn't add mixing delays, so it may be a better fit for general browsing where timing protection isn't needed. Nym's mixnet is designed for threat models where the adversary can perform traffic analysis.
## Nym vs I2P
@@ -6,7 +6,7 @@ section: "Network"
lastUpdated: "2026-03-15"
---
import { Callout } from 'nextra/components'
import { LewesPending } from '../../../components/lewes-pending'
# Acknowledgements
@@ -28,8 +28,6 @@ Acknowledgements operate hop-by-hop between adjacent nodes. They confirm that pa
## Implementation
This is handled entirely by the Nym binaries. Developers and operators don't need to implement or configure acknowledgements; the system handles packet loss transparently.
This is handled entirely by the Nym binaries. Developers and operators don't need to implement or configure acknowledgements; the system handles packet loss without any application involvement.
<Callout type="warning" emoji="">
**Lewes Protocol:** The upcoming Lewes release will introduce changes to how acknowledgements are handled. The current hop-by-hop ACK mechanism described above may be revised as part of broader protocol improvements. Details will be documented here once the changes are finalised.
</Callout>
<LewesPending variant="acks" />
@@ -1,6 +1,6 @@
---
title: "Epochs in the Nym Network"
description: "How epochs organize time in the Nym Network: reward distribution, topology reshuffling, SURB validity windows, and future automatic role assignment."
description: "How epochs organise time in the Nym Network: reward distribution, topology reshuffling, SURB validity windows, and future automatic role assignment."
schemaType: "TechArticle"
section: "Network"
lastUpdated: "2026-03-15"
@@ -8,11 +8,11 @@ lastUpdated: "2026-03-15"
# Epochs
Time in the Nym Network is organized into epochs: discrete periods during which certain network operations occur. The current epoch length is one hour.
Time in the Nym Network is organised into epochs: discrete periods during which certain network operations occur. The current epoch length is one hour.
## What happens at epoch boundaries
**Reward distribution** calculates performance metrics for each node and distributes NYM token rewards based on routing reliability and uptime, ensuring that nodes successfully forwarding packets earn more than those with poor performance.
**Reward distribution** calculates performance metrics for each node and distributes NYM token rewards based on routing reliability and uptime, so that nodes successfully forwarding packets earn more than those with poor performance.
**Topology rerandomization** shuffles the arrangement of nodes in each layer. This prevents long-term route prediction attacks and limits the damage from any compromised nodes. Nodes may also enter or leave the active set based on uptime monitoring and stake changes.
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More