From f648349e82f2708c51833ca379eee10eda8fd637 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Thu, 9 Apr 2026 15:25:31 +0000 Subject: [PATCH] Max/docs-diataxis-ify (#6494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Diatixisify! * First pass at Typedoc generation for TS SDK * Remove overview pages * Fix typos and remove codebase references from docs Fix typos across network and developer docs: Quorum, available, cryptosystem, transaction, proportional, Standalone. Remove TODO placeholder from dVPN protocol page. Strip GitHub source links from network docs to decouple documentation from repo structure. * Expand thin landing pages across network and developer docs - Add intro content to network overview, infrastructure, and reference landing pages - Expand developer index with "where to start" guide - Add usage instructions and explanations to all five TS playground pages - Expand WebSocket client page with setup and message format examples * Restructure Rust SDK developer docs - Delete redundant mixnet example, message-helpers, and message-types subpages - Delete client-pool architecture and example subpages (content folded into landing) - Delete tcpproxy troubleshooting (folded into landing page) - Add deprecation notices to TcpProxy pages, pointing to Stream module - Add stream module docs: landing page, architecture, tutorial, and 4 example pages - Add mixnet and client-pool tutorials - Add SDK tour page - Update navigation and landing pages with docs.rs links * Restructure TS SDK developer docs - Merge overview, installation, and getting started into TS SDK landing page - Fold FAQ content into bundling/troubleshooting section - Delete redundant overview, installation, start, and FAQ pages - Update internal links in browsers.mdx and native.mdx - Update navigation and example page imports * Flatten and expand APIs section - Collapse nested API subpages into single pages with inline Redoc embeds - Rewrite introduction as landing page with decision table - Add endpoint categories, quick curl examples to each API page - Mark Explorer API as deprecated - Move NS API deployment guide to operators/performance-and-testing - Fix dangling /apis/nym-api/mainnet link in network-components - Remove sandbox endpoints from all API pages * Add redirects for moved and deleted pages - Add 25 redirects covering TS SDK, Rust SDK, APIs, and network sections - Fix dangling /developers/typescript/start link in operators changelog * Replace individual example doc pages with GitHub-linked tables, expand tutorials - replace individual example doc pages with GitHub-linked tables - expand mixnet tutorial with persistent identity and split_sender sections - add tcpproxy tutorial - rename "API Reference" to "TypeDoc Reference" in TS SDK sidebar - rename "Misc" to "Extras" in developer sidebar, move VPN CLI up - remove echo server from tools - update message-queue callout to reference actual modules - fix mixnet/examples redirect collision * Add SEO frontmatter, validate encryption standards, clean up URLs - add title/description/schemaType/section/lastUpdated frontmatter to 48 pages across developers, network, and APIs sections - remove network/.archive/ directory (compare against develop instead) - update nymtech.net → nym.com for website/blog links (keep infra URLs) - add native proxy "in progress" callout for Rust/C/Go * API-scraper update (#6598) * read nodes and locations * update python-prebuild.sh * Address PR #6494 review feedback - Use "mode" consistently instead of "role" on nym-nodes page - Replace "staking" with "bonding" for NYM token collateral - Wire up auto-scraped node counts via TimeNow + nodes-count.json - Fix broken licensing images: download CC icons locally, replace inline HTML - Fix 9 stale redirects pointing through deleted /network/architecture path * Fix linkcheck errors - Fix stale cross-links: /network/concepts/ → /network/mixnet-mode/ - Replace README.md references with globals.md in TypeDoc output - Add entryFileName: globals to typedoc.json configs to prevent recurrence * Fix remaining stale /network/architecture links - zk-nym-overview: architecture/nyx#nym-api → /network/infrastructure/nyx#nym-api - setup: network/architecture → /network/overview * Remove accidentally re-included architecture.md file from rebase * Standardize tutorials, document examples, add llms.txt, apply tone fixes - Expand Rust SDK tutorials with step-by-step structure; document all SDK examples across mixnet, client-pool, and tcpproxy pages - Add llms.txt generation script, wire into build and CI workflows - Apply tone/style fixes: deduplicate callouts, vary sentence structure, standardize voice consistency across changed pages * Consolidate redundant network overview docs * Trim dev docs: git-first imports, stream notice, collapse TcpProxy * Update tutorial * Refresh auto-generated API and command outputs * Update network section docs * Update developer and API docs: reusable components, stream protocol, conventions, tutorial fixes * Fix Rust SDK tutorial bugs: setup_env, port conflicts, logging, open_stream race condition * Update stream.mdx * Remove docs.rs link from Stream overview for the moment * add llms.txt and llms-full.txt note to readme --------- Co-authored-by: import this <97586125+serinko@users.noreply.github.com> --- .github/workflows/cd-docs.yml | 2 + .github/workflows/ci-docs.yml | 21 + documentation/README.md | 9 +- .../docs/components/code-verified.tsx | 24 + .../docs/components/crates-paused.tsx | 13 + .../api-scraping-outputs/nodes-count.json | 6 + .../node-api-check-query-help.md | 2 +- .../outputs/command-outputs/nym-api-help.md | 13 +- .../nym-node-cli-install-help.md | 3 +- .../outputs/command-outputs/nym-node-help.md | 13 +- .../command-outputs/nym-node-run-help.md | 91 +- .../outputs/command-outputs/nymvisor-help.md | 9 +- documentation/docs/next.config.js | 210 +- documentation/docs/package.json | 3 +- documentation/docs/pages/apis/_meta.json | 4 +- .../docs/pages/apis/cosmos-sdk-nyx.mdx | 56 +- .../docs/pages/apis/cosmos-sdk-nyx/_meta.json | 5 - .../pages/apis/cosmos-sdk-nyx/mainnet.mdx | 20 - .../pages/apis/cosmos-sdk-nyx/sandbox.mdx | 18 - .../docs/pages/apis/explorer-api.mdx | 37 +- .../docs/pages/apis/explorer-api/_meta.json | 5 - .../docs/pages/apis/explorer-api/mainnet.mdx | 19 - .../docs/pages/apis/explorer-api/sandbox.mdx | 18 - .../docs/pages/apis/introduction.mdx | 57 +- documentation/docs/pages/apis/ns-api.mdx | 53 +- .../docs/pages/apis/ns-api/_meta.json | 5 - .../docs/pages/apis/ns-api/mainnet.mdx | 20 - .../docs/pages/apis/ns-api/sandbox.mdx | 18 - documentation/docs/pages/apis/nym-api.mdx | 62 +- .../docs/pages/apis/nym-api/_meta.json | 3 - .../docs/pages/apis/nym-api/mainnet.mdx | 27 - .../docs/pages/developers/_meta.json | 6 +- .../{nym-connect.md => nym-connect.mdx} | 10 +- .../docs/pages/developers/browsers.mdx | 48 +- documentation/docs/pages/developers/chain.md | 2 +- .../pages/developers/chain/ledger-live.md | 6 +- .../pages/developers/clients/socks5/setup.mdx | 2 +- .../pages/developers/clients/socks5/usage.mdx | 2 +- .../pages/developers/clients/websocket.md | 53 +- .../developers/clients/websocket/examples.md | 2 +- .../developers/clients/websocket/usage.md | 2 +- .../developers/concepts/message-queue.mdx | 12 +- documentation/docs/pages/developers/index.mdx | 21 +- .../docs/pages/developers/integrations.mdx | 36 +- .../docs/pages/developers/licensing.md | 4 +- .../docs/pages/developers/native.mdx | 85 +- .../docs/pages/developers/nymvpncli.mdx | 6 +- documentation/docs/pages/developers/rust.mdx | 31 +- .../docs/pages/developers/rust/_meta.json | 6 +- .../pages/developers/rust/client-pool.mdx | 72 +- .../developers/rust/client-pool/_meta.json | 4 +- .../rust/client-pool/architecture.mdx | 31 - .../developers/rust/client-pool/example.mdx | 110 - .../developers/rust/client-pool/examples.mdx | 19 + .../developers/rust/client-pool/tutorial.mdx | 277 + .../docs/pages/developers/rust/ffi.mdx | 130 +- .../docs/pages/developers/rust/importing.mdx | 54 +- .../docs/pages/developers/rust/mixnet.mdx | 54 +- .../pages/developers/rust/mixnet/_meta.json | 5 +- .../pages/developers/rust/mixnet/examples.mdx | 38 +- .../rust/mixnet/examples/_meta.json | 10 - .../rust/mixnet/examples/builders.mdx | 4 - .../rust/mixnet/examples/builders/_meta.json | 4 - .../builders/builder-with-storage.mdx | 73 - .../rust/mixnet/examples/builders/builder.mdx | 44 - .../rust/mixnet/examples/custom-topology.mdx | 17 - .../examples/custom-topology/_meta.json | 4 - .../custom-topology/custom-provider.mdx | 96 - .../manually-overwrite-topology.mdx | 103 - .../rust/mixnet/examples/simple.mdx | 37 - .../developers/rust/mixnet/examples/socks.mdx | 50 - .../rust/mixnet/examples/split-send.mdx | 52 - .../rust/mixnet/examples/storage.mdx | 229 - .../developers/rust/mixnet/examples/surbs.mdx | 86 - .../rust/mixnet/examples/testnet.mdx | 46 - .../rust/mixnet/message-helpers.mdx | 75 - .../developers/rust/mixnet/message-types.mdx | 30 - .../rust/mixnet/troubleshooting.mdx | 144 +- .../pages/developers/rust/mixnet/tutorial.mdx | 314 + .../docs/pages/developers/rust/stream.mdx | 149 + .../pages/developers/rust/stream/_meta.json | 5 + .../developers/rust/stream/architecture.mdx | 92 + .../pages/developers/rust/stream/examples.mdx | 22 + .../pages/developers/rust/stream/tutorial.mdx | 353 + .../docs/pages/developers/rust/tcpproxy.mdx | 245 +- .../pages/developers/rust/tcpproxy/_meta.json | 5 - .../developers/rust/tcpproxy/architecture.mdx | 209 - .../rust/tcpproxy/examples/_meta.json | 4 - .../rust/tcpproxy/examples/multiconn.mdx | 170 - .../rust/tcpproxy/examples/singleconn.mdx | 229 - .../rust/tcpproxy/troubleshooting.mdx | 5 - .../docs/pages/developers/rust/tour.mdx | 164 + documentation/docs/pages/developers/tools.mdx | 14 +- .../docs/pages/developers/tools/_meta.json | 1 - .../pages/developers/tools/echo-server.mdx | 29 - .../pages/developers/tools/nym-cli/usage.mdx | 6 +- .../developers/tools/standalone-tcpproxy.mdx | 6 + .../docs/pages/developers/typescript.mdx | 182 +- .../docs/pages/developers/typescript/FAQ.mdx | 23 - .../pages/developers/typescript/_meta.json | 9 +- .../developers/typescript/api/_meta.json | 4 + .../typescript/api/mix-fetch/_meta.json | 8 + .../api/mix-fetch/enumerations/EventKinds.md | 17 + .../api/mix-fetch/enumerations/_meta.json | 3 + .../api/mix-fetch/functions/_meta.json | 5 + .../api/mix-fetch/functions/createMixFetch.md | 25 + .../mix-fetch/functions/disconnectMixFetch.md | 19 + .../api/mix-fetch/functions/mixFetch.md | 33 + .../typescript/api/mix-fetch/globals.md | 34 + .../api/mix-fetch/interfaces/IMixFetch.md | 49 + .../interfaces/IMixFetchWebWorker.md | 49 + .../api/mix-fetch/interfaces/LoadedEvent.md | 31 + .../interfaces/MixFetchWebWorkerResponse.md | 87 + .../api/mix-fetch/interfaces/ResponseBody.md | 57 + .../interfaces/ResponseBodyConfigMap.md | 79 + .../api/mix-fetch/interfaces/_meta.json | 8 + .../api/mix-fetch/type-aliases/IMixFetchFn.md | 25 + .../type-aliases/ResponseBodyMethod.md | 13 + .../type-aliases/SetupMixFetchOps.md | 19 + .../api/mix-fetch/type-aliases/_meta.json | 5 + .../ResponseBodyConfigMapDefaults.md | 15 + .../api/mix-fetch/variables/_meta.json | 3 + .../developers/typescript/api/sdk/_meta.json | 7 + .../api/sdk/enumerations/EventKinds.md | 69 + .../api/sdk/enumerations/MimeTypes.md | 39 + .../api/sdk/enumerations/_meta.json | 4 + .../typescript/api/sdk/functions/_meta.json | 3 + .../sdk/functions/createNymMixnetClient.md | 31 + .../developers/typescript/api/sdk/globals.md | 33 + .../interfaces/BinaryMessageReceivedEvent.md | 39 + .../typescript/api/sdk/interfaces/Client.md | 252 + .../api/sdk/interfaces/ConnectedEvent.md | 31 + .../typescript/api/sdk/interfaces/Events.md | 125 + .../api/sdk/interfaces/LoadedEvent.md | 31 + .../api/sdk/interfaces/NymMixnetClient.md | 30 + .../sdk/interfaces/NymMixnetClientOptions.md | 29 + .../typescript/api/sdk/interfaces/Payload.md | 37 + .../sdk/interfaces/RawMessageReceivedEvent.md | 31 + .../interfaces/StringMessageReceivedEvent.md | 43 + .../typescript/api/sdk/interfaces/_meta.json | 12 + .../api/sdk/type-aliases/EventHandlerFn.md | 33 + .../type-aliases/EventHandlerSubscribeFn.md | 33 + .../type-aliases/EventHandlerUnsubscribeFn.md | 26 + .../api/sdk/type-aliases/_meta.json | 5 + .../developers/typescript/bundling/_meta.json | 7 +- .../typescript/bundling/bundling.mdx | 57 +- .../typescript/examples/mix-fetch.mdx | 400 +- .../developers/typescript/examples/mixnet.mdx | 165 +- .../examples/nym-smart-contracts.mdx | 273 +- .../developers/typescript/installation.mdx | 57 - .../pages/developers/typescript/overview.mdx | 72 - .../typescript/playground/cosmos-kit.mdx | 25 +- .../typescript/playground/mixfetch.mdx | 8 +- .../typescript/playground/mixnodes.mdx | 10 +- .../typescript/playground/traffic.mdx | 9 +- .../typescript/playground/wallet.mdx | 21 +- .../pages/developers/typescript/start.mdx | 79 - documentation/docs/pages/network/_meta.json | 11 +- .../docs/pages/network/architecture.mdx | 6 - .../pages/network/architecture/_meta.json | 6 - .../pages/network/architecture/mixnet.mdx | 51 - .../network/architecture/nym-not-p2p.mdx | 1 - .../network/architecture/nym-vs-others.md | 31 - .../docs/pages/network/architecture/nyx.mdx | 89 - documentation/docs/pages/network/concepts.mdx | 2 - .../docs/pages/network/concepts/_meta.json | 7 - .../network/concepts/anonymous-replies.mdx | 17 - .../pages/network/concepts/cover-traffic.mdx | 11 - .../docs/pages/network/concepts/epochs.md | 7 - .../docs/pages/network/concepts/loopix.mdx | 14 - .../docs/pages/network/concepts/mixing.mdx | 23 - .../docs/pages/network/cryptography.md | 17 + .../docs/pages/network/cryptography.mdx | 3 - .../pages/network/cryptography/_meta.json | 2 +- .../cryptography/encryption-standards.md | 11 + .../docs/pages/network/cryptography/sphinx.md | 49 + .../pages/network/cryptography/sphinx.mdx | 15 - .../pages/network/cryptography/zk-nym.mdx | 4 +- .../cryptography/zk-nym/double-spend-prot.mdx | 5 +- .../cryptography/zk-nym/rerandomise.mdx | 8 +- .../cryptography/zk-nym/unlinkability.mdx | 3 +- .../cryptography/zk-nym/zk-nym-overview.mdx | 32 +- documentation/docs/pages/network/dvpn-mode.md | 39 + .../docs/pages/network/dvpn-mode/_meta.json | 4 + .../dvpn-mode/censorship-resistance.md | 50 + .../docs/pages/network/dvpn-mode/protocol.mdx | 63 + documentation/docs/pages/network/index.md | 33 +- .../docs/pages/network/infrastructure.md | 16 + .../pages/network/infrastructure/_meta.json | 4 + .../network/infrastructure/nym-nodes.mdx | 41 + .../docs/pages/network/infrastructure/nyx.mdx | 49 + documentation/docs/pages/network/licensing.md | 4 +- .../docs/pages/network/mixnet-mode.mdx | 47 + .../docs/pages/network/mixnet-mode/_meta.json | 7 + .../network/mixnet-mode/anonymous-replies.mdx | 64 + .../network/mixnet-mode/cover-traffic.md | 54 + .../docs/pages/network/mixnet-mode/loopix.md | 49 + .../docs/pages/network/mixnet-mode/mixing.mdx | 59 + .../network/mixnet-mode/traffic-flow.mdx | 133 + documentation/docs/pages/network/overview.md | 21 + .../docs/pages/network/overview/_meta.json | 5 + .../pages/network/overview/choosing-a-mode.md | 55 + .../pages/network/overview/comparisons.md | 49 + .../pages/network/overview/privacy-problem.md | 29 + documentation/docs/pages/network/reference.md | 17 + .../docs/pages/network/reference/_meta.json | 5 + .../docs/pages/network/reference/acks.mdx | 35 + .../pages/network/reference/addressing.md | 41 + .../docs/pages/network/reference/epochs.md | 29 + documentation/docs/pages/network/traffic.mdx | 23 - .../docs/pages/network/traffic/_meta.json | 9 - .../docs/pages/network/traffic/acks.md | 21 - .../network/traffic/addressing-system.md | 15 - .../network/traffic/anonymous-replies.mdx | 78 - .../docs/pages/network/traffic/flow.mdx | 232 - .../docs/pages/network/traffic/hops.mdx | 2 - .../pages/operators/binaries/building-nym.mdx | 2 +- .../docs/pages/operators/changelog.mdx | 2 +- .../docs/pages/operators/faq/general-faq.mdx | 2 +- .../pages/operators/nodes/nym-node/setup.mdx | 2 +- .../preliminary-steps/wallet-preparation.mdx | 2 +- .../performance-and-testing/_meta.json | 3 +- .../ns-api-deployment.mdx} | 2 +- .../operators/tokenomics/mixnet-rewards.mdx | 2 +- .../docs/public/images/cc-icons/by.svg | 20 + .../docs/public/images/cc-icons/cc.svg | 27 + .../docs/public/images/cc-icons/nc.svg | 23 + .../docs/public/images/cc-icons/sa.svg | 22 + documentation/docs/public/llms-full.txt | 23136 ++++++++++++++++ documentation/docs/public/llms.txt | 73 + .../scripts/api-scraping/api_targets.py | 118 + .../next-scripts/generate-llms-txt.mjs | 176 + .../scripts/next-scripts/generate-typedoc.sh | 28 + .../scripts/next-scripts/python-prebuild.sh | 2 + .../examples/stream_simple_read_write.rs | 2 +- .../packages/mix-fetch-node/typedoc.json | 1 - .../packages/mix-fetch/typedoc.json | 5 +- .../packages/nodejs-client/typedoc.json | 1 - sdk/typescript/packages/sdk/typedoc.json | 5 +- 239 files changed, 29789 insertions(+), 3702 deletions(-) create mode 100644 documentation/docs/components/code-verified.tsx create mode 100644 documentation/docs/components/crates-paused.tsx create mode 100644 documentation/docs/components/outputs/api-scraping-outputs/nodes-count.json delete mode 100644 documentation/docs/pages/apis/cosmos-sdk-nyx/_meta.json delete mode 100644 documentation/docs/pages/apis/cosmos-sdk-nyx/mainnet.mdx delete mode 100644 documentation/docs/pages/apis/cosmos-sdk-nyx/sandbox.mdx delete mode 100644 documentation/docs/pages/apis/explorer-api/_meta.json delete mode 100644 documentation/docs/pages/apis/explorer-api/mainnet.mdx delete mode 100644 documentation/docs/pages/apis/explorer-api/sandbox.mdx delete mode 100644 documentation/docs/pages/apis/ns-api/_meta.json delete mode 100644 documentation/docs/pages/apis/ns-api/mainnet.mdx delete mode 100644 documentation/docs/pages/apis/ns-api/sandbox.mdx delete mode 100644 documentation/docs/pages/apis/nym-api/_meta.json delete mode 100644 documentation/docs/pages/apis/nym-api/mainnet.mdx rename documentation/docs/pages/developers/archive/{nym-connect.md => nym-connect.mdx} (91%) delete mode 100644 documentation/docs/pages/developers/rust/client-pool/architecture.mdx delete mode 100644 documentation/docs/pages/developers/rust/client-pool/example.mdx create mode 100644 documentation/docs/pages/developers/rust/client-pool/examples.mdx create mode 100644 documentation/docs/pages/developers/rust/client-pool/tutorial.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/_meta.json delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/builders.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/builders/_meta.json delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/builders/builder-with-storage.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/builders/builder.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/custom-topology.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/_meta.json delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/custom-provider.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/manually-overwrite-topology.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/simple.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/socks.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/split-send.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/storage.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/surbs.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/examples/testnet.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/message-helpers.mdx delete mode 100644 documentation/docs/pages/developers/rust/mixnet/message-types.mdx create mode 100644 documentation/docs/pages/developers/rust/mixnet/tutorial.mdx create mode 100644 documentation/docs/pages/developers/rust/stream.mdx create mode 100644 documentation/docs/pages/developers/rust/stream/_meta.json create mode 100644 documentation/docs/pages/developers/rust/stream/architecture.mdx create mode 100644 documentation/docs/pages/developers/rust/stream/examples.mdx create mode 100644 documentation/docs/pages/developers/rust/stream/tutorial.mdx delete mode 100644 documentation/docs/pages/developers/rust/tcpproxy/_meta.json delete mode 100644 documentation/docs/pages/developers/rust/tcpproxy/architecture.mdx delete mode 100644 documentation/docs/pages/developers/rust/tcpproxy/examples/_meta.json delete mode 100644 documentation/docs/pages/developers/rust/tcpproxy/examples/multiconn.mdx delete mode 100644 documentation/docs/pages/developers/rust/tcpproxy/examples/singleconn.mdx delete mode 100644 documentation/docs/pages/developers/rust/tcpproxy/troubleshooting.mdx create mode 100644 documentation/docs/pages/developers/rust/tour.mdx delete mode 100644 documentation/docs/pages/developers/tools/echo-server.mdx delete mode 100644 documentation/docs/pages/developers/typescript/FAQ.mdx create mode 100644 documentation/docs/pages/developers/typescript/api/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/enumerations/EventKinds.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/enumerations/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/functions/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/functions/createMixFetch.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/functions/disconnectMixFetch.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/functions/mixFetch.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/globals.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/IMixFetch.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/IMixFetchWebWorker.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/LoadedEvent.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/MixFetchWebWorkerResponse.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/ResponseBody.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/ResponseBodyConfigMap.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/IMixFetchFn.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/ResponseBodyMethod.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/SetupMixFetchOps.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/variables/ResponseBodyConfigMapDefaults.md create mode 100644 documentation/docs/pages/developers/typescript/api/mix-fetch/variables/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/enumerations/EventKinds.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/enumerations/MimeTypes.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/enumerations/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/functions/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/functions/createNymMixnetClient.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/globals.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/BinaryMessageReceivedEvent.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/Client.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/ConnectedEvent.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/Events.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/LoadedEvent.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/NymMixnetClient.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/NymMixnetClientOptions.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/Payload.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/RawMessageReceivedEvent.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/StringMessageReceivedEvent.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/interfaces/_meta.json create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerFn.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerSubscribeFn.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerUnsubscribeFn.md create mode 100644 documentation/docs/pages/developers/typescript/api/sdk/type-aliases/_meta.json delete mode 100644 documentation/docs/pages/developers/typescript/installation.mdx delete mode 100644 documentation/docs/pages/developers/typescript/overview.mdx delete mode 100644 documentation/docs/pages/developers/typescript/start.mdx delete mode 100644 documentation/docs/pages/network/architecture.mdx delete mode 100644 documentation/docs/pages/network/architecture/_meta.json delete mode 100644 documentation/docs/pages/network/architecture/mixnet.mdx delete mode 100644 documentation/docs/pages/network/architecture/nym-not-p2p.mdx delete mode 100644 documentation/docs/pages/network/architecture/nym-vs-others.md delete mode 100644 documentation/docs/pages/network/architecture/nyx.mdx delete mode 100644 documentation/docs/pages/network/concepts.mdx delete mode 100644 documentation/docs/pages/network/concepts/_meta.json delete mode 100644 documentation/docs/pages/network/concepts/anonymous-replies.mdx delete mode 100644 documentation/docs/pages/network/concepts/cover-traffic.mdx delete mode 100644 documentation/docs/pages/network/concepts/epochs.md delete mode 100644 documentation/docs/pages/network/concepts/loopix.mdx delete mode 100644 documentation/docs/pages/network/concepts/mixing.mdx create mode 100644 documentation/docs/pages/network/cryptography.md delete mode 100644 documentation/docs/pages/network/cryptography.mdx create mode 100644 documentation/docs/pages/network/cryptography/encryption-standards.md create mode 100644 documentation/docs/pages/network/cryptography/sphinx.md delete mode 100644 documentation/docs/pages/network/cryptography/sphinx.mdx create mode 100644 documentation/docs/pages/network/dvpn-mode.md create mode 100644 documentation/docs/pages/network/dvpn-mode/_meta.json create mode 100644 documentation/docs/pages/network/dvpn-mode/censorship-resistance.md create mode 100644 documentation/docs/pages/network/dvpn-mode/protocol.mdx create mode 100644 documentation/docs/pages/network/infrastructure.md create mode 100644 documentation/docs/pages/network/infrastructure/_meta.json create mode 100644 documentation/docs/pages/network/infrastructure/nym-nodes.mdx create mode 100644 documentation/docs/pages/network/infrastructure/nyx.mdx create mode 100644 documentation/docs/pages/network/mixnet-mode.mdx create mode 100644 documentation/docs/pages/network/mixnet-mode/_meta.json create mode 100644 documentation/docs/pages/network/mixnet-mode/anonymous-replies.mdx create mode 100644 documentation/docs/pages/network/mixnet-mode/cover-traffic.md create mode 100644 documentation/docs/pages/network/mixnet-mode/loopix.md create mode 100644 documentation/docs/pages/network/mixnet-mode/mixing.mdx create mode 100644 documentation/docs/pages/network/mixnet-mode/traffic-flow.mdx create mode 100644 documentation/docs/pages/network/overview.md create mode 100644 documentation/docs/pages/network/overview/_meta.json create mode 100644 documentation/docs/pages/network/overview/choosing-a-mode.md create mode 100644 documentation/docs/pages/network/overview/comparisons.md create mode 100644 documentation/docs/pages/network/overview/privacy-problem.md create mode 100644 documentation/docs/pages/network/reference.md create mode 100644 documentation/docs/pages/network/reference/_meta.json create mode 100644 documentation/docs/pages/network/reference/acks.mdx create mode 100644 documentation/docs/pages/network/reference/addressing.md create mode 100644 documentation/docs/pages/network/reference/epochs.md delete mode 100644 documentation/docs/pages/network/traffic.mdx delete mode 100644 documentation/docs/pages/network/traffic/_meta.json delete mode 100644 documentation/docs/pages/network/traffic/acks.md delete mode 100644 documentation/docs/pages/network/traffic/addressing-system.md delete mode 100644 documentation/docs/pages/network/traffic/anonymous-replies.mdx delete mode 100644 documentation/docs/pages/network/traffic/flow.mdx delete mode 100644 documentation/docs/pages/network/traffic/hops.mdx rename documentation/docs/pages/{apis/ns-api/ns-api-run-deploy.mdx => operators/performance-and-testing/ns-api-deployment.mdx} (98%) create mode 100644 documentation/docs/public/images/cc-icons/by.svg create mode 100644 documentation/docs/public/images/cc-icons/cc.svg create mode 100644 documentation/docs/public/images/cc-icons/nc.svg create mode 100644 documentation/docs/public/images/cc-icons/sa.svg create mode 100644 documentation/docs/public/llms-full.txt create mode 100644 documentation/docs/public/llms.txt create mode 100644 documentation/scripts/next-scripts/generate-llms-txt.mjs create mode 100755 documentation/scripts/next-scripts/generate-typedoc.sh diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index c36e393b6a..98995e6986 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -39,6 +39,8 @@ jobs: - name: Install project dependencies run: pnpm i + - name: Generate llms-full.txt + run: pnpm run generate:llms - name: Build project run: pnpm run build - name: Generate sitemap diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index 1501111f46..50b168b82d 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -6,6 +6,8 @@ on: branches-ignore: [master] paths: - "documentation/docs/**" + - "sdk/typescript/packages/sdk/src/**" + - "sdk/typescript/packages/mix-fetch/src/**" - ".github/workflows/ci-docs.yml" jobs: @@ -42,8 +44,27 @@ jobs: command: build args: --workspace --release + - name: Check if TypeScript SDK source changed + id: check-ts-sdk + run: | + if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -qE '^sdk/typescript/packages/(sdk|mix-fetch)/src/'; then + echo "changed=true" >> $GITHUB_OUTPUT + else + echo "changed=false" >> $GITHUB_OUTPUT + fi + working-directory: ${{ github.workspace }} + + - name: Regenerate TypeDoc API reference + if: steps.check-ts-sdk.outputs.changed == 'true' + run: | + npm install -g typedoc@0.25.13 typedoc-plugin-markdown@4.0.3 + cd ${{ github.workspace }}/sdk/typescript/packages/sdk && typedoc --skipErrorChecking + cd ${{ github.workspace }}/sdk/typescript/packages/mix-fetch && typedoc --skipErrorChecking + - name: Install project dependencies run: pnpm i + - name: Generate llms-full.txt + run: pnpm run generate:llms - name: Build project run: pnpm run build - name: Generate sitemap diff --git a/documentation/README.md b/documentation/README.md index b5029066b4..72919a423c 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -20,7 +20,7 @@ Our `prebuild` script relies on the following: - [`tabulate`](https://pypi.org/project/tabulate/) - `jq` -Otherwise make sure to have `node` installed. +Otherwise make sure to have `node` and `rust` installed. ### Link checking (optional) We use [lychee](https://github.com/lycheeverse/lychee) to check for broken links. Install via your package manager or `cargo install lychee`, then run: @@ -89,6 +89,13 @@ NEXT_PUBLIC_SITE_URL=https://nym.com/docs | HowTo | Step-by-step install/setup guides | | FAQPage | Question-answer pages | +## LLM-readability +Two files are generated in the deployment workflow: `llms.txt` and `llms-full.txt`. These files follow [Cloudflare's approach](https://developers.cloudflare.com/style-guide/how-we-docs/ai-consumability/) to generation and use. + +When running locally can you find these at `http://localhost:3000/docs/llms.txt` and `http://localhost:3000/docs/llms-full.txt`. + +When deployed to production, these can be found at [https://nym.com/docs/llms.txt](https://nym.com/docs/llms.txt) and [https://nym.com/docs/llms-full.txt](https://nym.com/docs/llms-full.txt). + ## Licensing and copyright information This is a monorepo and components that make up Nym as a system are licensed individually, so for accurate information, please check individual files. diff --git a/documentation/docs/components/code-verified.tsx b/documentation/docs/components/code-verified.tsx new file mode 100644 index 0000000000..b986c21d3d --- /dev/null +++ b/documentation/docs/components/code-verified.tsx @@ -0,0 +1,24 @@ +import { Callout } from "nextra/components"; + +const COMMIT_SHORT = "4077717"; +const COMMIT_FULL = "4077717d3"; +const EXAMPLES_URL = + "https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples"; + +export const CodeVerified = () => ( + + Code verified against commit{" "} + + {COMMIT_SHORT} + + . If the API has changed since then, check the{" "} + + examples in the repo + {" "} + for the latest usage. + +); diff --git a/documentation/docs/components/crates-paused.tsx b/documentation/docs/components/crates-paused.tsx new file mode 100644 index 0000000000..1194a5222e --- /dev/null +++ b/documentation/docs/components/crates-paused.tsx @@ -0,0 +1,13 @@ +import { Callout } from "nextra/components"; + +const CRATES_VERSION = "1.20.4"; +const INSTALL_PATH = "/developers/rust/importing"; + +export const CratesPaused = () => ( + + Crate publication is paused. 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{" "} + Installation. + +); diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nodes-count.json b/documentation/docs/components/outputs/api-scraping-outputs/nodes-count.json new file mode 100644 index 0000000000..ed17070561 --- /dev/null +++ b/documentation/docs/components/outputs/api-scraping-outputs/nodes-count.json @@ -0,0 +1,6 @@ +{ + "nodes": 753, + "locations": 76, + "mixnodes": 272, + "exit_gateways": 472 +} diff --git a/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md b/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md index bfa068ddb0..999f3d4a6f 100644 --- a/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md +++ b/documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md @@ -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) ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-api-help.md b/documentation/docs/components/outputs/command-outputs/nym-api-help.md index 1ba192c6e8..ff31ad9442 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-api-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-api-help.md @@ -8,12 +8,9 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -c, --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 + -c, --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 ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md index 70baf6f736..ddc7c867b3 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md @@ -12,8 +12,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- diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-help.md index f1af88721d..6b624cf953 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-help.md @@ -12,12 +12,9 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -c, --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 + -c, --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 ``` diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md index 1f43cff534..d0b84d94b4 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-run-help.md @@ -9,93 +9,124 @@ Options: --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 [env: NYMNODE_ACCEPT_OPERATOR_TERMS=] + Explicitly specify whether you agree with the terms and conditions of a nym node operator as defined at + [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=] + 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=] + 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 [...] - Specifies the current mode(s) of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway, exit-providers-only] + Specifies the current mode(s) of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway, + exit-providers-only] --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] + 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=] + 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 - 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=] + 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 - Specify the output format of the bonding information (`text` or `json`) [env: NYMNODE_OUTPUT=] [default: text] [possible values: text, json] + Specify the output format of the bonding information (`text` or `json`) [env: NYMNODE_OUTPUT=] [default: text] [possible values: + text, json] --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=] + 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 - Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the clients [env: NYMNODE_HOSTNAME=] + Optional hostname associated with this gateway that will be announced to the nym-api and subsequently to the clients [env: + NYMNODE_HOSTNAME=] --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=] + 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 Socket address this node will use for binding its http API. default: `[::]:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=] --landing-page-assets-path Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=] --http-access-token - An optional bearer token for accessing certain http endpoints. Currently only used for prometheus metrics [env: NYMNODE_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 - Specify whether basic system information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_INFO=] [possible values: true, false] + Specify whether basic system information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_INFO=] [possible values: + true, false] --expose-system-hardware - Specify whether basic system hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE=] [possible values: true, false] + Specify whether basic system hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_SYSTEM_HARDWARE=] + [possible values: true, false] --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] + 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 Address this node will bind to for listening for mixnet packets default: `[::]:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=] --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=] + 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 Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=] --nyxd-urls Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=] --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] + Specify whether running statistics of this node should be logged to the console [env: NYMNODE_ENABLE_CONSOLE_LOGGING=] [possible + values: true, false] --wireguard-enabled Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false] --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 - 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=] + 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 - 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=] + 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 - 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] + 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 Socket address this node will use for binding its verloc API. default: `[::]:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=] --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=] + 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 Socket address this node will use for binding its client websocket API. default: `[::]:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=] --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=] + 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 If applicable, announced port for listening for secure websocket client traffic [env: NYMNODE_ENTRY_ANNOUNCE_WSS_PORT=] --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] + 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 - 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=] + 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 - 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=] + 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 - 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=] + 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 Specifies the url for an upstream source of the exit policy used by this node [env: NYMNODE_UPSTREAM_EXIT_POLICY=] --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] + 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 Bind address for the TCP LP control traffic. default: `[::]:41264` [env: NYMNODE_LP_CONTROL_BIND_ADDRESS=] --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=] + 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 Bind address for the UDP LP data traffic. default: `[::]:51264` [env: NYMNODE_LP_DATA_BIND_ADDRESS=] --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=] + 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 - 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] + 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 ``` diff --git a/documentation/docs/components/outputs/command-outputs/nymvisor-help.md b/documentation/docs/components/outputs/command-outputs/nymvisor-help.md index e4124a3ea9..e3c23ff77d 100644 --- a/documentation/docs/components/outputs/command-outputs/nymvisor-help.md +++ b/documentation/docs/components/outputs/command-outputs/nymvisor-help.md @@ -11,10 +11,7 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -c, --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 + -c, --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 ``` diff --git a/documentation/docs/next.config.js b/documentation/docs/next.config.js index 59613ca1e6..d4be16bd92 100644 --- a/documentation/docs/next.config.js +++ b/documentation/docs/next.config.js @@ -64,19 +64,19 @@ const config = { }, { source: "/docs/architecture/nym-vs-others.html", - destination: "/docs/network/architecture/nym-vs-others", + destination: "/docs/network/overview/comparisons", permanent: true, basePath: false, }, { source: "/docs/architecture/traffic-flow.html", - destination: "/docs/network/traffic", // testing difference + destination: "/docs/network/mixnet-mode/traffic-flow", permanent: true, basePath: false, }, { source: "/docs/architecture/addressing-system.html", - destination: "/docs/network/traffic/addressing-system", + destination: "/docs/network/reference/addressing", permanent: true, basePath: false, }, @@ -100,7 +100,7 @@ const config = { }, { source: "/docs/nodes/overview.html ", - destination: "/docs/network/architecture/mixnet#nym-nodes", + destination: "/docs/network/infrastructure/nym-nodes", permanent: true, basePath: false, }, @@ -132,19 +132,19 @@ const config = { }, { source: "/docs/nyx/smart-contracts.html", - destination: "/docs/network/architecture/nyx#smart-contracts", + destination: "/docs/network/infrastructure/nyx#smart-contracts", permanent: true, basePath: false, }, { source: "/docs/nyx/mixnet-contract.html", - destination: "/docs/network/architecture/nyx#mixnet-contract", + destination: "/docs/network/infrastructure/nyx#mixnet-contract", permanent: true, basePath: false, }, { source: "/docs/nyx/vesting-contract.html", - destination: "/docs/network/architecture/nyx#vesting-contract", + destination: "/docs/network/infrastructure/nyx#vesting-contract", permanent: true, basePath: false, }, @@ -616,7 +616,7 @@ const config = { }, { source: "/docs/architecture/network-overview.html", - destination: "/docs/network/architecture", + destination: "/docs/network/overview", permanent: true, basePath: false, }, @@ -634,7 +634,7 @@ const config = { }, { source: "/docs/network/architecture/nyx/smart-contracts/ecash", - destination: "/docs/network/architecture/nyx#zk-nym-contract", + destination: "/docs/network/infrastructure/nyx#zk-nym-contract", permanent: true, basePath: false, }, @@ -1073,6 +1073,198 @@ const config = { // destination: "https://www./developers/typescript/FAQ", // permanent: true, // }, + + // ========================================== + // Docs rework redirects (2026) + // ========================================== + + // --- Network overview: deleted pages --- + { + source: "/docs/network/overview/two-modes", + destination: "/docs/network/overview/choosing-a-mode", + permanent: true, + basePath: false, + }, + { + source: "/docs/network/overview/network-components", + destination: "/docs/network/infrastructure", + permanent: true, + basePath: false, + }, + + // --- TypeScript SDK: merged pages --- + { + source: "/docs/developers/typescript/overview", + destination: "/docs/developers/typescript", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/typescript/installation", + destination: "/docs/developers/typescript", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/typescript/start", + destination: "/docs/developers/typescript", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/typescript/FAQ", + destination: "/docs/developers/typescript/bundling/bundling", + permanent: true, + basePath: false, + }, + + // --- Rust SDK: deleted mixnet example subpages --- + { + source: "/docs/developers/rust/mixnet/examples/:path*", + destination: "/docs/developers/rust/mixnet/examples", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/rust/mixnet/message-helpers", + destination: "/docs/developers/rust/mixnet", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/rust/mixnet/message-types", + destination: "/docs/developers/rust/mixnet", + permanent: true, + basePath: false, + }, + + // --- Rust SDK: deleted client-pool subpages --- + { + source: "/docs/developers/rust/client-pool/architecture", + destination: "/docs/developers/rust/client-pool", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/rust/client-pool/example", + destination: "/docs/developers/rust/client-pool", + permanent: true, + basePath: false, + }, + + // --- Rust SDK: collapsed TcpProxy subpages --- + { + source: "/docs/developers/rust/tcpproxy/troubleshooting", + destination: "/docs/developers/rust/tcpproxy", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/rust/tcpproxy/tutorial", + destination: "/docs/developers/rust/tcpproxy", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/rust/tcpproxy/architecture", + destination: "/docs/developers/rust/tcpproxy", + permanent: true, + basePath: false, + }, + { + source: "/docs/developers/rust/tcpproxy/examples", + destination: "/docs/developers/rust/tcpproxy", + permanent: true, + basePath: false, + }, + + // --- APIs: flattened structure --- + { + source: "/docs/apis/ns-api/mainnet", + destination: "/docs/apis/ns-api", + permanent: true, + basePath: false, + }, + { + source: "/docs/apis/ns-api/sandbox", + destination: "/docs/apis/ns-api", + permanent: true, + basePath: false, + }, + { + source: "/docs/apis/ns-api/ns-api-run-deploy", + destination: "/docs/operators/performance-and-testing/ns-api-deployment", + permanent: true, + basePath: false, + }, + { + source: "/docs/apis/nym-api/mainnet", + destination: "/docs/apis/nym-api", + permanent: true, + basePath: false, + }, + { + source: "/docs/apis/explorer-api/mainnet", + destination: "/docs/apis/explorer-api", + permanent: true, + basePath: false, + }, + { + source: "/docs/apis/explorer-api/sandbox", + destination: "/docs/apis/explorer-api", + permanent: true, + basePath: false, + }, + { + source: "/docs/apis/cosmos-sdk-nyx/mainnet", + destination: "/docs/apis/cosmos-sdk-nyx", + permanent: true, + basePath: false, + }, + { + source: "/docs/apis/cosmos-sdk-nyx/sandbox", + destination: "/docs/apis/cosmos-sdk-nyx", + permanent: true, + basePath: false, + }, + + // --- Network: archived sections --- + { + source: "/docs/network/architecture", + destination: "/docs/network/overview", + permanent: true, + basePath: false, + }, + { + source: "/docs/network/architecture/:path*", + destination: "/docs/network/overview", + permanent: true, + basePath: false, + }, + { + source: "/docs/network/concepts", + destination: "/docs/network/overview", + permanent: true, + basePath: false, + }, + { + source: "/docs/network/concepts/:path*", + destination: "/docs/network/overview", + permanent: true, + basePath: false, + }, + { + source: "/docs/network/traffic", + destination: "/docs/network/overview", + permanent: true, + basePath: false, + }, + { + source: "/docs/network/traffic/:path*", + destination: "/docs/network/overview", + permanent: true, + basePath: false, + }, ]; }, images: { diff --git a/documentation/docs/package.json b/documentation/docs/package.json index 8d2a07a1ac..f553370103 100644 --- a/documentation/docs/package.json +++ b/documentation/docs/package.json @@ -8,7 +8,8 @@ "generate:commands": "../scripts/next-scripts/autodoc.sh", "generate:tables": "../scripts/next-scripts/python-prebuild.sh", "predev": "../scripts/next-scripts/python-prebuild.sh", - "build": "next build && next-sitemap", + "generate:llms": "node ../scripts/next-scripts/generate-llms-txt.mjs", + "build": "node ../scripts/next-scripts/generate-llms-txt.mjs && next build && next-sitemap", "dev": " next dev", "lint": "next lint", "lint:fix": "next lint --fix", diff --git a/documentation/docs/pages/apis/_meta.json b/documentation/docs/pages/apis/_meta.json index 616cc14334..279dc8a707 100644 --- a/documentation/docs/pages/apis/_meta.json +++ b/documentation/docs/pages/apis/_meta.json @@ -1,9 +1,9 @@ { "introduction": "Introduction", "ns-api": "Node Status API", - "nym-api": "NymAPI Validator Sidecar", - "explorer-api": "Explorer API", + "nym-api": "NymAPI", "cosmos-sdk-nyx": "Validator REST API", + "explorer-api": "Explorer API (Deprecated)", "---": { "type": "separator" }, diff --git a/documentation/docs/pages/apis/cosmos-sdk-nyx.mdx b/documentation/docs/pages/apis/cosmos-sdk-nyx.mdx index 822f1a753f..900752d513 100644 --- a/documentation/docs/pages/apis/cosmos-sdk-nyx.mdx +++ b/documentation/docs/pages/apis/cosmos-sdk-nyx.mdx @@ -1,3 +1,57 @@ +--- +title: "Nyx Validator REST API - Cosmos SDK Endpoints" +description: "Reference for the Nyx Validator REST API, providing Cosmos SDK endpoints for account balances, staking, governance, and CosmWasm smart contract queries." +schemaType: "TechArticle" +section: "APIs" +lastUpdated: "2026-03-15" +--- + +import { RedocStandalone } from 'redoc' + # Validator REST API -Since the [Nyx validators](/operators/nodes/validator-setup) are built with the Cosmos SDK, they by default expose a [REST API](https://docs.cosmos.network/api) which can be used to query the state of the chain. +Nyx Validators are built with the [Cosmos SDK](https://docs.cosmos.network/) and expose a standard [REST API](https://docs.cosmos.network/api) for querying chain state directly. + +**Key endpoint categories:** +- **Accounts**: balances, transaction history +- **Staking**: validators, delegations, rewards +- **Governance**: proposals, votes +- **CosmWasm**: smart contract queries + +For validator setup instructions, see the [Nyx Validator Setup Guide](/operators/nodes/validator-setup). + +Other validator endpoints are listed at [cosmos.directory/nyx](https://cosmos.directory/nyx). + +## Quick examples + +**Query an account balance:** +```bash +curl https://api.nymtech.net/cosmos/bank/v1beta1/balances/n1... +``` + +**List active validators:** +```bash +curl https://api.nymtech.net/cosmos/staking/v1beta1/validators?status=BOND_STATUS_BONDED +``` + +## Mainnet endpoints + +- **OpenAPI spec:** [api.nymtech.net/swagger/swagger.yaml](https://api.nymtech.net/swagger/swagger.yaml) +- **Swagger UI:** [api.nymtech.net/swagger/](https://api.nymtech.net/swagger/) + +## Full API reference + +
+ + diff --git a/documentation/docs/pages/apis/cosmos-sdk-nyx/_meta.json b/documentation/docs/pages/apis/cosmos-sdk-nyx/_meta.json deleted file mode 100644 index 8e5aad88d7..0000000000 --- a/documentation/docs/pages/apis/cosmos-sdk-nyx/_meta.json +++ /dev/null @@ -1,5 +0,0 @@ - -{ - "mainnet":"Mainnet Endpoints", - "sandbox":"Sandbox Endpoints" -} diff --git a/documentation/docs/pages/apis/cosmos-sdk-nyx/mainnet.mdx b/documentation/docs/pages/apis/cosmos-sdk-nyx/mainnet.mdx deleted file mode 100644 index ed8074d628..0000000000 --- a/documentation/docs/pages/apis/cosmos-sdk-nyx/mainnet.mdx +++ /dev/null @@ -1,20 +0,0 @@ -import { RedocStandalone } from 'redoc'; - -The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://api.nymtech.net/swagger/swagger.yaml](https://api.nymtech.net/swagger/swagger.yaml) which is also used to generate the Swagger docs deployed at [https://api.nymtech.net/swagger/](https://api.nymtech.net/swagger/). - -There is also an overview of other Validator endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx). - -

- - diff --git a/documentation/docs/pages/apis/cosmos-sdk-nyx/sandbox.mdx b/documentation/docs/pages/apis/cosmos-sdk-nyx/sandbox.mdx deleted file mode 100644 index 834d30ecbc..0000000000 --- a/documentation/docs/pages/apis/cosmos-sdk-nyx/sandbox.mdx +++ /dev/null @@ -1,18 +0,0 @@ -import { RedocStandalone } from 'redoc'; - -The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://api.sandbox.nymtech.net/swagger/swagger.yaml](https://api.sandbox.nymtech.net/swagger/swagger.yaml). - -

- - diff --git a/documentation/docs/pages/apis/explorer-api.mdx b/documentation/docs/pages/apis/explorer-api.mdx index 77cd141ddc..77237bbd97 100644 --- a/documentation/docs/pages/apis/explorer-api.mdx +++ b/documentation/docs/pages/apis/explorer-api.mdx @@ -1,8 +1,39 @@ +--- +title: "Explorer API (Deprecated)" +description: "Legacy Explorer API reference for the Nym Mixnet Explorer. Deprecated in favor of the Node Status API." +schemaType: "TechArticle" +section: "APIs" +lastUpdated: "2026-03-15" +--- + +import { RedocStandalone } from 'redoc' +import { Callout } from 'nextra/components' # Explorer API -The Explorer API is the backend for the [Mixnet Explorer](https://nym.com/explorer). + +The Explorer API is deprecated. Use the [Node Status API](/apis/ns-api) instead, which provides the same data and more. + -**This will soon be deprecated in favour of the [Node Status API](ns-api.mdx).** +The Explorer API is the legacy backend for the [Mixnet Explorer](https://nym.com/explorer). + +## Mainnet endpoints + +- **OpenAPI spec:** [explorer.nymtech.net/api/v1/openapi.json](https://explorer.nymtech.net/api/v1/openapi.json) +- **Swagger UI:** [explorer.nymtech.net/api/swagger/index.html](https://explorer.nymtech.net/api/swagger/index.html) + +
+ + -The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/explorer). diff --git a/documentation/docs/pages/apis/explorer-api/_meta.json b/documentation/docs/pages/apis/explorer-api/_meta.json deleted file mode 100644 index 8e5aad88d7..0000000000 --- a/documentation/docs/pages/apis/explorer-api/_meta.json +++ /dev/null @@ -1,5 +0,0 @@ - -{ - "mainnet":"Mainnet Endpoints", - "sandbox":"Sandbox Endpoints" -} diff --git a/documentation/docs/pages/apis/explorer-api/mainnet.mdx b/documentation/docs/pages/apis/explorer-api/mainnet.mdx deleted file mode 100644 index 2e0d960750..0000000000 --- a/documentation/docs/pages/apis/explorer-api/mainnet.mdx +++ /dev/null @@ -1,19 +0,0 @@ - -import { RedocStandalone } from 'redoc'; - -The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://explorer.nymtech.net/api/v1/openapi.json](https://explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://explorer.nymtech.net/api/swagger/index.html](https://explorer.nymtech.net/api/swagger/index.html). - -

- - diff --git a/documentation/docs/pages/apis/explorer-api/sandbox.mdx b/documentation/docs/pages/apis/explorer-api/sandbox.mdx deleted file mode 100644 index 07d03143df..0000000000 --- a/documentation/docs/pages/apis/explorer-api/sandbox.mdx +++ /dev/null @@ -1,18 +0,0 @@ -import { RedocStandalone } from 'redoc'; - -The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-explorer.nymtech.net/api/v1/openapi.json](https://sandbox-explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-explorer.nymtech.net/api/swagger/index.html](https://sandbox-explorer.nymtech.net/api/swagger/index.html). - -

- - diff --git a/documentation/docs/pages/apis/introduction.mdx b/documentation/docs/pages/apis/introduction.mdx index 176ed71489..8f6364916d 100644 --- a/documentation/docs/pages/apis/introduction.mdx +++ b/documentation/docs/pages/apis/introduction.mdx @@ -1,13 +1,58 @@ --- -title: "Nym API Reference: Network Infrastructure" -description: "Interactive API documentation for Nym network infrastructure. Query node status, network topology, blockchain state & mixnet performance programmatically." +title: "Nym Network APIs Overview" +description: "Overview of Nym HTTP APIs for querying node performance, token supply, credential data, and blockchain state, with guidance on which API to use." schemaType: "TechArticle" section: "APIs" -lastUpdated: "2026-02-01" +lastUpdated: "2026-03-15" --- -# Introduction +import { Callout } from 'nextra/components' -This site contains interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet. +# APIs -You can find links to the generated specs for each API on their respective pages as well as their different uses for operators and developers. +The Nym network exposes several HTTP APIs for querying network state, node performance, blockchain data, and credential information. Each API serves a different purpose. + +## Which API should I use? + +| I want to... | API | +|---|---| +| Query node performance, roles, and mixnet statistics | [Node Status API](/apis/ns-api) | +| Query circulating NYM supply or zk-nym credential data | [NymAPI](/apis/nym-api) | +| Query blockchain state, account balances, or transactions | [Validator REST API](/apis/cosmos-sdk-nyx) | +| Query legacy explorer data | [Explorer API](/apis/explorer-api) *(deprecated)* | + +## Node Status API + +The primary API for querying node information. It serves data about individual `nym-node` instances: roles, statistics, Network Requester services, and overall mixnet summaries. If you're building an explorer, analytics dashboard, or monitoring tool, start here. + + +If you're building a service that makes heavy use of the Node Status API, consider [running your own instance](/operators/performance-and-testing/ns-api-deployment) to distribute load across the network. + + +**Endpoints:** [OpenAPI spec](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) · [Swagger](https://mainnet-node-status-api.nymtech.cc/swagger/) + +## NymAPI + +A sidecar binary operated by Nyx Validators. It caches smart contract data and exposes endpoints for circulating NYM supply, zk-nym credential data, and ticketbook information. Use this when you need token economics or credential data. + +**Endpoints:** [OpenAPI spec](https://validator.nymtech.net/api-docs/openapi.json) · [Swagger](https://validator.nymtech.net/api/swagger/index.html) + +Other NymAPI instances are listed at [cosmos.directory/nyx](https://cosmos.directory/nyx). + +## Validator REST API + +The standard Cosmos SDK REST API exposed by Nyx Validators. Use this for direct chain queries: account balances, transaction history, governance proposals, and staking information. + +**Endpoints:** [OpenAPI spec](https://api.nymtech.net/swagger/swagger.yaml) · [Swagger](https://api.nymtech.net/swagger/) + +Other validator endpoints are listed at [cosmos.directory/nyx](https://cosmos.directory/nyx). + +## Explorer API + + +The Explorer API is deprecated. Use the [Node Status API](/apis/ns-api) instead. + + +The legacy backend for the [Mixnet Explorer](https://nym.com/explorer). This API is being replaced by the Node Status API, which provides the same data and more. + +**Endpoints:** [OpenAPI spec](https://explorer.nymtech.net/api/v1/openapi.json) · [Swagger](https://explorer.nymtech.net/api/swagger/index.html) diff --git a/documentation/docs/pages/apis/ns-api.mdx b/documentation/docs/pages/apis/ns-api.mdx index 9e14e1509c..619f640b3e 100644 --- a/documentation/docs/pages/apis/ns-api.mdx +++ b/documentation/docs/pages/apis/ns-api.mdx @@ -1,8 +1,57 @@ +--- +title: "Node Status API - Nym Node Performance and Mixnet Stats" +description: "Reference for the Node Status API, which provides nym-node identity, performance scores, role assignments, and mixnet health summaries." +schemaType: "TechArticle" +section: "APIs" +lastUpdated: "2026-03-15" +--- + +import { RedocStandalone } from 'redoc' import { Callout } from 'nextra/components' # Node Status API -The Node Status API serves information about individual `nym-nodes` in the Mixnet, such as which role they are operating in, statistics about them, services such as Network Requesters, as well as summaries of the state of the Mixnet. + +The Node Status API serves information about individual `nym-node` instances in the Nym network: which role they are operating in, performance statistics, Network Requester services, and summaries of the overall mixnet state. It is the primary API for anyone building explorers, analytics dashboards, or monitoring tools. + +**Key endpoint categories:** +- **Node information**: identity, bonding status, declared roles, build version, host details +- **Performance scores**: routing reliability, configuration scores, probe results +- **Mixnet summaries**: active set composition, role distribution, network health -We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible. +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. + +## Quick examples + +**Get a summary of all gateways:** +```bash +curl https://mainnet-node-status-api.nymtech.cc/api/v1/gateways +``` + +**Get details for a specific node by identity key:** +```bash +curl https://mainnet-node-status-api.nymtech.cc/api/v1/gateways/23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb +``` + +## Mainnet endpoints + +- **OpenAPI spec:** [mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) +- **Swagger UI:** [mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/) + +## Full API reference + +
+ + diff --git a/documentation/docs/pages/apis/ns-api/_meta.json b/documentation/docs/pages/apis/ns-api/_meta.json deleted file mode 100644 index 7d443d9f6d..0000000000 --- a/documentation/docs/pages/apis/ns-api/_meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ns-api-run-deploy":"Run Instance", - "mainnet":"Mainnet Endpoints", - "sandbox":"Sandbox Endpoints" -} diff --git a/documentation/docs/pages/apis/ns-api/mainnet.mdx b/documentation/docs/pages/apis/ns-api/mainnet.mdx deleted file mode 100644 index 00a6523ae7..0000000000 --- a/documentation/docs/pages/apis/ns-api/mainnet.mdx +++ /dev/null @@ -1,20 +0,0 @@ -import { RedocStandalone } from 'redoc'; -import { Callout } from 'nextra/components' - -The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/). - -

- - - diff --git a/documentation/docs/pages/apis/ns-api/sandbox.mdx b/documentation/docs/pages/apis/ns-api/sandbox.mdx deleted file mode 100644 index 29c61273a6..0000000000 --- a/documentation/docs/pages/apis/ns-api/sandbox.mdx +++ /dev/null @@ -1,18 +0,0 @@ -import { RedocStandalone } from 'redoc'; - -The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json](https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-node-status-api.nymte.ch/swagger/](https://sandbox-node-status-api.nymte.ch/swagger/). - -

- - diff --git a/documentation/docs/pages/apis/nym-api.mdx b/documentation/docs/pages/apis/nym-api.mdx index cf479a7a37..fe5b8f39c2 100644 --- a/documentation/docs/pages/apis/nym-api.mdx +++ b/documentation/docs/pages/apis/nym-api.mdx @@ -1,5 +1,63 @@ +--- +title: "NymAPI - Token Supply and Credential Endpoints" +description: "Reference for the NymAPI, a validator sidecar that caches Nyx blockchain data and exposes endpoints for circulating NYM supply, zk-nym credentials, and network topology." +schemaType: "TechArticle" +section: "APIs" +lastUpdated: "2026-03-15" +--- + +import APITable from 'components/api-table.tsx' +import { RedocStandalone } from 'redoc' +import { Callout } from 'nextra/components' + # NymAPI -The [NymAPI](/operators/nodes/validator-setup/nym-api) is a sidecar binary operated by members of the Nym Validator set. Amongst other things, the API offers endpoints to query regarding circulating `NYM` supply, and global and ticketbook-specific [zk-nym](/network/cryptography/zk-nym) data. This is all information contained in various smart contracts on the Nyx blockchain; the NymAPI caches this information periodically to make queries faster and more scalable. +The NymAPI is a sidecar binary operated by members of the Nyx Validator set. It caches smart contract data from the Nyx blockchain and exposes it via HTTP endpoints, making queries faster and more scalable than querying the chain directly. -The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/nym-api). +**Key endpoint categories:** +- **Token economics:** circulating NYM supply, staking information +- **Credential data:** zk-nym ticketbook information, global and per-ticketbook data +- **Network topology:** cached node and mixnet data from on-chain contracts + +For operator setup instructions, see the [NymAPI Operator Guide](/operators/nodes/validator-setup/nym-api). + +## Quick examples + +**Get the circulating NYM supply:** +```bash +curl https://validator.nymtech.net/api/v1/circulating-supply +``` + +**Get the current network topology (Mix Nodes and gateways):** +```bash +curl https://validator.nymtech.net/api/v1/mixnodes/active +``` + +## Mainnet endpoints + +- **OpenAPI spec:** [validator.nymtech.net/api-docs/openapi.json](https://validator.nymtech.net/api-docs/openapi.json) +- **Swagger UI:** [validator.nymtech.net/api/swagger/index.html](https://validator.nymtech.net/api/swagger/index.html) + + +Other NymAPI instances are available. You can find their Swagger endpoints here: + + +There is also an overview of endpoints at [cosmos.directory/nyx](https://cosmos.directory/nyx). + + +## Full API reference + +
+ + diff --git a/documentation/docs/pages/apis/nym-api/_meta.json b/documentation/docs/pages/apis/nym-api/_meta.json deleted file mode 100644 index 3949bf02de..0000000000 --- a/documentation/docs/pages/apis/nym-api/_meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mainnet":"Mainnet Endpoints" -} diff --git a/documentation/docs/pages/apis/nym-api/mainnet.mdx b/documentation/docs/pages/apis/nym-api/mainnet.mdx deleted file mode 100644 index c5b9eec25c..0000000000 --- a/documentation/docs/pages/apis/nym-api/mainnet.mdx +++ /dev/null @@ -1,27 +0,0 @@ -import APITable from 'components/api-table.tsx'; -import { RedocStandalone } from 'redoc'; -import { Callout } from 'nextra/components' - -You can find the OpenAPI spec found at [https://validator.nymtech.net/api-docs/openapi.json](https://validator.nymtech.net/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://validator.nymtech.net/api/swagger/index.html](https://validator.nymtech.net/api/swagger/index.html). - - -You can find the Swagger endpoints of other NymAPI instances at the following endpoints: - - -There is also an overview of endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx). - - -

- - diff --git a/documentation/docs/pages/developers/_meta.json b/documentation/docs/pages/developers/_meta.json index 65cfa18527..63a7809c49 100644 --- a/documentation/docs/pages/developers/_meta.json +++ b/documentation/docs/pages/developers/_meta.json @@ -16,12 +16,12 @@ "typescript": "Typescript SDK", "---": { "type": "separator", - "title": "Misc" + "title": "Extras" }, + "nymvpncli": "Nym VPN CLI", "chain": "Interacting with Nyx Blockchain", "tools": "Tools", - "nymvpncli": "Nym VPN CLI", - "clients": "Standlone Clients", + "clients": "Standalone Clients", "----": { "type": "separator" }, diff --git a/documentation/docs/pages/developers/archive/nym-connect.md b/documentation/docs/pages/developers/archive/nym-connect.mdx similarity index 91% rename from documentation/docs/pages/developers/archive/nym-connect.md rename to documentation/docs/pages/developers/archive/nym-connect.mdx index bc2b720b16..024eff6ab5 100644 --- a/documentation/docs/pages/developers/archive/nym-connect.md +++ b/documentation/docs/pages/developers/archive/nym-connect.mdx @@ -1,9 +1,11 @@ # Archive page: NymConnect Setup -```admonish warning -Since the beginning of 2024 NymConnect is no longer maintained. Nym is developing a new client called [NymVPN](https://nymvpn.com), an application routing all users traffic thorugh the mixnet. -If users want to route their traffic through socks5 we advice to use maintained [Nym Socks5 Client](../clients/socks5/setup). -``` +import { Callout } from 'nextra/components' + + +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). + In case you want to run deprecated NymConnect, follow these steps: diff --git a/documentation/docs/pages/developers/browsers.mdx b/documentation/docs/pages/developers/browsers.mdx index b8453ed32d..e61cacd694 100644 --- a/documentation/docs/pages/developers/browsers.mdx +++ b/documentation/docs/pages/developers/browsers.mdx @@ -1,45 +1,43 @@ +--- +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 very restricted environment to work in, with limited options for external communications (websockets, Web Transport API, WebRTC), mixed content restrictions (HTTPS-only), and no access to the file system or any syscalls. These aside, the main issue when trying to capture traffic and send it via a different transport - such as the Mixnet - is the lack of access to browser TLS negotiation from JS or the CA certificate store. -This means that the functionality offered by our current browser-based solutions are quite restricted / specific. There are currently two options for interacting with the Mixnet from the browser: `mixFetch`, and the WASM SDK. +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) -Both `mixFetch` and the WASM client are delivered to the client bundled into a web application. - ## mixFetch -Drop-in replacement for browser's `fetch` API that makes HTTP(S) requests via Exit Gateways using the SOCKS Network Requester. -Uses an embedded CA certificate store to establish TLS session between `mixFetch` and the remote host, creating a client-host secure channel from the browser to the host over the Mixnet. +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 it uses the WASM client. +Internally, `mixFetch` uses the WASM client. -- [docs](./typescript/start#mixfetch) -- [example](./typescript/playground/mixfetch) +- [Docs](./typescript#mixfetch) +- [Example](./typescript/playground/mixfetch) - - ### Current Limitations of `mixFetch` - - `mixFetch` can currently only perform 10 concurrent requests (i.e. in-flight requests where a request has been sent to a remote endpoint, but no result has been recieved). - - `mixFetchv2` - which will act more like a general-purpose userspace IP stack - is currently in development. - - It is shipped with a pre-bundled CA store. - +`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. - ## WASM Client -Makes Sphinx packets and cover traffic using WASM and sent over a Websocket to the Entry Gateway and receive responses. -This only works in messaging mode (i.e. messages sent either as text or binary data), and currently doesn’t support making IP packets that are routed to the Internet by an Exit Gateway IPR, nor does it currently expose any stream-like API. If you want to send HTTP(S) requests, use `mixFetch`. +Constructs Sphinx packets and cover traffic in WASM, sent over a WebSocket to the Entry Gateway. Responses arrive the same way. -Note that the limitations of CSPs and Mixed Content restrictions (i.e HTTPS only) apply to the Websocket connection as normal in browsers or embedded WebViews. +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`. -Runs in a web worker to leave UI thread free for the user. +Standard browser CSP and mixed content restrictions (HTTPS only) apply to the WebSocket connection, including in embedded WebViews. -- [docs](./typescript/start#mixnet-client) -- [example](./typescript/playground/traffic) +The client runs in a web worker to keep the UI thread free. + +- [Docs](./typescript#mixnet-client) +- [Example](./typescript/playground/traffic) diff --git a/documentation/docs/pages/developers/chain.md b/documentation/docs/pages/developers/chain.md index aa77836f61..e99085cbba 100644 --- a/documentation/docs/pages/developers/chain.md +++ b/documentation/docs/pages/developers/chain.md @@ -13,7 +13,7 @@ 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 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. Instructions on how to do so can be found on the [`nym-cli` docs page](./tools/nym-cli) diff --git a/documentation/docs/pages/developers/chain/ledger-live.md b/documentation/docs/pages/developers/chain/ledger-live.md index 8b7fbceaaa..dd7f933c2a 100644 --- a/documentation/docs/pages/developers/chain/ledger-live.md +++ b/documentation/docs/pages/developers/chain/ledger-live.md @@ -44,7 +44,7 @@ nyxd tx bank send ledger_account $DESTINATION_ACCOOUNT 1000000unym --ledger --no > When a command is run, the transaction will appear on the Ledger device and will require physical confirmation from the device before being signed. ## Nym-specific transactions -Nym-specific commands and queries, like bonding a mix node or delegating unvested tokens, are available in the `wasm` module, and follow the following pattern: +Nym-specific commands and queries, like bonding a Mix Node or delegating unvested tokens, are available in the `wasm` module, and follow the following pattern: ``` # Executing commands @@ -59,8 +59,8 @@ You can find the value of `$CONTRACT_ADDRESS` in the [`network defaults`](https: The value of `$JSON_MSG` will be a blog of `json` formatted as defined for each command and query. You can find these definitions for the mixnet smart contract [here](https://github.com/nymtech/nym/blob/master/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs) and for the vesting contract [here](https://github.com/nymtech/nym/blob/master/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs) under `ExecuteMsg` and `QueryMsg`. ### Example command execution: -#### Delegate to a mix node -You can delegate to a mix node from the CLI using `nyxd` and signing the transaction with your ledger by filling in the values of this example: +#### Delegate to a Mix Node +You can delegate to a Mix Node from the CLI using `nyxd` and signing the transaction with your ledger by filling in the values of this example: ``` CONTRACT_ADDRESS=mixnet_contract_address diff --git a/documentation/docs/pages/developers/clients/socks5/setup.mdx b/documentation/docs/pages/developers/clients/socks5/setup.mdx index 207ed95329..f62388eb06 100644 --- a/documentation/docs/pages/developers/clients/socks5/setup.mdx +++ b/documentation/docs/pages/developers/clients/socks5/setup.mdx @@ -28,7 +28,7 @@ Before you can use the client, you need to initalise a new instance of it, which The `--id` in the example above is a local identifier so that you can name your clients and keep track of them on your local system; it is **never** transmitted over the network. -The `--use-reply-surbs` field denotes whether you wish to send [SURBs](/network/concepts/anonymous-replies) along with your request. It defaults to `false`, we are explicitly setting it as `true`. It defaults to `false` for compatibility with versions of the pre-smoosh `nym-network-requester` binary which will soon be deprecated. +The `--use-reply-surbs` field denotes whether you wish to send [SURBs](/network/mixnet-mode/anonymous-replies) along with your request. It defaults to `false`, we are explicitly setting it as `true`. It defaults to `false` for compatibility with versions of the pre-smoosh `nym-network-requester` binary which will soon be deprecated. The `--provider` field needs to be filled with the Nym address of an Exit Gateway that can make network requests on your behalf. You can select one from the [mixnet explorer](https://nym.com/explorer) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [Harbourmaster](https://harbourmaster.nymtech.net/). diff --git a/documentation/docs/pages/developers/clients/socks5/usage.mdx b/documentation/docs/pages/developers/clients/socks5/usage.mdx index ac76276eb0..e4bc0442d9 100644 --- a/documentation/docs/pages/developers/clients/socks5/usage.mdx +++ b/documentation/docs/pages/developers/clients/socks5/usage.mdx @@ -46,7 +46,7 @@ Here is an example of setting the proxy connecting in Blockstream Green: ![Blockstream Green settings](/images/developers/blockstream-green.gif) -Most wallets and other applications will work basically the same way: find the network proxy settings, enter the proxy url (host: **localhost**, port: **1080**). +Most wallets and other applications work the same way: find the network proxy settings and enter the proxy url (host: **localhost**, port: **1080**). In some other applications, this might be written as **localhost:1080** if there's only one proxy entry field. diff --git a/documentation/docs/pages/developers/clients/websocket.md b/documentation/docs/pages/developers/clients/websocket.md index 249df901ad..61ace93e0d 100644 --- a/documentation/docs/pages/developers/clients/websocket.md +++ b/documentation/docs/pages/developers/clients/websocket.md @@ -1,13 +1,52 @@ -# Websocket Client (Standalone) +# WebSocket Client (Standalone) -> This client can also be utilised via the [Rust SDK](../rust) and [Go/C++ FFI](../rust/ffi). +> This client can also be used via the [Rust SDK](../rust) and [Go/C++ FFI](../rust/ffi). -You can run this client as a standalone process and pipe traffic into it to be sent through the mixnet. This is useful if you're building an application in a language other than Typescript or Rust and cannot utilise one of the SDKs. +The standalone WebSocket client connects to the Nym Mixnet and exposes a WebSocket interface on localhost. Applications in any language can connect to this WebSocket to send and receive messages through the Mixnet. -You can find the code for this client [here](https://github.com/nymtech/nym/tree/master/clients/native). +This is useful if you're building an application in a language other than TypeScript or Rust and cannot use one of the SDKs directly. Your application connects to the local WebSocket, and the client handles Sphinx packet construction, gateway registration, and key management. -## Download or compile client +## Download or compile -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). +Pre-built binaries for macOS and Debian-based Linux are available on the [GitHub releases page](https://github.com/nymtech/nym/releases). Look for the `nym-client` binary. -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. +To build from source: + +```bash +git clone https://github.com/nymtech/nym.git +cd nym +cargo build --release -p nym-client +``` + +The binary will be at `target/release/nym-client`. + +## Initialize and run + +```bash +# Create a new client identity +./nym-client init --id my-client + +# Start the client +./nym-client run --id my-client +``` + +The client prints its Nym address on startup and opens a WebSocket on `ws://127.0.0.1:1977`. + +## Sending and receiving + +Connect to `ws://127.0.0.1:1977` from your application. Messages are JSON-formatted: + +**Send a message:** +```json +{ + "type": "send", + "message": "hello", + "recipient": "" +} +``` + +**Receive messages:** The client pushes incoming messages to your WebSocket connection as they arrive through the Mixnet. + +## Source code + +The client source is in the [Nym monorepo](https://github.com/nymtech/nym/tree/master/clients/native). diff --git a/documentation/docs/pages/developers/clients/websocket/examples.md b/documentation/docs/pages/developers/clients/websocket/examples.md index df9eb6d657..c06b1707d6 100644 --- a/documentation/docs/pages/developers/clients/websocket/examples.md +++ b/documentation/docs/pages/developers/clients/websocket/examples.md @@ -13,5 +13,5 @@ All of these code examples will do the following: 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. diff --git a/documentation/docs/pages/developers/clients/websocket/usage.md b/documentation/docs/pages/developers/clients/websocket/usage.md index 333875b716..9f60a48e49 100644 --- a/documentation/docs/pages/developers/clients/websocket/usage.md +++ b/documentation/docs/pages/developers/clients/websocket/usage.md @@ -54,7 +54,7 @@ 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)**. -You can read more about SURBs [here](/network/concepts/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**. +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**. 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. diff --git a/documentation/docs/pages/developers/concepts/message-queue.mdx b/documentation/docs/pages/developers/concepts/message-queue.mdx index b5df5325d8..c93dc5bf3c 100644 --- a/documentation/docs/pages/developers/concepts/message-queue.mdx +++ b/documentation/docs/pages/developers/concepts/message-queue.mdx @@ -1,13 +1,21 @@ +--- +title: "Nym Client Message Queue and Cover Traffic" +description: "How the Nym client queues messages, sends cover traffic via Poisson processes, and manages Sphinx packet streams to prevent timing attacks." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + import { Callout } from 'nextra/components' # Message Queue - Although good to understand how the Nym Client works under the hood, this information is only of practical use if you're using the `Mixnet` module of the RustSDK and interacting with the SDK Mixnet Client at a low level. This is all mostly abstracted away with the `MixSocket`, `MixStream`, and `IpMixStream` abstractions. + 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). ## Sphinx Packet Streams -Clients, once connected to the Mixnet, **are always sending traffic into the Mixnet**; as well as the packets that you as a developer are sending from your application logic, they send [cover traffic](../../network/concepts/cover-traffic) at a constant rate defined by a Poisson process. This is part of the network's mitigation of timing attacks. +Clients, once connected to the Mixnet, **are always sending traffic into the Mixnet**; as well as the packets that you as a developer are sending from your application logic, they send [cover traffic](/network/mixnet-mode/cover-traffic) at a constant rate defined by a Poisson process. This is part of the network's mitigation of timing attacks. There are two constant streams of sphinx packets leaving the client at the rate defined by the Poisson process. - one that is solely cover traffic diff --git a/documentation/docs/pages/developers/index.mdx b/documentation/docs/pages/developers/index.mdx index a6e1d197f1..846afb616a 100644 --- a/documentation/docs/pages/developers/index.mdx +++ b/documentation/docs/pages/developers/index.mdx @@ -6,5 +6,22 @@ section: "Developers" lastUpdated: "2026-02-01" --- -# Introduction -Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs. +# Developer Documentation + +Build applications that protect user metadata using the Nym Mixnet. This section covers SDK integration, blockchain interaction, and developer tools. + +## Where to start + +**Choosing an integration approach:** read [Integrations](/developers/integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: + +- **[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. + +## Blockchain interaction + +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). + +## API reference + +Auto-generated API specs for Nym infrastructure endpoints are in the [APIs section](/apis/introduction). diff --git a/documentation/docs/pages/developers/integrations.mdx b/documentation/docs/pages/developers/integrations.mdx index 8568f7285a..0233ca75bd 100644 --- a/documentation/docs/pages/developers/integrations.mdx +++ b/documentation/docs/pages/developers/integrations.mdx @@ -1,16 +1,34 @@ +--- +title: "Integrating With Nym" +description: "Choose an integration path for sending application traffic through the Nym mixnet, depending on your runtime environment and architecture." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-04-07" +--- + import { Callout } from 'nextra/components'; # Integrating With Nym -Any application that wants to integrate with Nym involves sending its application traffic through the Mixnet using one of the available Nym Clients. There is no single solution for this, as different environments offer different access and transport options (e.g. if operating in a web browser, you do not have access to syscalls or sockets, have to deal with content security policies, etc). -As such, we have several solutions available for developers to choose from depending on the **environment** their application is expected to run in: native apps which are running on a desktop, or webapps running in a browser. +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**. - - The list of current options available to developers to do not cover all environments and setups - we are working on expanding this list and approaching more general solutions, but there is no one-size-fits-all approach when dealing with rerouting network traffic. +## Environment + +Different runtimes have different transport constraints. A browser cannot open raw sockets or access the filesystem; 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). + +## Architecture + +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. + + +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. -Integration options are then further subdivided by app **architecture**; whether the application interacts with remote hosts on the public internet running independently of the app (e.g. public blockchain RPC endpoints, third-party APIs) or whether app developers have some control over the versions of the software being run on both sides of an interaction (e.g. peer to peer apps running the same software version, or client-server architectures which are running software written by the same team). - - - This is because of the different security considerations each option offers. These are detailed in the following pages. - +See the [Native / Desktop](./native) and [Browser](./browsers) pages for the specific modules available in each environment. diff --git a/documentation/docs/pages/developers/licensing.md b/documentation/docs/pages/developers/licensing.md index daff62dcb6..c67c89ff08 100644 --- a/documentation/docs/pages/developers/licensing.md +++ b/documentation/docs/pages/developers/licensing.md @@ -1,8 +1,8 @@ # Licensing -As a general approach, licensing is as follows this pattern: +As a general approach, licensing follows this pattern: -*

Nym Documentation by Nym Technologies is licensed under CC BY-NC-SA 4.0

+* [Nym Documentation](https://nym.com/docs) by [Nym Technologies](https://nym.com) is licensed under [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) ![CC](/images/cc-icons/cc.svg) ![BY](/images/cc-icons/by.svg) ![NC](/images/cc-icons/nc.svg) ![SA](/images/cc-icons/sa.svg) * Nym applications and binaries are [GPL-3.0-only](https://www.gnu.org/licenses/) diff --git a/documentation/docs/pages/developers/native.mdx b/documentation/docs/pages/developers/native.mdx index 8fee54371f..0a5a889852 100644 --- a/documentation/docs/pages/developers/native.mdx +++ b/documentation/docs/pages/developers/native.mdx @@ -1,86 +1,63 @@ +--- +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 -Developers wanting to integrate into desktop apps & CLIs can use our Rust SDK. There are two broad approaches to using the Mixnet (E2E or as a proxy), with different modules suited for each, each with their own specific usecase and limitations. +Desktop apps and CLIs integrate via the [Rust SDK](./rust). There are 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 -You might want to embed Nym Clients in both sides of your app, and have them send all of your app network traffic through the Mixnet: maybe two clients in a peer to peer setup, or a client and a server where it is possible for you as a developer to release both the client and server side code, and have some ability to make sure that it is being run. +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) -There are several options available: +### Stream Module +The [Stream module](./rust/stream) provides `AsyncRead + AsyncWrite` byte streams multiplexed over the mixnet, the closest analogue to TCP sockets. -{ /* ### Stream Wrapper Module -Exposes `MixSocket`/`MixStream` abstractions that can be split a reader/writer halves that consumes bytes, with an interface inspired by `std::net::TcpStream`. For developers who just want to read/write bytes to/from the Mixnet working with something socket-like. - -- docs TODO LINK -- example TODO LINK */ } +- [docs](./rust/stream) +- [tutorial](./rust/stream/tutorial) ### Mixnet & Client Pool Modules -The Mixnet module of the SDK exposes low level connection functionality and the Mixnet Client. The Client Pool is one answer to concurrency, and allows developers to run several Nym Clients at once which can be quickly used. - -{ /* This approach might be useful if you want to build custom connection logic, but **`MixSocket`/`MixStream` will probably be sufficient for the majority of usecases** where developers just want to send and receive traffic as streams. */ } - -This approach might be useful if you want to build custom connection logic, but the TcpProxy Module will probably be sufficient for the majority of usecases where developers just want to send and receive traffic as streams. +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) -- [examples](./rust/mixnet/examples) +- [tutorial](./rust/mixnet/tutorial) -### TcpProxy Module -{ /* - This module has been superseded by the Stream Wrapper module, and will soon be deprecated. **New features will not be added to it.** The main drawback of this (which is fixed with `MixSocket`/`MixStream`) is that TLS is impossible, as it exposes a localhost port for the consuming process to communicate with. - */ } -A pair of abstractions built for use in a client-server setup, which both expose a `localhost` TCP Socket which apps can read/write bytes to/from. +### TcpProxy Module (Unmaintained) -- [docs](./rust/tcpproxy) -- [examples](./rust/tcpproxy/examples/singleconn) - - - There is a new abstraction coming soon mirroring the interface and use of a TCP Socket, making it easier for developers to use the Mixnet, and also perform TLS through a Mixnet connection. Stay tuned. + +**This module is unmaintained.** Use the [Stream module](./rust/stream) for new projects. Existing users should plan to migrate when possible. +Exposes localhost TCP sockets that proxy traffic through the mixnet. + +- [docs](./rust/tcpproxy) + ## Option 2: Mixnet-As-Proxy -For developers who are only able to control the client-side code, and/or need to communicate with a 3rd party service, such as a public blockchain RPC or a remote host they do not control. +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) - ### Security Considerations +### Security Considerations - Since traffic is only packaged as Sphinx until it gets to the Exit Gateway, where it is unwrapped into either HTTPS packets (by a Network Requester) or IP packets (by an IP Packet Router), the last hop between the Gateway and the remote host **travels as normal internet traffic**. +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**. - As such, this option has fewer protections than the E2E option against a global passive adversary, but still grants you timing obfuscation and sender-receiver unlinkability between your client software and whatever service it is interacting with. +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. ### SOCKS Client -Developers with apps that support SOCKS4,4a, or 5 can use the Socks Client exposed by the Mixnet module. This uses the Network Requester service of the chosen Exit Gateway to interact with the remote host via the chosen SOCKS proxy protocol. The Network Requester uses SURBs to anonymously reply to the original sender with whatever response it gets from the remote host. +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/examples/socks) -- [example](./rust/mixnet/examples/socks) +- [docs](./rust/mixnet) - - There is a new abstraction coming soon that will allow the SDK to send IP packets, the beginning of a longer project to make a native Rust version of [`mixFetch`](./typescript/start#mixfetch). Stay tuned. + +Development is in progress to allow for this proxy method from native Rust, C, and Go without requiring a separate SOCKS client. Stay tuned. - - -{ /* ### `IPMixStream` -This is a version of the `MixSocket` that consumes IP packets before wrapping them in Sphinx and forwarding through the Mixnet to the IP Packet Router of the chosen Exit Gateway, where they are unwrapped and treated as normal IP packets. The IPR uses SURBs to anonymously reply to the original sender with whatever response it gets from the remote host. - - - Currently only consumes IP packets - those who do not want to work with IP packets directly should check `mixtcp` below for doing something with HTTP(S). - - -- docs TODO LINK -- examples TODO LINK - -### `MixTCP` -A proof of concept TCP/IP crate containing a [`smoltcp`](https://docs.rs/smoltcp/latest/smoltcp/index.html) `device` that uses the Mixnet for transport. Examples of using this to make a TLS handshake and perform an HTTPS request can be found linked below. - - - This crate is currently a proof of concept which is in active development. This crate will become the basis of a general-purpose HTTP-through-Mixnet crate in the near future. - - -- docs TODO LINK -- example TODO LINK */ } diff --git a/documentation/docs/pages/developers/nymvpncli.mdx b/documentation/docs/pages/developers/nymvpncli.mdx index f161a2f100..67b2ed7c21 100644 --- a/documentation/docs/pages/developers/nymvpncli.mdx +++ b/documentation/docs/pages/developers/nymvpncli.mdx @@ -173,13 +173,13 @@ Print current tunnel configuration: nym-vpnc tunnel get ``` -Enable two-hop mode (WireGuard) — traffic jumps directly from entry gateway to exit gateway: +Enable two-hop mode (WireGuard): traffic jumps directly from entry gateway to exit gateway: ```sh nym-vpnc tunnel set --two-hop on ``` -Enable Mixnet (5-hop) — disable two-hop to route traffic through the full mixnet for maximum privacy: +Enable Mixnet (5-hop): disable two-hop to route traffic through the full mixnet for maximum privacy: ```sh nym-vpnc tunnel set --two-hop off @@ -293,7 +293,7 @@ nym-vpnc ad-block set disabled ``` - You can test ad-blocking with [adblock.turtlecute.org](https://adblock.turtlecute.org/). Some browsers cache DNS internally, so toggling ad-block on/off at runtime may not have an immediate effect — a browser restart may be needed. Use `nslookup` or `dig` to verify that domains are being blocked. + You can test ad-blocking with [adblock.turtlecute.org](https://adblock.turtlecute.org/). Some browsers cache DNS internally, so toggling ad-block on/off at runtime may not have an immediate effect; a browser restart may be needed. Use `nslookup` or `dig` to verify that domains are being blocked. ## DNS diff --git a/documentation/docs/pages/developers/rust.mdx b/documentation/docs/pages/developers/rust.mdx index 08c93ccc12..93affb53ae 100644 --- a/documentation/docs/pages/developers/rust.mdx +++ b/documentation/docs/pages/developers/rust.mdx @@ -1,11 +1,34 @@ --- title: "Nym Rust SDK: Privacy Apps for the Mixnet" -description: "Rust SDK reference for building privacy applications on the Nym mixnet. Covers TcpProxy, Mixnet module, Client Pool, FFI bindings, and code examples." +description: "Rust SDK reference for building privacy applications on the Nym mixnet. Covers the Mixnet client, Stream multiplexing, Client Pool, and code examples." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-02-01" +lastUpdated: "2026-03-13" --- -# Introduction +# Rust SDK -The Rust SDK allows exposes a few different modules, some more plug and play than others. Each of which handles exposes a Nym Client, which handles finding and using a route for packets through the Mixnet, encryption, and cover traffic, all under the hood. +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. + + +Full API reference: [**docs.rs/nym-sdk**](https://docs.rs/nym-sdk/latest/nym_sdk/) + + + + +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.** + +- **[Mixnet](./rust/mixnet)**: raw message payloads, independently routed, no connections or ordering. Use this when you want full control over the communication model. + +- **[Client Pool](./rust/client-pool)**: keeps ready-to-use `MixnetClient` instances warm for bursty workloads. + +- **[TcpProxy](./rust/tcpproxy)** *(deprecated)*: TCP socket proxying with session management and message ordering. Use Stream for new projects. + +- **[FFI](./rust/ffi)**: Go and C/C++ bindings. diff --git a/documentation/docs/pages/developers/rust/_meta.json b/documentation/docs/pages/developers/rust/_meta.json index 5ce667aa28..d00e161c61 100644 --- a/documentation/docs/pages/developers/rust/_meta.json +++ b/documentation/docs/pages/developers/rust/_meta.json @@ -1,7 +1,9 @@ { - "importing": "Importing", - "tcpproxy": "TcpProxy Module", + "tour": "Tour", + "importing": "Installation", "mixnet": "Mixnet Module", + "stream": "Stream Module", + "tcpproxy": "TcpProxy Module (Deprecated)", "client-pool": "Client Pool Module", "ffi": "FFI" } diff --git a/documentation/docs/pages/developers/rust/client-pool.mdx b/documentation/docs/pages/developers/rust/client-pool.mdx index 70c3f54ca1..311bd147be 100644 --- a/documentation/docs/pages/developers/rust/client-pool.mdx +++ b/documentation/docs/pages/developers/rust/client-pool.mdx @@ -1,9 +1,73 @@ +--- +title: "Client Pool: Pre-Connected Mixnet Clients" +description: "The Nym ClientPool maintains ready-to-use MixnetClient instances, eliminating connection latency for bursty traffic patterns." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + # Client Pool -import { Callout } from 'nextra/components'; +import { Callout } from 'nextra/components' -We have a configurable-size Client Pool for processes that require multiple clients in quick succession (this is used by default by the [`TcpProxyClient`](./tcpproxy) for instance) +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. -This will be useful for developers looking to build connection logic, or just are using raw SDK clients in a sitatuation where there are multiple connections with a lot of churn. +## How it works -> You can find this code [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs) +```mermaid +--- +config: + theme: neo-dark +--- +flowchart LR + BG["Background loop"] -->|creates clients| P["Pool (Vec)"] + P -->|"get_mixnet_client()"| APP["Your application"] + APP -->|uses and disconnects| D["Done"] + 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 + +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). + + +The `NymProxyClient` (TcpProxy) uses a `ClientPool` internally: one client per incoming TCP connection. + + +## Quick example + +```rust +use nym_sdk::client_pool::ClientPool; +use nym_network_defaults::setup_env; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + nym_bin_common::logging::setup_tracing_logger(); + // Load mainnet network defaults into env vars (required by ClientPool) + setup_env(None::); + + let pool = ClientPool::new(5); // maintain 5 clients in reserve + + let pool_clone = pool.clone(); + tokio::spawn(async move { pool_clone.start().await }); + + // Get a client when needed + if let Some(client) = pool.get_mixnet_client().await { + println!("Got client: {}", client.nym_address()); + client.disconnect().await; + } + + pool.disconnect_pool().await; + Ok(()) +} +``` + +## Further reading + +- [Tutorial: Handle bursty traffic](./client-pool/tutorial): step-by-step guide covering pool creation, burst handling, and fallback logic +- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/client_pool/): type details, method signatures, and architecture docs +- [Example source on GitHub](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs): complete working example diff --git a/documentation/docs/pages/developers/rust/client-pool/_meta.json b/documentation/docs/pages/developers/rust/client-pool/_meta.json index 3d6dc8b87b..2e89cb7aac 100644 --- a/documentation/docs/pages/developers/rust/client-pool/_meta.json +++ b/documentation/docs/pages/developers/rust/client-pool/_meta.json @@ -1,4 +1,4 @@ { - "architecture": "Architecture", - "example": "Example" + "tutorial": "Tutorial", + "examples": "Examples" } diff --git a/documentation/docs/pages/developers/rust/client-pool/architecture.mdx b/documentation/docs/pages/developers/rust/client-pool/architecture.mdx deleted file mode 100644 index 415ad51ce1..0000000000 --- a/documentation/docs/pages/developers/rust/client-pool/architecture.mdx +++ /dev/null @@ -1,31 +0,0 @@ -# Client Pool Architecture - - -import { Callout } from 'nextra/components'; - - - There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with. - -This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet. - -It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/). - - - -## Motivations -In situations where multiple connections are expected, and the number of connections can vary greatly, the Client Pool reduces time spent waiting for the creation of a Mixnet Client blocking your code sending traffic through the Mixnet. Instead, a configurable number of Clients can be generated and run in the background which can be very quickly grabbed, used, and disconnected. - -The Pool can be simply run as a background process for the runtime of your program. - -## Clients & Lifetimes -The Client Pool creates **ephemeral Mixnet Clients** which are used and then disconnected. Using the [`TcpProxy`](../tcpproxy) as an example, Clients are used for the lifetime of a single incoming TCP connection; after the TCP connection is closed, the Mixnet client is disconnected. - -Clients are popped from the pool when in use, and another Client is created to take its place. If connections are coming in faster than Clients are replenished, you can instead generate an ephemeral Client on the fly, or wait; this is up to the developer to decide. You can see an example of this logic in the example on the next page. - -## Runtime Loop -Aside from a few helper / getter functions and a graceful `disconnect_pool()`, the Client Pool is mostly made up of a very simple loop around some conditional logic making up `start()`: -- if the number of Clients in the pool is `< client_pool_reserve_number` (set on `new()`) then create more, -- if the number of Clients in the pool `== client_pool_reserve_number` (set on `new()`) then `sleep`, -- if `client_pool_reserve_number == 0` just `sleep`. - -`disconnect_pool()` will cause this loop to `break` via cancellation token. diff --git a/documentation/docs/pages/developers/rust/client-pool/example.mdx b/documentation/docs/pages/developers/rust/client-pool/example.mdx deleted file mode 100644 index 90c2e5f331..0000000000 --- a/documentation/docs/pages/developers/rust/client-pool/example.mdx +++ /dev/null @@ -1,110 +0,0 @@ -# Client Pool Example - -import { Callout } from 'nextra/components'; - - - There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with. - -This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet. - -It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/). - - -> You can find this code [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs) - -```rust -use anyhow::Result; -use nym_network_defaults::setup_env; -use nym_sdk::client_pool::ClientPool; -use nym_sdk::mixnet::{MixnetClientBuilder, NymNetworkDetails}; -use tokio::signal::ctrl_c; - -// This client pool is used internally by the TcpProxyClient but can also be used by the Mixnet module, in case you're quickly swapping clients in and out but won't want to use the TcpProxy module. -// -// Run with: cargo run --example client_pool -- ../../../envs/.env -#[tokio::main] -async fn main() -> Result<()> { - nym_bin_common::logging::setup_logging(); - setup_env(std::env::args().nth(1)); - - let conn_pool = ClientPool::new(2); // Start the Client Pool with 2 Clients always being kept in reserve - let client_maker = conn_pool.clone(); - tokio::spawn(async move { - client_maker.start().await?; - Ok::<(), anyhow::Error>(()) - }); - - println!("\n\nWaiting a few seconds to fill pool\n\n"); - tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; - - let pool_clone_one = conn_pool.clone(); - let pool_clone_two = conn_pool.clone(); - - tokio::spawn(async move { - let client_one = match pool_clone_one.get_mixnet_client().await { - Some(client) => { - println!("Grabbed client {} from pool", client.nym_address()); - client - } - None => { - println!("Not enough clients in pool, creating ephemeral client"); - let net = NymNetworkDetails::new_from_env(); - let client = MixnetClientBuilder::new_ephemeral() - .network_details(net) - .build()? - .connect_to_mixnet() - .await?; - println!( - "Using {} for the moment, created outside of the connection pool", - client.nym_address() - ); - client - } - }; - let our_address = client_one.nym_address(); - println!("\n\nClient 1: {our_address}\n\n"); - client_one.disconnect().await; - tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; // Emulate doing something - return Ok::<(), anyhow::Error>(()); - }); - - tokio::spawn(async move { - let client_two = match pool_clone_two.get_mixnet_client().await { - Some(client) => { - println!("Grabbed client {} from pool", client.nym_address()); - client - } - None => { - println!("Not enough clients in pool, creating ephemeral client"); - let net = NymNetworkDetails::new_from_env(); - let client = MixnetClientBuilder::new_ephemeral() - .network_details(net) - .build()? - .connect_to_mixnet() - .await?; - println!( - "Using {} for the moment, created outside of the connection pool", - client.nym_address() - ); - client - } - }; - let our_address = *client_two.nym_address(); - println!("\n\nClient 2: {our_address}\n\n"); - client_two.disconnect().await; - tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; // Emulate doing something - return Ok::<(), anyhow::Error>(()); - }); - - wait_for_ctrl_c(conn_pool).await?; - Ok(()) -} - -async fn wait_for_ctrl_c(pool: ClientPool) -> Result<()> { - println!("\n\nPress CTRL_C to disconnect pool\n\n"); - ctrl_c().await?; - println!("CTRL_C received. Killing client pool"); - pool.disconnect_pool().await; - Ok(()) -} -``` diff --git a/documentation/docs/pages/developers/rust/client-pool/examples.mdx b/documentation/docs/pages/developers/rust/client-pool/examples.mdx new file mode 100644 index 0000000000..4d5e510f43 --- /dev/null +++ b/documentation/docs/pages/developers/rust/client-pool/examples.mdx @@ -0,0 +1,19 @@ +--- +title: "Client Pool Examples" +description: "Runnable Rust example for the Nym Client Pool: managing multiple MixnetClients with ephemeral fallback." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-26" +--- + +# Examples + +Runnable examples in [`sdk/rust/nym-sdk/examples/`](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples). Each file is self-contained with step-by-step comments. + +```bash +cargo run --example +``` + +| Example | Source | What it demonstrates | +|---|---|---| +| Client Pool | [`client_pool.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs) | Creating a pool of `MixnetClient`s, retrieving clients from the pool, and falling back to ephemeral clients when the pool is empty | diff --git a/documentation/docs/pages/developers/rust/client-pool/tutorial.mdx b/documentation/docs/pages/developers/rust/client-pool/tutorial.mdx new file mode 100644 index 0000000000..c0e7f9e9ed --- /dev/null +++ b/documentation/docs/pages/developers/rust/client-pool/tutorial.mdx @@ -0,0 +1,277 @@ +--- +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" +--- + +# Tutorial: Handle Bursty Traffic with Client Pool + +import { Callout } from 'nextra/components' +import { CodeVerified } from '../../../../components/code-verified' + +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. + +## What you'll learn + +- Creating and starting a `ClientPool` +- Popping clients from the pool for concurrent operations +- Falling back to on-demand client creation when the pool is empty +- Observing pool replenishment +- Graceful shutdown + + + +## Prerequisites + +- Rust toolchain (1.70+) +- A working internet connection + +## Step 1: Set up the project + +```sh +cargo init nym-pool-demo +cd nym-pool-demo +``` + +Add dependencies to `Cargo.toml`: + +```toml +[dependencies] +nym-sdk = { git = "https://github.com/nymtech/nym", rev = "4077717" } +nym-network-defaults = { git = "https://github.com/nymtech/nym", rev = "4077717" } +nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "4077717", features = ["basic_tracing"] } +tokio = { version = "1", features = ["full"] } +blake3 = "=1.7.0" # required pin — see https://nymtech.net/developers/rust/importing +``` + +## Step 2: Create and start the pool + +The pool is created with a **reserve size**: the number of connected clients it tries to maintain at all times. The `start()` method runs a background loop that creates clients whenever the pool drops below the reserve. + +Create `src/main.rs`: + +```rust +use nym_sdk::client_pool::ClientPool; +use nym_sdk::mixnet::MixnetMessageSender; +use nym_network_defaults::setup_env; +use std::time::Duration; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + + // Load mainnet network defaults into env vars (required by ClientPool) + setup_env(None::); + + // Create a pool that maintains 3 clients in reserve + let pool = ClientPool::new(3); + + // Start the pool in a background task. + // It immediately begins connecting clients. + 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; + + // Check how many are ready + let count = pool.get_client_count().await; + println!("Pool has {count} clients ready"); +``` + + +Creating a `MixnetClient` takes several seconds (gateway handshake, key generation, topology fetch). The pool does this work ahead of time so your application doesn't block when it needs a client. + + +## Step 3: Pop clients and use them + +When you call `get_mixnet_client()`, the pool removes a client and returns it. The background loop notices the shortfall and starts creating a replacement. + +```rust + // Simulate a burst of 3 concurrent tasks, each needing a client + let mut handles = vec![]; + + for i in 1..=3 { + let pool = pool.clone(); + + let handle = tokio::spawn(async move { + // Pop a client from the pool + let mut client = match pool.get_mixnet_client().await { + Some(c) => { + println!("Task {i}: got client {} from pool", c.nym_address()); + c + } + None => { + // 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 + let addr = *client.nym_address(); + client + .send_plain_message(addr, format!("hello from task {i}")) + .await + .unwrap(); + + // Wait for the message to arrive + if let Some(msgs) = client.wait_for_messages().await { + for msg in msgs { + if !msg.message.is_empty() { + println!( + "Task {i}: received {:?}", + String::from_utf8_lossy(&msg.message) + ); + } + } + } + + // Disconnect when done — the pool will create a replacement + client.disconnect().await; + println!("Task {i}: done"); + }); + + handles.push(handle); + } + + // Wait for all tasks to finish + for h in handles { + h.await.unwrap(); + } +``` + +## Step 4: Observe replenishment + +After popping all 3 clients, the pool background loop starts creating replacements. Give it time and check: + +```rust + // Pool should be replenishing + println!("\nWaiting for pool to replenish..."); + tokio::time::sleep(Duration::from_secs(15)).await; + + let count = pool.get_client_count().await; + println!("Pool has {count} clients ready again"); +``` + +## Step 5: Shut down gracefully + +```rust + // Disconnect all remaining clients and stop the background loop + pool.disconnect_pool().await; + println!("Pool shut down"); +} +``` + +## Step 6: Run it + +```sh +RUST_LOG=info cargo run +``` + +You'll see output like: + +``` +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 +Task 3: got client A7bN2...@4pL8w... from pool +Task 1: received "hello from task 1" +Task 2: received "hello from task 2" +Task 3: received "hello from task 3" +Task 1: done +Task 2: done +Task 3: done + +Waiting for pool to replenish... +Pool has 3 clients ready again +Pool shut down +``` + +## When to use the pool + +The pool is most useful when: + +- **You have bursty traffic:** many concurrent operations that each need their own client +- **Latency matters:** you can't afford the several-second delay of creating a client on each request +- **You're building a service:** an API endpoint that creates a client per request would benefit from pre-warmed clients + +If your application only ever needs one client at a time, just use `MixnetClient::connect_new()` directly. + + +The `NymProxyClient` (TcpProxy module) uses a `ClientPool` internally: one client per incoming TCP connection. + + +## What you've learned + +- **`ClientPool::new(n)`** creates a pool targeting `n` reserve clients +- **`pool.start()`** runs a background loop that creates clients whenever the pool is below reserve +- **`pool.get_mixnet_client()`** pops a client; returns `None` if the pool is empty +- **Clients are consumed, not returned.** The pool automatically creates replacements +- **`pool.disconnect_pool()`** shuts down all remaining clients and stops the background loop +- **Fall back to on-demand creation** when the pool is empty for resilience + +## Complete code + +```rust +use nym_sdk::client_pool::ClientPool; +use nym_sdk::mixnet::MixnetMessageSender; +use nym_network_defaults::setup_env; +use std::time::Duration; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + setup_env(None::); + + 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..."); + tokio::time::sleep(Duration::from_secs(15)).await; + println!("Pool has {} clients", pool.get_client_count().await); + + let mut handles = vec![]; + for i in 1..=3 { + let pool = pool.clone(); + handles.push(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(), + }; + + let addr = *client.nym_address(); + client + .send_plain_message(addr, format!("hello from task {i}")) + .await + .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)); + } + } + + client.disconnect().await; + })); + } + + for h in handles { + h.await.unwrap(); + } + + println!("Waiting for replenishment..."); + tokio::time::sleep(Duration::from_secs(15)).await; + println!("Pool has {} clients", pool.get_client_count().await); + + pool.disconnect_pool().await; + println!("Done"); +} +``` diff --git a/documentation/docs/pages/developers/rust/ffi.mdx b/documentation/docs/pages/developers/rust/ffi.mdx index 349b298bc8..e32071eaa2 100644 --- a/documentation/docs/pages/developers/rust/ffi.mdx +++ b/documentation/docs/pages/developers/rust/ffi.mdx @@ -1,59 +1,105 @@ +--- +title: "FFI Bindings: Go and C/C++" +description: "Use the Nym SDK from Go and C/C++ via FFI bindings. Covers mixnet messaging, anonymous replies, and TcpProxy lifecycle from non-Rust languages." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- # FFI Bindings -import { Callout } from 'nextra/components'; -We currently have FFI bindings for Go and C/C++. See the table below to check the coverage of functionality we expect devs would like to see. +import { Callout } from 'nextra/components' -The [`nym/sdk/ffi`](https://github.com/nymtech/nym/tree/master/sdk/ffi) directory has the following structure: +The SDK exposes FFI bindings for Go and C/C++. The source lives in [`sdk/ffi`](https://github.com/nymtech/nym/tree/develop/sdk/ffi): ``` ffi -├── cpp -├── go -├── README.md -└── shared +├── cpp # C/C++ bindings (manual C FFI) +├── go # Go bindings (via uniffi-bindgen-go) +└── shared # Shared Rust implementation ``` -The main functionality of exposed functions will be imported from `sdk/ffi/shared` into `sdk/ffi/` in order to cut down on code duplication, and so that the imported bindings can be language-specific with regards to types and any `unsafe` code that is required, as well as allowing for the use of language-specific FFI libraries in the future (e.g. we are using `uniffi-bindgen-go` for Go, and at the moment have custom C/C++ bindings, which we might in the future replace with `cxx`). +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. -Furthermore, the `shared/` code makes sure that client access is thread-safe, and that client actions happen in blocking threads on the Rust side of the FFI boundary. +## What's exposed -## Mixnet Module -This is the basic mixnet component of the SDK, exposing client functionality with which people can build custom interfaces with the Mixnet. These functions are exposed to both Go and C/C++ via the `sdk/ffi/shared/` crate. +**Mixnet** (Go and C/C++): ephemeral and persistent client creation, sending messages, anonymous replies via SURBs, listening for incoming messages. -| `shared/lib.rs` function | Rust Function | -| ------------------------------------------------------------- | ----------------------------------------------------------------------- | -| `init_ephemeral_internal()` | `MixnetClient::connect_new()` | -| `init_default_storage_internal(config_dir: PathBuf)` | `MixnetClientBuilder::new_with_default_storage(config_dir)` | -| `get_self_address_internal()` | `MixnetClient.nym_address()` | -| `send_message_internal(recipient: Recipient, message: &str)` | `MixnetClient.send_plain_message(recipient, message)` | -| `reply_internal(recipient: AnonymousSenderTag, message: &str)`| `MixnetClient.send_reply(recipient, message)` | +**TcpProxy** (Go only): client and server creation and lifecycle. - -> We have also implemented `listen_for_incoming_internal()` which is a wrapper around the Mixnet client's `wait_for_messages()`. This is a helper method for listening out for and handling incoming messages. - -### Currently Unsupported Functionality -At the time of writing the following functionality is not exposed to the shared FFI library: -- `split_sender()`: the ability to [split a client into sender and receiver](./mixnet/examples/split-send) for concurrent send/receive. -- The use of [custom network topologies](./mixnet/examples/custom-topology). -- `Socks5::new()`: creation and use of the [socks5/4a/4 proxy client](./mixnet/examples/socks). - -## TcpProxy Module -A connection abstraction which exposes a local TCP socket which developers are able to interact with basically as expected, being able to read/write to/from a bytestream, without really having to take into account the workings of the Mixnet/Sphinx/the message-based format of the underlying client. - - - At the time of writing this functionality is **only** exposed to Go. C/C++ bindings will follow in the future in a larger update to the C FFI. + +The TcpProxy module is deprecated. For new projects, use the [Stream module](./stream) instead. +**Client Pool and Stream** have no standalone FFI bindings yet. The TcpProxy bindings use the Client Pool internally. -| `shared/lib.rs` function | Rust Function | -| --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| `proxy_client_new_internal(server_address: Recipient, listen_address: &str, listen_port: &str, close_timeout: u64, env: Option)`| `NymProxyClient::new(server_address, listen_address, listen_port, close_timeout, env)`| -| `proxy_client_new_defaults_internal(server_address, env)` | `NymProxyClient::new_with_defaults(server_address, env)` | -| `proxy_client_run_internal()` | `NymProxyClient.run()` | -| `proxy_server_new_internal(upstream_address: &str, config_dir: &str, env: Option)` | `NymProxyServer::new(upstream_address, config_dir, env)` | -| `proxy_server_run_internal()` | `NymProxyServer.run_with_shutdown()` | -| `proxy_server_address_internal()` | `NymProxyServer.nym_address()` | +## Quick example (Go) -## Client Pool -There are currently no FFI bindings for the Client Pool. This will be coming in the future. The bindings for the TcpProxy have been updated to be able to use the Client Pool under the hood, but the standalone Pool is not yet exposed to FFI. +```go +// Initialize an ephemeral client +bindings.InitEphemeral() + +// Get our Nym address +addr, _ := bindings.GetSelfAddress() + +// Send a message through the Mixnet +bindings.SendMessage(addr, "hello from Go") + +// Listen for incoming messages +msg, _ := bindings.ListenForIncoming() +fmt.Println("Received:", msg.Message) + +// Reply anonymously via SURBs +bindings.Reply(msg.Sender, "reply from Go") +``` + +## Quick example (C++) + +The C++ bindings use callbacks for return values and a `ReceivedMessage` struct for incoming data: + +```cpp +extern "C" { + struct ReceivedMessage { + const uint8_t* message; + size_t size; + const char* sender_tag; + }; + + void init_logging(); + char init_ephemeral(); + char get_self_address(void (*callback)(const char*)); + char send_message(const char*, const char*); + char listen_for_incoming(void (*callback)(ReceivedMessage)); + char reply(const char*, const char*); +} + +// Get address via callback +char addr[134]; +void on_address(const char* s) { strcpy(addr, s); } + +// Receive message via callback +char sender_tag[22]; +void on_message(ReceivedMessage msg) { + std::cout << "Received: " << msg.message << std::endl; + strcpy(sender_tag, msg.sender_tag); +} + +int main() { + init_ephemeral(); + get_self_address(on_address); + send_message(addr, "hello from C++"); + listen_for_incoming(on_message); + reply(sender_tag, "reply from C++"); +} +``` + +## Building + +Each language has a `build.sh` script that compiles the Rust shared library and generates bindings. See the README in each directory for prerequisites. + +## Examples and source + +- [Go mixnet example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/go/example.go): init, send, receive, SURB reply +- [Go TcpProxy example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/go/proxy_example.go): proxy client and server with TCP echo +- [C++ example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/cpp/src/main.cpp): same flow using Boost threads +- [`sdk/ffi` source](https://github.com/nymtech/nym/tree/develop/sdk/ffi): full source and build scripts diff --git a/documentation/docs/pages/developers/rust/importing.mdx b/documentation/docs/pages/developers/rust/importing.mdx index 255b45f466..ee0a17426a 100644 --- a/documentation/docs/pages/developers/rust/importing.mdx +++ b/documentation/docs/pages/developers/rust/importing.mdx @@ -1,13 +1,51 @@ -# Installation -import { Callout } from 'nextra/components'; +--- +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" +--- -The `nym-sdk` crate is **not yet available via [crates.io](https://crates.io)**. As such, in order to import the crate you must specify the Nym monorepo in your `Cargo.toml` file. Since the `HEAD` of `master` is always the most recent release, we recommend developers use that for their imports, unless they have a reason to pull in a specific historic version of the code. +# Installation + +import { Callout } from 'nextra/components'; +import { CratesPaused } from '../../../components/crates-paused' + + ```toml -# importing HEAD of master branch -nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" } -# importing HEAD of the third release of 2023, codename 'kinder' -nym-sdk = { git = "https://github.com/nymtech/nym", branch = "release/2023.3-kinder" } +[dependencies] +nym-sdk = { git = "https://github.com/nymtech/nym", rev = "4077717" } +blake3 = "=1.7.0" # pin to avoid a transitive dependency conflict — see note below ``` -Work will occur in the future to break the monorepo down into importable features, in order to reduce the number of dependencies imported by developers. + +**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. + + +You can also track a branch if you want the latest changes: + +```toml +# development branch (latest changes, may be unstable) +nym-sdk = { git = "https://github.com/nymtech/nym", branch = "develop" } + +# latest stable release +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. + + +**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. + diff --git a/documentation/docs/pages/developers/rust/mixnet.mdx b/documentation/docs/pages/developers/rust/mixnet.mdx index 2fc45fee11..812a428ca6 100644 --- a/documentation/docs/pages/developers/rust/mixnet.mdx +++ b/documentation/docs/pages/developers/rust/mixnet.mdx @@ -3,12 +3,60 @@ title: "Nym Rust SDK: Mixnet Messaging Module" description: "Use the Nym Rust SDK Mixnet module to send messages through the mixnet. Covers builder patterns, custom topologies, SOCKS proxy, and anonymous replies." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-02-01" +lastUpdated: "2026-03-13" --- # Mixnet Module + import { Callout } from 'nextra/components'; -This module exposes the logic of creating and interacting with clients and Mixnet messages. This is recommended for those wanting to either start playing around with the Mixnet and how it works, or build connection logic. +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. -> For developers wanting something more 'plug and play' we recommend the [`TcpProxy` module](./tcpproxy). + +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. + + +## Two operating modes + +The client operates in one of two mutually exclusive modes: + +**Message mode** (default): send and receive raw message payloads: +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + +// Send a message +client.send_plain_message(*client.nym_address(), "hello").await.unwrap(); + +// Receive messages +if let Some(msgs) = client.wait_for_messages().await { + for msg in msgs { + println!("Got: {}", String::from_utf8_lossy(&msg.message)); + } +} + +client.disconnect().await; +``` + +**Stream mode:** persistent `AsyncRead + AsyncWrite` channels. See the [Stream module](./stream) for details. + + +Stream mode is activated by calling `open_stream()` or `listener()`. Once active, message-mode methods return `Error::StreamModeActive`. This is a one-way transition. + + +## API reference + +- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/): full architecture documentation, all types, builder methods, traits, and configuration options +- [Examples on GitHub](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples): runnable examples covering simple send/receive, builder patterns, custom topologies, SOCKS proxy, anonymous replies, and more + +Run any example with: +```sh +cargo run --example +``` + +## Next steps + +- [Tutorial: Send your first private message](./mixnet/tutorial): step-by-step guide covering sending, receiving, SURBs, and persistent identity +- [Troubleshooting](./mixnet/troubleshooting): common issues with logging, empty messages, and client lifecycle +- [Stream module](./stream): if you need persistent bidirectional byte channels diff --git a/documentation/docs/pages/developers/rust/mixnet/_meta.json b/documentation/docs/pages/developers/rust/mixnet/_meta.json index 4797072ab7..e13d520b6e 100644 --- a/documentation/docs/pages/developers/rust/mixnet/_meta.json +++ b/documentation/docs/pages/developers/rust/mixnet/_meta.json @@ -1,6 +1,5 @@ { - "examples": "Basic Examples", - "message-helpers": "Message Helpers", - "message-types": "Message Types", + "tutorial": "Tutorial", + "examples": "Examples", "troubleshooting": "Troubleshooting" } diff --git a/documentation/docs/pages/developers/rust/mixnet/examples.mdx b/documentation/docs/pages/developers/rust/mixnet/examples.mdx index 5e21e1883a..66282a9413 100644 --- a/documentation/docs/pages/developers/rust/mixnet/examples.mdx +++ b/documentation/docs/pages/developers/rust/mixnet/examples.mdx @@ -1,19 +1,29 @@ +--- +title: "Mixnet Module Examples" +description: "Runnable Rust examples for the Nym mixnet module: sending messages, SURB anonymous replies, MixnetClientBuilder, persistent storage, and parallel send/receive." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + # Examples -import { Callout } from 'nextra/components'; +Runnable examples in [`sdk/rust/nym-sdk/examples/`](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples). Each file is self-contained with step-by-step comments. - - There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with. - -This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet. - -It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/). - - -All the following examples can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/master/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there with: - -```sh -cargo run --example +```bash +cargo run --example ``` -If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates. +| Example | Source | What it demonstrates | +|---|---|---| +| Simple | [`simple.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/simple.rs) | Send a message to yourself and print it | +| SURB Reply | [`surb_reply.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/surb_reply.rs) | Anonymous replies using `AnonymousSenderTag` and `send_reply()` | +| Builder | [`builder.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/builder.rs) | Using `MixnetClientBuilder` with ephemeral keys | +| Builder with Storage | [`builder_with_storage.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/builder_with_storage.rs) | Persisting keys to disk with `StoragePaths` | +| 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 | +| 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 | diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/_meta.json b/documentation/docs/pages/developers/rust/mixnet/examples/_meta.json deleted file mode 100644 index 14d6bc6ff6..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/_meta.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "simple": "Simple Send", - "builders": "Builder Patterns", - "testnet": "Configurable Network", - "custom-topology": "Custom Network Topologies", - "split-send": "Concurrent Send & Receive", - "socks": "Socks Proxy", - "storage": "Manually Handle Storage", - "surbs": "Anonymous Replies" -} diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/builders.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/builders.mdx deleted file mode 100644 index 3975a5aaac..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/builders.mdx +++ /dev/null @@ -1,4 +0,0 @@ -# Builder Patterns -import { Callout } from 'nextra/components'; - -Since there are two ways of creating an SDK client - ephemeral and with-storage - then there are two ways of applying the Builder Pattern to client creation. diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/builders/_meta.json b/documentation/docs/pages/developers/rust/mixnet/examples/builders/_meta.json deleted file mode 100644 index 778b1bce9d..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/builders/_meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "builder": "Ephemeral", - "builder-with-storage": "With Storage" -} diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/builders/builder-with-storage.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/builders/builder-with-storage.mdx deleted file mode 100644 index 07966916bf..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/builders/builder-with-storage.mdx +++ /dev/null @@ -1,73 +0,0 @@ -# Mixnet Client Builder with Storage -import { Callout } from 'nextra/components'; - -The previous example involves ephemeral keys - if we want to create and then maintain a client identity over time, our code becomes a little more complex as we need to create, store, and conditionally load these keys. - -> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder_with_storage.rs). - -```rust -use nym_sdk::mixnet; -use nym_sdk::mixnet::MixnetMessageSender; -use std::path::PathBuf; - -#[tokio::main] -async fn main() { - nym_bin_common::logging::setup_logging(); - - // Specify some config options - let config_dir = PathBuf::from("/tmp/mixnet-client"); - let storage_paths = mixnet::StoragePaths::new_from_dir(&config_dir).unwrap(); - - // Create the client with a storage backend, and enable it by giving it some paths. If keys - // exists at these paths, they will be loaded, otherwise they will be generated. - let client = mixnet::MixnetClientBuilder::new_with_default_storage(storage_paths) - .await - .unwrap() - .build() - .unwrap(); - - // Now we connect to the mixnet, using keys now stored in the paths provided. - let mut client = client.connect_to_mixnet().await.unwrap(); - - // Be able to get our client address - let our_address = client.nym_address(); - println!("Our client nym address is: {our_address}"); - - // Send a message throught the mixnet to ourselves - client - .send_plain_message(*our_address, "hello there") - .await - .unwrap(); - - println!("Waiting for message"); - if let Some(received) = client.wait_for_messages().await { - for r in received { - println!("Received: {}", String::from_utf8_lossy(&r.message)); - } - } - - client.disconnect().await; -} -``` - -As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple. - -Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated: -``` -$ tree /tmp/mixnet-client - -mixnet-client -├── ack_key.pem -├── db.sqlite -├── db.sqlite-shm -├── db.sqlite-wal -├── gateway_details.json -├── gateway_shared.pem -├── persistent_reply_store.sqlite -├── private_encryption.pem -├── private_identity.pem -├── public_encryption.pem -└── public_identity.pem - -1 directory, 11 files -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/builders/builder.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/builders/builder.mdx deleted file mode 100644 index 1b9f63517c..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/builders/builder.mdx +++ /dev/null @@ -1,44 +0,0 @@ -# Mixnet Client Builder -import { Callout } from 'nextra/components'; - -You can spin up an ephemeral client like so. This client will not have a persistent identity and its keys will be dropped on restart. Since there is currently no way of reconnecting a client that has been disconnected after use, then treat disconnecting a client the same as dropping its keys entirely. - -> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/builder.rs). - -```rust -use nym_sdk::mixnet; -use nym_sdk::mixnet::MixnetMessageSender; - -#[tokio::main] -async fn main() { - nym_bin_common::logging::setup_logging(); - - // Create client builder, including ephemeral keys. The builder can be usable in the context - // where you don't want to connect just yet. - let client = mixnet::MixnetClientBuilder::new_ephemeral() - .build() - .unwrap(); - - // Now we connect to the mixnet, using ephemeral keys already created - let mut client = client.connect_to_mixnet().await.unwrap(); - - // Be able to get our client address - let our_address = client.nym_address(); - println!("Our client nym address is: {our_address}"); - - // Send a message through the mixnet to ourselves - client - .send_plain_message(*our_address, "hello there") - .await - .unwrap(); - - println!("Waiting for message"); - if let Some(received) = client.wait_for_messages().await { - for r in received { - println!("Received: {}", String::from_utf8_lossy(&r.message)); - } - } - - client.disconnect().await; -} -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology.mdx deleted file mode 100644 index 1985900b06..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology.mdx +++ /dev/null @@ -1,17 +0,0 @@ -# Importing and using a custom network topology - -import { Callout } from 'nextra/components' - - - These examples are **not** the same as using a configurable network: these functions define a subset of nodes to use on a given network, whereas the [testnet](./testnet) example is an example of switching to use a different network entirely. The two can be combined, but if you are looking for how to connect your client to a testnet, see the `testnet` file. - - -If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or performing research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html). - -There are two ways to do this: - -## Custom Topology Provider -If you are also running a Validator and Nym API for your network, you can specify that endpoint. Clients will then use this endpoint to grab a network topology on startup. You can also use this to specify using a testnet. - -## Import a specific topology manually -If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can also overwrite the grabbed topology manually. diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/_meta.json b/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/_meta.json deleted file mode 100644 index 6d62d318f1..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/_meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "custom-provider": "Custom Topology Provider", - "manual-topology": "Manually Overwrite Topology" -} diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/custom-provider.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/custom-provider.mdx deleted file mode 100644 index 32218f1c91..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/custom-provider.mdx +++ /dev/null @@ -1,96 +0,0 @@ -# Custom Topology Provider -import { Callout } from 'nextra/components'; - -If you are also running a Validator and Nym API for your network, you can specify that endpoint as such and interact with it as clients usually do (under the hood). - -> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/custom_topology_provider.rs) - -```rust -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use nym_sdk::mixnet; -use nym_sdk::mixnet::MixnetMessageSender; -use nym_topology::provider_trait::{async_trait, TopologyProvider}; -use nym_topology::{nym_topology_from_detailed, NymTopology}; -use nym_validator_client::nym_api::NymApiClientExt; -use url::Url; - -struct MyTopologyProvider { - validator_client: nym_http_api_client::Client, -} - -impl MyTopologyProvider { - fn new(nym_api_url: Url) -> MyTopologyProvider { - let validator_client = nym_http_api_client::Client::builder::<_, nym_validator_client::models::RequestError>(nym_api_url) - .expect("Failed to create API client builder") - .build::() - .expect("Failed to build API client"); - - MyTopologyProvider { - validator_client, - } - } - - async fn get_topology(&self) -> NymTopology { - let mixnodes = self - .validator_client - .get_cached_active_mixnodes() - .await - .unwrap(); - - // in our topology provider only use mixnodes that have mix_id divisible by 3 - // and have more than 100k nym (i.e. 100'000'000'000 unym) in stake - // why? because this is just an example to showcase arbitrary uses and capabilities of this trait - let filtered_mixnodes = mixnodes - .into_iter() - .filter(|mix| { - mix.mix_id() % 3 == 0 && mix.total_stake() > "100000000000".parse().unwrap() - }) - .collect::>(); - - let gateways = self.validator_client.get_cached_gateways().await.unwrap(); - - nym_topology_from_detailed(filtered_mixnodes, gateways) - } -} - -#[async_trait] -impl TopologyProvider for MyTopologyProvider { - // this will be manually refreshed on a timer specified inside mixnet client config - async fn get_new_topology(&mut self) -> Option { - Some(self.get_topology().await) - } -} - -#[tokio::main] -async fn main() { - nym_bin_common::logging::setup_logging(); - - let nym_api = "https://validator.nymtech.net/api/".parse().unwrap(); - let my_topology_provider = MyTopologyProvider::new(nym_api); - - // Passing no config makes the client fire up an ephemeral session and figure things out on its own - let mut client = mixnet::MixnetClientBuilder::new_ephemeral() - .custom_topology_provider(Box::new(my_topology_provider)) - .build() - .unwrap() - .connect_to_mixnet() - .await - .unwrap(); - - let our_address = client.nym_address(); - println!("Our client nym address is: {our_address}"); - - // Send a message through the mixnet to ourselves - client - .send_plain_message(*our_address, "hello there") - .await - .unwrap(); - - println!("Waiting for message (ctrl-c to exit)"); - client - .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) - .await; -} -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/manually-overwrite-topology.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/manually-overwrite-topology.mdx deleted file mode 100644 index 61b4b3fdbc..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/custom-topology/manually-overwrite-topology.mdx +++ /dev/null @@ -1,103 +0,0 @@ -# Manually Overwrite Topology -import { Callout } from 'nextra/components'; - -If you aren't running a Validator and Nym API, and just want to import a specific sub-set of mix nodes, you can simply overwrite the grabbed topology manually. - -> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_overwrite_topology.rs) - -```rust -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use nym_sdk::mixnet; -use nym_sdk::mixnet::MixnetMessageSender; -use nym_topology::mix::Layer; -use nym_topology::{mix, NymTopology}; -use std::collections::BTreeMap; - -#[tokio::main] -async fn main() { - nym_bin_common::logging::setup_logging(); - - // Passing no config makes the client fire up an ephemeral session and figure shit out on its own - let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); - let starting_topology = client.read_current_topology().await.unwrap(); - - // but we don't like our default topology, we want to use only those very specific, hardcoded, nodes: - let mut mixnodes = BTreeMap::new(); - mixnodes.insert( - 1, - vec![mix::Node { - mix_id: 63, - owner: None, - host: "172.105.92.48".parse().unwrap(), - mix_host: "172.105.92.48:1789".parse().unwrap(), - identity_key: "GLdR2NRVZBiCoCbv4fNqt9wUJZAnNjGXHkx3TjVAUzrK" - .parse() - .unwrap(), - sphinx_key: "CBmYewWf43iarBq349KhbfYMc9ys2ebXWd4Vp4CLQ5Rq" - .parse() - .unwrap(), - layer: Layer::One, - version: "1.1.0".into(), - }], - ); - mixnodes.insert( - 2, - vec![mix::Node { - mix_id: 23, - owner: None, - host: "178.79.143.65".parse().unwrap(), - mix_host: "178.79.143.65:1789".parse().unwrap(), - identity_key: "4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz" - .parse() - .unwrap(), - sphinx_key: "8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG" - .parse() - .unwrap(), - layer: Layer::Two, - version: "1.1.0".into(), - }], - ); - mixnodes.insert( - 3, - vec![mix::Node { - mix_id: 66, - owner: None, - host: "139.162.247.97".parse().unwrap(), - mix_host: "139.162.247.97:1789".parse().unwrap(), - identity_key: "66UngapebhJRni3Nj52EW1qcNsWYiuonjkWJzHFsmyYY" - .parse() - .unwrap(), - sphinx_key: "7KyZh8Z8KxuVunqytAJ2eXFuZkCS7BLTZSzujHJZsGa2" - .parse() - .unwrap(), - layer: Layer::Three, - version: "1.1.0".into(), - }], - ); - - // but we like the available gateways, so keep using them! - // (we like them because the author of this example is too lazy to use the same hardcoded gateway - // during client initialisation to make sure we are able to send to ourselves : ) ) - let custom_topology = NymTopology::new(mixnodes, starting_topology.gateways().to_vec()); - - client.manually_overwrite_topology(custom_topology).await; - - // and everything we send now should only ever go via those nodes - - let our_address = client.nym_address(); - println!("Our client nym address is: {our_address}"); - - // Send a message through the mixnet to ourselves - client - .send_plain_message(*our_address, "hello there") - .await - .unwrap(); - - println!("Waiting for message (ctrl-c to exit)"); - client - .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) - .await; -} -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/simple.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/simple.mdx deleted file mode 100644 index 730d98608a..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/simple.mdx +++ /dev/null @@ -1,37 +0,0 @@ -# Simple Send - -import { Callout } from 'nextra/components' - -Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code. - -Simply importing the `nym_sdk` crate into your project allows you to create a client and send traffic through the mixnet. - -> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/simple.rs) - -```rust -use nym_sdk::mixnet; -use nym_sdk::mixnet::MixnetMessageSender; - -#[tokio::main] -async fn main() { - nym_bin_common::logging::setup_logging(); - - // Passing no config makes the client fire up an ephemeral session and figure shit out on its own - let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); - - // Be able to get our client address - let our_address = client.nym_address(); - println!("Our client nym address is: {our_address}"); - - // Send a message through the mixnet to ourselves - client - .send_plain_message(*our_address, "hello there") - .await - .unwrap(); - - println!("Waiting for message (ctrl-c to exit)"); - client - .on_messages(|msg| println!("Received: {}", String::from_utf8_lossy(&msg.message))) - .await; -} -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/socks.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/socks.mdx deleted file mode 100644 index 9dff137fd5..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/socks.mdx +++ /dev/null @@ -1,50 +0,0 @@ -# Socks Proxy - -import { Callout } from 'nextra/components' - -If you are looking at implementing Nym as a transport layer for a crypto wallet or desktop app, this is probably the best place to start if they can speak SOCKS5, 4a, or 4. - -> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/socks5.rs) - -```rust -use nym_sdk::mixnet; - -#[tokio::main] -async fn main() { - nym_bin_common::logging::setup_logging(); - - println!("Connecting receiver"); - let mut receiving_client = mixnet::MixnetClient::connect_new().await.unwrap(); - - let socks5_config = mixnet::Socks5::new(receiving_client.nym_address().to_string()); - let sending_client = mixnet::MixnetClientBuilder::new_ephemeral() - .socks5_config(socks5_config) - .build() - .unwrap(); - - println!("Connecting sender"); - let sending_client = sending_client.connect_to_mixnet_via_socks5().await.unwrap(); - - let proxy = reqwest::Proxy::all(sending_client.socks5_url()).unwrap(); - let reqwest_client = reqwest::Client::builder().proxy(proxy).build().unwrap(); - tokio::spawn(async move { - println!("Sending socks5-wrapped http request"); - // Message should be sent through the mixnet, via socks5 - // We don't expect to get anything, as there is no network requester on the other end - reqwest_client.get("https://nym.com").send().await.ok() - }); - - println!("Waiting for message"); - if let Some(received) = receiving_client.wait_for_messages().await { - for r in received { - println!( - "Received socks5 message requesting for endpoint: {}", - String::from_utf8_lossy(&r.message[10..27]) - ); - } - } - - receiving_client.disconnect().await; - sending_client.disconnect().await; -} -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/split-send.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/split-send.mdx deleted file mode 100644 index 88cd9cc059..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/split-send.mdx +++ /dev/null @@ -1,52 +0,0 @@ -# Send and Receive in Different Tasks - -import { Callout } from 'nextra/components' - -If you need to split the different actions of your client across different tasks, you can do so like this. You can think of this analogously to spliting a Tcp Stream into read/write. This functionality is also useful for embedding a sending and receiving client into different tasks. - -> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs) - -```rust -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use futures::StreamExt; -use nym_sdk::mixnet; -use nym_sdk::mixnet::MixnetMessageSender; - -#[tokio::main] -async fn main() { - nym_bin_common::logging::setup_logging(); - - // Passing no config makes the client fire up an ephemeral session and figure stuff out on its own - let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); - - // Be able to get our client address - let our_address = *client.nym_address(); - println!("Our client nym address is: {our_address}"); - - let sender = client.split_sender(); - - // receiving task - let receiving_task_handle = tokio::spawn(async move { - if let Some(received) = client.next().await { - println!("Received: {}", String::from_utf8_lossy(&received.message)); - } - - client.disconnect().await; - }); - - // sending task - let sending_task_handle = tokio::spawn(async move { - sender - .send_plain_message(our_address, "hello from a different task!") - .await - .unwrap(); - }); - - // wait for both tasks to be done - println!("waiting for shutdown"); - sending_task_handle.await.unwrap(); - receiving_task_handle.await.unwrap(); -} -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/storage.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/storage.mdx deleted file mode 100644 index 230e8eced5..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/storage.mdx +++ /dev/null @@ -1,229 +0,0 @@ -# Manually Handle Storage - -import { Callout } from 'nextra/components' - -If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform these actions. - -> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/manually_handle_storage.rs) - -```rust -use nym_sdk::mixnet::{ - self, ActiveGateway, BadGateway, ClientKeys, EmptyReplyStorage, EphemeralCredentialStorage, - GatewayRegistration, GatewaysDetailsStore, KeyStore, MixnetClientStorage, MixnetMessageSender, -}; -use nym_topology::provider_trait::async_trait; - -#[tokio::main] -async fn main() { - nym_bin_common::logging::setup_logging(); - - // Just some plain data to pretend we have some external storage that the application - // implementer is using. - let mock_storage = MockClientStorage::empty(); - let mut client = mixnet::MixnetClientBuilder::new_with_storage(mock_storage) - .build() - .unwrap() - .connect_to_mixnet() - .await - .unwrap(); - - // Be able to get our client address - let our_address = client.nym_address(); - println!("Our client nym address is: {our_address}"); - - // Send important info up the pipe to a buddy - client - .send_plain_message(*our_address, "hello there") - .await - .unwrap(); - - println!("Waiting for message"); - if let Some(received) = client.wait_for_messages().await { - for r in received { - println!("Received: {}", String::from_utf8_lossy(&r.message)); - } - } - - client.disconnect().await; -} - -#[allow(unused)] -struct MockClientStorage { - pub key_store: MockKeyStore, - pub gateway_details_store: MockGatewayDetailsStore, - pub reply_store: EmptyReplyStorage, - pub credential_store: EphemeralCredentialStorage, -} - -impl MockClientStorage { - fn empty() -> Self { - Self { - key_store: MockKeyStore, - gateway_details_store: MockGatewayDetailsStore, - reply_store: EmptyReplyStorage::default(), - credential_store: EphemeralCredentialStorage::default(), - } - } -} - -impl MixnetClientStorage for MockClientStorage { - type KeyStore = MockKeyStore; - type ReplyStore = EmptyReplyStorage; - type CredentialStore = EphemeralCredentialStorage; - type GatewaysDetailsStore = MockGatewayDetailsStore; - - fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) { - (self.reply_store, self.credential_store) - } - - fn key_store(&self) -> &Self::KeyStore { - &self.key_store - } - - fn reply_store(&self) -> &Self::ReplyStore { - &self.reply_store - } - - fn credential_store(&self) -> &Self::CredentialStore { - &self.credential_store - } - - fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore { - &self.gateway_details_store - } -} - -struct MockKeyStore; - -#[async_trait] -impl KeyStore for MockKeyStore { - type StorageError = MyError; - - async fn load_keys(&self) -> Result { - println!("loading stored keys"); - - Err(MyError) - } - - async fn store_keys(&self, _keys: &ClientKeys) -> Result<(), Self::StorageError> { - println!("storing keys"); - - Ok(()) - } -} - -struct MockGatewayDetailsStore; - -#[async_trait] -impl GatewaysDetailsStore for MockGatewayDetailsStore { - type StorageError = MyError; - - async fn active_gateway(&self) -> Result { - println!("getting active gateway"); - - Err(MyError) - } - - async fn set_active_gateway(&self, _gateway_id: &str) -> Result<(), Self::StorageError> { - println!("setting active gateway"); - - Ok(()) - } - - async fn all_gateways(&self) -> Result, Self::StorageError> { - println!("getting all registered gateways"); - - Err(MyError) - } - - async fn has_gateway_details(&self, _gateway_id: &str) -> Result { - println!("checking for gateway details"); - - Err(MyError) - } - - async fn load_gateway_details( - &self, - _gateway_id: &str, - ) -> Result { - println!("loading gateway details"); - - Err(MyError) - } - - async fn store_gateway_details( - &self, - _details: &GatewayRegistration, - ) -> Result<(), Self::StorageError> { - println!("storing gateway details"); - - Ok(()) - } - - async fn remove_gateway_details(&self, _gateway_id: &str) -> Result<(), Self::StorageError> { - println!("removing gateway details"); - - Ok(()) - } -} - -// -// struct MockReplyStore; -// -// #[async_trait] -// impl ReplyStorageBackend for MockReplyStore { -// type StorageError = MyError; -// -// async fn flush_surb_storage( -// &mut self, -// _storage: &CombinedReplyStorage, -// ) -> Result<(), Self::StorageError> { -// todo!() -// } -// -// async fn init_fresh(&mut self, _fresh: &CombinedReplyStorage) -> Result<(), Self::StorageError> { -// todo!() -// } -// -// async fn load_surb_storage(&self) -> Result { -// todo!() -// } -// } -// -// struct MockCredentialStore; -// -// #[async_trait] -// impl CredentialStorage for MockCredentialStore { -// type StorageError = MyError; -// -// async fn insert_coconut_credential( -// &self, -// _voucher_value: String, -// _voucher_info: String, -// _serial_number: String, -// _binding_number: String, -// _signature: String, -// _epoch_id: String, -// ) -> Result<(), Self::StorageError> { -// todo!() -// } -// -// async fn get_next_coconut_credential(&self) -> Result { -// todo!() -// } -// -// async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError> { -// todo!() -// } -// } - -#[derive(thiserror::Error, Debug)] -#[error("foobar")] -struct MyError; - -impl From for MyError { - fn from(_: BadGateway) -> Self { - MyError - } -} -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/surbs.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/surbs.mdx deleted file mode 100644 index d9b68f4848..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/surbs.mdx +++ /dev/null @@ -1,86 +0,0 @@ -# Anonymous Replies with SURBs (Single Use Reply Blocks) - -import { Callout } from 'nextra/components' - -Both functions used to send messages through the mixnet (`send_message` and `send_plain_message`) send a pre-determined number of SURBs along with their messages by default. - -You can read more about how SURBs function under the hood [here](../../../../network/traffic/anonymous-replies). - -In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to. - -> You can find this code [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/examples/surb_reply.rs) - -```rust -use nym_sdk::mixnet::{ - AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage, - StoragePaths, -}; -use std::path::PathBuf; - -#[tokio::main] -async fn main() { - nym_bin_common::logging::setup_logging(); - - // Specify some config options - let config_dir = PathBuf::from("/tmp/surb-example"); - let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap(); - - // Create the client with a storage backend, and enable it by giving it some paths. If keys - // exists at these paths, they will be loaded, otherwise they will be generated. - let client = MixnetClientBuilder::new_with_default_storage(storage_paths) - .await - .unwrap() - .build() - .unwrap(); - - // Now we connect to the mixnet, using keys now stored in the paths provided. - let mut client = client.connect_to_mixnet().await.unwrap(); - - // Be able to get our client address - let our_address = client.nym_address(); - println!("\nOur client nym address is: {our_address}"); - - // Send a message through the mixnet to ourselves using our nym address - client - .send_plain_message(*our_address, "hello there") - .await - .unwrap(); - - // we're going to parse the sender_tag (AnonymousSenderTag) from the incoming message and use it to 'reply' to ourselves instead of our Nym address. - // we know there will be a sender_tag since the sdk sends SURBs along with messages by default. - println!("Waiting for message\n"); - - // get the actual message - discard the empty vec sent along with a potential SURB topup request - let mut message: Vec = Vec::new(); - while let Some(new_message) = client.wait_for_messages().await { - if new_message.is_empty() { - continue; - } - message = new_message; - break; - } - - let mut parsed = String::new(); - if let Some(r) = message.first() { - parsed = String::from_utf8(r.message.clone()).unwrap(); - } - // parse sender_tag: we will use this to reply to sender without needing their Nym address - let return_recipient: AnonymousSenderTag = message[0].sender_tag.unwrap(); - println!( - "\nReceived the following message: {} \nfrom sender with surb bucket {}", - parsed, return_recipient - ); - - // reply to self with it: note we use `send_str_reply` instead of `send_str` - println!("Replying with using SURBs"); - client - .send_reply(return_recipient, "hi an0n!") - .await - .unwrap(); - - println!("Waiting for message (once you see it, ctrl-c to exit)\n"); - client - .on_messages(|msg| println!("\nReceived: {}", String::from_utf8_lossy(&msg.message))) - .await; -} -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/examples/testnet.mdx b/documentation/docs/pages/developers/rust/mixnet/examples/testnet.mdx deleted file mode 100644 index a6fc4b4b3e..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/examples/testnet.mdx +++ /dev/null @@ -1,46 +0,0 @@ -# Configurable Network -import { Callout } from 'nextra/components' - - -If you want to connect your Mixnet client to a different network than Mainnet, simply pull in a file from [`nym/envs`](https://github.com/nymtech/nym/tree/master/envs) as such: - -```rust -use futures::StreamExt; -use nym_network_defaults::setup_env; -use nym_sdk::mixnet; -use nym_sdk::mixnet::MixnetMessageSender; - -// An example of creating a client relying on a testnet, in this case Sandbox. -#[tokio::main] -async fn main() -> anyhow::Result<()> { - nym_bin_common::logging::setup_logging(); - // relative root is `sdk/rust/nym-sdk/` for fallback file path - let env_path = - std::env::var("NYM_ENV_PATH").unwrap_or_else(|_| "../../../envs/sandbox.env".to_string()); - setup_env(Some(&env_path)); - let sandbox_network = mixnet::NymNetworkDetails::new_from_env(); - - let mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral() - .network_details(sandbox_network) - .build()?; - - let mut client = mixnet_client.connect_to_mixnet().await?; - - let our_address = client.nym_address(); - - // Send a message throughout the mixnet to ourselves - client - .send_plain_message(*our_address, "hello there") - .await?; - - println!("Waiting for message"); - if let Some(received) = client.next().await { - println!("Received: {}", String::from_utf8_lossy(&received.message)); - } else { - eprintln!("Failed to receive message."); - } - - client.disconnect().await; - Ok(()) -} -``` diff --git a/documentation/docs/pages/developers/rust/mixnet/message-helpers.mdx b/documentation/docs/pages/developers/rust/mixnet/message-helpers.mdx deleted file mode 100644 index b398bdd554..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/message-helpers.mdx +++ /dev/null @@ -1,75 +0,0 @@ -# Message Helpers - -import { Callout } from 'nextra/components'; - - - There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with. - -This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet. - -It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/). - - -## Handling incoming messages -When listening out for a response to a sent message (e.g. if you have sent a request to a service, and are awaiting the response) you will want to await [non-empty messages (if you don't know why, read the info on this here)](./troubleshooting#client-receives-empty-messages-when-listening-for-response). This can be done with something like the helper functions here: - -```rust -use nym_sdk::mixnet::ReconstructedMessage; - -pub async fn wait_for_non_empty_message( - client: &mut MixnetClient, -) -> anyhow::Result { - while let Some(mut new_message) = client.wait_for_messages().await { - if !new_message.is_empty() { - return Ok(new_message.pop().unwrap()); - } - } - - bail!("did not receive any non-empty message") -} - -pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result { - ResponseTypes::try_deserialize(message.message) -} - -// Note here that the only difference between handling a request and a response -// is that a request will have a sender_tag to parse. -// -// This is used for anonymous replies with SURBs. -pub fn handle_request( - message: ReconstructedMessage, -) -> anyhow::Result<(RequestTypes, Option)> { - let request = RequestTypes::try_deserialize(message.message)?; - Ok((request, message.sender_tag)) -} -``` - -The above helper functions are used as such by the client in tutorial example: it sends a message to the service (what the message is isn't important - just that your client has sent a message _somewhere_ and you are awaiting a response), waits for a _non_empty_ message, then handles it (then logs it - but you can do whatever you want, parse it, etc): - -```rust -// Send serialised request to service via mixnet what is await-ed here is -// placing the message in the client's message queue, NOT the sending itself. -let _ = client - .send_message(sp_address, message.serialize(), Default::default()) - .await; - -// Await a non-empty message -let received = wait_for_non_empty_message(client).await?; - -// Handle the response received (the non-empty message awaited above) -let sp_response = handle_response(received)?; - -// Match JSON -> ResponseType -let res = match sp_response { - crate::ResponseTypes::Balance(response) => { - println!("{:#?}", response); - response.balance - } -}; -``` - -## Iterating over incoming messages -It is recommended to use `nym_client.next().await` over `nym_client.wait_for_messages().await` as the latter will return one message at a time which will probably be easier to deal with. See the [parallel send and receive example](./examples/split-send) for an example. - -## Remember to disconnect your client -You should always **manually disconnect your client** with `client.disconnect().await` as seen in the code examples. This is important as your client is writing to a local DB and dealing with SURB storage, so needs to gracefully shutdown. diff --git a/documentation/docs/pages/developers/rust/mixnet/message-types.mdx b/documentation/docs/pages/developers/rust/mixnet/message-types.mdx deleted file mode 100644 index 80502b77e6..0000000000 --- a/documentation/docs/pages/developers/rust/mixnet/message-types.mdx +++ /dev/null @@ -1,30 +0,0 @@ -# Message Types - -import { Callout } from 'nextra/components'; - - - There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with. - -This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet. - -It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/). - - -There are several functions used to send outgoing messages through the Mixnet, each with a different level of customisation: - -- `send(&self, message: InputMessage) -> Result<()>` -Sends a `InputMessage` to the mixnet. This is the most low-level sending function, for full customization. Called by `send_message()`. - -- `send_message(&self, address: Recipient, message: M, surbs: IncludedSurbs) -> Result<()>` -Sends bytes to the supplied Nym address. There is the option to specify the number of reply-SURBs to include. Called by `send_plain_message()`. - -- `send_plain_message(&self, address: Recipient, message: M) -> Result<()>` -Sends data to the supplied Nym address with the default surb behaviour. - -> Note we specify *outgoing* messages above: this is because the SDK assumes that replies will be anonymous via [SURBs](../../../network/traffic/anonymous-replies). - -Replies rely on the creation of an `AnonymousSenderTag` by parsing and storing the `sender_tag` from incoming messages, and using this to reply, instead of the `Receipient` type used by the functions outlined above: - -`send_reply(&self, recipient_tag: AnonymousSenderTag, message: M) -> Result<()>` will send the reply message to the supplied anonymous recipient. - -> You can find all of the function definitions [here](https://github.com/nymtech/nym/blob/master/sdk/rust/nym-sdk/src/mixnet/traits.rs). diff --git a/documentation/docs/pages/developers/rust/mixnet/troubleshooting.mdx b/documentation/docs/pages/developers/rust/mixnet/troubleshooting.mdx index 8ad5135a0e..2f1b9dd7bc 100644 --- a/documentation/docs/pages/developers/rust/mixnet/troubleshooting.mdx +++ b/documentation/docs/pages/developers/rust/mixnet/troubleshooting.mdx @@ -1,130 +1,78 @@ +--- +title: "Mixnet Module Troubleshooting" +description: "Solutions for common Nym Rust SDK issues: client disconnect errors, empty SURB messages, verbose logging, and database lock problems." +schemaType: "FAQPage" +section: "Developers" +lastUpdated: "2026-03-15" +--- + # Troubleshooting import { Callout } from 'nextra/components'; - - There will be a breaking SDK upgrade in the coming months. This upgrade will make the SDK a lot easier to build with. +Common issues and how to resolve them. -This upgrade will affect the interface of the SDK dramatically, and will be coupled with a protocol change - stay tuned for information on early access to the new protocol testnet. +## Always disconnect your client +You should always **manually disconnect your client** with `client.disconnect().await`. The client writes to a local DB and manages SURB storage, so it needs to shut down gracefully. Failing to do this can lead to the errors described below. -It will also be coupled with the documentation of the SDK on [crates.io](https://crates.io/). +## Waiting for non-empty messages + +When listening for a response, you may receive empty messages. These are SURB replenishment requests: the remote side asking for more reply SURBs. Filter them out: + +```rust +let mut message = None; +while let Some(new_message) = client.wait_for_messages().await { + if !new_message.is_empty() { + message = new_message.into_iter().next(); + break; + } +} +``` + + +Prefer `client.next().await` (from the `futures::StreamExt` trait, not the Nym Stream module) over `client.wait_for_messages().await`; it returns one message at a time which is easier to work with. You'll need `use futures::StreamExt;` in scope. - -Below are several common issues or questions you may have. - -If you come across something that isn't explained here, [PRs are welcome](https://github.com/nymtech/nym/issues/new/choose). - ## Verbose `task client is being dropped` logging + ### On client shutdown (expected) -If this is happening at the end of your code when disconnecting your client, this is fine; we just have a verbose client! When calling `client.disconnect().await` this is simply informing you that the client is shutting down. -On client shutdown / disconnect this is to be expected - this can be seen in many of the code examples as well. We use the [`nym_bin_common::logging`](https://github.com/nymtech/nym/blob/master/common/bin-common/src/logging/mod.rs) import to set logging in our example code. This defaults to `INFO` level. +When calling `client.disconnect().await`, the client logs that its background tasks are shutting down. This is normal and expected. -If you wish to quickly lower the verbosity of your client process logs when developing you can prepend your command with `RUST_LOG=`. - -If you want to run the `builder.rs` example with only `WARN` level logging and below: +Control log verbosity with `RUST_LOG`: ```sh -cargo run --example builder -``` - -Becomes: - -```sh -RUST_LOG=warn cargo run --example builder -``` - -You can also make the logging _more_ verbose with: - -```sh -RUST_LOG=debug cargo run --example builder +RUST_LOG=warn cargo run --example simple ``` ### Not on client shutdown (unexpected) -If this is happening unexpectedly then you might be shutting your client process down too early. See the [accidentally killing your client process](#accidentally-killing-your-client-process-too-early) below for possible explanations and how to fix this issue. + +If you see these messages unexpectedly, you may be killing the client process too early. See the next section. ## Accidentally killing your client process too early -If you are seeing either of the following errors when trying to run a client, specifically sending a message, then you may be accidentally killing your client process. -```sh - 2023-11-02T10:31:03.930Z INFO TaskClient-BaseNymClient-real_traffic_controller-ack_control-action_controller > the task client is getting dropped - 2023-11-02T10:31:04.625Z INFO TaskClient-BaseNymClient-received_messages_buffer-request_receiver > the task client is getting dropped - 2023-11-02T10:31:04.626Z DEBUG nym_client_core::client::real_messages_control::acknowledgement_control::input_message_listener > InputMessageListener: Exiting - 2023-11-02T10:31:04.626Z INFO TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener > the task client is getting dropped - 2023-11-02T10:31:04.626Z INFO TaskClient-BaseNymClient-real_traffic_controller-reply_control > the task client is getting dropped - 2023-11-02T10:31:04.626Z DEBUG nym_client_core::client::real_messages_control > The reply controller has finished execution! - 2023-11-02T10:31:04.626Z DEBUG nym_client_core::client::real_messages_control::acknowledgement_control > The input listener has finished execution! - 2023-11-02T10:31:04.626Z INFO nym_task::manager > All registered tasks succesfully shutdown -``` +If you see errors like `Polling shutdown failed: channel closed` or panics about `action control task has died`, your client is being dropped before it finishes sending. -```sh - 2023-11-02T11:22:08.408Z ERROR TaskClient-BaseNymClient-topology_refresher > Assuming this means we should shutdown... - 2023-11-02T11:22:08.408Z ERROR TaskClient-BaseNymClient-mix_traffic_controller > Polling shutdown failed: channel closed - 2023-11-02T11:22:08.408Z INFO TaskClient-BaseNymClient-gateway_transceiver-child > the task client is getting dropped - 2023-11-02T11:22:08.408Z ERROR TaskClient-BaseNymClient-mix_traffic_controller > Assuming this means we should shutdown... -thread 'tokio-runtime-worker' panicked at 'action control task has died: TrySendError { kind: Disconnected }', /home/.local/share/cargo/git/checkouts/nym-fbd2f6ea2e760da9/a800cba/common/client-core/src/client/real_messages_control/message_handler.rs:634:14 -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace - 2023-11-02T11:22:08.477Z INFO TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener > the task client is getting dropped - 2023-11-02T11:22:08.477Z ERROR TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener > Polling shutdown failed: channel closed - 2023-11-02T11:22:08.477Z ERROR TaskClient-BaseNymClient-real_traffic_controller-ack_control-input_message_listener > Assuming this means we should shutdown... -``` +`send_plain_message()` is async, but **it only blocks until the message is placed in the client's internal queue**, not until it's actually sent into the Mixnet. After queuing, the client still needs to route-encrypt the message and interleave it with cover traffic. -Using the following piece of code as an example: +Make sure the program stays alive long enough. In practice this means awaiting a response or calling `sleep` before disconnecting: ```rust -use nym_sdk::mixnet::{MixnetClient, MixnetMessageSender, Recipient}; -use clap::Parser; +// Send a message +client.send_plain_message(recipient, "hello").await.unwrap(); -#[derive(Debug, Clone, Parser)] -enum Opts { - Client { - recipient: Recipient +// Wait for the reply (keeps the client alive) +if let Some(received) = client.wait_for_messages().await { + for r in received { + println!("Received: {}", String::from_utf8_lossy(&r.message)); } } -#[tokio::main] -async fn main() { - let opts: Opts = Parser::parse(); - nym_bin_common::logging::setup_logging(); - - let mut nym_client = MixnetClient::connect_new().await.expect("Could not build Nym client"); - - match opts { - Opts::Client { recipient } => { - nym_client.send_plain_message(recipient, "some message string").await.expect("send failed"); - } - } -} +// Always disconnect gracefully +client.disconnect().await; ``` -This is a simplified snippet of code for sending a simple hardcoded message with the following command: - -```sh -cargo run client -``` - -You might assume that `send`-ing your message would _just work_ as `nym_client.send_plain_message()` is an async function; you might expect that the client will block until the message is actually sent into the mixnet, then shutdown. - -However, this is not true. - -**This will only block until the message is put into client's internal queue**. Therefore in the above example, the client is being shut down before the message is _actually sent to the mixnet_; after being placed in the client's internal queue, there is still work to be done under the hood, such as route encrypting the message and placing it amongst the stream of cover traffic. - -The simple solution? Make sure the program/client stays active, either by calling `sleep`, or listening out for new messages. As sending a one-shot message without listening out for a response is likely not what you'll be doing, then you will be then awaiting a response (see the [message helpers page](./message-helpers) for an example of this). - -Furthermore, you should always **manually disconnect your client** with `client.disconnect().await` as seen in the code examples. This is important as your client is writing to a local DB and dealing with SURB storage. - -## Client receives empty messages when listening for response -If you are sending out a message, it makes sense for your client to then listen out for incoming messages; this would probably be the reply you get from the service you've sent a message to. - -You might however be receiving messages without data attached to them / empty payloads. This is most likely because your client is receiving a message containing a [SURB request](../../../network/traffic/anonymous-replies) - a SURB requesting more SURB packets to be sent to the service, in order for them to have enough packets (with a big enough overall payload) to split the entire response to your initial request across. - -Whether the `data` of a SURB request being empty is a feature or a bug is to be decided - there is some discussion surrounding whether we can use SURB requests to send additional data to streamline the process of sending large replies across the mixnet. - -You can find a few helper functions [here](./message-helpers) to help deal with this issue in the meantime. - -> If you can think of a more succinct or different way of handling this do reach out - we're happy to hear other opinions - ## Lots of `duplicate fragment received` messages -You might see a lot of `WARN` level logs about duplicate fragments in your logs, depending on the log level you're using. This occurs when a packet is retransmitted somewhere in the Mixnet, but then the original makes it to the destination client as well. This is not something to do with your client logic, but instead the state of the Mixnet. + +`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission: the original and the retransmitted copy both arrive. This is not a bug in your client logic. diff --git a/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx b/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx new file mode 100644 index 0000000000..94a30cf1a7 --- /dev/null +++ b/documentation/docs/pages/developers/rust/mixnet/tutorial.mdx @@ -0,0 +1,314 @@ +--- +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" +--- + +# Tutorial: Send Your First Private Message + +import { Callout } from 'nextra/components' +import { CodeVerified } from '../../../../components/code-verified' + +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. + +**You'll need:** Rust 1.70+ and an internet connection (clients connect to the live Mixnet). + + + +## Step 1: Set up the project + +```sh +cargo init nym-mixnet-demo +cd nym-mixnet-demo +``` + +Add dependencies to `Cargo.toml`: + +```toml +[dependencies] +nym-sdk = { git = "https://github.com/nymtech/nym", rev = "4077717" } +nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "4077717", features = ["basic_tracing"] } +tokio = { version = "1", features = ["full"] } +blake3 = "=1.7.0" # required pin — see https://nymtech.net/developers/rust/importing +``` + +## Step 2: Connect and send + +Replace the contents of `src/main.rs`: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + + // 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 — + // encryption and mixing happen in background tasks. + client + .send_plain_message(*our_address, "hello from the mixnet!") + .await + .unwrap(); + + println!("Sent — waiting for arrival..."); +``` + + +`setup_tracing_logger()` shows what the SDK is doing under the hood: gateway connections, topology fetches, Sphinx packet encryption. If the output is too verbose, comment out the line or filter with `RUST_LOG=warn cargo run`. + + +## Step 3: Receive + +```rust + // wait_for_messages() returns the next batch of incoming messages. + // 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()) { + break msg; + } + } + }; + + println!("Received: {}", String::from_utf8_lossy(&message.message)); +``` + +## Step 4: Reply anonymously + +Every message includes a `sender_tag`, an opaque `AnonymousSenderTag` that lets you reply **without knowing the sender's address**. The SDK bundles SURBs (Single Use Reply Blocks) with every outgoing message by default: + +```rust + let sender_tag = message.sender_tag.expect("should have sender tag"); + + // 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 { + 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)); + + client.disconnect().await; +} +``` + +## Step 5: Run it + +```sh +RUST_LOG=info cargo run +``` + +``` +Connected: 8gk4Y...@2xU4d... +Sent — waiting for arrival... +Received: hello from the mixnet! +Reply: hello back, anonymously! +``` + +## Going further: persist your identity + +The ephemeral client above generates a new address on every run. To keep the same address across restarts, use `MixnetClientBuilder` with on-disk storage: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender, StoragePaths}; + +#[tokio::main] +async fn main() { + // Keys are generated on first run, then loaded from disk on subsequent runs. + let paths = StoragePaths::new_from_dir("./my-client-data").unwrap(); + + let mut client = mixnet::MixnetClientBuilder::new_with_default_storage(paths) + .await + .unwrap() + .build() + .unwrap() + .connect_to_mixnet() + .await + .unwrap(); + + println!("Address: {}", client.nym_address()); + + // Same API as before — send, receive, reply. + client + .send_plain_message(*client.nym_address(), "persistent identity!") + .await + .unwrap(); + + 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)); + } + } + + // Always disconnect for clean shutdown — background tasks need to be + // stopped and state files flushed. + client.disconnect().await; +} +``` + +Run it twice; the address stays the same. + +## Going further: send and receive from different tasks + +Add `futures` to your `Cargo.toml`: + +```toml +futures = "0.3" +``` + +Use `split_sender()` to get a clone-able send handle for use in separate tasks: + +```rust +use futures::StreamExt; +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +#[tokio::main] +async fn main() { + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + let addr = *client.nym_address(); + + // split_sender() returns a clone-able MixnetClientSender. + let sender = client.split_sender(); + + // 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)); + } + client.disconnect().await; + }); + + // Spawn a sender on a different task. + let tx = tokio::spawn(async move { + sender.send_plain_message(addr, "hello from another task!").await.unwrap(); + }); + + tx.await.unwrap(); + rx.await.unwrap(); +} +``` + +## What's happening underneath + +`connect_new()` generates an ephemeral identity (ed25519 + x25519 keypair), fetches the current network topology, selects a gateway, and opens a persistent WebSocket connection. `send_plain_message()` wraps the payload in Sphinx packets, layered encryption where each of the 5 Mix Nodes can only decrypt one layer and learn the next hop, never the full route. `wait_for_messages()` drains a local queue fed by the gateway; messages arrive out of order by design, to defeat timing analysis. + +SURBs (Single Use Reply Blocks) are pre-computed return routes bundled with each outgoing message. The recipient uses them to reply without learning the sender's address. Each is single-use; the SDK replenishes them automatically. + +`split_sender()` clones the send channel while the original client retains the receive side. Both halves can run on separate tokio tasks without synchronization. + +## Complete code + +### Ephemeral client + +New address on every run, good for quick experiments: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + let our_address = client.nym_address(); + println!("Connected: {our_address}"); + + client + .send_plain_message(*our_address, "hello from the mixnet!") + .await + .unwrap(); + println!("Sent — waiting for arrival..."); + + 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()) { + break msg; + } + } + }; + 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(); + + 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)); + + client.disconnect().await; +} +``` + +### Persistent identity + +Same address across restarts. Use this for real applications: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender, StoragePaths}; + +#[tokio::main] +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() + .build() + .unwrap() + .connect_to_mixnet() + .await + .unwrap(); + + let our_address = client.nym_address(); + println!("Connected: {our_address}"); + + client + .send_plain_message(*our_address, "hello from the mixnet!") + .await + .unwrap(); + println!("Sent — waiting for arrival..."); + + 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()) { + break msg; + } + } + }; + 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(); + + 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)); + + client.disconnect().await; +} +``` diff --git a/documentation/docs/pages/developers/rust/stream.mdx b/documentation/docs/pages/developers/rust/stream.mdx new file mode 100644 index 0000000000..38d8d7e10a --- /dev/null +++ b/documentation/docs/pages/developers/rust/stream.mdx @@ -0,0 +1,149 @@ +--- +title: "Stream Module: AsyncRead/AsyncWrite Over the Mixnet" +description: "The Nym Stream module provides persistent, bidirectional byte channels over the mixnet with standard Rust AsyncRead and AsyncWrite traits." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + +# Stream Module + +import { Callout } from 'nextra/components' +import { CratesPaused } from '../../../components/crates-paused' + + + +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.** + +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. + +## How it works + +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. +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. + +```text +┌─────────────────────────────────────────────────────────┐ +│ MixnetClient │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ MixnetStream │ │ MixnetStream │ ... │ +│ │ (peer A) │ │ (peer B) │ │ +│ └──────┬───────┘ └──────┬───────┘ │ +│ │writes │writes │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────┐ │ +│ │ ClientInput.input_sender │ │ +│ └──────────────┬──────────────────┘ │ +│ │ │ +│ ▼ │ +│ ── mixnet ── │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────┐ │ +│ │ reconstructed_receiver │ │ +│ └──────────────┬──────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────┐ │ +│ │ Router task │ │ +│ │ decode header → dispatch by ID │ │ +│ └──┬──────────────────────────┬───┘ │ +│ │ Open messages │ Data messages │ +│ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │MixnetListener│ │ StreamMap lookup │ │ +│ │ .accept() │ │ → per-stream tx │ │ +│ └──────────────┘ └──────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +## Complete example + +A minimal example with two clients on the same machine: one opens a stream to the other, sends a message, and reads a reply. + +```rust +use nym_sdk::mixnet; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use std::time::Duration; + +const TIMEOUT: Duration = Duration::from_secs(60); + +#[tokio::main] +async fn main() { + // Connect two ephemeral clients + let mut sender = mixnet::MixnetClient::connect_new().await.unwrap(); + let mut receiver = mixnet::MixnetClient::connect_new().await.unwrap(); + let receiver_addr = *receiver.nym_address(); + + // The receiver creates a listener (activates stream mode) + let mut listener = receiver.listener().unwrap(); + + // The sender opens a stream to the receiver's Nym address + let mut outbound = sender.open_stream(receiver_addr, None).await.unwrap(); + + // The receiver accepts the incoming stream + let mut inbound = tokio::time::timeout(TIMEOUT, listener.accept()) + .await + .expect("timed out") + .expect("listener closed"); + + // Send data and read it back — just like a TCP socket + outbound.write_all(b"hello from sender").await.unwrap(); + outbound.flush().await.unwrap(); + + let mut buf = vec![0u8; 1024]; + let n = tokio::time::timeout(TIMEOUT, inbound.read(&mut buf)) + .await + .expect("timed out") + .expect("read failed"); + println!("Receiver got: {}", String::from_utf8_lossy(&buf[..n])); + + // Reply back through the same stream + inbound.write_all(b"hello from receiver").await.unwrap(); + inbound.flush().await.unwrap(); + + let n = tokio::time::timeout(TIMEOUT, outbound.read(&mut buf)) + .await + .expect("timed out") + .expect("read failed"); + println!("Sender got: {}", String::from_utf8_lossy(&buf[..n])); + + // Streams deregister on drop, then disconnect clients + drop(outbound); + drop(inbound); + sender.disconnect().await; + receiver.disconnect().await; +} +``` + + +The receiver replies via **reply SURBs** (Single Use Reply Blocks) and never learns the sender's Nym address. + + +## When to use streams vs messages + +| | Messages | Streams | TcpProxy | +|---|---|---|---| +| **Pattern** | Raw message payloads | Persistent bidirectional channels | TCP socket proxying | +| **API** | `send_plain_message()` / `wait_for_messages()` | `AsyncRead` + `AsyncWrite` | Localhost TCP socket | +| **Multiplexing** | N/A | Multiple streams per client | One client per TCP connection | +| **Ordering** | No guarantees | Sequence-based reordering | Session-based ordering | +| **Best for** | Simple notifications, one-shot requests | Interactive protocols, streaming data, any code expecting async I/O | Wrapping existing TCP applications | +| **Status** | Stable | New | Deprecated | + + +**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. + + +## Next steps + +- [Tutorial: Build a private echo server](./stream/tutorial): server and client communicating over streams +- [Architecture](./stream/architecture): wire protocol, router task, data flow, stream cleanup, and known limitations +- [Examples](./stream/examples): annotated walkthroughs of the SDK examples (multi-stream, idle timeout, throughput testing) diff --git a/documentation/docs/pages/developers/rust/stream/_meta.json b/documentation/docs/pages/developers/rust/stream/_meta.json new file mode 100644 index 0000000000..863bb09c91 --- /dev/null +++ b/documentation/docs/pages/developers/rust/stream/_meta.json @@ -0,0 +1,5 @@ +{ + "tutorial": "Tutorial", + "architecture": "Architecture", + "examples": "Examples" +} diff --git a/documentation/docs/pages/developers/rust/stream/architecture.mdx b/documentation/docs/pages/developers/rust/stream/architecture.mdx new file mode 100644 index 0000000000..205eee370e --- /dev/null +++ b/documentation/docs/pages/developers/rust/stream/architecture.mdx @@ -0,0 +1,92 @@ +--- +title: "Stream Module Architecture" +description: "Internal architecture of the Nym Stream subsystem: wire protocol, multiplexing, router task, and how concurrent byte channels share a single MixnetClient." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + +# Stream Architecture + +import { Callout } from 'nextra/components' + +{/* Canonical source: sdk/rust/nym-sdk/src/mixnet/stream/ARCHITECTURE.md */} + +## Overview + +The stream subsystem gives each `MixnetClient` the ability to hold many concurrent byte channels (`AsyncRead + AsyncWrite`) to different remote peers, multiplexed over a single client connection. + +```mermaid +--- +config: + theme: neo-dark +--- +flowchart TD + subgraph MixnetClient + SA["MixnetStream A"] -->|writes| CI["Client input channel"] + SB["MixnetStream B"] -->|writes| CI + CI --> MX["── Mixnet ──"] + MX --> RT["Router task"] + RT -->|Open messages| ML["MixnetListener.accept()"] + RT -->|Data messages| SM["Stream routing table"] + SM --> SA + SM --> SB + end +``` + +## Wire protocol + +Every stream message has a fixed 16-byte LP frame header prepended to the payload: + +``` +[LpFrameKind: 2 bytes LE][StreamId: 8 bytes BE][MsgType: 1 byte][SequenceNum: 4 bytes BE][Reserved: 1 byte][payload ...] +``` + +- **LpFrameKind:** `3` (SphinxStream). Distinguishes stream traffic from other LP frame types (Opaque, Registration, Forward). +- **StreamId:** random `u64` generated by the opener, used to multiplex streams. +- **MsgType:** `Open` (0) or `Data` (1). +- **SequenceNum:** `u32` counter, incremented per write. Used by the receiver's per-stream reorder buffer to deliver data in the correct order. +- **Reserved:** must be `0x00`. + +There is no `Close` message type; see [Known Limitations](#known-limitations) for why. + +## Stream mode + +Stream mode is activated lazily on the first call to `open_stream()` or `listener()`. This is a **one-way transition**: + +1. The client's message receiver is handed off to a background router task +2. `stream_mode` flag is set to `true` +3. Message-based methods (`send_plain_message`, `wait_for_messages`) are disabled and return errors + +There is no switching back without disconnecting and creating a new client. + +## Opening and accepting streams + +**Opening (outbound):** +1. `open_stream(recipient, surbs)` generates a random `StreamId` +2. An `Open` message is sent through the Mixnet to the recipient +3. A `MixnetStream` is returned, ready for writing and reading + +**Accepting (inbound):** +1. `listener.accept()` waits for an `Open` message from a remote peer +2. A `MixnetStream` is created with the opener's `sender_tag` for anonymous replies +3. The stream is ready for bidirectional I/O + +## Cleanup + +- **On `drop`:** the stream deregisters from the routing table. No close message is sent over the wire. +- **Idle timeout:** streams idle for longer than the configured timeout (default: 30 minutes) are automatically cleaned up. Configure with [`MixnetClientBuilder::with_stream_idle_timeout()`](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetClientBuilder.html). + +## Known limitations + + +**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. + +- **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. +- There is no `Close` message type, since a close could race ahead of in-flight data. + + +## 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. diff --git a/documentation/docs/pages/developers/rust/stream/examples.mdx b/documentation/docs/pages/developers/rust/stream/examples.mdx new file mode 100644 index 0000000000..2c72cc47a2 --- /dev/null +++ b/documentation/docs/pages/developers/rust/stream/examples.mdx @@ -0,0 +1,22 @@ +--- +title: "Stream Module Examples" +description: "Runnable Rust examples for the Nym Stream module: bidirectional read/write, idle timeouts, mode guards, and throughput benchmarks." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + +# Examples + +Runnable examples in [`sdk/rust/nym-sdk/examples/`](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples). Each file is self-contained with step-by-step comments. + +```bash +cargo run --example +``` + +| Example | Source | What it demonstrates | +|---|---|---| +| Simple Read/Write | [`stream_simple_read_write.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_simple_read_write.rs) | Multiple concurrent streams, bidirectional communication | +| Idle Timeout | [`stream_idle_timeout.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_idle_timeout.rs) | Configuring `with_stream_idle_timeout`, observing EOF after cleanup | +| Mode Guard | [`stream_mode_guard.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_mode_guard.rs) | Mutual exclusion between stream and message modes | +| Throughput | [`stream_throughput.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_throughput.rs) | Sending 1 MB over a single stream, verifying data integrity | diff --git a/documentation/docs/pages/developers/rust/stream/tutorial.mdx b/documentation/docs/pages/developers/rust/stream/tutorial.mdx new file mode 100644 index 0000000000..5ebef6382b --- /dev/null +++ b/documentation/docs/pages/developers/rust/stream/tutorial.mdx @@ -0,0 +1,353 @@ +--- +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" +--- + +# Tutorial: Build a Private Echo Server + +import { Callout } from 'nextra/components' +import { CodeVerified } from '../../../../components/code-verified' + +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. + +## What you'll learn + +- Setting up a `MixnetListener` to accept incoming streams +- Opening an outbound stream with `open_stream()` +- Reading and writing with standard tokio I/O traits +- How streams are multiplexed over a single `MixnetClient` +- Clean shutdown and stream lifecycle + + + +## Prerequisites + +- Rust toolchain (1.70+) +- A working internet connection (clients connect to the live Nym Mixnet) + +## Step 1: Set up the project + +```sh +cargo init nym-echo +cd nym-echo +rm src/main.rs +``` + +Add dependencies to `Cargo.toml`: + +```toml +[dependencies] +nym-sdk = { git = "https://github.com/nymtech/nym", rev = "4077717" } +nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "4077717", features = ["basic_tracing"] } +tokio = { version = "1", features = ["full"] } +blake3 = "=1.7.0" # required pin — see https://nymtech.net/developers/rust/importing +``` + +## Step 2: Build the echo server + +The server connects a `MixnetClient`, creates a listener, and accepts streams in a loop. Each stream gets its own task that reads data and writes it back. + +Create `src/bin/server.rs`: + +```rust +use nym_sdk::mixnet; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + + // Connect to the Mixnet + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + println!("Echo server listening at: {}", client.nym_address()); + + // Create a listener — this activates stream mode. + // From this point, message-based methods are disabled. + let mut listener = client.listener().unwrap(); + + // Accept streams in a loop + loop { + let mut stream = match listener.accept().await { + Some(s) => s, + None => { + println!("Listener closed"); + break; + } + }; + + let stream_id = stream.id(); + println!("Accepted stream {stream_id}"); + + // Spawn a task to handle each stream concurrently + tokio::spawn(async move { + let mut buf = vec![0u8; 4096]; + + loop { + let n = match stream.read(&mut buf).await { + Ok(0) => break, // EOF — stream closed + Ok(n) => n, + Err(e) => { + eprintln!("Stream {stream_id} read error: {e}"); + break; + } + }; + + let data = &buf[..n]; + println!( + "Stream {stream_id} received {} bytes: {:?}", + n, + String::from_utf8_lossy(data) + ); + + // Echo it back + 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"); + }); + } +} +``` + + +**`listener()` can only be called once per client.** It takes exclusive ownership of the inbound message channel. A second call returns `Error::ListenerAlreadyTaken`. + + +## Step 3: Build the client + +The client connects, opens a stream to the server, sends a few messages, reads back the echoes, and disconnects. + +Create `src/bin/client.rs`: + +```rust +use nym_sdk::mixnet::{self, Recipient}; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +const TIMEOUT: Duration = Duration::from_secs(60); + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + + // Read the server's Nym address from the command line + let server_addr: Recipient = std::env::args() + .nth(1) + .expect("Usage: client ") + .parse() + .expect("Invalid Nym address"); + + // Connect to the Mixnet + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + println!("Client address: {}", client.nym_address()); + + // Open a stream to the server. + // The second argument (None) uses the default number of reply SURBs. + let mut stream = client.open_stream(server_addr, None).await.unwrap(); + 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 + // 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 messages and read back the echo for each + for i in 1..=3 { + let msg = format!("message {i}"); + println!("Sending: {msg}"); + + stream.write_all(msg.as_bytes()).await.unwrap(); + stream.flush().await.unwrap(); + + // Read the echo + let mut buf = vec![0u8; 1024]; + let n = tokio::time::timeout(TIMEOUT, stream.read(&mut buf)) + .await + .expect("timed out waiting for echo") + .expect("read failed"); + + println!("Echo: {}", String::from_utf8_lossy(&buf[..n])); + } + + // Drop the stream to deregister it from the router + drop(stream); + + // Disconnect the client + client.disconnect().await; + println!("Done!"); +} +``` + +## Step 4: Run it + +In one terminal, start the server: + +```sh +RUST_LOG=info cargo run --bin server +``` + +It prints its Nym address: + +``` +Echo server listening at: 8gk4Y...@2xU4d... +``` + +In a second terminal, start the client with the server's address: + +```sh +RUST_LOG=info cargo run --bin client -- 8gk4Y...@2xU4d... +``` + +You'll see the messages traverse the Mixnet and echo back: + +``` +Client address: F3qR7...@9nK2m... +Stream opened: 12345678 +Sending: message 1 +Echo: message 1 +Sending: message 2 +Echo: message 2 +Sending: message 3 +Echo: message 3 +Done! +``` + +On the server side: + +``` +Accepted stream 12345678 +Stream 12345678 received 9 bytes: "message 1" +Stream 12345678 received 9 bytes: "message 2" +Stream 12345678 received 9 bytes: "message 3" +Stream 12345678 closed +``` + +## How it works internally + +1. The server's `listener()` activates **stream mode**, which spawns a **router task** that decodes incoming Mixnet messages and dispatches them by stream ID. + +2. The client's `open_stream()` generates a random 8-byte `StreamId`, sends an `Open` message through the Mixnet, and registers the stream in a local routing table. + +3. When the server's router receives the `Open` message, it delivers it to `listener.accept()`, which creates the inbound `MixnetStream`. + +4. Each `write_all()` prepends a 16-byte LP frame header (`[LpFrameKind: 2B][StreamId: 8B][MsgType: 1B][SequenceNum: 4B][Reserved: 1B]`) and sends the data through the Mixnet as a Sphinx packet. + +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. + +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. + +See the [Architecture](./architecture) page for the full technical details. + +## What you've learned + +- `client.listener()` activates stream mode and returns a `MixnetListener` +- `listener.accept()` blocks until a remote peer opens a stream +- `client.open_stream(recipient, surbs)` opens an outbound stream to a Nym address +- `MixnetStream` implements `AsyncRead + AsyncWrite`, so standard tokio I/O works unchanged +- Multiple streams are multiplexed over a single client +- Streams deregister on `drop`; no close handshake is needed +- The server replies via SURBs and never learns the client's address + +## Complete code + +### Server (`src/bin/server.rs`) + +```rust +use nym_sdk::mixnet; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + println!("Echo server listening at: {}", client.nym_address()); + + let mut listener = client.listener().unwrap(); + + loop { + let mut stream = match listener.accept().await { + Some(s) => s, + None => break, + }; + + let stream_id = stream.id(); + println!("Accepted stream {stream_id}"); + + tokio::spawn(async move { + let mut buf = vec![0u8; 4096]; + loop { + let n = match stream.read(&mut buf).await { + Ok(0) | Err(_) => break, + Ok(n) => n, + }; + if let Err(_) = stream.write_all(&buf[..n]).await { + break; + } + stream.flush().await.unwrap(); + } + println!("Stream {stream_id} closed"); + }); + } +} +``` + +### Client (`src/bin/client.rs`) + +```rust +use nym_sdk::mixnet::{self, Recipient}; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +const TIMEOUT: Duration = Duration::from_secs(60); + +#[tokio::main] +async fn main() { + nym_bin_common::logging::setup_tracing_logger(); + + let server_addr: Recipient = std::env::args() + .nth(1) + .expect("Usage: client ") + .parse() + .expect("Invalid Nym address"); + + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + println!("Client address: {}", client.nym_address()); + + let mut stream = client.open_stream(server_addr, None).await.unwrap(); + println!("Stream opened: {}", stream.id()); + + // Wait for the Open message to reach the server through the mixnet + tokio::time::sleep(Duration::from_secs(5)).await; + + for i in 1..=3 { + let msg = format!("message {i}"); + println!("Sending: {msg}"); + + stream.write_all(msg.as_bytes()).await.unwrap(); + stream.flush().await.unwrap(); + + let mut buf = vec![0u8; 1024]; + let n = tokio::time::timeout(TIMEOUT, stream.read(&mut buf)) + .await + .expect("timed out waiting for echo") + .expect("read failed"); + + println!("Echo: {}", String::from_utf8_lossy(&buf[..n])); + } + + drop(stream); + client.disconnect().await; + println!("Done!"); +} +``` diff --git a/documentation/docs/pages/developers/rust/tcpproxy.mdx b/documentation/docs/pages/developers/rust/tcpproxy.mdx index e8fb886374..af84b9bf07 100644 --- a/documentation/docs/pages/developers/rust/tcpproxy.mdx +++ b/documentation/docs/pages/developers/rust/tcpproxy.mdx @@ -3,14 +3,253 @@ 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." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-02-11" +lastUpdated: "2026-03-27" --- # TcpProxy Module import { Callout } from 'nextra/components'; +import { CodeVerified } from '../../../components/code-verified' -This module exposes the `TcpProxyClient` and the `TcpProxyServer` which can be used to proxy traffic through the Mixnet in a way that is more familiar to developers than the methods exposed by the [`Mixnet` module](./mixnet). + + **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. + -Both `Client` and `Server` are intended to be initialised and then run in a background thread, exposing a configurable `localhost` socket which developers can read/write/stream to without having to worry about the message-based nature of sending and receiving traffic to/from the Mixnet. +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 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). + +## Examples + +| Example | Source | +|---|---| +| Single connection | [`tcp_proxy_single_connection.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_single_connection.rs) | +| Multiple connections | [`tcp_proxy_multistream.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_multistream.rs) | + +```bash +cargo run --example tcp_proxy_single_connection +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 + + + +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 = "4077717" } +nym-network-defaults = { git = "https://github.com/nymtech/nym", rev = "4077717" } +nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "4077717", features = ["basic_tracing"] } +tokio = { version = "1", features = ["full"] } +anyhow = "1" +blake3 = "=1.7.0" # required pin — see https://nymtech.net/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::); + + let server_addr: Recipient = std::env::args() + .nth(1).expect("Usage: proxy_client ") + .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 -- +``` + +The response will take 30–60 seconds to arrive as it traverses the Mixnet in both directions. + +## 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. + +### 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: + +```rust +pub struct ProxiedMessage { + message: Payload, + session_id: Uuid, + message_id: u16, +} +``` + +### 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. diff --git a/documentation/docs/pages/developers/rust/tcpproxy/_meta.json b/documentation/docs/pages/developers/rust/tcpproxy/_meta.json deleted file mode 100644 index 7b7e6bb821..0000000000 --- a/documentation/docs/pages/developers/rust/tcpproxy/_meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "architecture": "Architecture", - "examples": "Examples", - "troubleshooting": "Troubleshooting" -} diff --git a/documentation/docs/pages/developers/rust/tcpproxy/architecture.mdx b/documentation/docs/pages/developers/rust/tcpproxy/architecture.mdx deleted file mode 100644 index 7a7965673b..0000000000 --- a/documentation/docs/pages/developers/rust/tcpproxy/architecture.mdx +++ /dev/null @@ -1,209 +0,0 @@ - -# Architecture - -import { Callout } from 'nextra/components' - -## Motivations -The motivation behind the creation of the `TcpProxy` module is to allow developers to interact with the Mixnet in a way that is far more familiar to them: simply setting up a connection with a transport, being returned a socket, and then being able to stream data to/from it, similar to something like the Tor [`arti`](https://gitlab.torproject.org/tpo/core/arti/-/tree/main/crates/arti-client) client. - - - This is an initial version of the module which we are releasing to developers to experiment with. If you run into problems or any functionality that is missing, do reach out on Matrix and let us know. - - Furthermore we will be working on optimisations to the module over time - most of this will occur under the hood (e.g. implementing a configurable connection pool for the `ProxyClient`), but all updates will occur according to SemVer, so don't worry about breaking changes! - - -## Clients -Each of the sub-modules exposed by the `TcpProxy` deal with Nym clients in a different way. -- the `NymProxyClient` relies on the [`Client Pool`](../client-pool) to create clients and keep a certain number of them in reserve. If the amount of incoming TCP connections rises quicker than the Client Pool can create clients, or you have the pool size set to `0`, the `TcpProxyClient` creates an ephemeral client per new TCP connection, which is closed according to the configurable timeout: we map one ephemeral client per TCP connection. This is to deal with multiple simultaneous streams. -- the `NymProxyServer` has a single Nym client with a persistent identity. - -## Framing -We are currently relying on the [`tokio::Bytecodec`](https://docs.rs/tokio-util/latest/tokio_util/codec/struct.BytesCodec.html) and [`framedRead`](https://docs.rs/tokio-util/latest/tokio_util/codec/struct.Framed.html) to frame bytes moving through the `NymProxyClient` and `NymProxyServer`. - -> For those interested, under the hood the client uses our own [`NymCodec`](https://github.com/nymtech/nym/blob/27ac34522cf0f8bfe1ca265e0b57ee52f2ded0d2/common/nymsphinx/framing/src/codec.rs) to frame message bytes as Sphinx packet payloads. - -## Sessions & Message Ordering -We have implemented session management and message ordering, where messages are wrapped in a session ID per connection, with individual messages being given an incrememting message ID. Once all the messages have been sent, the `NymProxyClient` then sends a `Close` message as the last outgoing message. This is to notify the `NymProxyServer` that there are no more outbound messages for this session, and that it can trigger the session timeout. - -> 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 gRPC parser before the 1st half, even if it is received first. - -Lets step through a full request/response path between a client process communicating with a remote host via the proxies: - -### Outgoing Client Request -The `NymProxyClient` instance, once initialised and running, listens out for incoming TCP connections on its localhost port. - -On receiving one, it will create a new session ID and packetise the incoming bytes into messages of the following structure: - -```rust -pub struct ProxiedMessage { - message: Payload, - session_id: Uuid, - message_id: u16, -} -``` - -> This code can be found [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/tcp_proxy/utils.rs#L147C1-L152C2) - -And then send these to the Nym address of the `NymProxyServer` instance. Not much to see here regarding message ordering, as the potential for reordering only starts once packets are travelling through the Mixnet. - -```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->>EntryGateway: Sphinx Packets: Message 1 - EntryGateway-->>NymProxyClient: Acks - NymProxyClient->>EntryGateway: Sphinx Packets: Message 2 - EntryGateway-->>NymProxyClient: Acks - NymProxyClient->>EntryGateway: Sphinx Packets: Message 3 - EntryGateway-->>NymProxyClient: Acks - NymProxyClient->>EntryGateway: Sphinx Packets: Close Message - NymProxyClient->>NymProxyClient: Start Client Close timeout - EntryGateway-->>NymProxyClient: Acks -``` - -### Server Receives Request & Responds - -Here is a diagrammatic representation of a situation in which the request arrives out of order, and how the message buffer deals with this so as not to pass a malformed request upstream to the process running on the same remote host: - -```mermaid ---- -config: - theme: neo-dark - layout: elk ---- -sequenceDiagram - Exit Gateway->>NymProxyServer: Sphinx Packets: Message 2 - NymProxyServer-->>Exit Gateway: Acks - Exit Gateway->>NymProxyServer: Sphinx Packets: Message 3 - 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: Message Close - 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 -``` - -> Note that this is per-session, with a session mapped to a single TCP connection. Both the `NymProxyClient` and `Server` are able to handle multiple concurrent connections. - -### Client Receives Response - -The `ProxyClient` deals with incoming traffic in the same way as the `ProxyServer`, with a per-session message queue: - -```mermaid ---- -config: - theme: neo-dark - layout: elk ---- -sequenceDiagram - box Local Machine - participant Client Process - participant NymProxyClient - end - Entry Gateway--xNymProxyClient: Sphinx Packets: Reply Message 1 dropped: No Ack! - Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 2 - NymProxyClient-->Entry Gateway: Ack - Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 3 - 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** -``` - -After receiving the packets, it can then forward the recoded bytes to the requesting process. - -### Full Flow Diagram -```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: Message 3 - 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 - Exit Gateway->>NymProxyServer: Sphinx Packets: Message 3 - 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--xNymProxyClient: Sphinx Packets: Reply Message 1 dropped: No Ack! - Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 2 - NymProxyClient-->Entry Gateway: Ack - Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 3 - 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** -``` diff --git a/documentation/docs/pages/developers/rust/tcpproxy/examples/_meta.json b/documentation/docs/pages/developers/rust/tcpproxy/examples/_meta.json deleted file mode 100644 index 925cba8b48..0000000000 --- a/documentation/docs/pages/developers/rust/tcpproxy/examples/_meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "singleconn": "Single Connection", - "multiconn": "Multi Connection" -} diff --git a/documentation/docs/pages/developers/rust/tcpproxy/examples/multiconn.mdx b/documentation/docs/pages/developers/rust/tcpproxy/examples/multiconn.mdx deleted file mode 100644 index daeb38069e..0000000000 --- a/documentation/docs/pages/developers/rust/tcpproxy/examples/multiconn.mdx +++ /dev/null @@ -1,170 +0,0 @@ -# Multi Connection Example -import { Callout } from 'nextra/components' - - -This example starts off several Tcp connections on a loop to a remote endpoint: in this case the `TcpListener` behind the `NymProxyServer` instance on the echo server found in -[`nym/tools/echo-server/`](https://github.com/nymtech/nym/tree/develop/tools/echo-server). It pipes a few messages to it, logs the replies, and keeps track of the number of replies received per connection. - -> You can find this code [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_multistream.rs) - -```rust -use nym_sdk::mixnet::Recipient; -use nym_sdk::tcp_proxy; -use rand::rngs::SmallRng; -use rand::Rng; -use rand::SeedableRng; -use serde::{Deserialize, Serialize}; -use std::env; -use tokio::io::AsyncWriteExt; -use tokio::net::TcpStream; -use tokio::signal; -use tokio_stream::StreamExt; -use tokio_util::codec; -use tokio_util::sync::CancellationToken; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; - -#[derive(Serialize, Deserialize, Debug)] -struct ExampleMessage { - message_id: i8, - message_bytes: Vec, - tcp_conn: i8, -} - -// This example just starts off a bunch of Tcp connections on a loop to a remote endpoint: in this case the TcpListener behind the NymProxyServer instance on the echo server found in `nym/tools/echo-server/`. It pipes a few messages to it, logs the replies, and keeps track of the number of replies received per connection. -// -// To run: -// - run the echo server with `cargo run` -// - run this example with `cargo run --example tcp_proxy_multistream -- ` e.g. -// cargo run --example tcp_proxy_multistream -- DMHyxo8n6sKWHHTVvjRVDxDSMX8gYXRU1AQ6UpwsrWiB.6STYCWGWyRxqn2juWdgjMkAMsT9EaAzPpLWq5zkS68MB@CJG5zTcmoLijmDrtAiLV9PZHxNz8LQu6hmgA89V2RxxL ../../../envs/canary.env 8080 -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let server_address = env::args().nth(1).expect("Server address not provided"); - let server: Recipient = - Recipient::try_from_base58_string(&server_address).expect("Invalid server address"); - - // Comment this out to just see println! statements from this example. - // Nym client logging is very informative but quite verbose. - // The Message Decay related logging gives you an ideas of the internals of the proxy message ordering: you need to switch - // to DEBUG to see the contents of the msg buffer, sphinx packet chunking, etc. - tracing_subscriber::registry() - .with(fmt::layer()) - .with( - EnvFilter::new("info") - .add_directive("nym_sdk::client_pool=info".parse().unwrap()) - .add_directive("nym_sdk::tcp_proxy_client=debug".parse().unwrap()), - ) - .init(); - - let env_path = env::args().nth(2).expect("Env file not specified"); - let env = env_path.to_string(); - - let listen_port = env::args().nth(3).expect("Port not specified"); - - // Within the TcpProxyClient, individual client shutdown is triggered by the timeout. The final argument is how many clients to keep in reserve in the client pool when running the TcpProxy. - let proxy_client = - tcp_proxy::NymProxyClient::new(server, "127.0.0.1", &listen_port, 45, Some(env), 2).await?; - - // For our disconnect() logic below - let proxy_clone = proxy_client.clone(); - - tokio::spawn(async move { - proxy_client.run().await?; - Ok::<(), anyhow::Error>(()) - }); - - let example_cancel_token = CancellationToken::new(); - let client_cancel_token = example_cancel_token.clone(); - let watcher_cancel_token = example_cancel_token.clone(); - - // Cancel listener thread - tokio::spawn(async move { - signal::ctrl_c().await?; - println!(":: CTRL_C received, shutting down + cleanup up proxy server config files"); - watcher_cancel_token.cancel(); - proxy_clone.disconnect().await; - Ok::<(), anyhow::Error>(()) - }); - - println!("waiting for everything to be set up.."); - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; - println!("done. sending bytes"); - - // In the info traces you will see the different session IDs being set up, one for each TcpStream. - for i in 0..8 { - let client_cancel_inner_token = client_cancel_token.clone(); - if client_cancel_token.is_cancelled() { - break; - } - let conn_id = i; - let local_tcp_addr = format!("127.0.0.1:{}", listen_port.clone()); - tokio::spawn(async move { - // Now the client and server proxies are running we can create and pipe traffic to/from - // a socket on the same port as our ProxyClient instance as if we were just communicating - // between a client and host via a normal TcpStream - albeit with a decent amount of additional latency. - // - // The assumption regarding integration is that you know what you're sending, and will do proper - // framing before and after, know what data types you're expecting; the proxies are just piping bytes - // back and forth using tokio's `Bytecodec` under the hood. - - let stream = TcpStream::connect(local_tcp_addr).await?; - let (read, mut write) = stream.into_split(); - - // Lets just send a bunch of messages to the server with variable delays between them, with a message and tcp connection ids to keep track of ordering on the server side (for illustrative purposes **only**; keeping track of anonymous replies is handled by the proxy under the hood with Single Use Reply Blocks (SURBs); for this illustration we want some kind of app-level message id, but irl most of the time you'll probably be parsing on e.g. the incoming response type instead) - tokio::spawn(async move { - for i in 0..8 { - if client_cancel_inner_token.is_cancelled() { - break; - } - let mut rng = SmallRng::from_entropy(); - let delay: f64 = rng.gen_range(2.5..5.0); - tokio::time::sleep(tokio::time::Duration::from_secs_f64(delay)).await; - let random_bytes = gen_bytes_fixed(i as usize); - let msg = ExampleMessage { - message_id: i, - message_bytes: random_bytes, - tcp_conn: conn_id, - }; - let serialised = bincode::serialize(&msg)?; - write - .write_all(&serialised) - .await - .expect("couldn't write to stream"); - println!(">> client sent msg {} on conn {}", &i, &conn_id); - } - Ok::<(), anyhow::Error>(()) - }); - - tokio::spawn(async move { - let mut reply_counter = 0; - let codec = codec::BytesCodec::new(); - let mut framed_read = codec::FramedRead::new(read, codec); - while let Some(Ok(bytes)) = framed_read.next().await { - match bincode::deserialize::(&bytes) { - Ok(msg) => { - reply_counter += 1; - println!("<< conn {} received {}/8", msg.tcp_conn, reply_counter); - } - Err(e) => { - println!("<< client received something that wasn't an example message of {} bytes. error: {}", bytes.len(), e); - } - } - } - }); - Ok::<(), anyhow::Error>(()) - }); - let mut rng = SmallRng::from_entropy(); - let delay: f64 = rng.gen_range(4.5..7.0); - tokio::time::sleep(tokio::time::Duration::from_secs_f64(delay)).await; - } - - Ok(()) -} - -// emulate a series of small messages followed by a closing larger one -fn gen_bytes_fixed(i: usize) -> Vec { - let amounts = [10, 15, 50, 1000, 10, 15, 500, 2000]; - let len = amounts[i]; - let mut rng = rand::thread_rng(); - (0..len).map(|_| rng.gen::()).collect() -} -``` diff --git a/documentation/docs/pages/developers/rust/tcpproxy/examples/singleconn.mdx b/documentation/docs/pages/developers/rust/tcpproxy/examples/singleconn.mdx deleted file mode 100644 index 47f341a893..0000000000 --- a/documentation/docs/pages/developers/rust/tcpproxy/examples/singleconn.mdx +++ /dev/null @@ -1,229 +0,0 @@ -# Single Connection Example -import { Callout } from 'nextra/components' - - -This is a basic example which opens a single TCP connection and writes a bunch of messages between a client and some 'echo server' logic, so only uses a single session under the hood and doesn't really show off the message ordering capabilities; this is mainly just a quick introductory illustration on how: -- the mixnet does message ordering -- the NymProxyClient and NymProxyServer can be hooked into and used to communicate between two otherwise pretty vanilla TcpStreams - -For a more irl example check the [multi connection example](./multiconn). - -> You can find this code [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_single_connection.rs) - -```rust -use nym_sdk::tcp_proxy; -use rand::rngs::SmallRng; -use rand::{Rng, SeedableRng}; -use serde::{Deserialize, Serialize}; -use std::env; -use std::fs; -use std::sync::atomic::{AtomicU8, Ordering}; -use tokio::io::AsyncWriteExt; -use tokio::net::{TcpListener, TcpStream}; -use tokio::signal; -use tokio_stream::StreamExt; -use tokio_util::codec; -use tokio_util::sync::CancellationToken; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; - -#[derive(Serialize, Deserialize, Debug)] -struct ExampleMessage { - message_id: i8, - message_bytes: Vec, -} - -// This is a basic example which opens a single TCP connection and writes a bunch of messages between a client and an echo -// server, so only uses a single session under the hood and doesn't really show off the message ordering capabilities; this is mainly -// just a quick introductory illustration on how: -// - the mixnet does message ordering -// - the NymProxyClient and NymProxyServer can be hooked into and used to communicate between two otherwise pretty vanilla TcpStreams -// -// For a more irl example checkout tcp_proxy_multistream.rs -// -// Run this with: -// `cargo run --example tcp_proxy_single_connection ` e.g. -// `cargo run --example tcp_proxy_single_connection 8081 ../../../envs/canary.env 8080 ` -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Keep track of sent/received messages - let counter = AtomicU8::new(0); - - // Comment this out to just see println! statements from this example, as Nym client logging is very informative but quite verbose. - // The Message Decay related logging gives you an ideas of the internals of the proxy message ordering. To see the contents of the msg buffer, sphinx packet chunking, etc change the tracing::Level to DEBUG. - tracing_subscriber::registry() - .with(fmt::layer()) - .with(EnvFilter::new("nym_sdk::tcp_proxy=info")) - .init(); - - let server_port = env::args() - .nth(1) - .expect("Server listen port not specified"); - let upstream_tcp_addr = format!("127.0.0.1:{}", server_port); - - // This dir gets cleaned up at the end: NOTE if you switch env between tests without letting the file do the automatic cleanup, make sure to manually remove this directory up before running again, otherwise your client will attempt to use these keys for the new env - let home_dir = dirs::home_dir().expect("Unable to get home directory"); - let conf_path = format!("{}/tmp/nym-proxy-server-config", home_dir.display()); - - let env_path = env::args().nth(2).expect("Env file not specified"); - let env = env_path.to_string(); - let client_port = env::args().nth(3).expect("Port not specified"); - - let mut proxy_server = - tcp_proxy::NymProxyServer::new(&upstream_tcp_addr, &conf_path, Some(env_path.clone())) - .await?; - let proxy_nym_addr = proxy_server.nym_address(); - - // We'll run the instance with a long timeout since we're sending everything down the same Tcp connection, so should be using a single session. - // Within the TcpProxyClient, individual client shutdown is triggered by the timeout. - // The final argument is how many clients to keep in reserve in the client pool when running the TcpProxy. - let proxy_client = - tcp_proxy::NymProxyClient::new(*proxy_nym_addr, "127.0.0.1", &client_port, 5, Some(env), 1) - .await?; - - // For our disconnect() logic below - let proxy_clone = proxy_client.clone(); - - tokio::spawn(async move { - proxy_server.run_with_shutdown().await?; - Ok::<(), anyhow::Error>(()) - }); - - tokio::spawn(async move { - proxy_client.run().await?; - Ok::<(), anyhow::Error>(()) - }); - - let example_cancel_token = CancellationToken::new(); - let server_cancel_token = example_cancel_token.clone(); - let client_cancel_token = example_cancel_token.clone(); - let watcher_cancel_token = example_cancel_token.clone(); - - // Cancel listener thread - tokio::spawn(async move { - signal::ctrl_c().await?; - println!(":: CTRL_C received, shutting down + cleanup up proxy server config files"); - fs::remove_dir_all(conf_path)?; - watcher_cancel_token.cancel(); - proxy_clone.disconnect().await; - Ok::<(), anyhow::Error>(()) - }); - - // 'Server side' thread: echo back incoming as response to the messages sent in the 'client side' thread below - tokio::spawn(async move { - let listener = TcpListener::bind(upstream_tcp_addr).await?; - loop { - if server_cancel_token.is_cancelled() { - break; - } - let (socket, _) = listener.accept().await.unwrap(); - let (read, mut write) = socket.into_split(); - let codec = codec::BytesCodec::new(); - let mut framed_read = codec::FramedRead::new(read, codec); - while let Some(Ok(bytes)) = framed_read.next().await { - match bincode::deserialize::(&bytes) { - Ok(msg) => { - println!( - "<< server received {}: {} bytes", - msg.message_id, - msg.message_bytes.len() - ); - let msg = ExampleMessage { - message_id: msg.message_id, - message_bytes: msg.message_bytes, - }; - let serialised = bincode::serialize(&msg)?; - write - .write_all(&serialised) - .await - .expect("couldnt send reply"); - println!( - ">> server sent {}: {} bytes", - msg.message_id, - msg.message_bytes.len() - ); - } - Err(e) => { - println!("<< server received something that wasn't an example message of {} bytes. error: {}", bytes.len(), e); - } - } - } - } - #[allow(unreachable_code)] - Ok::<(), anyhow::Error>(()) - }); - - // Just wait for Nym clients to connect, TCP clients to bind, etc. If there isn't a client in the pool (or you started it with 0) already then the TcpProxyClient just spins up an ephemeral client itself. - println!("waiting for everything to be set up.."); - tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; - println!("done. sending bytes"); - - // Now the client and server proxies are running we can create and pipe traffic to/from - // a socket on the same port as our ProxyClient instance as if we were just communicating - // between a client and host via a normal TcpStream - albeit with a decent amount of additional latency. - // - // The assumption regarding integration is that you know what you're sending, and will do proper - // framing before and after, know what data types you're expecting, etc; the proxies are just piping bytes - // back and forth using tokio's `Bytecodec` under the hood. - let local_tcp_addr = format!("127.0.0.1:{}", client_port); - let stream = TcpStream::connect(local_tcp_addr).await?; - let (read, mut write) = stream.into_split(); - - // 'Client side' thread; lets just send a bunch of messages to the server with variable delays between them, with an id to keep track of ordering in the printlns; the mixnet only guarantees message delivery, not ordering. You might not be necessarily streaming traffic in this manner IRL, but this example is a good illustration of how messages travel through the mixnet. - // - On the level of individual messages broken into multiple packets, the Proxy abstraction deals with making sure that everything is sent between the sockets in the corrent order. - // - On the level of different messages, this is not enforced: you might see in the logs that message 1 arrives at the server and is reconstructed after message 2. - tokio::spawn(async move { - let mut rng = SmallRng::from_entropy(); - for i in 0..10 { - if client_cancel_token.is_cancelled() { - break; - } - let random_bytes = gen_bytes_fixed(i as usize); - let msg = ExampleMessage { - message_id: i, - message_bytes: random_bytes, - }; - let serialised = bincode::serialize(&msg)?; - write - .write_all(&serialised) - .await - .expect("couldn't write to stream"); - println!(">> client sent {}: {} bytes", &i, msg.message_bytes.len()); - let delay = rng.gen_range(3.0..7.0); - tokio::time::sleep(tokio::time::Duration::from_secs_f64(delay)).await; - } - Ok::<(), anyhow::Error>(()) - }); - - let codec = codec::BytesCodec::new(); - let mut framed_read = codec::FramedRead::new(read, codec); - while let Some(Ok(bytes)) = framed_read.next().await { - match bincode::deserialize::(&bytes) { - Ok(msg) => { - println!( - "<< client received {}: {} bytes", - msg.message_id, - msg.message_bytes.len() - ); - counter.fetch_add(1, Ordering::SeqCst); - println!( - ":: messages received back: {:?}/10", - counter.load(Ordering::SeqCst) - ); - } - Err(e) => { - println!("<< client received something that wasn't an example message of {} bytes. error: {}", bytes.len(), e); - } - } - } - - Ok(()) -} - -fn gen_bytes_fixed(i: usize) -> Vec { - // let amounts = vec![1, 10, 50, 100, 150, 200, 350, 500, 750, 1000]; - let amounts = [158, 1088, 505, 1001, 150, 200, 3500, 500, 750, 100]; - let len = amounts[i]; - let mut rng = rand::thread_rng(); - (0..len).map(|_| rng.gen::()).collect() -} -``` diff --git a/documentation/docs/pages/developers/rust/tcpproxy/troubleshooting.mdx b/documentation/docs/pages/developers/rust/tcpproxy/troubleshooting.mdx deleted file mode 100644 index 8688ae63f4..0000000000 --- a/documentation/docs/pages/developers/rust/tcpproxy/troubleshooting.mdx +++ /dev/null @@ -1,5 +0,0 @@ -# Troubleshooting -import { Callout } from 'nextra/components' - -## Lots of `duplicate fragment received` messages -You might see a lot of `WARN` level logs about duplicate fragments in your logs, depending on the log level you're using. This occurs when a packet is retransmitted somewhere in the Mixnet, but then the original makes it to the destination client as well. This is not something to do with your client logic, but instead the state of the Mixnet. diff --git a/documentation/docs/pages/developers/rust/tour.mdx b/documentation/docs/pages/developers/rust/tour.mdx new file mode 100644 index 0000000000..2a2901853d --- /dev/null +++ b/documentation/docs/pages/developers/rust/tour.mdx @@ -0,0 +1,164 @@ +# Tour of the Rust SDK + +import { Callout } from 'nextra/components' + +A quick walkthrough of the most important things you can do with `nym-sdk`. Each section shows working code and links to the module that covers it in depth. + + +**The Mixnet is not like regular internet networking.** There are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network: you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination. + +The raw [message API](./mixnet) therefore works differently from what most developers expect. The [Stream module](./stream) bridges this gap by providing `AsyncRead + AsyncWrite` byte streams on top of the Mixnet. If you are coming from socket-based networking, start with streams. + + +## Send a raw message payload + +The message API gives you direct access to the Mixnet's native communication model: individually addressed payloads with no connections and no ordering guarantees. This is useful when you want full control, but it's not how most networking code works: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +#[tokio::main] +async fn main() { + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + let addr = *client.nym_address(); + println!("Connected: {addr}"); + + // Send a message to ourselves + client + .send_plain_message(addr, "hello mixnet!") + .await + .unwrap(); + + // Receive it (filter empty SURB management messages) + if let Some(msgs) = client.wait_for_messages().await { + for msg in msgs.iter().filter(|m| !m.message.is_empty()) { + println!("Got: {}", String::from_utf8_lossy(&msg.message)); + } + } + + // Always disconnect for clean shutdown + client.disconnect().await; +} +``` + +The message is Sphinx-encrypted, mixed across 5 nodes, and reconstructed on arrival. The whole round trip takes a few seconds. + +Next: [Mixnet module](./mixnet) | [Tutorial: Send Your First Private Message](./mixnet/tutorial) + +## Reply anonymously with SURBs + +Every received message carries a `sender_tag`, an opaque token that lets you reply **without knowing the sender's Nym address**. Replies travel back through pre-built Single Use Reply Blocks (SURBs): + +```rust +// After receiving a message... +let tag = received_msg.sender_tag.expect("message includes sender tag"); +client.send_reply(tag, "anonymous reply!").await.unwrap(); +``` + +The replying side never learns where the reply is going, enabling anonymous communication without mutual identity disclosure. + +## Open a bidirectional stream + +If you're used to working with TCP sockets, this is where you'll feel at home. The [Stream module](./stream) provides persistent, bidirectional byte channels that implement tokio's `AsyncRead + AsyncWrite`, so any code that works with sockets works with `MixnetStream`: + +```rust +use nym_sdk::mixnet; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[tokio::main] +async fn main() { + let mut sender = mixnet::MixnetClient::connect_new().await.unwrap(); + let mut receiver = mixnet::MixnetClient::connect_new().await.unwrap(); + let recv_addr = *receiver.nym_address(); + + // Receiver creates a listener (activates stream mode) + let mut listener = receiver.listener().unwrap(); + + // Sender opens a stream to the receiver + let mut out = sender.open_stream(recv_addr, None).await.unwrap(); + + // Receiver accepts it + let mut inc = listener.accept().await.unwrap(); + + // Standard tokio I/O — write, flush, read + out.write_all(b"hello stream").await.unwrap(); + out.flush().await.unwrap(); + + let mut buf = vec![0u8; 1024]; + let n = inc.read(&mut buf).await.unwrap(); + println!("{}", String::from_utf8_lossy(&buf[..n])); + + drop(out); + drop(inc); + sender.disconnect().await; + receiver.disconnect().await; +} +``` + + +Activating stream mode (by calling `listener()` or `open_stream()`) disables message-based methods like `send_plain_message()` and `wait_for_messages()`. A single client operates in one mode at a time. + + +Next: [Stream module](./stream) | [Tutorial: Build a Private Echo Server](./stream/tutorial) + +## Use a client pool for bursty traffic + +Creating a `MixnetClient` takes several seconds (gateway handshake, key generation, topology fetch). The [Client Pool](./client-pool) pre-creates clients in the background so they're ready when you need them: + +```rust +use nym_sdk::client_pool::ClientPool; + +#[tokio::main] +async fn main() { + let pool = ClientPool::new(3); // maintain 3 clients in reserve + let bg = pool.clone(); + tokio::spawn(async move { bg.start().await }); + + // Wait for pool to fill, then grab a ready client + tokio::time::sleep(std::time::Duration::from_secs(15)).await; + + if let Some(client) = pool.get_mixnet_client().await { + println!("Got client: {}", client.nym_address()); + client.disconnect().await; + } + + pool.disconnect_pool().await; +} +``` + +Clients are consumed, not returned; the pool creates replacements automatically. + +Next: [Client Pool module](./client-pool) | [Tutorial: Handle Bursty Traffic](./client-pool/tutorial) + +## Persist your identity + +By default, `connect_new()` creates ephemeral keys that are discarded on disconnect. To keep the same Nym address across restarts, use the builder with on-disk storage: + +```rust +use nym_sdk::mixnet::{MixnetClientBuilder, StoragePaths}; +use std::path::PathBuf; + +let storage = StoragePaths::new_from_dir( + &PathBuf::from("/tmp/my-nym-client") +).unwrap(); + +let client = MixnetClientBuilder::new_with_default_storage(storage) + .await + .unwrap() + .build() + .unwrap() + .connect_to_mixnet() + .await + .unwrap(); + +// This address is the same every time you run with the same path +println!("Persistent address: {}", client.nym_address()); +``` + +## Where to go next + +- [Installation](./importing): add `nym-sdk` to your project +- [Mixnet Tutorial](./mixnet/tutorial): send, receive, and reply with SURBs +- [Stream Tutorial](./stream/tutorial): build a private echo server +- [Client Pool Tutorial](./client-pool/tutorial): handle bursty traffic +- [API Reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/): type details, method signatures, architecture docs diff --git a/documentation/docs/pages/developers/tools.mdx b/documentation/docs/pages/developers/tools.mdx index ecfec9bd48..80a95ec5f5 100644 --- a/documentation/docs/pages/developers/tools.mdx +++ b/documentation/docs/pages/developers/tools.mdx @@ -1,6 +1,6 @@ --- -title: "Nym Developer Tools: CLI, Echo & TcpProxy" -description: "Overview of Nym developer tools including nym-cli for blockchain interaction, echo server for traffic testing, and standalone TcpProxy binary downloads." +title: "Nym Developer Tools: CLI, Diagnostics & TcpProxy" +description: "Overview of Nym developer tools including nym-cli for blockchain interaction, diagnostic tool for troubleshooting, and standalone TcpProxy binary downloads." schemaType: "TechArticle" section: "Developers" lastUpdated: "2026-02-01" @@ -8,8 +8,10 @@ lastUpdated: "2026-02-01" # Tools -There are a few tools available to developers for chain interaction: the `nym-cli` tool, which operates as an easier-to-use wrapper around `nyxd`, to allow operators to script interactions with their infrastructure (and those who prefer CLI tools). +Standalone binaries for development and testing. These don't require an SDK; download or compile them and use them directly. -There is also a basic echo server tool which app developers can use as a quick endpoint for traffic testing. This will be deployed onto a persistent public server in the future so devs dont have to run it themselves. - -Finally, there are also a pair of standalone versions of the TcpProxy Rust SDK module for developers to begin experimenting with sending app traffic through them mixnet (**this module will soon be deprecated in place of the `MixSocket`/`MixStream` abstractions**). +| 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. | diff --git a/documentation/docs/pages/developers/tools/_meta.json b/documentation/docs/pages/developers/tools/_meta.json index d2efea2a75..8b7628bd61 100644 --- a/documentation/docs/pages/developers/tools/_meta.json +++ b/documentation/docs/pages/developers/tools/_meta.json @@ -1,6 +1,5 @@ { "nym-cli": "Nym-cli", "diagnostic-tool": "Diagnostic Tool", - "echo-server": "Echo Server", "standalone-tcpproxy": "TcpProxy Binaries (Standalone)" } diff --git a/documentation/docs/pages/developers/tools/echo-server.mdx b/documentation/docs/pages/developers/tools/echo-server.mdx deleted file mode 100644 index 920a1f4bdd..0000000000 --- a/documentation/docs/pages/developers/tools/echo-server.mdx +++ /dev/null @@ -1,29 +0,0 @@ -# Echo Server - -There is an initial version of a simple echo server located at [`nym/tools/echo-server`](https://github.com/nymtech/nym/tree/develop/tools/echo-server). - -This is an initial minimal implementation of an echo server built using the [`NymProxyServer`](../rust/tcpproxy) Rust SDK abstraction that, aside from the initialisation and running of a `NymProxyServer` instance in the background, is essentially a vanilla TCP echo server written with `tokio`. - -This server was initially built for the `TcpProxy` tests, but can be useful for developers to need a constant endpoint to ping when developing. In the future this will be deployed to a remote server so developers don't have to run their own. - -## Build -Run `cargo build --release` from `nym/tools/echo-server`. The binary will be in the main workspace `target/release` dir. - -## Run -```sh -Usage: echo-server [OPTIONS] - -Options: - -g, --gateway Optional gateway to use - -c, --config-path Optional config path to specify - -e, --env Optional env file - defaults to Mainnet if None - -l, --listen-port Listen port [default: 8080] - -h, --help Print help -``` - -## Logging -Every 10 seconds, the server logs: -- the total number of bytes received since startup -- the total number of bytes sent since startup -- the current number of concurrent connections it has -- the total number of concurrent connections it has diff --git a/documentation/docs/pages/developers/tools/nym-cli/usage.mdx b/documentation/docs/pages/developers/tools/nym-cli/usage.mdx index 7611149cb7..6dced9fd92 100644 --- a/documentation/docs/pages/developers/tools/nym-cli/usage.mdx +++ b/documentation/docs/pages/developers/tools/nym-cli/usage.mdx @@ -10,15 +10,15 @@ See the [commands](commands.mdx) page for an overview of all command options. There is a limitation the staking address can only perform the following actions (and are 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) +- Bond on the gateway's or Mix Node's behalf. +- Delegate or Un-delegate (to a Mix Node in order to begin receiving rewards) - Claiming the rewards on the account ```admonish note title="" The staking address has no ability to withdraw any coins from the parent's account. ``` -The staking address must maintain the same level of security as the parent mnemonic; while the parent mnemonic's delegations and bonding events will be visible to the parent owner, the staking address will be the only account capable of undoing the bonding and delegating from the mix nodes or gateway. +The staking address must maintain the same level of security as the parent mnemonic; while the parent mnemonic's delegations and bonding events will be visible to the parent owner, the staking address will be the only account capable of undoing the bonding and delegating from the Mix Nodes or gateway. Query for staking on behalf of someone else ``` diff --git a/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx b/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx index 659b1c70de..1c4c82b26c 100644 --- a/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx +++ b/documentation/docs/pages/developers/tools/standalone-tcpproxy.mdx @@ -1,5 +1,11 @@ # Standalone TcpProxy Binaries +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. + + Standalone versions of the `TcpProxyClient` and `TcpProxyServer` [sdk module](../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. diff --git a/documentation/docs/pages/developers/typescript.mdx b/documentation/docs/pages/developers/typescript.mdx index 18929c5f1b..d2d8df68d4 100644 --- a/documentation/docs/pages/developers/typescript.mdx +++ b/documentation/docs/pages/developers/typescript.mdx @@ -3,9 +3,185 @@ 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." schemaType: "TechArticle" section: "Developers" -lastUpdated: "2026-02-11" +lastUpdated: "2026-03-13" --- -# Introduction +import { Callout } from 'nextra/components' +import { TableContainer, Table, TableBody, TableCell, TableRow, Paper } from '@mui/material' +import { NPMLink } from '../../components/npm'; -This guide contains information about the various TypeScript SDK modules that facilitate interaction with different components of the Nym stack: the Nym mixnet & the Nyx blockchain. +# 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 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. + + +## Packages + + + + + + + **mixFetch** + + + 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 + + +
+
+
+ +
+
+ + + **Mixnet Client** + + + Send and receive text and binary messages over the Nym mixnet + + +
+
+
+ +
+
+ + + **Nym Smart Contracts** + + + Query and execute methods on the smart contracts that run the Nym mixnet + + + + + +
+
+
+ +### Which variant should I use? + +All packages (except Contract Clients) come in four variants: + +- **ESM:** For new projects with current tooling. You may need to [configure your bundler](./typescript/bundling) to handle WASM and web worker components. +- **ESM full-fat:** Pre-bundled with inline WASM and web workers. No bundler config needed. +- **CommonJS:** For older projects using CommonJS. WASM and web workers need to be [bundled](./typescript/bundling/webpack). +- **CommonJS full-fat:** Pre-bundled, works without additional configuration. + + +All `*-full-fat` variants have large bundle sizes because they include WASM and web workers as inline Base64 strings. Use the standard ESM variant if bundle size matters. + + +## Installation + +### mixFetch + +```bash +npm install @nymproject/mix-fetch-full-fat +``` + +### Mixnet Client + +```bash +npm install @nymproject/sdk-full-fat +``` + +### Nym Smart Contracts + +```bash +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing +``` + +### Install everything + +```bash +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk-full-fat @nymproject/mix-fetch-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: + +```js +import { createNymMixnetClient } from '@nymproject/sdk'; + +const nym = await createNymMixnetClient(); +const nymApiUrl = 'https://validator.nymtech.net/api'; + +// Subscribe to incoming messages +nym.events.subscribeToTextMessageReceivedEvent((e) => { + console.log('Got a message: ', e.args.payload); +}); + +// Connect to the mixnet +await nym.client.start({ clientId: 'my-app', nymApiUrl }); + +// Send a message to yourself +const recipient = nym.client.selfAddress(); +nym.client.send({ payload: 'Hello mixnet', recipient }); +``` + +### Nym Smart Contracts + +Use the [Contract Clients](https://www.npmjs.com/package/@nymproject/contract-clients) to query or execute on Nym smart contracts: + +```js +import { contracts } from '@nymproject/contract-clients'; +import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; + +const signer = await DirectSecp256k1HdWallet.fromMnemonic("..."); +const accounts = await signer.getAccounts(); + +const cosmWasmSigningClient = await SigningCosmWasmClient.connectWithSigner( + "https://rpc.nymtech.net:443", signer +); +const client = new contracts.Mixnet.MixnetClient( + cosmWasmSigningClient, + accounts[0].address, + 'n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr' +); + +// Delegate 1 NYM to mixnode with id 100 +const result = await client.delegateToMixnode( + { mixId: 100 }, 'auto', undefined, + [{ amount: `${1_000_000}`, denom: 'unym' }] +); +console.log(`Tx Hash = ${result.transactionHash}`); +``` + +## Next steps + +- **[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 diff --git a/documentation/docs/pages/developers/typescript/FAQ.mdx b/documentation/docs/pages/developers/typescript/FAQ.mdx deleted file mode 100644 index e27628e69d..0000000000 --- a/documentation/docs/pages/developers/typescript/FAQ.mdx +++ /dev/null @@ -1,23 +0,0 @@ -# TS SDK FAQ -import { Callout } from 'nextra/components' - -## Why and when does the mixnet client complain about insufficient topology? - -It will in one of the following cases: -- There are empty mix layers - although this is rare; -- The gateway you've registered with does not appear in the network topology -> it is either unbonded or was blacklisted; -- The gateway you want to send packets to does not appear in the network topology -> it is either unbonded or was blacklisted; - -To avoid the last two, you need to make sure the gateway you are calling is bonded and whitelisted. - -## How can I check whether the gateway I am connecting to is bonded and not blacklisted? - -The easiest way of checking what gateway you're registered with is to look at your client address. -Client addresses are in the format of: -`client-id . client-dh @ gateway-id. ` - -To illustrate this: `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh ` - -- `DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko`: is the client's identity key; -- `ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx`: is the client's Diffie Hellman key; -- `2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh`: is the gateway's identity, which is what you'll need to check the state of the gateway in the [Nym Explorer](https://nym.com/explorer). diff --git a/documentation/docs/pages/developers/typescript/_meta.json b/documentation/docs/pages/developers/typescript/_meta.json index 8f9b320cb6..6e42d47d3d 100644 --- a/documentation/docs/pages/developers/typescript/_meta.json +++ b/documentation/docs/pages/developers/typescript/_meta.json @@ -1,9 +1,6 @@ { - "overview": "SDK overview", - "installation": "Installation", - "start": "Getting started", - "examples": "Step-by-step examples", + "examples": "Step-by-step Examples", "playground": "Live Playground", - "bundling": "Bundling", - "FAQ": "FAQ" + "bundling": "Bundling & Troubleshooting", + "api": "TypeDoc Reference" } diff --git a/documentation/docs/pages/developers/typescript/api/_meta.json b/documentation/docs/pages/developers/typescript/api/_meta.json new file mode 100644 index 0000000000..25c777ab94 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/_meta.json @@ -0,0 +1,4 @@ +{ + "sdk": "@nymproject/sdk", + "mix-fetch": "@nymproject/mix-fetch" +} diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/_meta.json b/documentation/docs/pages/developers/typescript/api/mix-fetch/_meta.json new file mode 100644 index 0000000000..54813c156f --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/_meta.json @@ -0,0 +1,8 @@ +{ + "globals": "API Index", + "functions": "Functions", + "interfaces": "Interfaces", + "enumerations": "Enumerations", + "type-aliases": "Type Aliases", + "variables": "Variables" +} diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/enumerations/EventKinds.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/enumerations/EventKinds.md new file mode 100644 index 0000000000..312e5cc01a --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/enumerations/EventKinds.md @@ -0,0 +1,17 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / EventKinds + +# Enumeration: EventKinds + +## Enumeration Members + +### Loaded + +> **Loaded**: `"Loaded"` + +#### Source + +[types.ts:25](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L25) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/enumerations/_meta.json b/documentation/docs/pages/developers/typescript/api/mix-fetch/enumerations/_meta.json new file mode 100644 index 0000000000..a94da1b9b4 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/enumerations/_meta.json @@ -0,0 +1,3 @@ +{ + "EventKinds": "EventKinds" +} diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/_meta.json b/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/_meta.json new file mode 100644 index 0000000000..a43319ee97 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/_meta.json @@ -0,0 +1,5 @@ +{ + "mixFetch": "mixFetch", + "createMixFetch": "createMixFetch", + "disconnectMixFetch": "disconnectMixFetch" +} diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/createMixFetch.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/createMixFetch.md new file mode 100644 index 0000000000..20dce0515c --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/createMixFetch.md @@ -0,0 +1,25 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / createMixFetch + +# Function: createMixFetch() + +> **createMixFetch**(`opts`?): `Promise`\<[`IMixFetch`](../interfaces/IMixFetch.md)\> + +Create a global mixFetch instance and optionally configure settings. + +## Parameters + +• **opts?**: `any` + +Optional settings + +## Returns + +`Promise`\<[`IMixFetch`](../interfaces/IMixFetch.md)\> + +## Source + +[index.ts:24](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/index.ts#L24) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/disconnectMixFetch.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/disconnectMixFetch.md new file mode 100644 index 0000000000..4c00116e75 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/disconnectMixFetch.md @@ -0,0 +1,19 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / disconnectMixFetch + +# Function: disconnectMixFetch() + +> **disconnectMixFetch**(): `Promise`\<`void`\> + +Stops the usage of mixFetch and disconnect the client from the mixnet. + +## Returns + +`Promise`\<`void`\> + +## Source + +[index.ts:66](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/index.ts#L66) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/mixFetch.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/mixFetch.md new file mode 100644 index 0000000000..3a91e6357a --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/functions/mixFetch.md @@ -0,0 +1,33 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / mixFetch + +# Function: mixFetch() + +> **mixFetch**(`url`, `args`, `opts`?): `Promise`\<`Response`\> + +mixFetch is a drop-in replacement for the standard `fetch` interface. + +## Parameters + +• **url**: `string` + +The URL to fetch from. + +• **args**: `any` + +Fetch options. + +• **opts?**: `any` + +Optionally configure mixFetch when it gets created. This only happens once, the first time it gets used. + +## Returns + +`Promise`\<`Response`\> + +## Source + +[index.ts:50](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/index.ts#L50) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/globals.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/globals.md new file mode 100644 index 0000000000..9e816c60a5 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/globals.md @@ -0,0 +1,34 @@ +[**@nymproject/mix-fetch**](globals.md) • **Docs** + +*** + +# @nymproject/mix-fetch + +## Enumerations + +- [EventKinds](enumerations/EventKinds.md) + +## Interfaces + +- [IMixFetchWebWorker](interfaces/IMixFetchWebWorker.md) +- [IMixFetch](interfaces/IMixFetch.md) +- [LoadedEvent](interfaces/LoadedEvent.md) +- [ResponseBody](interfaces/ResponseBody.md) +- [ResponseBodyConfigMap](interfaces/ResponseBodyConfigMap.md) +- [MixFetchWebWorkerResponse](interfaces/MixFetchWebWorkerResponse.md) + +## Type Aliases + +- [IMixFetchFn](type-aliases/IMixFetchFn.md) +- [SetupMixFetchOps](type-aliases/SetupMixFetchOps.md) +- [ResponseBodyMethod](type-aliases/ResponseBodyMethod.md) + +## Variables + +- [ResponseBodyConfigMapDefaults](variables/ResponseBodyConfigMapDefaults.md) + +## Functions + +- [createMixFetch](functions/createMixFetch.md) +- [mixFetch](functions/mixFetch.md) +- [disconnectMixFetch](functions/disconnectMixFetch.md) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/IMixFetch.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/IMixFetch.md new file mode 100644 index 0000000000..4954f1db25 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/IMixFetch.md @@ -0,0 +1,49 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / IMixFetch + +# Interface: IMixFetch + +## Properties + +### mixFetch + +> **mixFetch**: [`IMixFetchFn`](../type-aliases/IMixFetchFn.md) + +#### Source + +[types.ts:19](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L19) + +*** + +### setupMixFetch() + +> **setupMixFetch**: (`opts`?) => `Promise`\<`void`\> + +#### Parameters + +• **opts?**: `any` + +#### Returns + +`Promise`\<`void`\> + +#### Source + +[types.ts:20](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L20) + +*** + +### disconnectMixFetch() + +> **disconnectMixFetch**: () => `Promise`\<`void`\> + +#### Returns + +`Promise`\<`void`\> + +#### Source + +[types.ts:21](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L21) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/IMixFetchWebWorker.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/IMixFetchWebWorker.md new file mode 100644 index 0000000000..cb30391d74 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/IMixFetchWebWorker.md @@ -0,0 +1,49 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / IMixFetchWebWorker + +# Interface: IMixFetchWebWorker + +## Properties + +### mixFetch + +> **mixFetch**: `IMixFetchWorkerFn` + +#### Source + +[types.ts:13](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L13) + +*** + +### setupMixFetch() + +> **setupMixFetch**: (`opts`?) => `Promise`\<`void`\> + +#### Parameters + +• **opts?**: `any` + +#### Returns + +`Promise`\<`void`\> + +#### Source + +[types.ts:14](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L14) + +*** + +### disconnectMixFetch() + +> **disconnectMixFetch**: () => `Promise`\<`void`\> + +#### Returns + +`Promise`\<`void`\> + +#### Source + +[types.ts:15](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L15) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/LoadedEvent.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/LoadedEvent.md new file mode 100644 index 0000000000..a41f873b48 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/LoadedEvent.md @@ -0,0 +1,31 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / LoadedEvent + +# Interface: LoadedEvent + +## Properties + +### kind + +> **kind**: [`Loaded`](../enumerations/EventKinds.md#loaded) + +#### Source + +[types.ts:29](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L29) + +*** + +### args + +> **args**: `object` + +#### loaded + +> **loaded**: `true` + +#### Source + +[types.ts:30](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L30) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/MixFetchWebWorkerResponse.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/MixFetchWebWorkerResponse.md new file mode 100644 index 0000000000..87bbef2f0d --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/MixFetchWebWorkerResponse.md @@ -0,0 +1,87 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / MixFetchWebWorkerResponse + +# Interface: MixFetchWebWorkerResponse + +## Properties + +### body + +> **body**: [`ResponseBody`](ResponseBody.md) + +#### Source + +[types.ts:90](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L90) + +*** + +### url + +> **url**: `string` + +#### Source + +[types.ts:91](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L91) + +*** + +### headers + +> **headers**: `any` + +#### Source + +[types.ts:92](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L92) + +*** + +### status + +> **status**: `number` + +#### Source + +[types.ts:93](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L93) + +*** + +### statusText + +> **statusText**: `string` + +#### Source + +[types.ts:94](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L94) + +*** + +### type + +> **type**: `string` + +#### Source + +[types.ts:95](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L95) + +*** + +### ok + +> **ok**: `boolean` + +#### Source + +[types.ts:96](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L96) + +*** + +### redirected + +> **redirected**: `boolean` + +#### Source + +[types.ts:97](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L97) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/ResponseBody.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/ResponseBody.md new file mode 100644 index 0000000000..cb56a9ce96 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/ResponseBody.md @@ -0,0 +1,57 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / ResponseBody + +# Interface: ResponseBody + +## Properties + +### uint8array? + +> `optional` **uint8array**: `Uint8Array` + +#### Source + +[types.ts:36](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L36) + +*** + +### json? + +> `optional` **json**: `any` + +#### Source + +[types.ts:37](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L37) + +*** + +### text? + +> `optional` **text**: `string` + +#### Source + +[types.ts:38](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L38) + +*** + +### formData? + +> `optional` **formData**: `any` + +#### Source + +[types.ts:39](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L39) + +*** + +### blobUrl? + +> `optional` **blobUrl**: `string` + +#### Source + +[types.ts:40](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L40) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/ResponseBodyConfigMap.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/ResponseBodyConfigMap.md new file mode 100644 index 0000000000..6fb752323b --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/ResponseBodyConfigMap.md @@ -0,0 +1,79 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / ResponseBodyConfigMap + +# Interface: ResponseBodyConfigMap + +## Properties + +### uint8array? + +> `optional` **uint8array**: (`string` \| `RegExp`)[] + +Set the response `Content-Type`s to decode as uint8array. + +#### Source + +[types.ts:49](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L49) + +*** + +### json? + +> `optional` **json**: (`string` \| `RegExp`)[] + +Set the response `Content-Type`s to decode with the `json()` response body method. + +#### Source + +[types.ts:54](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L54) + +*** + +### text? + +> `optional` **text**: (`string` \| `RegExp`)[] + +Set the response `Content-Type`s to decode with the `text()` response body method. + +#### Source + +[types.ts:59](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L59) + +*** + +### formData? + +> `optional` **formData**: (`string` \| `RegExp`)[] + +Set the response `Content-Type`s to decode with the `formData()` response body method. + +#### Source + +[types.ts:64](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L64) + +*** + +### blob? + +> `optional` **blob**: (`string` \| `RegExp`)[] + +Set the response `Content-Type`s to decode with the `blob()` response body method. + +#### Source + +[types.ts:69](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L69) + +*** + +### fallback? + +> `optional` **fallback**: [`ResponseBodyMethod`](../type-aliases/ResponseBodyMethod.md) + +Set this to the default fallback method. Set to `undefined` if you want to ignore unknown types. + +#### Source + +[types.ts:74](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L74) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/_meta.json b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/_meta.json new file mode 100644 index 0000000000..4bbc532077 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/interfaces/_meta.json @@ -0,0 +1,8 @@ +{ + "IMixFetch": "IMixFetch", + "IMixFetchWebWorker": "IMixFetchWebWorker", + "ResponseBody": "ResponseBody", + "ResponseBodyConfigMap": "ResponseBodyConfigMap", + "MixFetchWebWorkerResponse": "MixFetchWebWorkerResponse", + "LoadedEvent": "LoadedEvent" +} diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/IMixFetchFn.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/IMixFetchFn.md new file mode 100644 index 0000000000..9c1d4c5724 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/IMixFetchFn.md @@ -0,0 +1,25 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / IMixFetchFn + +# Type alias: IMixFetchFn() + +> **IMixFetchFn**: (`url`, `args`, `opts`?) => `Promise`\<`Response`\> + +## Parameters + +• **url**: `string` + +• **args**: `any` + +• **opts?**: [`SetupMixFetchOps`](SetupMixFetchOps.md) + +## Returns + +`Promise`\<`Response`\> + +## Source + +[types.ts:6](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L6) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/ResponseBodyMethod.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/ResponseBodyMethod.md new file mode 100644 index 0000000000..d27b669db7 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/ResponseBodyMethod.md @@ -0,0 +1,13 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / ResponseBodyMethod + +# Type alias: ResponseBodyMethod + +> **ResponseBodyMethod**: `"uint8array"` \| `"json"` \| `"text"` \| `"formData"` \| `"blob"` + +## Source + +[types.ts:43](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L43) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/SetupMixFetchOps.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/SetupMixFetchOps.md new file mode 100644 index 0000000000..cc43e102ad --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/SetupMixFetchOps.md @@ -0,0 +1,19 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / SetupMixFetchOps + +# Type alias: SetupMixFetchOps + +> **SetupMixFetchOps**: `MixFetchOpts` & `object` + +## Type declaration + +### responseBodyConfigMap? + +> `optional` **responseBodyConfigMap**: [`ResponseBodyConfigMap`](../interfaces/ResponseBodyConfigMap.md) + +## Source + +[types.ts:8](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L8) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/_meta.json b/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/_meta.json new file mode 100644 index 0000000000..f4c7d81fbe --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/type-aliases/_meta.json @@ -0,0 +1,5 @@ +{ + "IMixFetchFn": "IMixFetchFn", + "SetupMixFetchOps": "SetupMixFetchOps", + "ResponseBodyMethod": "ResponseBodyMethod" +} diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/variables/ResponseBodyConfigMapDefaults.md b/documentation/docs/pages/developers/typescript/api/mix-fetch/variables/ResponseBodyConfigMapDefaults.md new file mode 100644 index 0000000000..31b06eaaa2 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/variables/ResponseBodyConfigMapDefaults.md @@ -0,0 +1,15 @@ +[**@nymproject/mix-fetch**](../globals.md) • **Docs** + +*** + +[@nymproject/mix-fetch](../globals.md) / ResponseBodyConfigMapDefaults + +# Variable: ResponseBodyConfigMapDefaults + +> `const` **ResponseBodyConfigMapDefaults**: [`ResponseBodyConfigMap`](../interfaces/ResponseBodyConfigMap.md) + +Default values for the handling of response bodies. + +## Source + +[types.ts:80](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/mix-fetch/src/types.ts#L80) diff --git a/documentation/docs/pages/developers/typescript/api/mix-fetch/variables/_meta.json b/documentation/docs/pages/developers/typescript/api/mix-fetch/variables/_meta.json new file mode 100644 index 0000000000..2e3ff8efa3 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/mix-fetch/variables/_meta.json @@ -0,0 +1,3 @@ +{ + "ResponseBodyConfigMapDefaults": "ResponseBodyConfigMapDefaults" +} diff --git a/documentation/docs/pages/developers/typescript/api/sdk/_meta.json b/documentation/docs/pages/developers/typescript/api/sdk/_meta.json new file mode 100644 index 0000000000..500720b149 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/_meta.json @@ -0,0 +1,7 @@ +{ + "globals": "API Index", + "functions": "Functions", + "interfaces": "Interfaces", + "enumerations": "Enumerations", + "type-aliases": "Type Aliases" +} diff --git a/documentation/docs/pages/developers/typescript/api/sdk/enumerations/EventKinds.md b/documentation/docs/pages/developers/typescript/api/sdk/enumerations/EventKinds.md new file mode 100644 index 0000000000..517bb49048 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/enumerations/EventKinds.md @@ -0,0 +1,69 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / EventKinds + +# Enumeration: EventKinds + +Enum representing various event kinds. + +## Enumeration Members + +### Loaded + +> **Loaded**: `"Loaded"` + +The event emitted when the nodetester is ready to be used. + +#### Source + +[mixnet/wasm/types.ts:206](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L206) + +*** + +### Connected + +> **Connected**: `"Connected"` + +The event emitted when connection to the gateway is established. + +#### Source + +[mixnet/wasm/types.ts:211](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L211) + +*** + +### StringMessageReceived + +> **StringMessageReceived**: `"StringMessageReceived"` + +The event for when a message is received and interpreted as a string. + +#### Source + +[mixnet/wasm/types.ts:216](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L216) + +*** + +### BinaryMessageReceived + +> **BinaryMessageReceived**: `"BinaryMessageReceived"` + +The event for when a binary message is received. BinaryMessage is a type of message that contains additional metadata, such as MIME type and some headers, along with the actual payload data. + +#### Source + +[mixnet/wasm/types.ts:221](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L221) + +*** + +### RawMessageReceived + +> **RawMessageReceived**: `"RawMessageReceived"` + +The event for when a raw message is received. RawMessage represents the bytes that are received directly from the mixnet with no further parsing or interpretation done on them. + +#### Source + +[mixnet/wasm/types.ts:226](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L226) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/enumerations/MimeTypes.md b/documentation/docs/pages/developers/typescript/api/sdk/enumerations/MimeTypes.md new file mode 100644 index 0000000000..cc27360e40 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/enumerations/MimeTypes.md @@ -0,0 +1,39 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / MimeTypes + +# Enumeration: MimeTypes + +Some common mime types, however, you can always just specify the mime-type as a string + +## Enumeration Members + +### ApplicationOctetStream + +> **ApplicationOctetStream**: `"application/octet-stream"` + +#### Source + +[mixnet/wasm/types.ts:272](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L272) + +*** + +### TextPlain + +> **TextPlain**: `"text/plain"` + +#### Source + +[mixnet/wasm/types.ts:273](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L273) + +*** + +### ApplicationJson + +> **ApplicationJson**: `"application/json"` + +#### Source + +[mixnet/wasm/types.ts:274](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L274) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/enumerations/_meta.json b/documentation/docs/pages/developers/typescript/api/sdk/enumerations/_meta.json new file mode 100644 index 0000000000..9cc5fea279 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/enumerations/_meta.json @@ -0,0 +1,4 @@ +{ + "EventKinds": "EventKinds", + "MimeTypes": "MimeTypes" +} diff --git a/documentation/docs/pages/developers/typescript/api/sdk/functions/_meta.json b/documentation/docs/pages/developers/typescript/api/sdk/functions/_meta.json new file mode 100644 index 0000000000..bdd6bd65b8 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/functions/_meta.json @@ -0,0 +1,3 @@ +{ + "createNymMixnetClient": "createNymMixnetClient" +} diff --git a/documentation/docs/pages/developers/typescript/api/sdk/functions/createNymMixnetClient.md b/documentation/docs/pages/developers/typescript/api/sdk/functions/createNymMixnetClient.md new file mode 100644 index 0000000000..de2c8b02e2 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/functions/createNymMixnetClient.md @@ -0,0 +1,31 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / createNymMixnetClient + +# Function: createNymMixnetClient() + +> **createNymMixnetClient**(`options`?): `Promise`\<[`NymMixnetClient`](../interfaces/NymMixnetClient.md)\> + +Create a client to send and receive traffic from the Nym mixnet. + +## Parameters + +• **options?**: [`NymMixnetClientOptions`](../interfaces/NymMixnetClientOptions.md) + +## Returns + +`Promise`\<[`NymMixnetClient`](../interfaces/NymMixnetClient.md)\> + +## Required + +## Example + +```typescript +const client = await createNymMixnetClient(); +``` + +## Source + +[mixnet/wasm/index.ts:51](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/index.ts#L51) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/globals.md b/documentation/docs/pages/developers/typescript/api/sdk/globals.md new file mode 100644 index 0000000000..2122c127e3 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/globals.md @@ -0,0 +1,33 @@ +[**@nymproject/sdk**](globals.md) • **Docs** + +*** + +# @nymproject/sdk + +## Enumerations + +- [EventKinds](enumerations/EventKinds.md) +- [MimeTypes](enumerations/MimeTypes.md) + +## Interfaces + +- [NymMixnetClientOptions](interfaces/NymMixnetClientOptions.md) +- [NymMixnetClient](interfaces/NymMixnetClient.md) +- [Client](interfaces/Client.md) +- [Events](interfaces/Events.md) +- [LoadedEvent](interfaces/LoadedEvent.md) +- [ConnectedEvent](interfaces/ConnectedEvent.md) +- [StringMessageReceivedEvent](interfaces/StringMessageReceivedEvent.md) +- [BinaryMessageReceivedEvent](interfaces/BinaryMessageReceivedEvent.md) +- [RawMessageReceivedEvent](interfaces/RawMessageReceivedEvent.md) +- [Payload](interfaces/Payload.md) + +## Type Aliases + +- [EventHandlerSubscribeFn](type-aliases/EventHandlerSubscribeFn.md) +- [EventHandlerFn](type-aliases/EventHandlerFn.md) +- [EventHandlerUnsubscribeFn](type-aliases/EventHandlerUnsubscribeFn.md) + +## Functions + +- [createNymMixnetClient](functions/createNymMixnetClient.md) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/BinaryMessageReceivedEvent.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/BinaryMessageReceivedEvent.md new file mode 100644 index 0000000000..f864e60134 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/BinaryMessageReceivedEvent.md @@ -0,0 +1,39 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / BinaryMessageReceivedEvent + +# Interface: BinaryMessageReceivedEvent + +## Properties + +### kind + +> **kind**: [`BinaryMessageReceived`](../enumerations/EventKinds.md#binarymessagereceived) + +#### Source + +[mixnet/wasm/types.ts:253](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L253) + +*** + +### args + +> **args**: `object` + +#### mimeType + +> **mimeType**: [`MimeTypes`](../enumerations/MimeTypes.md) + +#### payload + +> **payload**: `Uint8Array` + +#### headers? + +> `optional` **headers**: `string` + +#### Source + +[mixnet/wasm/types.ts:254](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L254) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/Client.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/Client.md new file mode 100644 index 0000000000..3172aa263e --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/Client.md @@ -0,0 +1,252 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / Client + +# Interface: Client + +## Properties + +### start() + +> **start**: (`opts`?) => `Promise`\<`void`\> + +Start the client. + +#### Example + +```typescript +const client = await createNymMixnetClient(); +await client.start({ + clientId: 'my-client', + nymApiUrl: 'https://validator.nymtech.net/api', +}); + +#### Parameters + +• **opts?**: `any` + +#### Returns + +`Promise`\<`void`\> + +#### Source + +[mixnet/wasm/types.ts:33](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L33) + +*** + +### stop() + +> **stop**: () => `Promise`\<`void`\> + +Stop the client. + +#### Example + +```typescript +const client = await createNymMixnetClient(); +await client.start({ + clientId: 'my-client', + nymApiUrl: 'https://validator.nymtech.net/api', +}); +await client.stop(); +``` + +#### Returns + +`Promise`\<`void`\> + +#### Source + +[mixnet/wasm/types.ts:46](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L46) + +*** + +### selfAddress() + +> **selfAddress**: () => `Promise`\<`undefined` \| `string`\> + +Get the client address + +#### Example + +```typescript +const client = await createNymMixnetClient(); +await client.start({ + clientId: 'my-client', + nymApiUrl: 'https://validator.nymtech.net/api', +}); +const address = await client.selfAddress(); +``` + +#### Returns + +`Promise`\<`undefined` \| `string`\> + +#### Source + +[mixnet/wasm/types.ts:59](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L59) + +*** + +### setTextMimeTypes() + +> **setTextMimeTypes**: (`mimeTypes`) => `void` + +Set the mime-types that should be used when using the [Client.send](Client.md#send) method. + +#### Example + +```typescript +const client = await createNymMixnetClient(); +await client.start({ +clientId: 'my-client', +nymApiUrl: 'https://validator.nymtech.net/api', +}); +await client.setTextMimeTypes(['text/plain', 'application/json']); +``` + +#### See + + - [MimeTypes](../enumerations/MimeTypes.md) + - [Client.send](Client.md#send) + - [Client.getTextMimeTypes](Client.md#gettextmimetypes) + +#### Parameters + +• **mimeTypes**: `string`[] + +#### Returns + +`void` + +#### Source + +[mixnet/wasm/types.ts:76](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L76) + +*** + +### getTextMimeTypes() + +> **getTextMimeTypes**: () => `Promise`\<`string`[]\> + +Get the mime-types that are automatically converted to strings. + +#### Example + +```typescript +const client = await createNymMixnetClient(); +await client.start({ +clientId: 'my-client', +nymApiUrl: 'https://validator.nymtech.net/api', +}); +const mimeTypes = await client.getTextMimeTypes(); +``` + +#### See + + - [MimeTypes](../enumerations/MimeTypes.md) + - [Payload](Payload.md) + - [Client.send](Client.md#send) + - [Client.setTextMimeTypes](Client.md#settextmimetypes) + +#### Returns + +`Promise`\<`string`[]\> + +#### Source + +[mixnet/wasm/types.ts:93](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L93) + +*** + +### send() + +> **send**: (`args`) => `Promise`\<`void`\> + +Send some data through the mixnet message. + +#### Example + +```typescript +const client = await createNymMixnetClient(); +await client.start({ + clientId: 'my-client', + nymApiUrl: 'https://validator.nymtech.net/api', +}); +await client.send({ + payload: 'Hello world', + recipient: // recipient address, +}); +``` + +#### See + + - [MimeTypes](../enumerations/MimeTypes.md) + - [Payload](Payload.md) + +#### Parameters + +• **args** + +• **args.payload**: [`Payload`](Payload.md) + +• **args.recipient**: `string` + +• **args.replySurbs?**: `number` + +#### Returns + +`Promise`\<`void`\> + +#### Source + +[mixnet/wasm/types.ts:111](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L111) + +*** + +### rawSend() + +> **rawSend**: (`args`) => `Promise`\<`void`\> + +Send a raw payload, without any mime-type conversion. + +#### Example + +```typescript +const client = await createNymMixnetClient(); +await client.start({ + clientId: 'my-client', + nymApiUrl: 'https://validator.nymtech.net/api', +}); +const payload = new Uint8Array([1, 2, 3]); +await client.rawSend({ + payload, + recipient: // recipient address, +}); +``` + +#### See + + - [MimeTypes](../enumerations/MimeTypes.md) + - [Payload](Payload.md) + +#### Parameters + +• **args** + +• **args.payload**: `Uint8Array` + +• **args.recipient**: `string` + +• **args.replySurbs?**: `number` + +#### Returns + +`Promise`\<`void`\> + +#### Source + +[mixnet/wasm/types.ts:130](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L130) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/ConnectedEvent.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/ConnectedEvent.md new file mode 100644 index 0000000000..e8081b49ed --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/ConnectedEvent.md @@ -0,0 +1,31 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / ConnectedEvent + +# Interface: ConnectedEvent + +## Properties + +### kind + +> **kind**: [`Connected`](../enumerations/EventKinds.md#connected) + +#### Source + +[mixnet/wasm/types.ts:237](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L237) + +*** + +### args + +> **args**: `object` + +#### address? + +> `optional` **address**: `string` + +#### Source + +[mixnet/wasm/types.ts:238](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L238) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/Events.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/Events.md new file mode 100644 index 0000000000..203b824092 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/Events.md @@ -0,0 +1,125 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / Events + +# Interface: Events + +## Properties + +### subscribeToLoaded + +> **subscribeToLoaded**: [`EventHandlerSubscribeFn`](../type-aliases/EventHandlerSubscribeFn.md)\<[`LoadedEvent`](LoadedEvent.md)\> + +#### See + +[LoadedEvent](LoadedEvent.md) + +#### Example + +```typescript +events.subscribeToLoaded((e) => { + console.log(e.args); // { loaded: true } +}); +``` + +#### Source + +[mixnet/wasm/types.ts:143](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L143) + +*** + +### subscribeToConnected + +> **subscribeToConnected**: [`EventHandlerSubscribeFn`](../type-aliases/EventHandlerSubscribeFn.md)\<[`ConnectedEvent`](ConnectedEvent.md)\> + +#### See + +[ConnectedEvent](ConnectedEvent.md) + +#### Example + +```typescript +events.subscribeConnected((e) => { + console.log(e.args.address); // Client address +}); + +#### Source + +[mixnet/wasm/types.ts:153](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L153) + +*** + +### subscribeToTextMessageReceivedEvent + +> **subscribeToTextMessageReceivedEvent**: [`EventHandlerSubscribeFn`](../type-aliases/EventHandlerSubscribeFn.md)\<[`StringMessageReceivedEvent`](StringMessageReceivedEvent.md)\> + +#### See + +[StringMessageReceivedEvent](StringMessageReceivedEvent.md) + +#### Example + +```typescript +const unsubscribe = events.subscribeToTextMessageReceivedEvent((e) => { + console.log(e.args.payload); // string +}); + +// Stop listening to the event +unsubscribe(); +``` + +#### Source + +[mixnet/wasm/types.ts:167](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L167) + +*** + +### subscribeToBinaryMessageReceivedEvent + +> **subscribeToBinaryMessageReceivedEvent**: [`EventHandlerSubscribeFn`](../type-aliases/EventHandlerSubscribeFn.md)\<[`BinaryMessageReceivedEvent`](BinaryMessageReceivedEvent.md)\> + +#### See + +[BinaryMessageReceivedEvent](BinaryMessageReceivedEvent.md) + +#### Example + +```typescript +const unsubscribe = events.subscribeToBinaryMessageReceivedEvent((e) => { + console.log(e.args.payload); // Uint8Array +}); + +// Stop listening to the event +unsubscribe(); +``` + +#### Source + +[mixnet/wasm/types.ts:181](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L181) + +*** + +### subscribeToRawMessageReceivedEvent + +> **subscribeToRawMessageReceivedEvent**: [`EventHandlerSubscribeFn`](../type-aliases/EventHandlerSubscribeFn.md)\<[`RawMessageReceivedEvent`](RawMessageReceivedEvent.md)\> + +#### See + +[RawMessageReceivedEvent](RawMessageReceivedEvent.md) + +#### Example + +```typescript +const unsubscribe = events.subscribeToRawMessageReceivedEvent((e) => { + console.log(e.args.payload); // Uint8Array +}); + +// Stop listening to the event +unsubscribe(); +``` + +#### Source + +[mixnet/wasm/types.ts:195](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L195) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/LoadedEvent.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/LoadedEvent.md new file mode 100644 index 0000000000..e3fb1296a8 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/LoadedEvent.md @@ -0,0 +1,31 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / LoadedEvent + +# Interface: LoadedEvent + +## Properties + +### kind + +> **kind**: [`Loaded`](../enumerations/EventKinds.md#loaded) + +#### Source + +[mixnet/wasm/types.ts:230](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L230) + +*** + +### args + +> **args**: `object` + +#### loaded + +> **loaded**: `true` + +#### Source + +[mixnet/wasm/types.ts:231](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L231) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/NymMixnetClient.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/NymMixnetClient.md new file mode 100644 index 0000000000..1d0e71ee70 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/NymMixnetClient.md @@ -0,0 +1,30 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / NymMixnetClient + +# Interface: NymMixnetClient + +The client for the Nym mixnet which gives access to client methods and event subscriptions. +Returned by the [createNymMixnetClient](../functions/createNymMixnetClient.md) function. + +## Properties + +### client + +> **client**: [`Client`](Client.md) + +#### Source + +[mixnet/wasm/index.ts:38](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/index.ts#L38) + +*** + +### events + +> **events**: [`Events`](Events.md) + +#### Source + +[mixnet/wasm/index.ts:39](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/index.ts#L39) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/NymMixnetClientOptions.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/NymMixnetClientOptions.md new file mode 100644 index 0000000000..6e274e7630 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/NymMixnetClientOptions.md @@ -0,0 +1,29 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / NymMixnetClientOptions + +# Interface: NymMixnetClientOptions + +Options for the Nym mixnet client. + +## Example + +```typescript +const client = await createNymMixnetClient({ + autoConvertStringMimeTypes: [MimeTypes.ApplicationJson, MimeTypes.TextPlain], +}); +``` + +## Properties + +### autoConvertStringMimeTypes? + +> `optional` **autoConvertStringMimeTypes**: `string`[] \| [`MimeTypes`](../enumerations/MimeTypes.md)[] + +An array of mime types. + +#### Source + +[mixnet/wasm/index.ts:29](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/index.ts#L29) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/Payload.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/Payload.md new file mode 100644 index 0000000000..42b45ded21 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/Payload.md @@ -0,0 +1,37 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / Payload + +# Interface: Payload + +## Properties + +### message + +> **message**: `string` \| `Uint8Array` + +#### Source + +[mixnet/wasm/types.ts:278](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L278) + +*** + +### mimeType? + +> `optional` **mimeType**: `string` + +#### Source + +[mixnet/wasm/types.ts:279](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L279) + +*** + +### headers? + +> `optional` **headers**: `string` + +#### Source + +[mixnet/wasm/types.ts:280](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L280) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/RawMessageReceivedEvent.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/RawMessageReceivedEvent.md new file mode 100644 index 0000000000..99611b0936 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/RawMessageReceivedEvent.md @@ -0,0 +1,31 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / RawMessageReceivedEvent + +# Interface: RawMessageReceivedEvent + +## Properties + +### kind + +> **kind**: [`RawMessageReceived`](../enumerations/EventKinds.md#rawmessagereceived) + +#### Source + +[mixnet/wasm/types.ts:262](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L262) + +*** + +### args + +> **args**: `object` + +#### payload + +> **payload**: `Uint8Array` + +#### Source + +[mixnet/wasm/types.ts:263](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L263) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/StringMessageReceivedEvent.md b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/StringMessageReceivedEvent.md new file mode 100644 index 0000000000..1828555757 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/StringMessageReceivedEvent.md @@ -0,0 +1,43 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / StringMessageReceivedEvent + +# Interface: StringMessageReceivedEvent + +## Properties + +### kind + +> **kind**: [`StringMessageReceived`](../enumerations/EventKinds.md#stringmessagereceived) + +#### Source + +[mixnet/wasm/types.ts:244](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L244) + +*** + +### args + +> **args**: `object` + +#### mimeType + +> **mimeType**: [`MimeTypes`](../enumerations/MimeTypes.md) + +#### payload + +> **payload**: `string` + +#### payloadRaw + +> **payloadRaw**: `Uint8Array` + +#### headers? + +> `optional` **headers**: `string` + +#### Source + +[mixnet/wasm/types.ts:245](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L245) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/interfaces/_meta.json b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/_meta.json new file mode 100644 index 0000000000..8735629ecb --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/interfaces/_meta.json @@ -0,0 +1,12 @@ +{ + "NymMixnetClient": "NymMixnetClient", + "NymMixnetClientOptions": "NymMixnetClientOptions", + "Client": "Client", + "Events": "Events", + "Payload": "Payload", + "ConnectedEvent": "ConnectedEvent", + "LoadedEvent": "LoadedEvent", + "StringMessageReceivedEvent": "StringMessageReceivedEvent", + "BinaryMessageReceivedEvent": "BinaryMessageReceivedEvent", + "RawMessageReceivedEvent": "RawMessageReceivedEvent" +} diff --git a/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerFn.md b/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerFn.md new file mode 100644 index 0000000000..577dd51dd6 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerFn.md @@ -0,0 +1,33 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / EventHandlerFn + +# Type alias: EventHandlerFn()\ + +> **EventHandlerFn**\<`E`\>: (`e`) => `void` \| `Promise`\<`void`\> + +The **EventHandlerFn** is a callback function that is passed to the [EventHandlerSubscribeFn](EventHandlerSubscribeFn.md) + +## See + + - [Events](../interfaces/Events.md) + - [EventHandlerFn](EventHandlerFn.md) + - [EventHandlerSubscribeFn](EventHandlerSubscribeFn.md) + +## Type parameters + +• **E** + +## Parameters + +• **e**: `E` + +## Returns + +`void` \| `Promise`\<`void`\> + +## Source + +[mixnet/wasm/types.ts:309](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L309) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerSubscribeFn.md b/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerSubscribeFn.md new file mode 100644 index 0000000000..6b1d9ab4a1 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerSubscribeFn.md @@ -0,0 +1,33 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / EventHandlerSubscribeFn + +# Type alias: EventHandlerSubscribeFn()\ + +> **EventHandlerSubscribeFn**\<`E`\>: (`fn`) => [`EventHandlerUnsubscribeFn`](EventHandlerUnsubscribeFn.md) + +The **EventHandlerSubscribeFn** is a function that takes a callback of type [EventHandlerFn](EventHandlerFn.md) + +## See + + - [Events](../interfaces/Events.md) + - [EventHandlerFn](EventHandlerFn.md) + - [EventHandlerUnsubscribeFn](EventHandlerUnsubscribeFn.md) + +## Type parameters + +• **E** + +## Parameters + +• **fn**: [`EventHandlerFn`](EventHandlerFn.md)\<`E`\> + +## Returns + +[`EventHandlerUnsubscribeFn`](EventHandlerUnsubscribeFn.md) + +## Source + +[mixnet/wasm/types.ts:301](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L301) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerUnsubscribeFn.md b/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerUnsubscribeFn.md new file mode 100644 index 0000000000..935318c51c --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/EventHandlerUnsubscribeFn.md @@ -0,0 +1,26 @@ +[**@nymproject/sdk**](../globals.md) • **Docs** + +*** + +[@nymproject/sdk](../globals.md) / EventHandlerUnsubscribeFn + +# Type alias: EventHandlerUnsubscribeFn() + +> **EventHandlerUnsubscribeFn**: () => `void` + +The **EventHandlerUnsubscribeFn** function is returned by the [EventHandlerSubscribeFn](EventHandlerSubscribeFn.md) +and can be used to stop listening for particular events + +## See + + - [Events](../interfaces/Events.md) + - [EventHandlerFn](EventHandlerFn.md) + - [EventHandlerSubscribeFn](EventHandlerSubscribeFn.md) + +## Returns + +`void` + +## Source + +[mixnet/wasm/types.ts:318](https://github.com/nymtech/nym/blob/5065c5579e2c961211276dcdfd8aaa127f29bf26/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts#L318) diff --git a/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/_meta.json b/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/_meta.json new file mode 100644 index 0000000000..09821fa3a4 --- /dev/null +++ b/documentation/docs/pages/developers/typescript/api/sdk/type-aliases/_meta.json @@ -0,0 +1,5 @@ +{ + "EventHandlerFn": "EventHandlerFn", + "EventHandlerSubscribeFn": "EventHandlerSubscribeFn", + "EventHandlerUnsubscribeFn": "EventHandlerUnsubscribeFn" +} diff --git a/documentation/docs/pages/developers/typescript/bundling/_meta.json b/documentation/docs/pages/developers/typescript/bundling/_meta.json index 5ee55efacc..f5d1464fb4 100644 --- a/documentation/docs/pages/developers/typescript/bundling/_meta.json +++ b/documentation/docs/pages/developers/typescript/bundling/_meta.json @@ -1,6 +1,5 @@ { - "bundling": "General troubleshooting", - "esbuild": "ESbuild", - "webpack": "Webpack" + "bundling": "General", + "esbuild": "ESBuild", + "webpack": "Webpack" } - \ No newline at end of file diff --git a/documentation/docs/pages/developers/typescript/bundling/bundling.mdx b/documentation/docs/pages/developers/typescript/bundling/bundling.mdx index 6fed215105..d58d1ec2f7 100644 --- a/documentation/docs/pages/developers/typescript/bundling/bundling.mdx +++ b/documentation/docs/pages/developers/typescript/bundling/bundling.mdx @@ -1,26 +1,26 @@ -# Troubleshooting bundling +--- +title: "TypeScript SDK Bundling Troubleshooting" +description: "Fix common bundling issues with the Nym TypeScript SDK: WASM files missing from output, web worker configuration for Webpack and other bundlers." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + +# Troubleshooting import { Callout } from 'nextra/components'; -You might need some help bundling packages from the Nym Typescript SDK into your package. +## Bundling issues -Here are some things that could go wrong: - -## WebAssembly (WASM) and web worker not included in output bundle - -### Webpack +### WASM and web worker not included in output bundle (Webpack) You might need to use the CopyPlugin by adding this to your Webpack config: ```js const CopyPlugin = require('copy-webpack-plugin'); -... - module.exports = { - ... plugins: [ - ... new CopyPlugin({ patterns: [ { @@ -37,20 +37,37 @@ module.exports = { } ``` -How does this work? The statement `require.resolve('@nymproject/mix-fetch/package.json')` finds the disk location of -the Nym SDK package, and resolve the directory name is `path.dirname`, the add the `*.wasm` glob to the search pattern -list. Use `[name][ext]` to preserve the output filename, because the package expects the filename to stay the same. +`require.resolve('@nymproject/mix-fetch/package.json')` finds the disk location of the Nym SDK package. `path.dirname` resolves the directory, and the `*.wasm` glob matches the WASM files. Use `[name][ext]` to preserve the output filename, because the package expects it to stay the same. -## ESM not supported +### ESM not supported If your bundler does not support ECMAScript Modules (ESM), CommonJS packages are supported for most parts of the SDK. -For those that don't have ESM versions, you will need to use a tool like [Babel](https://babeljs.io/) to convert -ESM to CommonJS. +For those that don't have ESM versions, you will need to use a tool like [Babel](https://babeljs.io/) to convert ESM to CommonJS. -## CSP prevents loading +### CSP prevents loading -If you are using a `*-full-fat` package, or if you inline WASM or web workers, you may not be able to load them if the -[CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) prevents WASM from being instantiated from a string. +If you are using a `*-full-fat` package, or if you inline WASM or web workers, you may not be able to load them if the [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) prevents WASM from being instantiated from a string. You'll have to experiment with either adjusting the CSP or use another variant that is unbundled. + +## Mixnet client issues + +### Insufficient topology error + +The mixnet client will complain about insufficient topology in the following cases: +- There are empty mix layers (rare) +- The gateway you've registered with does not appear in the network topology; it is either unbonded or was blacklisted +- The gateway you want to send packets to does not appear in the network topology; it is either unbonded or was blacklisted + +To avoid the last two, make sure the gateway you are using is bonded and whitelisted. + +### Checking gateway status + +Your client address has the format: `client-id.client-dh@gateway-id` + +For example: `DpB3cHAchJi...suko.ANNWrvHq...U2Vx@2BuMSfMW...3SEh` + +- First part: client's identity key +- Second part: client's Diffie-Hellman key +- After `@`: gateway's identity key. Search for this in the [Nym Explorer](https://nym.com/explorer) to check its status diff --git a/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx b/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx index 0a0f76db59..961df307ec 100644 --- a/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx +++ b/documentation/docs/pages/developers/typescript/examples/mix-fetch.mdx @@ -1,211 +1,293 @@ +--- +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." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + import { Callout } from 'nextra/components'; -# `mixFetch` +# mixFetch -An easy way to secure parts or all of your web app is to replace calls to [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with `mixFetch`: +An easy way to secure parts or all of your web app is to replace calls to [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with `mixFetch`. It works the same as vanilla `fetch`: it's a proxied wrapper around the original function. -MixFetch works the same as vanilla `fetch` as it's a proxied wrapper around the original function. -Sounds great, are there any catches? Well, there are a few (for now): - -- CA certificates in `mixFetch` are periodically updated, so if you get a certificate error, the root certificate you need might not be valid. If that's the case, [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 to avoid getting a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error. - -- For now, `mixfetch` doesn't work with SURBS, although this will change in the future. +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. - - 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. - -For the moment you have to select a Gateway that has WSS from [Harbourmaster Gateways for mixFetch list](https://harbourmaster.nymtech.net/). - -``` -curl -X 'GET' \ -'https://validator.nymtech.net/api/v1/gateways/described' \ --H 'accept: application/json' -``` - + +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/). -```ts -// For mainnet -import type { SetupMixFetchOps } from '@nymproject/mix-fetch'; +## Environment Setup -const mixFetchOptions: SetupMixFetchOps = { - clientId: "my-mixfetch-client", // explicit ID to avoid stale default IndexedDB storage - preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", // with WSS - mixFetchOverride: { - requestTimeoutMs: 60_000, - }, - forceTls: true, // force WSS - extra: {}, -}; -``` - -##### 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 + TypeScript, then: ```bash -cd < YOUR_APP > +cd npm i npm run dev ``` -##### Installation - -Install the required package: +## Installation ```bash npm install @nymproject/mix-fetch-full-fat ``` -##### Imports - -In the `src` folder, open the `App.tsx` file and delete all the code. - -Import the client in your app: - -```js -import { mixFetch } from '@nymproject/mix-fetch-full-fat'; -``` - -##### Example: using the `mixFetch` client: - -`Get`, `Post`, and `Concurrent` outputs will be observable from your console. MixFetch auto-initializes on the first request. Individual concurrent results are logged as they arrive. +## Configuration ```ts -import './App.css'; -import { mixFetch, SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat'; -import React from 'react'; +import type { SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat'; const mixFetchOptions: SetupMixFetchOps = { - clientId: "my-mixfetch-client", // explicit ID to avoid stale default IndexedDB storage - preferredGateway: '23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', // with WSS - preferredNetworkRequester: - 'HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', + clientId: "docs-mixfetch-demo", + preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", mixFetchOverride: { requestTimeoutMs: 60_000, }, forceTls: true, // force WSS - extra: {}, +}; +``` + +## 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. + + +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. + + +```tsx +import React, { useState, useRef, useEffect } from "react"; +import CircularProgress from "@mui/material/CircularProgress"; +import Button from "@mui/material/Button"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; +import { mixFetch, createMixFetch } from "@nymproject/mix-fetch-full-fat"; +import Stack from "@mui/material/Stack"; +import Paper from "@mui/material/Paper"; +import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat"; + +const defaultUrl = + "https://nymtech.net/.wellknown/network-requester/exit-policy.txt"; +const args = { mode: "unsafe-ignore-cors" }; +const mixFetchOptions: SetupMixFetchOps = { + clientId: "docs-mixfetch-demo", + preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", + mixFetchOverride: { + requestTimeoutMs: 60_000, + }, + forceTls: true, }; -export function HttpGET() { - const [html, setHtml] = React.useState(''); - async function get() { - //Make sure the URL is whitelisted (see 'standard allowed list') otherwise you will get a network requester filter check error - const response = await mixFetch('https://nym.com/favicon.svg', { mode: 'unsafe-ignore-cors' }, mixFetchOptions); - const text = await response.text(); - console.log('response was', text); - setHtml(text); - } +// Log entry type for the visible log panel +type LogLevel = "info" | "error" | "send" | "receive"; +type LogEntry = { timestamp: string; message: string; level: LogLevel }; - return ( - <> - - - ); -} +const logColors: Record = { + info: "gray", + error: "red", + send: "blue", + receive: "green", +}; -export function HttpPOST() { - async function post() { - //Make sure the URL is whitelisted (see 'standard allowed list') otherwise you will get a network requester filter check error - const apiResponse = await mixFetch( - 'https://httpbin.org/post', - { - method: 'POST', - body: JSON.stringify({ foo: 'bar' }), - headers: { 'Content-Type': 'application/json' }, - }, - mixFetchOptions, - ); - console.log(apiResponse); - } - return ( - <> - - - ); -} +const logLabels: Record = { + info: "INFO", + error: "ERROR", + send: "SEND", + receive: "RECV", +}; -// Send 5 concurrent requests to different URLs on the same domain using Promise.all -export function HttpConcurrent() { - const [results, setResults] = React.useState([]); - const [busy, setBusy] = React.useState(false); +export const MixFetch = () => { + // MixFetch initialization state + const [status, setStatus] = useState<"idle" | "starting" | "ready" | "error">("idle"); + const [errorMsg, setErrorMsg] = useState(null); - async function fetchConcurrent() { - const baseUrl = 'https://jsonplaceholder.typicode.com/posts/'; - const count = 5; - setBusy(true); - setResults([]); - console.log(`Starting ${count} concurrent requests to ${baseUrl}1-${count}...`); + // Log panel state + const [logs, setLogs] = useState([]); + const logEndRef = useRef(null); + // Single fetch state + const [url, setUrl] = useState(defaultUrl); + const [html, setHtml] = useState(); + const [busy, setBusy] = useState(false); + + // Concurrent fetch state + const [concurrentResults, setConcurrentResults] = useState([]); + const [concurrentBusy, setConcurrentBusy] = useState(false); + + // Auto-scroll log panel to bottom + useEffect(() => { + logEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [logs]); + + // Helper to add a timestamped log entry + const addLog = (message: string, level: LogLevel) => { + const timestamp = new Date().toISOString().substring(11, 23); + setLogs((prev) => [...prev, { timestamp, message, level }]); + }; + + // Initialize MixFetch explicitly via createMixFetch + const handleStart = async () => { try { - // Fire off all requests at once with Promise.all - const requests = Array.from({ length: count }, (_, i) => { - const url = `${baseUrl}${i + 1}`; - return mixFetch(url, { mode: 'unsafe-ignore-cors' }, mixFetchOptions) - .then((res) => res.json()) - .then((json: { id: number; title: string }) => { - const entry = `[${json.id}] ${json.title}`; - console.log(entry); - return entry; - }); - }); - - const allResults = await Promise.all(requests); - setResults(allResults); - console.log('All concurrent requests completed!', allResults); + setStatus("starting"); + setErrorMsg(null); + addLog("Starting MixFetch...", "info"); + await createMixFetch(mixFetchOptions); + setStatus("ready"); + addLog("MixFetch is ready!", "info"); } catch (err) { - console.error('Concurrent fetch error:', err); + const msg = err instanceof Error ? err.message : String(err); + setStatus("error"); + setErrorMsg(msg); + addLog(`Error: ${msg}`, "error"); + } + }; + + // Single URL fetch — reuses the existing MixFetch singleton + const handleFetch = async () => { + try { + setBusy(true); + setHtml(undefined); + addLog(`Sending request to ${url}...`, "send"); + const response = await mixFetch(url, args, mixFetchOptions); + const resHtml = await response.text(); + setHtml(resHtml); + addLog(`Response received (${resHtml.length} bytes)`, "receive"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + addLog(`Fetch error: ${msg}`, "error"); } finally { setBusy(false); } - } + }; + + // Send 5 concurrent requests to different URLs on the same domain + const handleConcurrentFetch = async () => { + const baseUrl = "https://jsonplaceholder.typicode.com/posts/"; + const count = 5; + try { + setConcurrentBusy(true); + setConcurrentResults([]); + addLog( + `Starting ${count} concurrent requests to ${baseUrl}1-${count}...`, + "send", + ); + const requests = Array.from({ length: count }, (_, i) => { + const targetUrl = `${baseUrl}${i + 1}`; + return mixFetch(targetUrl, args, mixFetchOptions) + .then((res) => res.json()) + .then((json: { id: number; title: string }) => { + const entry = `[${json.id}] ${json.title}`; + addLog(entry, "receive"); + return entry; + }); + }); + const results = await Promise.all(requests); + setConcurrentResults(results); + addLog(`All ${count} concurrent requests completed!`, "info"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + addLog(`Concurrent fetch error: ${msg}`, "error"); + } finally { + setConcurrentBusy(false); + } + }; + + const isReady = status === "ready"; return ( - <> - - {results.length > 0 && ( -
    - {results.map((r, i) => ( -
  • {r}
  • +
    + {/* Start MixFetch */} + + + + {status === "starting" && } + + {status === "idle" ? "Not started" : + status === "starting" ? "Starting..." : + status === "ready" ? "Ready" : + `Error: ${errorMsg}`} + + + + + {/* Fetch controls — disabled until MixFetch is ready */} + + {/* Single fetch */} + + setUrl(e.target.value)} + /> + + + {busy && } + {html && ( + <> + Response + + {html} + + + )} + + {/* Concurrent fetch */} + + Concurrent Requests + + + + + {concurrentBusy && } + {concurrentResults.length > 0 && ( + + {concurrentResults.map((result, i) => ( + {result} + ))} + + )} + + + {/* Log Panel */} + {logs.length > 0 && ( + + Log + {logs.map((entry, i) => ( + + {entry.timestamp} [{logLabels[entry.level]}] {entry.message} + ))} -
+
+ )} - +
); -} - -export default function App() { - return ( - <> - - - - - ); -} +}; ``` diff --git a/documentation/docs/pages/developers/typescript/examples/mixnet.mdx b/documentation/docs/pages/developers/typescript/examples/mixnet.mdx index db015511a9..ef3e292fab 100644 --- a/documentation/docs/pages/developers/typescript/examples/mixnet.mdx +++ b/documentation/docs/pages/developers/typescript/examples/mixnet.mdx @@ -1,104 +1,111 @@ +--- +title: "TypeScript Mixnet Client Example" +description: "Send and receive private messages in the browser using the Nym TypeScript SDK. Includes setup, SURB anonymous replies, and environment configuration." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + import { Callout } from 'nextra/components' # Mixnet Client -As you know by now, in order to send or receive messages over the mixnet, you'll need to use the [`SDK Client`](https://www.npmjs.com/package/@nymproject/sdk), which will allow you to create apps that can use the Nym mixnet and Coconut credentials. -This client is message based - it can only send a one-way message to another client's address. +The [`SDK Client`](https://www.npmjs.com/package/@nymproject/sdk) lets you send and receive messages over the Nym mixnet. -Replying can be achieved in two ways: -- reveal the sender's address to the recipient (as part of the payload) -- use a SURB (single use reply block) that allows the recipient to reply to the sender without compromising the identity of either party +The client is message-based: it sends one-way messages to another client's address. Replying can be achieved in two ways: +- Reveal the sender's address to the recipient (as part of the payload) +- Use a SURB (single use reply block) that lets the recipient reply without compromising the identity of either party -##### Environment Setup -Begin by creating a directory and configuring your application environment: +## Environment Setup + +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 + TypeScript, then: + ```bash -cd < YOUR_APP > +cd npm i npm run dev ``` -##### Installation -Install the required package: +## Installation + ```bash npm install @nymproject/sdk-full-fat ``` -##### Imports -In the `src` folder, open the `App.tsx` file and delete all the code. +## Full Example -Import the SDK's Mixnet Client in your app: -````js -import { createNymMixnetClient, NymMixnetClient, Payload } from "@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. -##### Example: using the SDK's Mixnet Client to send and receive messages over the Nym mixnet -By pasting the below code example, you should be able to send and receive messages through the mixnet through an unstyled mixnet app template! - - For this example, we will be using the `full-fat` version of the ESM SDK. If you'd like to use the unbundled version of the ESM one, make sure your [bundler configuration](../bundling/bundling) copies the WebAssembly (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](../bundling/bundling) copies the WASM and web worker files to the output bundle. -```ts -import "./App.css"; -import { useEffect, useState } from "react"; +```ts copy filename="App.tsx" +import React, { useEffect, useState } from "react"; import { createNymMixnetClient, NymMixnetClient, Payload, } from "@nymproject/sdk-full-fat"; +import Box from "@mui/material/Box"; +import CircularProgress from "@mui/material/CircularProgress"; +import Paper from "@mui/material/Paper"; +import Typography from "@mui/material/Typography"; +import Stack from "@mui/material/Stack"; +import TextField from "@mui/material/TextField"; +import Button from "@mui/material/Button"; const nymApiUrl = "https://validator.nymtech.net/api"; -export function MixnetClient() { +export const Traffic = () => { const [nym, setNym] = useState(); const [selfAddress, setSelfAddress] = useState(); const [recipient, setRecipient] = useState(); const [payload, setPayload] = useState(); const [receivedMessage, setReceivedMessage] = useState(); + const [buttonEnabled, setButtonEnabled] = useState(false); const init = async () => { const client = await createNymMixnetClient(); setNym(client); - // Start the client and connect to a gateway + // start the client and connect to a gateway await client?.client.start({ clientId: crypto.randomUUID(), nymApiUrl, forceTls: true, // force WSS }); - // Check when is connected and set the self address + // check when is connected and set the self address client?.events.subscribeToConnected((e) => { const { address } = e.args; setSelfAddress(address); }); - // Show whether the client is ready or not + // show whether the client is ready or not client?.events.subscribeToLoaded((e) => { console.log("Client ready: ", e.args); }); - // Show message payload content when received + // show message payload content when received client?.events.subscribeToTextMessageReceivedEvent((e) => { console.log(e.args.payload); setReceivedMessage(e.args.payload); }); }; - const stop = async () => { await nym?.client.stop(); }; - const send = () => { - if (!nym || !payload || !recipient) return - nym.client.send({ payload, recipient }); - } + const send = () => + payload && recipient && nym?.client.send({ payload, recipient }); useEffect(() => { init(); @@ -107,46 +114,66 @@ export function MixnetClient() { }; }, []); - if (!nym) return
Waiting for the mixnet client...
; - - if (!selfAddress) return
Connecting...
; + useEffect(() => { + if (recipient && payload) { + setButtonEnabled(true); + } else { + setButtonEnabled(false); + } + }, [recipient, payload]); + if (!nym || !selfAddress) { + return ( + + + + ); + } return ( -
-

Send messages through the Nym mixnet

-

- My self address is: {selfAddress ? selfAddress : "loading"} -

-
- - setRecipient(e.target.value)} - > - - setPayload({ message: e.target.value, mimeType: "text/plain" }) - } - > - -
-

Received message: {receivedMessage}

-
+ + + + My self address is: + {selfAddress || "loading"} + Communication through the Mixnet + setRecipient(e.target.value)} + size="small" + /> + + setPayload({ message: e.target.value, mimeType: "text/plain" }) + } + size="small" + /> + + + {receivedMessage && ( + + Message Received! + {receivedMessage} + + )} + + ); }; - -export default function App () { - return ( - <> - - - ) -} ``` - - If you encounter a Gateway client error that persists even after a hard refresh, you may need to take the following steps: Open your browser's console => Navigate to the "Application" tab => Delete the databases listed under "IndexedDB". - Additionally, please be aware that the mixnet client is currently limited to functioning in local development environments due to SSL-related issues. + +If you encounter a Gateway client error that persists even after a hard refresh, open your browser console, navigate to the "Application" tab, and delete the databases listed under "IndexedDB". diff --git a/documentation/docs/pages/developers/typescript/examples/nym-smart-contracts.mdx b/documentation/docs/pages/developers/typescript/examples/nym-smart-contracts.mdx index c2f3303bf7..a3a0d3a466 100644 --- a/documentation/docs/pages/developers/typescript/examples/nym-smart-contracts.mdx +++ b/documentation/docs/pages/developers/typescript/examples/nym-smart-contracts.mdx @@ -1,143 +1,143 @@ +--- +title: "Nym Smart Contract Clients" +description: "Query and execute on Nym smart contracts using TypeScript contract clients. Covers Mixnet, Coconut, Vesting, Name Service, and other on-chain contracts." +schemaType: "TechArticle" +section: "Developers" +lastUpdated: "2026-03-15" +--- + import { Callout } from 'nextra/components' # Nym Smart Contract Clients -As previously mentioned, to query or execute on any of the Nym contracts, you'll need to use one of the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients), which contains read-only query and signing clients for all of Nym's smart contracts. +To query or execute on any of the Nym contracts, use the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients), which contain read-only query and signing clients for all of Nym's smart contracts. -##### Contract Clients list -Lists of the different available clients and methods from the `Contract Clients` can be found in the `.client.ts` files: -| Client name | Functionality| Methods list | -| :-------------: | :----------: | :----------: | -| Coconut Bandwidth Client| Manages the depositing and release of funds. Tracks double spending. | [Coconut Bandwidth](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutBandwidth.client.ts) | -| Coconut DKG Client | Allows signers participating in issuing Coconut credentials to derive keys to be used. | [Coconut DKG](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutDkg.client.ts) | -| Cw3FlexMultisig Client | Used by the Coconut APIs to issue credentials. [This](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw3-flex-multisig) is a multisig contract that is backed by the cw4 (group) contract, which independently maintains the voter set. | [Cw3Flex Multisig](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw3FlexMultisig.client.ts) | -| Cw4Group Client | Used by the Coconut APIs to issue credentials. [Cw4 Group](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw4-group) stores a set of members along with an admin, and allows the admin to update the state. | [Cw4Group](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw4Group.client.ts) | -| Mixnet Client | Manages the network topology of the mixnet, tracking delegations and rewards. | [Mixnet](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Mixnet.client.ts) | -| Name Service Client | Operates as a directory of user-defined aliases, analogous to a Domain Name System (DNS). | [Name service](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/NameService.client.ts) | -| Service provider Directory Client| Allows users to register their service provider in a public directory. | [Service Provider](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/ServiceProviderDirectory.client.ts) | -| Vesting Client | Manages NYM token vesting functionality. | [Vesting](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Vesting.client.ts) | +## Contract Clients +| Client | Functionality | Methods | +| :---: | :---: | :---: | +| Coconut Bandwidth | Manages depositing and release of funds. Tracks double spending. | [Source](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutBandwidth.client.ts) | +| Coconut DKG | Allows signers to derive keys for issuing Coconut credentials. | [Source](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutDkg.client.ts) | +| Cw3FlexMultisig | Used by the Coconut APIs to issue credentials. [cw3-flex-multisig](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw3-flex-multisig) backed by cw4. | [Source](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw3FlexMultisig.client.ts) | +| Cw4Group | Used by the Coconut APIs. [Cw4 Group](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw4-group) stores members with an admin. | [Source](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw4Group.client.ts) | +| Mixnet | Manages the network topology, tracking delegations and rewards. | [Source](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Mixnet.client.ts) | +| Name Service | Directory of user-defined aliases, analogous to DNS. | [Source](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/NameService.client.ts) | +| Service Provider Directory | Public directory for registering service providers. | [Source](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/ServiceProviderDirectory.client.ts) | +| Vesting | Manages NYM token vesting functionality. | [Source](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Vesting.client.ts) | +## Environment Setup -##### 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 + TypeScript, then: + ```bash -cd < YOUR_APP > +cd npm i npm run dev ``` -##### Installation -Install the packages and their dependencies if you don't already have them: +## Query Example + +### Installation + ```bash npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate ``` -## Query clients +### Querying the Mixnet Contract -In the `src` folder, open the `App.tsx` file and delete all the code. +This example uses `MixnetQueryClient` to fetch a paged list of mixnodes from the contract. Create a `settings.ts` file for your network configuration: -##### Imports -Import the contracts' client in your app: -````js -import { contracts } from '@nymproject/contract-clients'; -import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; -```` - -##### Example: using the mixnet smart contract client to query -In this example, we will use the `MixnetQueryClient`from the `Contract Clients` to simply query the contract and return a list of mixnodes. - -```ts -import "./App.css"; -import { contracts } from "@nymproject/contract-clients"; -import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; -import { useEffect, useState } from "react"; - -export default function Mixnodes() { - - const [mixnodes, setMixnodes] = useState([]); - - async function fetchMixnodes(){ - // Set-up the CosmWasm Client - const cosmWasmClient = await SigningCosmWasmClient.connect("wss://rpc.nymtech.net:443"); - const client = new contracts.Mixnet.MixnetQueryClient( - cosmWasmClient, - "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr" // The mainnet mixnet contract address (which will be different on mainnet, QA, etc) - ); - const result = await client.getMixNodesDetailed({}); - setMixnodes(result.nodes) - } - - useEffect(() => { - fetchMixnodes(); - }, []) - - return( - <> - - - {mixnodes?.map((value: any, index: number) => { - return( - - - - ) - }) - } - -
{value?.bond_information?.mix_node?.identity_key}
- - ) -} +```ts filename="settings.ts" +export const settings = { + url: "wss://rpc.nymtech.net:443", + mixnetContractAddress: "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr", +}; ``` -By pasting the above code in the `App.tsx` file and `npm run dev` your app from the terminal, you should see an unstyled printed list of Nym mixnodes! +```ts copy filename="App.tsx" +import { useEffect, useState } from "react"; +import { contracts } from "@nymproject/contract-clients"; +import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { settings } from "./settings"; +import Box from "@mui/material/Box"; +import CircularProgress from "@mui/material/CircularProgress"; +const getClient = async () => { + const cosmWasmClient = await SigningCosmWasmClient.connect(settings.url); + const client = new contracts.Mixnet.MixnetQueryClient( + cosmWasmClient, + settings.mixnetContractAddress + ); + return client; +}; +export const Mixnodes = () => { + const [mixnodes, setMixnodes] = useState(); + const getMixnodes = async () => { + const client = await getClient(); + const { nodes } = await client.getMixNodesDetailed({}); + setMixnodes(nodes); + }; -## Execute clients + useEffect(() => { + getMixnodes(); + }, []); -##### Installation -Install the packages and their dependencies if you don't already have them: + if (!mixnodes) { + return ( + + + + ); + } + + return ( +
+ {mixnodes?.length && + mixnodes.map((mixnode: any) => ( + + + {`id: ${mixnode.bond_information.mix_id}`} + + {`owner: ${mixnode.bond_information.owner}`} + + ))} +
+ ); +}; +``` + +## Execute Example + +### Installation ```bash npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing ``` +### Executing Contract Methods -##### Imports -Import the contracts' execute clients in your app: -````js -import { contracts } from '@nymproject/contract-clients'; -import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; -import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; -```` +This example uses `MixnetClient` with a signer to execute methods like delegation. -##### Example: using the Mixnet smart contract client to execute methods -In this example, we will use the `MixnetClient`and the `signer` from the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients) to execute methods. +Update your `settings.ts` to include signing credentials: -Note that you will need to create a `settings.ts` file (here created in the same directory), using the following structure: -```json - -export const mySettings = { - url: "wss://rpc.nymtech.net:443", - mixnetContractAddress: '', - mnemonic: '', - address: '' +```ts filename="settings.ts" +export const settings = { + url: "wss://rpc.nymtech.net:443", + mixnetContractAddress: "", + mnemonic: "", + address: "", }; - -export const settings = mySettings; ``` -```ts -import "./App.css"; +```ts copy filename="App.tsx" import { contracts } from "@nymproject/contract-clients"; import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; @@ -155,47 +155,33 @@ export default function Exec() { let delegations: any; async function ExecuteOnNyx() { - // Cosmos client signer = await DirectSecp256k1HdWallet.fromMnemonic(settings.mnemonic, { prefix: "n", }); const cosmWasmClient = await SigningCosmWasmClient.connectWithSigner( settings.url, signer, - { - gasPrice: GasPrice.fromString("0.025unym"), - } + { gasPrice: GasPrice.fromString("0.025unym") } ); - // Save globally cosmWasmSigningClient = cosmWasmClient; - // Nym client const mixnetClient = new contracts.Mixnet.MixnetClient( cosmWasmSigningClient, - settings.address, // Sender (that account of the signer) - settings.mixnetContractAddress // Contract address (different on mainnet, QA, etc) + settings.address, + settings.mixnetContractAddress ); - // Save globally signerMixnetClient = mixnetClient; - } - // Get delegations const getDelegations = async () => { - if (!signerMixnetClient) { - return; - } - const delegationsObject = await signerMixnetClient.getDelegatorDelegations({ + if (!signerMixnetClient) return; + delegations = await signerMixnetClient.getDelegatorDelegations({ delegator: settings.address, }); - delegations = delegationsObject; }; - // Make delegation const doDelegation = async () => { - if (!signerMixnetClient) { - return; - } + if (!signerMixnetClient) return; const res = await signerMixnetClient.delegateToMixnode( { mixId }, "auto", @@ -205,7 +191,6 @@ export default function Exec() { console.log(res); }; - // Undelegate all const doUndelegateAll = async () => { for (const delegation of delegations.delegations) { await signerMixnetClient.undelegateFromMixnode( @@ -215,15 +200,13 @@ export default function Exec() { } }; - // Sending tokens const doSendTokens = async () => { - const memo = "test sending tokens"; const res = await cosmWasmSigningClient.sendTokens( settings.address, nodeAddress, [{ amount: amountToSend, denom: "unym" }], "auto", - memo + "test sending tokens" ); console.log(res); }; @@ -233,42 +216,16 @@ export default function Exec() { return (
-

Exec

-
-

Send Tokens

- (nodeAddress = e.target.value)} - /> - (amountToSend = e.target.value)} - /> -
- -
-
-
-

Delegate

- (mixId = +e.target.value)} - /> - (amountToDelegate = e.target.value)} - /> -
- -
-
- -
-
+

Send Tokens

+ (nodeAddress = e.target.value)} /> + (amountToSend = e.target.value)} /> + + +

Delegate

+ (mixId = +e.target.value)} /> + (amountToDelegate = e.target.value)} /> + +
); } diff --git a/documentation/docs/pages/developers/typescript/installation.mdx b/documentation/docs/pages/developers/typescript/installation.mdx deleted file mode 100644 index 0596ef0828..0000000000 --- a/documentation/docs/pages/developers/typescript/installation.mdx +++ /dev/null @@ -1,57 +0,0 @@ -import { Callout } from 'nextra/components' - -# Overview - -The different modules in the Typescript SDK allow developers to start building browser-based applications quickly. Simply import the SDK module of your choice – depending on the component from the Nym architecture you want to use – into your code via NPM, as you would any other TypeScript library. - - - Other than the `Contract Clients`, SDK modules come in four different flavours (ESM, CJS and full-fat for ESM and CJS). - This documentation focuses on examples using the `full-fat` versions. - - -#### Install all - -```bash -npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk-full-fat @nymproject/mix-fetch-full-fat -``` - -## MixFetch -#### Overview -MixFetch is a drop-in replacement for [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) that sends HTTP requests through the Nym mixnet. It does this by grabbing the same arguments as traditional fetch and constructing a SOCKS5 request that will be made to the destination host on the Internet via an Exit Gateway. - -#### Installation: MixFetch package -In order to fetch data through mixFetch you'll need to use the [`MixFetch package`](https://www.npmjs.com/package/@nymproject/mix-fetch). - -First install the package and its dependencies: - -```bash -npm install @nymproject/mix-fetch-full-fat -``` - -## Mixnet -#### Installation: Mixnet Client -In order to send or receive traffic over the mixnet, you'll need to use the [`Mixnet Client`](https://www.npmjs.com/package/@nymproject/sdk). - -First install the package and its dependencies: - -```bash -npm install @nymproject/sdk-full-fat -``` - -## Nym Smart Contracts -#### Overview -The Nyx blockchain is a general-purpose CosmWasm-enabled smart contract platform, and the home of the smart contracts which keep track of the mixnet, amongst others. -Further information about the chain can be found on the [Nyx blockchain explorer](https://nym.explorers.guru/). - -Using the [Nym mixnet smart contract clients](../../network/architecture/nyx#smart-contracts), you will be able to query contract states or execute methods when providing a signing key. - -*You can learn about our different methods to interact with the chain [here](../chain)*. - -#### Installation: Contract Clients -In order to query or execute on any of the Nym smart contracts, you'll need to use the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients), which contains read-only query and signing clients for all Nyx's smart contracts. - -First install the package and its dependencies from Cosmos Stargate: - -```bash -npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing -``` diff --git a/documentation/docs/pages/developers/typescript/overview.mdx b/documentation/docs/pages/developers/typescript/overview.mdx deleted file mode 100644 index 171cbc50cf..0000000000 --- a/documentation/docs/pages/developers/typescript/overview.mdx +++ /dev/null @@ -1,72 +0,0 @@ -import { Callout } from 'nextra/components' -import { TableContainer, Table, TableBody, TableCell, TableRow, Paper } from '@mui/material' -import { NPMLink } from '../../../components/npm'; - -## SDK overview - -The Typescript SDK allows developers to start building browser-based Nym-based applications quickly, by simply importing the SDK modules into their code via NPM as they would any other Typescript library. - -Currently developers can use different packages from the Typescript SDK to run the following entirely in browser: - - - - - - - **Nym Smart Contracts** - - - Create a simple query or query and execute methods on the smart contracts that run the Nym Mixnet - - - - - - - - **Mixnet Client** - - - Send & receive text and binary traffic over the Nym Mixnet - - -
-
-
- -
-
- - - **mixFetch** - - - 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 - - -
-
-
- -
-
-
-
-
- -## Which package should I use? - -All packages come in four different variations: -- **ESM**: For new projects with current tooling. These packages use the ECMAScript Modules (ESM) system. You may need to [configure your bundler](./bundling/bundling) to handle the packages WASM and web worker components; -- **ESM full-fat**: These ESM packages are pre-bundled and include inline WebAssembly and web worker code; -- **CommonJS**: For older projects that still use CommonJS. All WebAssembly (WASM) and web workers in the package need to be [bundled](./bundling/bundling) to work correctly; -- **CommonJS full-fat**: These packages are already pre-bundled and should work in your project without additional configuration; - - - All `*-full-fat` variants have large bundle sizes because they include all WASM and web-workers as inline Base64 strings. If you care about your app's bundle size, then use the ESM variant. - - - - As the Typescript SDK and associated modules are still a work in progress, note that the inline WASM and web worker versions are likely to have issues if the web site / app / extension has a strict [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). - diff --git a/documentation/docs/pages/developers/typescript/playground/cosmos-kit.mdx b/documentation/docs/pages/developers/typescript/playground/cosmos-kit.mdx index d6640e0344..4498cfc72f 100644 --- a/documentation/docs/pages/developers/typescript/playground/cosmos-kit.mdx +++ b/documentation/docs/pages/developers/typescript/playground/cosmos-kit.mdx @@ -6,24 +6,27 @@ import FormattedCosmoskitExampleCode from '../../../../code-examples/sdk/typescr import { Callout } from 'nextra/components' -Below is an example that uses [CosmosKit](https://cosmoskit.com/) to connect and sign a fake transaction with your [Keplr wallet](https://www.keplr.app/) or -[Ledger hardware wallet](https://www.ledger.com/) to this page: +Sign a transaction using [CosmosKit](https://cosmoskit.com/) wallet adapters. This demo connects a [Keplr](https://www.keplr.app/) browser wallet or [Ledger](https://www.ledger.com/) hardware wallet and signs a test message; no transaction is broadcast. + +**Try it:** Click Connect to link your wallet, then click Sign to sign a test message. The resulting signature hash is displayed. -Once you connect either Keplr or your hardware Ledger, you can request the fake transaction to be signed. The hash -of the message will be displayed. - No transactions will be broadcast. You will only be signing a transaction. -If you are using the Ledger hardware wallet, please make sure: +## Ledger setup -- You have the `cosmoshub` app installed on the Ledger; -- It is connected to your computer; -- It is unlocked; -- The Cosmos Hub app is open; -- Grant permissions to your browser when you click the button above to connect to the Ledger (if you do not see a prompt, try another browser); +If you are using a Ledger hardware wallet: + +- Install the `cosmoshub` app on the Ledger +- Connect it to your computer and unlock it +- Open the Cosmos Hub app +- Grant browser permissions when prompted (try another browser if no prompt appears) + +## 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. diff --git a/documentation/docs/pages/developers/typescript/playground/mixfetch.mdx b/documentation/docs/pages/developers/typescript/playground/mixfetch.mdx index 307dd35bd4..b32c60f24d 100644 --- a/documentation/docs/pages/developers/typescript/playground/mixfetch.mdx +++ b/documentation/docs/pages/developers/typescript/playground/mixfetch.mdx @@ -5,9 +5,9 @@ import Box from '@mui/material/Box'; import FormattedMixFetchExampleCode from '../../../../code-examples/sdk/typescript/mixfetch-example-code.mdx'; import { Callout } from 'nextra/components' +Fetch a URL through the Nym Mixnet. This demo creates a mixFetch client in your browser, routes your HTTP request through the Mixnet, and displays the response. Your IP address is hidden from the destination server. - - +**Try it:** Enter a URL and click Send. The default URL fetches a plaintext exit policy file. Try replacing it with any public HTTP endpoint. @@ -15,4 +15,8 @@ import { Callout } from 'nextra/components' Open your browser's console to see the connection and send/receive logging for this example.
+## 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. + diff --git a/documentation/docs/pages/developers/typescript/playground/mixnodes.mdx b/documentation/docs/pages/developers/typescript/playground/mixnodes.mdx index 66f4bc06a5..9745757dc3 100644 --- a/documentation/docs/pages/developers/typescript/playground/mixnodes.mdx +++ b/documentation/docs/pages/developers/typescript/playground/mixnodes.mdx @@ -5,12 +5,14 @@ import Box from '@mui/material/Box'; import FormattedExampleCode from '../../../../code-examples/sdk/typescript/mixnodes-example-code.mdx'; import { Callout } from 'nextra/components' +Query the Nym Mixnet smart contract for a live list of registered Mix Nodes. This demo uses `@nymproject/contract-clients` to read on-chain data directly from your browser, no backend required. - -The Nym Mixnet contract keeps a directory of all mixnodes that can be used to mix traffic. - -Here is a live example of querying the Mixnet Contract for a paged list of mixnodes: +**Try it:** Click the button to fetch the current page of Mix Nodes from the Mixnet Contract. Each entry shows the node's identity key, owner address, and bonding details. +## How this works + +The component creates a `MixnetContractClient` connected to the Nyx blockchain and calls `getMixNodesPaged()` to retrieve a paginated list of bonded Mix Nodes. This is a read-only query; no wallet or signing is needed. + diff --git a/documentation/docs/pages/developers/typescript/playground/traffic.mdx b/documentation/docs/pages/developers/typescript/playground/traffic.mdx index d3852de9e3..56170d16a2 100644 --- a/documentation/docs/pages/developers/typescript/playground/traffic.mdx +++ b/documentation/docs/pages/developers/typescript/playground/traffic.mdx @@ -1,15 +1,22 @@ # Traffic + import { Callout } from 'nextra/components' import { Traffic } from '../../../../components/traffic'; import Box from '@mui/material/Box'; import FormattedTrafficExampleCode from '../../../../code-examples/sdk/typescript/traffic-example-code.mdx'; +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. -Use this tool to experiment with the mixnet: send and receive messages! +**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. Open your browser's console to see the connection and send/receive logging for this example. + +## How this works + +The component creates a `NymMixnetClient`, subscribes to incoming messages via event listeners, and uses `client.send()` to dispatch messages through the Mixnet. When sending to your own address, the message travels through the full 5-hop route (Entry Gateway → 3 Mix Nodes → Exit Gateway) before arriving back at your client. + diff --git a/documentation/docs/pages/developers/typescript/playground/wallet.mdx b/documentation/docs/pages/developers/typescript/playground/wallet.mdx index 183ddbfe61..c0749c42d9 100644 --- a/documentation/docs/pages/developers/typescript/playground/wallet.mdx +++ b/documentation/docs/pages/developers/typescript/playground/wallet.mdx @@ -1,7 +1,6 @@ # Wallet import Box from '@mui/material/Box'; - import { WalletContextProvider } from '../../../../components/wallet/utils/wallet.context'; import { ConnectWallet } from '../../../../components/wallet/connect'; import { SendTokes } from '../../../../components/wallet/sendTokens'; @@ -9,19 +8,33 @@ import { Delegations } from '../../../../components/wallet/delegations'; import FormattedWalletConnectCode from '../../../../code-examples/sdk/typescript/wallet-connect-code.mdx'; import FormattedWalletSendTokensCode from '../../../../code-examples/sdk/typescript/wallet-sendTokens-code.mdx'; import FormattedWalletDelegationsCode from '../../../../code-examples/sdk/typescript/wallet-delegations-code.mdx'; - - import { Callout } from 'nextra/components' -Here's a small wallet example using testnet for you to test out! +Interactive wallet operations on the Nyx Sandbox testnet. This demo walks through connecting to the chain, sending tokens, and querying delegations, all from the browser using `@nymproject/contract-clients`. + + +This connects to the **Sandbox testnet**. No real tokens are involved. + + ## Connect + + Create or restore a wallet from a mnemonic to connect to the Nyx Sandbox testnet. + + ## Send Tokens + + Transfer testnet tokens between accounts. + + ## Delegations + + Query staking delegations for an account. + diff --git a/documentation/docs/pages/developers/typescript/start.mdx b/documentation/docs/pages/developers/typescript/start.mdx deleted file mode 100644 index 0d3c5b4c0f..0000000000 --- a/documentation/docs/pages/developers/typescript/start.mdx +++ /dev/null @@ -1,79 +0,0 @@ -import { Callout } from 'nextra/components' - - -## MixFetch - -Use the [`mixFetch`](https://www.npmjs.com/package/@nymproject/mix-fetch) package as a drop-in replacement for `fetch`to send HTTP requests over the Nym 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', Authorization: `Bearer ${AUTH_TOKEN}` } -}); -``` - -## Mixnet Client -After instantiating the [`Mixnet Client`](https://www.npmjs.com/package/@nymproject/sdk), you can use it and send messages to yourself and output them in the console by following these steps: -````js -import { createNymMixnetClient } from '@nymproject/sdk'; - -const main = async () => { - const nym = await createNymMixnetClient(); - - const nymApiUrl = 'https://validator.nymtech.net/api'; - - // Show message payload content when received - nym.events.subscribeToTextMessageReceivedEvent((e) => { - console.log('Got a message: ', e.args.payload); - }); - - // Start the client and connect to a gateway - await nym.client.start({ - clientId: 'My awesome client', - nymApiUrl, - }); - - // Stop the client connection - const stop = async () => { - await nym?.client.stop(); - }; - - // Send a message to yourself - const payload = 'Hello mixnet'; - const recipient = nym.client.selfAddress(); - nym.client.send({ payload, recipient }); - -}; -```` - -## Nym Smart Contracts - -After having installed your client from the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients) to query any of the Nym smart contracts, you can import the packages and execute some methods, signing them with a mnemonic: -````js -import { contracts } from '@nymproject/contract-clients'; -import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; -import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; - -async function main() { - // Generate a signer from a mnemonic - const signer = await DirectSecp256k1HdWallet.fromMnemonic("..."); - const accounts = await signer.getAccounts(); - - // Make a signing client for the Nym Mixnet contract on mainnet - const cosmWasmSigningClient = await SigningCosmWasmClient.connectWithSigner("https://rpc.nymtech.net:443", signer); - const client = new contracts.Mixnet.MixnetClient(cosmWasmSigningClient, accounts[0].address, 'n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr'); - - // Delegate 1 NYM to mixnode with id 100 - const result = await client.delegateToMixnode({ mixId: 100 }, 'auto', undefined, [{ amount: `${1_000_000}`, denom: 'unym' }]); - - console.log(`Tx Hash = ${result.transactionHash}`); -}; -```` diff --git a/documentation/docs/pages/network/_meta.json b/documentation/docs/pages/network/_meta.json index 4953f606e8..10b5e069e3 100644 --- a/documentation/docs/pages/network/_meta.json +++ b/documentation/docs/pages/network/_meta.json @@ -1,11 +1,14 @@ { "index": "Introduction", - "concepts": "Core Concepts", - "architecture": "Architecture", - "traffic": "Traffic Flow", + "overview": "Overview", + "dvpn-mode": "dVPN Mode", + "mixnet-mode": "Mixnet Mode", "cryptography": "Cryptography", + "infrastructure": "Infrastructure", + "reference": "Reference", "---": { "type": "separator" }, - "licensing": "Licencing" + "licensing": "Licensing", + "coc": "Code of Conduct" } diff --git a/documentation/docs/pages/network/architecture.mdx b/documentation/docs/pages/network/architecture.mdx deleted file mode 100644 index a3b8086b9b..0000000000 --- a/documentation/docs/pages/network/architecture.mdx +++ /dev/null @@ -1,6 +0,0 @@ -# Network Components - -Core components: -* A **Mixnet**, which mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the [**Loopix** design](concepts/loopix). This is made up of [Nym nodes](architecture/mixnet#nodes) runnning on servers around the world maintained by a decentralised group of Operators. -* Various [**Nym clients**](architecture/mixnet#nym-clients) which manage sending and receiving Sphinx packets, encrypting/decrypting traffic, and providing [cover traffic](./concepts/cover-traffic) to hide 'real' traffic timing. -* A CosmWasm-enabled blockchain called [**Nyx**](architecture/nyx), the home of the various smart contracts used by the mixnet. A subset of Nyx Validators run [NymAPI](./architecture/nyx#nymapi) instances, taking part in also producing and verifying [zk-nym credentials](cryptography/zk-nym). \ No newline at end of file diff --git a/documentation/docs/pages/network/architecture/_meta.json b/documentation/docs/pages/network/architecture/_meta.json deleted file mode 100644 index ecb2f7557b..0000000000 --- a/documentation/docs/pages/network/architecture/_meta.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "mixnet": "Mixnet", - "nyx": "Nyx Blockchain", - "nym-vs-others": "Nym vs Other Platforms", - "nym-not-p2p": "(Coming soon) Why Nym is Not P2P" -} diff --git a/documentation/docs/pages/network/architecture/mixnet.mdx b/documentation/docs/pages/network/architecture/mixnet.mdx deleted file mode 100644 index bd86fb6774..0000000000 --- a/documentation/docs/pages/network/architecture/mixnet.mdx +++ /dev/null @@ -1,51 +0,0 @@ -import { Callout } from 'nextra/components' - -# Mixnet Components - -## Nym Nodes - - - -If you want to run a node, the setup and maintenance guides can be found in the [Operator Docs](/operators/introduction). - - - -Although a large proportion of the Nym mixnet's functionality is implemented client-side, several key anonymity features rely on the decentralised node network that make up the Mixnet that run in different modes: - -* Nym Nodes running in **Mix Node** mode provide network security for network content _and_ metadata by performing packet-mixing on traffic travelling through the network: accepting incoming Sphinx packets from other Nym nodes and, using a variable delay, 'mixing' them with other packets (not forwarding on received packets according to FIFO but instead relying on a randomised delay function). -* Nym Nodes running in **Entry Gateway** mode act as message storage for clients which may go offline and come back online again, and (once zk-nyms are enabled) check for anonymous proof of access credentials. They represent the first hop Mixnet packets travel through when travelling between clients. -* Nym Nodes running in **Exit Gateway** mode act as message storage for clients which may go offline and come back online again, and communicate with the wider internet on behalf of Nym Clients. They represent the last hop Mixnet packets travel through when travelling between clients and/or external services. These can be thought of somewhat analogously to Tor Exit Nodes. -* **Services** are applications that communicate with Nym Clients, listening and sending traffic to the Mixnet. - -See the [traffic flow](../traffic) page for detailed information on how traffic moves through the Mixnet as well as the [Loopix](https://arxiv.org/pdf/1703.00536) design paper for overview of the stratified nature of the Mixnet. - -## Node Smoosh Status -The various Mixnet components were originally completely separate binaries. They are in the process of being 'smooshed' together into a single `nym-node` binary which runs in different modes for ease of use, as well as to allow for a more developed and responsive Mixnet design, where the role of a node in a given time period is decided and changed automatically based on network conditions (more on this in the future). - -Completed: -* All nodes are now a `nym-node`. A node's role is defined manually at runtime by the operator. -* The `nym-network-requester` is now part of a `nym-node` running in Exit Gateway mode. - -Upcoming: -* Whether a `nym-node` is running as a Gateway or Mix Node will be set based on network conditions, and change epoch to epoch. Currently the role is set manually by the operator and does not change automatically over time. A node will be able to be running in the role of a Mix Node, an Entry Gateway, or an Exit Gateway. - -## Nym Clients - - - You can read about setting up and using various clients in the [Developer Docs](../../developers/clients/socks5). - - -A large proportion of the Nym Mixnet's functionality is implemented client-side. - -Clients perform the following actions on behalf of users: - -* Determine network topology - what nodes exist, their public encryption keys and IP, etc. -* Register with a Gateway -* Authenticate with a Gateway -* Receive and decrypt messages from the Gateway -* Create layer-encrypted [Sphinx packets](../cryptography/sphinx) -* Send Sphinx packets with real messages -* Send Sphinx packet [cover traffic](../concepts/cover-traffic) when no real messages are being sent -* Retransmit [un-acknowledged packet sends](../traffic/acks) - -> At the moment due to the fact that Nym clients are message-based, using the Mixnet requires another client on the 'other side' of the mixet to send packets to, unless you're using the `nymvpn` client (part of the NymVPN app) or the `socks5` client, which operates as a SOCKS4,4a, or 5 proxy and is able to utilise the client embedded within the `nym-node`'s Exit Gateway functionality (prev. this functionality was a standalone service, the Network Requester). In the future we wish to remove this point of friction and have all Nym clients construct IP packets instead, easing the integration burden and abstracting away the message-based nature of client communication. diff --git a/documentation/docs/pages/network/architecture/nym-not-p2p.mdx b/documentation/docs/pages/network/architecture/nym-not-p2p.mdx deleted file mode 100644 index 8349c4a6e6..0000000000 --- a/documentation/docs/pages/network/architecture/nym-not-p2p.mdx +++ /dev/null @@ -1 +0,0 @@ -# Coming Soon! diff --git a/documentation/docs/pages/network/architecture/nym-vs-others.md b/documentation/docs/pages/network/architecture/nym-vs-others.md deleted file mode 100644 index 037234cd76..0000000000 --- a/documentation/docs/pages/network/architecture/nym-vs-others.md +++ /dev/null @@ -1,31 +0,0 @@ -# Nym vs Other Systems - -The diagram and brief explainer texts below give a high level overview of the difference between Nym and other comparable systems. - -### Nym vs VPNs - -The most popular network-level privacy solution currently is the VPN (virtual private network), which provides network-level protection via an encrypted tunnel between a user’s computer and one run by a VPN provider. VPNs are often misconfigured, however, and even when configured correctly, don’t offer real privacy or adequate resistance to censorship. - -VPN providers can also fully observe all network traffic between users and the public internet, knowing exactly what services its users are accessing at a given time. The user must trust that the VPN provider is not using their information in a malicious manner or keeping logs. - -The Nym mixnet is an anonymous overlay network that provides strong network-level anonymity, even in the face of powerful systems capable of passively monitoring the entire network. The mixnet is decentralized, with no trusted third parties, and so does not require a trusted provider like a VPN. More importantly, Nym provides superior privacy to VPNs and can support high-quality of service and low latency through incentives. - -### Nym vs Tor - -Tor is the best-known anonymous overlay network today. Unlike VPNs, Tor provides a ‘circuit’ of three hops that provides better privacy than single-node VPNs, so any single node in Tor can’t deanonymize traffic. Tor’s onion-routing encrypts traffic between each hop so that only the final hop, the Tor ‘exit node’, can decrypt the package. - -However, Tor’s anonymity properties can be defeated by an entity that is capable of monitoring the entire network’s ‘entry’ and ‘exit’ nodes, because while onion-routing encrypts traffic, Tor does not add timing obfuscation or use decoy traffic to obfuscate the traffic patterns which can be used to deanonymize users. Although these kinds of attacks were thought to be unrealistic when Tor was invented, in the era of powerful government agencies and private companies, these kinds of attacks are a real threat. Tor’s design is also based on a centralized directory authority for routing. - -While Tor may be the best existing solution for general-purpose web-browsing that accesses the entire internet, it is inarguable that mixnets are better than Tor for message-passing systems such as cryptocurrency transactions and secure messaging, and we believe well designed incentives can also enable the use of Nym as a general purpose decentralized VPN. The Nym mixnet provides superior privacy by making packets indistinguishable from each other, adding cover traffic, and providing timing obfuscation. Unlike both previous mixnet designs and Tor, the Nym mixnet decentralizes its shared operations using blockchain technology and uses incentives to both scale and provide censorship-resistance. - -### Nym vs I2P - -I2P (‘Invisible Internet Project’) replaces Tor’s directory authority with a distributed hash table for routing. How to design a secure and private distributed hash table is still an open research question, and I2P is open to a number of attacks that isolate, misdirect, or deanonymize users. Like Tor, I2P is based on ‘security by obscurity’, where it is assumed that no adversary can watch the entire network. While security by obscurity may have been cutting-edge at the turn of the millennium, such an approach is rapidly showing its age. - -Nym’s cutting-edge mixnet design guarantees network anonymity and resistance to surveillance even in the face of powerful deanonymizing attacks. Unlike I2P, Nym adds decoy traffic and timing obfuscation. Rather than a centralized directory authority or distributed hash table, Nym uses blockchain technology and economic incentives to decentralize its network.The Nym mixnet can anonymize metadata even against government agencies or private companies who can monitor network links and observe the incoming and outgoing traffic of all clients and servers. - -### Nym vs Facebook Connect - -The Nym credential system decentralizes the functions of systems like Facebook Connect while adding privacy. Personal data has become a toxic asset, even to companies who base their entire business around it, as evidenced by the hack of Facebook’s OAuth identity system in 2018 and the subsequent release of the data of 50 million users. - -Unlike Facebook Connect and similar OAuth-based services like Sign in with Google, traditional usernames and passwords, or even public/private key pairs, Nym credentials allow users to authenticate and authorize data sharing without unwillingly revealing any information to a third party. There is no central third party in charge of the credentials, and users remain totally in control of their own data, disclosing it only to those who they want to. A user can store their data wherever they want (including on their own devices), and unlike alternatives like W3C’s DIDs, a user does not store anything on the blockchain, offering better privacy. diff --git a/documentation/docs/pages/network/architecture/nyx.mdx b/documentation/docs/pages/network/architecture/nyx.mdx deleted file mode 100644 index 69ec52b06e..0000000000 --- a/documentation/docs/pages/network/architecture/nyx.mdx +++ /dev/null @@ -1,89 +0,0 @@ -# Nyx Blockchain - -import { Callout } from 'nextra/components' - - - -If you want to interact with the chain please check the [interacting with Nyx](../../developers/chain) section of the developer docs. - -If you want to run a Validator node, check the [Operator guides](/operators/nodes/validator-setup). - - - -Nyx is a Cosmos SDK blockchain. The blockchain plays a supporting but fundamental role in the mixnet: the `NYM` token used to incentivise node operators is one of two native tokens of the chain, and the chain is where the [Mixnet](#mixnet-contract), [Vesting](#vesting-contract) and [zk-nym](#zk-nym-contract) smart contracts are deployed. - -## Validators - - The validator setup and maintenance guide can be found in the [Operator Docs](/operators/nodes/validator-setup). - - -Validators secure the Nyx blockchain via Proof of Stake consensus. The Nyx blockchain records the ledger of `NYM` transactions and executes the smart contracts for distributing `NYM` rewards. The Nyx validators are run via the `nyxd` binary ([codebase](https://github.com/nymtech/nyxd)), maintaining a CosmWasm- and IBC-enabled blockchain. - -Detailed info on Nyx Validators and token flow can be found in [Nym Reward Sharing for Mixnets document](https://nym.com/nym-cryptoecon-paper.pdf) in section 2.3 and 2.4 and in the [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) section 3.1. - -## NymAPI - - The Nym API setup and maintenance guide has moved to the [Operator Guides book](/operators/nodes/validator-setup/nym-api). - - -The NymAPI is a binary operated by a subset of the Nyx validator set. This binary can be run in several different modes, and has two main bits of functionality: - -* Network monitoring (calculating the routing score of Mixnet nodes) -* Generation and validation of [zk-nyms](../cryptography/zk-nym/zk-nym-overview), a combination of the Coconut Selective Disclosure Credential and Offline Ecash schemes. - -This is important for both the proper decentralisation of the network uptime calculation and, more pressingly, enabling the NymVPN to utilise privacy preserving payments. - -## Smart Contracts -The Nyx blockchain is [CosmWasm](https://cosmwasm.com/) enabled. - -The following contracts are deployed to the chain: -* The [Mixnet contract](#mixnet-contract) which manages the network topology of the mixnet and tracks delegations & rewarding. -* The [Vesting contract](#vesting-contract) which manages `NYM` token vesting functionality. This will soon be deprecated. -* The [Quorum Multisig](#multisig-contract) used by the subset of the Nyx Validators that generate and validate [zk-nyms](../cryptography/zk-nym) to manage reward payouts for nodes. -* The [zk-nym contract](#zk-nym-contract) which keeps track of `NYM` deposits used as payment for zk-nym generation. - -The addresses of deployed smart contracts can be found in the [`network-defaults`](https://github.com/nymtech/nym/blob/master/common/network-defaults/src/mainnet.rs) directory of the codebase alongside other network default values. - -### Interacting with Contracts -You can use the [API docs](../../apis/introduction) to see how you can interact with the contracts. The [NymAPI](../../apis/nym-api) in particular has multiple endpoints to query the Mixnet state, topology, and various zk-Nym-related endpoints. - -### Mixnet Contract -The Mixnet smart contract is a core piece of the Nym system, functioning as the mixnet directory and keeping track of delegations and rewards: the core functionality required by an incentivised mixnet. You can find the code and build instructions [here](https://github.com/nymtech/nym/tree/master/contracts/mixnet). - -> Having a smart contract act as a decentralised topology directory for clients connecting to the Mixnet allows us to mitigate several possible attacks which systems relying on P2P networking are susceptible to. See [Why Nym is not P2P](./nym-not-p2p). - -The Mixnet contract has multiple functions: -* Storing bonded mix node and gateway information (and removing this on unbonding). -* **Providing the network-topology to the (cached) Validator API endpoint used by clients on startup for routing information.** -* Storing delegation and bond amounts. -* Storing reward amounts. - -### Vesting Contract -The vesting contract allows for the creation of vesting accounts, allowing `NYM` tokens to vest over time, and for users to minimally interact with the Mixnet using their unvested tokens. You can find the code and build instructions [here](https://github.com/nymtech/nym/tree/master/contracts/vesting). - -The Vesting contract has multiple functions: -* Creating and storing vesting `NYM` token vesting accounts. -* Interacting with the Mixnet using vesting (i.e. non-transferable) tokens, allowing users to delegate their unvested tokens. - -### Multisig Contract -The multisig contract used by the [NymAPI Quroum](../cryptography/zk-nym/zk-nym-overview) - a subset of the Nyx Validator set taking on the additional work of generating and validating zk-nyms - to execute certain actions in the [zk-nym](../cryptography/zk-nym) contract. - -It is essentially an instance of the [canonical](https://github.com/CosmWasm/cw-plus/tree/main/contracts) `cw3-flex-multisig` using the `cw4-group` contract, with one minor change to restrict the addresses allowed to submit proposals. - -### Zk-Nym Contract - - -Note that within the monorepo contract repo this contract is referred to as `ecash`. This is a historical artifact that hasn't yet been changed. - - -This contract is a hub for zk-nym related actions, being called by either: -- The zk-nym payment backend -- Nodes running in Gateway mode -- The multisig contract - -The following functionality is controlled by the multisig contract: - - Getting the list of blacklisted addresses who have tried to double-spend a zk-nym. - - Proposing to add an address to the blacklist. - - Executing an open proposal. - - The the zk-nym [payment backend](../cryptography/zk-nym/zk-nym-overview) can deposit funds with information used to identify the deposit. - - Finally, nodes running as Gateways can create a proposal to redeem a set of spend zk-nyms. diff --git a/documentation/docs/pages/network/concepts.mdx b/documentation/docs/pages/network/concepts.mdx deleted file mode 100644 index 683aaff563..0000000000 --- a/documentation/docs/pages/network/concepts.mdx +++ /dev/null @@ -1,2 +0,0 @@ -# Core Concepts -The balancing act of creating a working incentivised decentralised overlay network involves multiple mutually-supporting mechanisms that are novel and/or experimental. This section contains an overview of the main concepts you will need to understand the motivations and technologies behind the Mixnet and how it provides the privacy qualities that it does. diff --git a/documentation/docs/pages/network/concepts/_meta.json b/documentation/docs/pages/network/concepts/_meta.json deleted file mode 100644 index 86464fc2b3..0000000000 --- a/documentation/docs/pages/network/concepts/_meta.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mixing": "Packet Mixing", - "loopix": "Loopix", - "cover-traffic": "Cover Traffic", - "anonymous-replies": "Anonymous Replies", - "epochs": "Epochs" -} diff --git a/documentation/docs/pages/network/concepts/anonymous-replies.mdx b/documentation/docs/pages/network/concepts/anonymous-replies.mdx deleted file mode 100644 index 3876b6438b..0000000000 --- a/documentation/docs/pages/network/concepts/anonymous-replies.mdx +++ /dev/null @@ -1,17 +0,0 @@ -# Anonymous Replies using SURBs - -> SURBs are pre-computed Sphinx packet headers encoding a mixnet route that ends in the participant that created the SURB. A sender can generate one or more SURBs and include them in their Sphinx message to a recipient. The recipient can use the SURBs as Sphinx headers to send back replies – or acknowledgements – that anonymously reach back the original sender after going through the mixnet. -> -> SURBs are the Sphinx equivalent of "onion addresses" in Tor, with the caveat that a SURB can only be used once (to prevent replay attacks) and within its epoch of validity (the mix node public keys used to prepare the SURB are only valid for a limited period). SURB headers are encrypted by the sender, so the recipient sending it back cannot infer from it any information about the message route, the per-hop latency, or the sender’s address, which is encoded in the innermost (last) routing layer of the SURB. SURBs ('Single Use Reply Blocks') allow clients to reply to incoming messages anonymously. -> -> [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) §4.5 - -It will often be the case that a client app wants to interact with a service of some kind, or a P2P application on someone else's machine. It defeats the purpose of the whole system if a client app needs to reveal its own gateway public key and client public key in order to get a response. - -Luckily, SURBs (Single Use Reply Blocks) allow for anonymous replies. A SURB is a layer encrypted set of Sphinx headers detailing a reply path ending in the original client's [Nym address](../traffic/addressing-system). SURBs are encrypted by the client, so the recieving service/app can attach its response and send back the resulting Sphinx packet, but it **never has sight of who it is replying to**. - -MultiSURBs were implemented in `v1.1.4`. Clients, when sending a message to another client, attach a bundle of SURBs which can be used by the receiver to construct large anonymous replies. - -If a reply is too large still (i.e. it would use more space than the available combined payload of the SURBs sent with the original message), the receiver will use a SURB to ask the sender for more SURBs. - -You can read more about SURBs in §4.5 of the [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf) as well as the [SURB traffic page](../traffic/anonymous-replies) to learn more about how SURBs are used in the Mixnet, known attacks relying on SURBs, etc. diff --git a/documentation/docs/pages/network/concepts/cover-traffic.mdx b/documentation/docs/pages/network/concepts/cover-traffic.mdx deleted file mode 100644 index 15812e8790..0000000000 --- a/documentation/docs/pages/network/concepts/cover-traffic.mdx +++ /dev/null @@ -1,11 +0,0 @@ -# Cover Traffic - -> The Nym mixnet generates cover traffic "loops" ... [l]oops generated by mix nodes ensure a minimum level of anonymity at all times, while end users can generate loops to obfuscate the timing and volume of their active communication through Nym and thus achieve unobservability. -> -> [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) §4 - -In order to avoid timing attacks where an attacker tries to deanonymise which clients are communicating with each other, cover traffic is implemented. As soon as any client is connected to the mixnet, they will send [Sphinx](../cryptography/sphinx) packets in 'loops' between nodes at a steady rate, inserting packets containing actual payload data (i.e. actual messages being sent between clients) when they are available. - -In this way, Sphinx packets containing actual payload data are hidden in a steady stream of 'background noise' in the form of 'cover' Sphinx packets with empty payloads, but which are indistinguishable to an observer. - -An in-depth explainer can be found in §4.6 of the [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf). diff --git a/documentation/docs/pages/network/concepts/epochs.md b/documentation/docs/pages/network/concepts/epochs.md deleted file mode 100644 index a1cfa95215..0000000000 --- a/documentation/docs/pages/network/concepts/epochs.md +++ /dev/null @@ -1,7 +0,0 @@ -# Epochs - -Time in the context of the Mixnet is organised into epochs. The length of an epoch is configurable but currently set to be one hour. - -Several actions happen per epoch: -- Reward calculation and distribution for Nym Nodes. See the [Operator Docs](../../operators/tokenomics/mixnet-rewards) for more information on reward calculation. -- Topology rerandomisation: the arrangement of each layer of the Mixnet is re-randomised in order to make it more difficult for dishonest nodes to create 'full routes' of dishonest nodes running modified software. Currently, this is also where Nodes may enter or leave the active set based on uptime monitoring. diff --git a/documentation/docs/pages/network/concepts/loopix.mdx b/documentation/docs/pages/network/concepts/loopix.mdx deleted file mode 100644 index 72af2088cb..0000000000 --- a/documentation/docs/pages/network/concepts/loopix.mdx +++ /dev/null @@ -1,14 +0,0 @@ -# Loopix - -> Hiding "who messages whom" is a necessary mixnet property in terms of metadata protection – but it is not always sufficient to prevent surveillance. Adversaries that observe the volume and timing of sent and received messages over time may still be able to infer private information, even if individual messages are strongly anonymized. Detailed data on the volume and timing of user interactions leaks information over time about which services the user accesses and the patterns of usage of such services. If the user engages in persistent behavior (e.g., always messaging the same friend or always accessing the same service), communication profiles may leak over time and be recoverable through long-term statistical disclosure attacks. Mixnets – including classical Chaumian batch mixnets – provide unlinkability, but do not provide unobservability. In order for access to Nym to be unobservable, the adversary should not know when or how much actual traffic is being sent or received by a participant. -> -> Cover traffic disguises real traffic patterns by adding “dummy” messages that carry no payload data and are simply discarded at their final destination. While routing a message, mix nodes cannot distinguish whether it is a dummy message or a normal message carrying user data. Routing dummy traffic to circle back to the sender rather than ending at a randomly chosen destination was originally proposed to proactively detect active attacks on mixnets. Loopix, the name of which refers to its use of "loops" of dummy traffic, extends this approach to guarantee both a lower bound on anonymity and unobservability properties for end users. Nym follows a similar approach, with participants generating dummy messages that travel in a loop and have themselves as final destination. -> -> [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) §4.6 - -The Nym Mixnet is based upon the [Loopix](https://arxiv.org/pdf/1703.00536) design. - -This design lays out a stratified design of several layers of Mix Nodes and Gateways in which: -- Traffic path selection is chosen independently per-message, unlike designs such as Onion routing. -- Messages are routed through an Entry Gateway, 3 layers of Mix Nodes, and an Exit Gateway, where each node is connected only to adjecent layers. -- Continuous-time mixing is employed, wherein connected clients and nodes continuously generate packets that are sent into the network. This generation can by modeled by a Poisson process. Whether a packet contains a user-defined payload or not defines the difference between 'real' and 'cover' traffic but it is impossible to make this distinction by just monitoring the traffic flow itself by an observer. diff --git a/documentation/docs/pages/network/concepts/mixing.mdx b/documentation/docs/pages/network/concepts/mixing.mdx deleted file mode 100644 index cd1eb2ca7e..0000000000 --- a/documentation/docs/pages/network/concepts/mixing.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Packet Mixing -description: Mixnets are networks of nodes that route traffic in a way that makes it untraceable ---- - -# Packet Mixing - -> Continuous-time mixing strategies ... delay each message independently, forwarding it to its next destination once a specified delay has timed out. The aggregate effect of independently delaying each message is an output sequence of messages that is randomly reordered with respect to the input sequence. -> -> [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) §4.4 - -Mixnets are networks of nodes that route traffic in a way that makes it untraceable, even for Global Passive Adversaries employing Machine Learning to try and deanonymise traffic based on timing and fingerprinting attacks. - -One of the key features of a Mixnet - unsurprisingly - is that these nodes 'mix' traffic. As traffic moves through the network, each node, on receiving a message, will wait a variable length amount of time before sending it onwards - aka nodes do **not** pass messages on in a FIFO manner. An easy analogy is each node constantly receiving and sending out cards, shuffling their local deck each time and randomly selecting a card to pass along in the chain of messages. - -The Mixnet employs continuous-time mixing, in which each message is dealt with independently of the other messages in the node's local storage. This is in contrast to other Mixnet designs which rely on nodes sending out periodic bursts of accrued messages, such as the Chaumian Mixnet design _"which collects a number of input messages and outputs a random permutation of those messages, is known to suffer from some disadvantages: the end-to-end latency of messages in such mixnets is neither bounded nor predictable, and the bursty communication caused by periodically flushing batches of messages makes these mix designs inefficient at utilizing bandwidth. In terms of anonymity, simple batching strategies are known to offer low anonymity as well as being particularly vulnerable to attacks"_ ([Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) §4.4) - -Continuous-time mixing, in contrast: -- _"offer[s] optimal anonymity properties for a given mean end-to-end latency"_ due to the per-message randomised delay amount: by using an exponential delay distribution, we acheive a situation in which _"if we observe two messages going into the mix node at times t0 < t1, and a message coming out at a later time t2, the probability that the output is any of the two inputs is equal, regardless of -differences in their arrival times t0 and t1."_ -- offers a larger anonymity set than Chaumian batch Mixnets such as [Elixxir](https://learn.xx.network/) due again to the exponential delay distribution: _"even if most delays will be short, there is a non-zero probability that a message will incur a large delay; therefore, the adversary cannot discard future mix output messages as candidates for an input it wants to trace"_ -- allows nodes to send data more efficiently by continously sending/receiving packets. -- allows for a lower overall latency due to not having to wait for batches to be filled before messages are sent through to the next hop. diff --git a/documentation/docs/pages/network/cryptography.md b/documentation/docs/pages/network/cryptography.md new file mode 100644 index 0000000000..0ecc00757c --- /dev/null +++ b/documentation/docs/pages/network/cryptography.md @@ -0,0 +1,17 @@ +--- +title: "Nym Network Cryptography" +description: "Overview of the cryptographic systems powering Nym: transport encryption, Sphinx packet format, per-hop encryption, and zk-nym anonymous credentials." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Cryptography + +The Nym Network relies on several cryptographic systems working together. This section covers the algorithms, packet formats, and credential systems that provide privacy guarantees. + +## 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. + +[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. diff --git a/documentation/docs/pages/network/cryptography.mdx b/documentation/docs/pages/network/cryptography.mdx deleted file mode 100644 index 7212832745..0000000000 --- a/documentation/docs/pages/network/cryptography.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Nym Crypto - -This subsection includes overviews of all the crypto used by the Mixnet, from standards used by nodes when communicating and the Sphinx messaging format, to the zk-nym credential system, which was designed and implemented by the Nym team. diff --git a/documentation/docs/pages/network/cryptography/_meta.json b/documentation/docs/pages/network/cryptography/_meta.json index a155c32226..7b2bfa727f 100644 --- a/documentation/docs/pages/network/cryptography/_meta.json +++ b/documentation/docs/pages/network/cryptography/_meta.json @@ -1,4 +1,4 @@ { "sphinx": "Sphinx", - "zk-nym": "Zk-Nym Credentials" + "zk-nym": "zk-nym Credentials" } diff --git a/documentation/docs/pages/network/cryptography/encryption-standards.md b/documentation/docs/pages/network/cryptography/encryption-standards.md new file mode 100644 index 0000000000..7b88bf680e --- /dev/null +++ b/documentation/docs/pages/network/cryptography/encryption-standards.md @@ -0,0 +1,11 @@ +--- +title: "Encryption Standards Used in Nym" +description: "Cryptographic algorithms used across the Nym Network. This page is being rewritten for the Lewes Protocol release." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-04-07" +--- + +# Encryption Standards + +This page is being rewritten to reflect the cryptographic changes in the Lewes Protocol release. For the current overview, see the [Nym Trust Center: Cryptography](https://nym.com/trust-center/cryptography). diff --git a/documentation/docs/pages/network/cryptography/sphinx.md b/documentation/docs/pages/network/cryptography/sphinx.md new file mode 100644 index 0000000000..403b9512ec --- /dev/null +++ b/documentation/docs/pages/network/cryptography/sphinx.md @@ -0,0 +1,49 @@ +--- +title: "Sphinx Packet Format" +description: "How Sphinx packets provide layered encryption for anonymous mixnet routing, with fixed-size payloads, per-hop key derivation, and integrity verification via HMACs." +schemaType: "TechArticle" +section: "Network" +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. + +## How Sphinx works + +When a client sends a message through the mixnet, it constructs a Sphinx packet with multiple encryption layers, one for each hop in the route. The outermost layer is encrypted for the first hop (Entry Gateway), the next layer for the second hop (Mix Node Layer 1), and so on until the innermost layer contains the actual payload encrypted for the recipient. + +At each hop, the node uses its private key to decrypt its layer, revealing the address of the next hop and a new Sphinx packet to forward. The node cannot see any other routing information or the payload contents. + +## Packet structure + +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. + +## Integrity verification + +Each layer includes an HMAC (Hash-based Message Authentication Code) that the receiving node verifies before processing. This prevents malicious nodes from modifying packet contents en route. If the HMAC doesn't match, the packet is dropped. + +The payload uses Lioness wide-block encryption, which means any modification to any part of the payload invalidates the entire payload. This prevents bit-flipping attacks where an adversary might try to modify specific bytes. + +## Key derivation + +For each hop, the client performs an ECDH key exchange using the node's public key and an ephemeral key embedded in the packet header. This shared secret is then used with HKDF to derive the symmetric keys for that layer's encryption and HMAC. + +The ephemeral key is "blinded" at each hop so that successive nodes cannot correlate packets by the key value. Each node sees a different ephemeral key even though they're mathematically related. + +## Message fragmentation + +Messages larger than a single Sphinx payload are split into fragments. Each fragment travels independently through the network, potentially taking different routes. The recipient reassembles the fragments into the original message. + +## External implementation + +Nym uses the [`sphinx-packet`](https://github.com/nymtech/sphinx) crate for core Sphinx operations. This crate handles packet construction, header encryption, layer processing, and the mathematical operations for key blinding. + +## References + +- [Sphinx paper](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf): Original specification and security proofs +- [Elle Mouton's Sphinx explainer](https://ellemouton.com/posts/sphinx/): Detailed walkthrough of packet construction +- [Nym Whitepaper §4](https://nym.com/nym-whitepaper.pdf): Sphinx in the context of Nym diff --git a/documentation/docs/pages/network/cryptography/sphinx.mdx b/documentation/docs/pages/network/cryptography/sphinx.mdx deleted file mode 100644 index a80dd61d66..0000000000 --- a/documentation/docs/pages/network/cryptography/sphinx.mdx +++ /dev/null @@ -1,15 +0,0 @@ -# Sphinx Packet Format - -> Sphinx is a cryptographic message format used to relay anonymized messages within a mix network. It is more compact than any comparable scheme, and supports a full set of security features: indistinguish- able replies, hiding the path length and relay position, as well as providing unlinkability for each leg of the -message’s journey over the network. -> -> [Sphinx: A Compact and Provably Secure Mix Format](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) abstract - -[Sphinx](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) is the packet format used to multiply encrypt all messages sent by Nym clients through the Mixnet. All Sphinx packets constructed by clients have a payload of 2048 bytes. - -Some notable features: -- Clients derive a shared secret key with each node that the Sphinx packet will be sent between using their public encryption key and the nodes' public keys, assuring that only the information pertinent to each node - the address headers of the following hop - can be decrypted, with the payload being encrypted using the keys of the receiving client. -- In order to avoid dishonest nodes injecting false payload information en-route to the receiving client, [HMACs](https://en.wikipedia.org/wiki/HMAC) are present at each layer of the Sphinx packet so nodes can verify that the packet contents have not been changed. -- Payloads are padded in order to remain a fixed length. This is pertinent as it means that nodes do not know which hop in the packet's route they are. - -> You can find a detailed explanation of Sphinx packet construction and verification written by Elle Mouton [here](https://ellemouton.com/posts/sphinx/) (Sphinx is also used by the Lightning Network, amongst others). diff --git a/documentation/docs/pages/network/cryptography/zk-nym.mdx b/documentation/docs/pages/network/cryptography/zk-nym.mdx index 720373fcd8..3febcdaebe 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym.mdx @@ -16,7 +16,7 @@ However, _who_ is not necessarily a question we want to be asking when designing - Does the entity taking this action have a right to do _X_? -This allows a different kind of security. Many of the computer systems we talk to every day don't need to know _who we are_, they only need to know if the entity kicking off a request has the _right to use_ the system. +This allows a different kind of security. Most networked services do not need to know _who_ is making a request, only whether the requester has the _right to use_ the system. The zk-nym scheme allows for this move to take place. Credentials are generated cooperatively by decentralised, trustless systems, and once the credentials are generated, they can be _re-randomized_; entirely new credentials, which no one has ever seen before, can be presented to the ingress point of the Nym Network, and validated without being linkable back to the signatures produced by the Quorum of credential signers used to generate them, or any credentials previously used by an entity wanting access. These properties allow zk-nyms to act as something like cryptographic bearer tokens generated by decentralised systems. The tokens can be mutated so that they are not traceable, but still verified with the original permissions intact. @@ -42,4 +42,4 @@ Let's say you have a `message` with the content `This credential controls X` in 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). -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, you might think of it as a sort of supercharged decentralized privacy-friendly [JWT](https://jwt.io/). +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/). diff --git a/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx b/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx index 6da843967b..53b76db403 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/double-spend-prot.mdx @@ -1,7 +1,8 @@ # Double Spend Protection + Double spend protection in the context of zk-nym is a balancing act between speed, reliability, and UX. There are two possible modes for protecting against attempted double spending of zk-nyms: -- Online: The online approach mandates that ingress Gateways instantly deposit zk-nyms received from clients to the NymAPI Quorum for verification. Once verified by the Quroum, the ingress Gateway is paid proprtional to the amount of bandwidth 'spent' with the zk-nym, and proceeds to grant the client access to the network. +- Online: The online approach mandates that ingress Gateways instantly deposit zk-nyms received from clients to the NymAPI Quorum for verification. Once verified by the Quorum, the ingress Gateway is paid proportional to the amount of bandwidth 'spent' with the zk-nym, and proceeds to grant the client access to the network. - Offline: In contrast, the offline approach involves the periodic submission of collected zk-nyms by ingress Gateways to the Quorum, instead of an instant check. Subsequently, the Quorum nodes perform checks to detect any instances of double-spending and identify the public key associated with such occurrences, whereas the ingress Gateways only do a simple check to check that _that particular_ zk-nym had not been spent with itself before. > The zk-nym system takes the **offline** approach. @@ -22,7 +23,7 @@ An exploitable scenario arises from these limitations: ## 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. -### Entry Node Implementation: Real-Time Ticket Unspending Validation +### 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. > Since each time a zk-nym credential is rerandomised its serial number is changed, the serial number being shared in no way identifies a client or user. diff --git a/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx b/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx index e82d4f510d..797bc02d6b 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/rerandomise.mdx @@ -4,16 +4,14 @@ import { Callout } from 'nextra/components' Each ticket will not be valid for the entire amount of data that the ticketbook aggregated from the PSCs is; if the aggregated ticketbook is worth (e.g.) 10GB of Mixnet data, each ticket will be worth far less (e.g. 100MB). This amount will be globally uniform in order to avoid situations where differently sized tickets allow for patterns to emerge. - - 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 ticket sizes will be uniform across the Network. + + The `nym-cli` examples below are for illustration only and do not reflect how credentials are accessed in production. The specific figures (ticket counts, bandwidth amounts) are illustrative; production values may differ, though individual ticket sizes are uniform across the network. ## 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 means that clients are not tied to particular Gateways they have 'spent' their entire subscription amount with; if the ingress Gateway goes down, or the client simply wishes to use another ingress Gateway, the user has multiple other tickets they can use that account for their remaining purchased bandwidth. +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. Going back to the `nym-cli` tool to illustrate this; we can generate multiple unlinkable tickets from a single ticketbook aggregated from PSCs: diff --git a/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx b/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx index d2c7768b49..3714a64255 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/unlinkability.mdx @@ -1,7 +1,8 @@ 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 rereandomised 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 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. diff --git a/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx b/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx index 61ab6d3895..793c5a9281 100644 --- a/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx +++ b/documentation/docs/pages/network/cryptography/zk-nym/zk-nym-overview.mdx @@ -2,32 +2,26 @@ import { Callout } from 'nextra/components' # Generating and using zk-nym anonymous credentials - - The first use-case of zk-nyms is for anonymously proving the right to use the Nym mixnet for privacy. - - The Nym mixnet is - at the time of publication - free for everyone. However, soon™ it will be required for each connecting client to present a valid credential - a zk-nym - to their ingress Gateway to access the Mixnet. - - Accessing zk-nym credentials will vary depending on use: - - Individual developers building on the mixnet will be able to get zk-nym credentials via something like a faucet. - - Larger application integrations will have their own 'under the hood' credential generation and distribution scheme to generate access credentials on behalf of their users automatically. - - NymVPN users will have a variety of payment methods avaliable to them. The vast majority, if not all of the steps outlined on this page, will happen under the hood from their perspective. _More on this soon_. + + 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. Generation of zk-nyms involves the following actors / pieces of infrastructure: - **Requester needing a zk-nym** for example a single user using the NymVPN app, or a company purchasing zk-nyms to distribute to their app users, in the instance of an app integrating a Mixnet client via one of the SDKs. The Requester is represented by a Bech32 address on the Nyx blockchain. -- [NymAPI](../../architecture/nyx#nym-api) instances working together on signature generation and spent credential validation, referred to as the **NymAPI Quorum**. Members of the Quorum are a subset of the Nyx chain Validator set (other tasks they perform include a multisig used for triggering reward payouts to the Network Infrastructure Node Operators and maintaining the global Bloom Filter for double-spend protection). -- **OrderAPI**: an API creating crypto/fiat to `NYM` swaps and then depositing the NYM tokens in a smart contract managed by the NymAPI Quroum for payment verification. Implementation details of the API will be released in the coming months. +- [NymAPI](/network/infrastructure/nyx#nym-api) instances working together on signature generation and spent credential validation, referred to as the **NymAPI Quorum**. Members of the Quorum are a subset of the Nyx chain Validator set (other tasks they perform include a multisig used for triggering reward payouts to the Network Infrastructure Node Operators and maintaining the global Bloom Filter for double-spend protection). +- **OrderAPI**: an API creating crypto/fiat to `NYM` swaps and then depositing the NYM tokens in a smart contract managed by the NymAPI Quorum for payment verification. Implementation details of the API will be released in the coming months. Generation happens in 3 distinct stages: - Key Generation & payment - Issue credential - Generate unlinkable zk-nyms for Nym Network access -From the perspective of the Requester most of this happens under the hood, but results in the creation and usage of an **unlinkable, rerandomisable anonymous proof-of-payment credential** - a zk-nym - with which to access the Mixnet without fear of doxxing themselves via linking app usage and payment information. The user experience is further enhanced by the fact that a single credential can be split into multiple small zk-nyms, meaning that a Requester may buy a large chunk of bandwidth but 'spend' this in the form of multiple zk-nyms with different ingress Gateways. Whilst this happens under the hood, what it affords the Requester is an ease of experience in that they have to 'top up' their bandwidth less and are able to chop and change ingress points to the Nym Network as they see fit, akin to the UX of most modern day VPNs and dVPNs. +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. ## Key Generation & Payment -- First, a Cosmos [Bech32 address](https://docs.cosmos.network/main/build/spec/addresses/bech32) 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 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. - 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. - Payment options will trigger the OrderAPI. This will: - Create a swap for `` to `NYM` tokens. @@ -35,17 +29,16 @@ From the perspective of the Requester most of this happens under the hood, but r - The Requester sends a request to each member of the Quorum requesting a zk-nym credential. This request is signed with their private key and includes the transaction hash of the NYM deposit into the deposit contract, performed either by themselves or the OrderAPI. ## Issue zk-nym -At this point, NYM tokens have been deposited into the smart contract controlled by the Quorum's multisig and a zk-nym has been requested. Next, each member of the Quorum who responds to the Requester's request for a zk-nym checks the validity and returns a partial blinded signature - a 'partial signed credential' ('PSC') - signed with part of the master key (since this is a threshold cryptsystem, not all members of the Quroum must respond to create a zk-nym, only enough to pass the threshold). The process looks like this: +At this point, NYM tokens have been deposited into the smart contract controlled by the Quorum's multisig and a zk-nym has been requested. Next, each member of the Quorum who responds to the Requester's request for a zk-nym checks the validity and returns a partial blinded signature - a 'partial signed credential' ('PSC') - signed with part of the master key (since this is a threshold cryptosystem, not all members of the Quorum must respond to create a zk-nym, only enough to pass the threshold). The process looks like this: -- Members of the Quroum performs several checks to verify the request is valid: +- Members of the Quorum perform several checks to verify the request is valid: - They verify the signature sent as part of the request is valid and that the request was made in the last 48 hours. - - They verify that the amount requested matches the amount deposited in the transation, the hash of which was signed by the Requester's ed25519 key and sent as part of the request. -- Members then create a PSC from their fragment of the master key generated and split amongst them at the beginning of the Quroum in the initial DKG ceremony. + - They verify that the amount requested matches the amount deposited in the transaction, the hash of which was signed by the Requester's ed25519 key and sent as part of the request. +- Members then create a PSC from their fragment of the master key generated and split amongst them at the beginning of the Quorum in the initial DKG ceremony. - The member also creates a `key:value` entry in their local cache with the transaction hash as the key, and the PSC + encrypted signature as the value. This is used later for zk-nym validation and is cleaned after a predefined timeout. - These PSCs are given back to the Requester after setting up a secure channel via DH key exchange, with each replying Quorum member also sending their public key for verification that the returned PSC was signed by them. -Once the Requester has received over the threshold number of PSCs they can assemble them into a 'ticketbook' of 'tickets' - spendable credentials - signed by the master key. The Requester never learns this master key (it is a private attribute) but the credential can be verified by the Quroum as being valid by checking for a proof that the credential's private attribute - the value of the master key - is valid. - +Once the Requester has received over the threshold number of PSCs they can assemble them into a 'ticketbook' of 'tickets' - spendable credentials - signed by the master key. The Requester never learns this master key (it is a private attribute) but the credential can be verified by the Quorum as being valid by checking for a proof that the credential's private attribute - the value of the master key - is valid. ![](/images/network/deposit-generate.png) @@ -54,5 +47,4 @@ Once the Requester has received over the threshold number of PSCs they can assem - Once the ticketbook has been aggregated from the PSCs returned from > threshold of Quorum members, smaller 'ticket' credentials can be generated from it, accounting for smaller chunks of bandwidth which can be 'spent' with ingress Gateways. This occurs entirely offline, on the device of the zk-nym Requester. See pages on the scheme's [unlinkability](unlinkability) and [rerandomisation and incremental spending](./rerandomise) features for further information on this. - This ticket is later presented to the Quorum by the Gateway that collected it, which is used to calculate reward percentages given to Nym Network infrastructure operators by the Quorum, with payouts triggered by their multisig wallet. Both ingress Gateways and the Quorum use spent tickets when engaging in [double spending protection](./double-spend-prot). - ![](/images/network/use-zknym.png) diff --git a/documentation/docs/pages/network/dvpn-mode.md b/documentation/docs/pages/network/dvpn-mode.md new file mode 100644 index 0000000000..cb5c3a22b9 --- /dev/null +++ b/documentation/docs/pages/network/dvpn-mode.md @@ -0,0 +1,39 @@ +--- +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." +schemaType: "TechArticle" +section: "Network" +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. + +## How it works + +``` +User --> Entry Gateway --> Exit Gateway --> Internet +``` + +Your device wraps each packet in two layers of encryption, one per gateway. The Entry Gateway strips the outer layer and forwards a packet it cannot read; the Exit Gateway strips the inner layer and sends the plaintext request to the destination. Responses follow the reverse path. The Entry Gateway therefore knows your IP address but not the destination, while the Exit Gateway knows the destination but not the sender. + +## Privacy guarantees + +dVPN mode hides your IP from destination servers and splits trust across two operators. It does not add timing obfuscation or cover traffic. Packets are forwarded immediately, so an adversary watching both gateways could still correlate timing to link your requests. If you need protection against traffic analysis, see [Mixnet Mode](/network/mixnet-mode). + +## Performance + +Added latency is comparable to traditional VPNs. WireGuard keeps cryptographic overhead low, so browsing, streaming, and downloads are not noticeably affected. + +## Technical details + +- [dVPN Protocol](/network/dvpn-mode/protocol): protocol stack and encryption details +- [Censorship Resistance](/network/dvpn-mode/censorship-resistance): AmneziaWG and DPI evasion + +## Further reading + +- [Introducing AmneziaWG for NymVPN](https://nym.com/blog/introducing-amneziawg-for-nymvpn): censorship resistance +- [What Is a Double VPN?](https://nym.com/blog/double-vpn): multi-hop privacy explained +- [Building a Decentralized WireGuard VPN](https://nym.com/blog/building-decentralized-wireguard-vpn): architecture decisions +- [What is NymVPN?](https://nym.com/blog/what-is-nymvpn): general overview diff --git a/documentation/docs/pages/network/dvpn-mode/_meta.json b/documentation/docs/pages/network/dvpn-mode/_meta.json new file mode 100644 index 0000000000..c2eb840161 --- /dev/null +++ b/documentation/docs/pages/network/dvpn-mode/_meta.json @@ -0,0 +1,4 @@ +{ + "protocol": "Protocol & Encryption", + "censorship-resistance": "Censorship Resistance" +} diff --git a/documentation/docs/pages/network/dvpn-mode/censorship-resistance.md b/documentation/docs/pages/network/dvpn-mode/censorship-resistance.md new file mode 100644 index 0000000000..b96af85695 --- /dev/null +++ b/documentation/docs/pages/network/dvpn-mode/censorship-resistance.md @@ -0,0 +1,50 @@ +--- +title: "Censorship Resistance in dVPN Mode" +description: "How AmneziaWG obfuscation, QUIC transport mode, and Stealth API Connect help Nym dVPN users evade deep packet inspection and protocol blocking." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Censorship Resistance + +dVPN mode incorporates several techniques to help users connect in restrictive network environments where VPN protocols are actively detected and blocked. + +## The problem: protocol fingerprinting + +Deep Packet Inspection (DPI) systems deployed by ISPs and governments can identify VPN protocols by their handshake patterns, packet sizes, and timing characteristics. Standard WireGuard, for instance, has a recognisable handshake initiation pattern that DPI rules can match against. Once identified, connections can be throttled or blocked entirely. + +This is not a theoretical concern: countries including China, Russia, Iran, and others actively deploy DPI to restrict VPN usage. + +## AmneziaWG + +dVPN mode uses [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/), a fork of WireGuard that adds obfuscation techniques to make the protocol harder to fingerprint. + +AmneziaWG modifies the WireGuard handshake by introducing decoy packets before the handshake initiation. These decoy packets disrupt DPI rules that rely on matching the standard WireGuard handshake sequence. The actual WireGuard protocol behaviour is preserved; the modifications sit around the handshake rather than replacing it, so all of WireGuard's security properties (Curve25519 key exchange, ChaCha20-Poly1305 encryption, forward secrecy) remain intact. + +## Limitations + +AmneziaWG raises the bar for censors relying on simple protocol fingerprinting, but it doesn't help against deeper analysis: statistical fingerprinting of packet timing and sizes, IP-based blocking of known Gateway addresses, or active probing where the censor sends packets to suspected VPN servers to confirm their identity. + +## QUIC transport mode + +QUIC transport mode wraps the WireGuard/AmneziaWG connection inside a [QUIC](https://datatracker.ietf.org/doc/html/rfc9000) layer, so the traffic looks like standard HTTPS/HTTP3 to DPI systems rather than a VPN tunnel. Since QUIC is now used by a significant portion of regular web traffic (over 30% of Cloudflare's traffic in 2023 was HTTP/3 over QUIC), blocking it outright would break large parts of the web for everyone, making it an unattractive target for censors. + +QUIC transport applies to the Entry Gateway connection only (the first hop). Not all Gateways support it yet; enabling QUIC in the NymVPN app will filter the Gateway list to those that do. Because the QUIC wrapper adds overhead, it can reduce speeds slightly, so it's worth leaving disabled unless you're in a censored environment or having connectivity issues. + +## Stealth API Connect + +Even if a user can establish a VPN tunnel, censors can also block access to the API that the NymVPN app needs to discover Gateways and fetch network state in the first place. Stealth API Connect addresses this by routing the app's API requests through a mechanism that is harder to identify and block, so the app can bootstrap its connection to the Nym network even in environments where the Nym API endpoints are actively censored. + +## Limitations + +These techniques are layered: AmneziaWG obfuscates the handshake, QUIC disguises the tunnel as regular web traffic, and Stealth API Connect protects the initial API discovery. Together they cover several common censorship methods, but none of them are guarantees. Censorship resistance is an ongoing arms race, and new techniques will be documented here as they ship. + +## Further reading + +- [Introducing AmneziaWG for NymVPN](https://nym.com/blog/introducing-amneziawg-for-nymvpn) +- [AmneziaWG documentation](https://docs.amnezia.org/documentation/amnezia-wg/) +- [What is QUIC? Censorship-Resistant Internet Connections](https://nym.com/blog/what-is-quic) +- [What is QUIC transport mode in NymVPN?](https://support.nym.com/hc/en-us/articles/39648047741457-What-is-QUIC-transport-mode-in-NymVPN) +- [What is Stealth API Connect in NymVPN?](https://support.nym.com/hc/en-us/articles/39652289741329-What-is-Stealth-API-connect-in-NymVPN) +- [NymVPN's roadmap for censorship resistance](https://nym.com/blog/NymVPN-Roadmap-for-censorship-resistance-2025) diff --git a/documentation/docs/pages/network/dvpn-mode/protocol.mdx b/documentation/docs/pages/network/dvpn-mode/protocol.mdx new file mode 100644 index 0000000000..38b42338a5 --- /dev/null +++ b/documentation/docs/pages/network/dvpn-mode/protocol.mdx @@ -0,0 +1,63 @@ +--- +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." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-04-07" +--- + +# dVPN Protocol + +import { Callout } from 'nextra/components' + + +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). + + +This page covers the technical details of dVPN mode's protocol stack and encryption. + +## Protocol layers + +dVPN mode uses two nested WireGuard tunnels. The client establishes an inner tunnel to the Exit Gateway and an outer tunnel to the Entry Gateway. The inner tunnel is created first; the outer tunnel encapsulates it. + +``` ++-----------------------------------------+ +| Application Data | ++-----------------------------------------+ +| Inner WireGuard tunnel (Client → Exit) | ++-----------------------------------------+ +| Outer WireGuard tunnel (Client → Entry)| ++-----------------------------------------+ +| UDP/IP | ++-----------------------------------------+ +``` + +The Entry Gateway decrypts only the outer tunnel and forwards the inner tunnel, still fully encrypted, to the Exit Gateway. The Exit Gateway decrypts the inner tunnel and forwards traffic to its destination. Because the Entry Gateway never holds keys for the inner tunnel, it is cryptographically excluded from the payload. + +## Encryption + +Both tunnels use standard WireGuard cryptography: Curve25519 for key exchange, ChaCha20-Poly1305 for authenticated encryption, and BLAKE2s for hashing. Each tunnel derives independent session keys, providing 256-bit security with modern, well-audited primitives. + +## 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. + +## Connection lifecycle + +When connecting, the client first selects Entry and Exit Gateways based on latency, location preference, or random selection. It then presents a zk-nym credential to the Entry Gateway for anonymous authentication. The credential proves payment without revealing identity; it is re-randomized for each connection and cannot be linked to previous usage. + +Once authenticated, the client establishes both WireGuard tunnels: first the inner tunnel keyed with the Exit Gateway, then the outer tunnel keyed with the Entry Gateway. Traffic flows through both hops until the session ends. + +## Security properties + +The protocol provides forward secrecy: new session keys are derived for each connection, so compromising long-term keys does not expose past sessions. WireGuard's key rotation provides additional forward secrecy within sessions. + +The nested-tunnel architecture enforces split knowledge. The Entry Gateway knows your IP but cannot decrypt the inner tunnel, so it sees neither your destinations nor your payload. The Exit Gateway decrypts the inner tunnel and sees your destinations but never learns your IP. Neither gateway can correlate the two. + +Replay protection comes from WireGuard's counter-based mechanism and from zk-nym serial numbers that prevent credential reuse. + +## Relationship to mixnet mode + +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. + +This shared infrastructure means improvements to Gateways and credentials benefit both modes. diff --git a/documentation/docs/pages/network/index.md b/documentation/docs/pages/network/index.md index 0cec22555c..102056f7e9 100644 --- a/documentation/docs/pages/network/index.md +++ b/documentation/docs/pages/network/index.md @@ -6,21 +6,32 @@ section: "Network" lastUpdated: "2026-02-11" --- -# Introduction -Nym's network documentation covering network architecture, node types, tokenomics, and crypto systems. +# The Nym Network -## Technical Motivations for Nym -When you send data across the internet, it can be recorded by a wide range of observers: your ISP, internet infrastructure providers, large tech companies, and governments. +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. -Even if the content of a network request is encrypted, observers can still see that data was transmitted, its size, frequency of transmission, and gather metadata from unencrypted parts of the data (such as IP routing information). Adversaries may then combine all the leaked information to probabilistically de-anonymize users. +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. -The Nym mixnet provides very strong security guarantees against this sort of surveillance. It _packetises_ and _mixes_ together IP traffic from many users inside the _Mixnet_. It does this by obfuscating and anonymising traffic patterns: hiding the signal in background noise. It aims to make passive network surveillance obselete by hiding who is talking to who at any one time at the _network level_; using an anonymous email service, even one paid for anonymously, is not enough to protect you from surveillance unless you are also hiding your IP and other metadata which can be used to deanonymise you over time. We are up against agencies and companies employing enourmous compute resources scraping the web for swathes of traffic information and piping this into Machine Learning algorithms. +### NymVPN -## Mixnet TL;DR -The Mixnet is a decentralised network of nodes run by various operators in various jurisdictions around the world, who are incentivised to do so via `NYM` token rewards: cryptocurrency allows for the creation of an incentivised, decentralised privacy network. +[NymVPN](https://nymvpn.com) is a subscription-based application that provides access to both modes: +- **dVPN mode** routes traffic through 2 hops using WireGuard with enhanced layer encryption. Fast enough for browsing and streaming, with strong privacy against typical adversaries. +- **Mixnet mode** routes traffic through 5 hops with packet mixing, timing delays, and cover traffic. Every packet is the same size, each hop only sees the next destination, and a constant stream of dummy packets hides when real communication is occurring. Designed for privacy against adversaries capable of observing the entire network. -> If you're into comparisons, the Nym mixnet is conceptually similar to other systems such as Tor, but provides improved protections against end-to-end timing attacks which can de-anonymize users. When Tor was first fielded, in 2002, those kinds of attacks were regarded as science fiction. But the future is now here. +Both modes use the same underlying infrastructure. -User applications do not have to run their own node (although we do encourage people to run infrastructure if they have the skills and time), but instead connect to the Mixnet via a Nym client either running as a separate process or (more likely) embedded in an existing application. +### Developer SDKs -The Mixnet (once the credentialing system is turned on in late 2024) is pay-to-play: anonymous selective disclosure zk-nym credentials are used to ensure that clients have paid for sending bandwidth through the mixnet, but in a way that is unlinkable to their payment accounts. Furthermore each time they use a credential, it can be rerandomised: each time a user spends a credential, regardless of whether they have connected before, it appears as a new credential to the Nym network. The zk-nym scheme is a combination of the Coconut and Offline Ecash credential schemes. +The [Nym SDKs](/developers) allow developers to embed mixnet functionality directly into applications, with the same privacy guarantees as NymVPN's Mixnet mode. SDK usage is currently free for development and testing. The SDKs do **not** provide access to dVPN mode. + +## 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. + +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. + +## Further reading + +- **Network architecture:** [Overview](/network/overview) · [dVPN Mode](/network/dvpn-mode) · [Mixnet Mode](/network/mixnet-mode) · [Cryptography](/network/cryptography) · [Infrastructure](/network/infrastructure) · [Reference](/network/reference) +- **Application development:** [Developer documentation](/developers) +- **Node operation:** [Operator documentation](/operators/introduction) diff --git a/documentation/docs/pages/network/infrastructure.md b/documentation/docs/pages/network/infrastructure.md new file mode 100644 index 0000000000..09b359c0b4 --- /dev/null +++ b/documentation/docs/pages/network/infrastructure.md @@ -0,0 +1,16 @@ +--- +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." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Infrastructure + +The Nym Network runs on decentralized infrastructure: a set of independently operated nodes coordinated by the Nyx blockchain. No single party controls routing, key management, or credential issuance. + +## In this section + +- [Nyx Blockchain](/network/infrastructure/nyx): the Cosmos SDK chain that maintains the node registry, manages token economics, and hosts the smart contracts for credentials and rewards +- [Nym Nodes](/network/infrastructure/nym-nodes): the unified `nym-node` binary that operates as Entry Gateways, Mix Nodes, or Exit Gateways depending on network demand diff --git a/documentation/docs/pages/network/infrastructure/_meta.json b/documentation/docs/pages/network/infrastructure/_meta.json new file mode 100644 index 0000000000..d8114f2306 --- /dev/null +++ b/documentation/docs/pages/network/infrastructure/_meta.json @@ -0,0 +1,4 @@ +{ + "nyx": "Nyx Blockchain", + "nym-nodes": "Nym Nodes" +} diff --git a/documentation/docs/pages/network/infrastructure/nym-nodes.mdx b/documentation/docs/pages/network/infrastructure/nym-nodes.mdx new file mode 100644 index 0000000000..da7e548130 --- /dev/null +++ b/documentation/docs/pages/network/infrastructure/nym-nodes.mdx @@ -0,0 +1,41 @@ +import { Callout } from 'nextra/components' +import { TimeNow } from 'components/time-now.tsx'; +import stats from 'components/outputs/api-scraping-outputs/nodes-count.json'; + +# Nym Nodes + +All traffic-routing infrastructure runs on the `nym-node` binary. This unified binary operates in different modes (Entry Gateway, Mix Node, or Exit Gateway), simplifying deployment and enabling future dynamic role assignment. + + +To run a node, see the [Operator Documentation](/operators/introduction). + + +## Node modes + +**Entry Gateways** are the user's first point of contact with the network. They accept WebSocket connections from clients, verify zk-nym credentials to confirm payment, and store messages for clients that go offline. Entry Gateways know the client's IP address but cannot see message contents or final destinations. + +**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. + +## Unified binary + +The various components were originally separate binaries. They've 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. + +## Nym clients + + +For client setup, see the [Developer Documentation](/developers/clients/socks5). + + +Clients are the user-side software that connects to the network. They discover network topology from the blockchain, register with an Entry Gateway, construct Sphinx packets with layered encryption, generate cover traffic, and handle acknowledgements and retransmission. + +Client types include native Rust clients, WASM clients for browsers, the SOCKS5 proxy client, and the NymVPN client. The NymVPN client supports both dVPN and mixnet modes. + +## Running infrastructure + + + +The current deployment includes {stats.nodes} active nodes across {stats.locations} countries, operated by independent parties worldwide. This includes {stats.mixnodes} Mix Nodes and {stats.exit_gateways} Exit Gateways. Running a node requires meeting minimum hardware specifications, bonding NYM tokens as collateral, and maintaining high uptime for rewards. diff --git a/documentation/docs/pages/network/infrastructure/nyx.mdx b/documentation/docs/pages/network/infrastructure/nyx.mdx new file mode 100644 index 0000000000..b7137c034b --- /dev/null +++ b/documentation/docs/pages/network/infrastructure/nyx.mdx @@ -0,0 +1,49 @@ +--- +title: "Nyx Blockchain" +description: "How the Nyx Cosmos SDK blockchain coordinates the Nym Network by maintaining node topology, managing NYM token economics, and hosting smart contracts for credentials and rewards." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +import { Callout } from 'nextra/components' + +# Nyx Blockchain + +Nyx is a Cosmos SDK blockchain that coordinates the Nym Network. It maintains the topology of active nodes, manages token economics, and hosts the smart contracts that power the credential system. + + +To interact with the chain, see [Interacting with Nyx](/developers/chain). To run a Validator, see the [Operator Documentation](/operators/nodes/validator-setup). + + +## Role in the network + +The blockchain serves several functions. It maintains the **topology registry**: the list of active nodes and their public keys. This eliminates the need for a centralized directory server and prevents attacks that plague peer-to-peer directory systems. + +It manages **token economics**. The NYM token is a native token of the chain, used for staking, rewards, and credential payments. Validators secure the chain via proof-of-stake consensus. + +And it hosts **smart contracts** for mixnet coordination and the zk-nym credential system. + +## Validators + +Nyx Validators run the `nyxd` binary to maintain the blockchain. They process transactions, execute smart contracts, and participate in consensus. A subset of validators also run Nym API instances for credential issuance. + +## Nym API + + +For setup instructions, see the [Nym API Operator Guide](/operators/nodes/validator-setup/nym-api). + + +The Nym API is operated by a subset of validators forming the "Quorum." This group performs network monitoring: sending test packets through the mixnet and calculating reliability scores for nodes. More critically, it handles credential issuance, generating the partial blind signatures that form zk-nyms. + +The Quorum uses threshold cryptography. No single member can issue credentials alone. The system remains functional even if some members are offline. This distributes trust across multiple independent parties. + +## Smart contracts + +The Nyx chain is CosmWasm-enabled. The **Mixnet Contract** stores bonded node information, provides network topology for client routing, and tracks delegations and rewards. The **Vesting Contract** manages NYM token vesting schedules. The **zk-nym Contract** tracks deposits for credential generation and manages the blacklist for double-spend attempts. + +Contract addresses for different networks can be queried via the [Nym API](/apis/nym-api). + +## Querying the chain + +The [Nym API](/apis/nym-api) provides HTTP endpoints for querying network topology, node status, rewards, and credential information. For direct contract interaction, see the [Developer Documentation](/developers/chain). diff --git a/documentation/docs/pages/network/licensing.md b/documentation/docs/pages/network/licensing.md index daff62dcb6..c67c89ff08 100644 --- a/documentation/docs/pages/network/licensing.md +++ b/documentation/docs/pages/network/licensing.md @@ -1,8 +1,8 @@ # Licensing -As a general approach, licensing is as follows this pattern: +As a general approach, licensing follows this pattern: -*

Nym Documentation by Nym Technologies is licensed under CC BY-NC-SA 4.0

+* [Nym Documentation](https://nym.com/docs) by [Nym Technologies](https://nym.com) is licensed under [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) ![CC](/images/cc-icons/cc.svg) ![BY](/images/cc-icons/by.svg) ![NC](/images/cc-icons/nc.svg) ![SA](/images/cc-icons/sa.svg) * Nym applications and binaries are [GPL-3.0-only](https://www.gnu.org/licenses/) diff --git a/documentation/docs/pages/network/mixnet-mode.mdx b/documentation/docs/pages/network/mixnet-mode.mdx new file mode 100644 index 0000000000..e708934dc0 --- /dev/null +++ b/documentation/docs/pages/network/mixnet-mode.mdx @@ -0,0 +1,47 @@ +--- +title: "Mixnet Mode" +description: "How Nym's Mixnet mode works: 5-hop routing through Mix Nodes with random delays, packet reordering, and cover traffic for unlinkability and unobservability." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Mixnet Mode + +import { Callout } from 'nextra/components' + +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). + +## How it works + +``` +User --> Entry --> Mix L1 --> Mix L2 --> Mix L3 --> Exit --> Internet + | | | + delay delay delay +``` + +Each Mix Node strips one layer of [Sphinx](/network/cryptography/sphinx) encryption to learn the next hop, holds the packet for a random delay, then forwards it. No node ever sees both the origin and the final destination. The client also continuously sends [cover traffic](/network/mixnet-mode/cover-traffic), dummy packets cryptographically indistinguishable from real ones, so an observer sees a constant stream of packets regardless of whether any real communication is taking place. + +## Privacy properties + +- **Unlinkability**: the random delays and reordering at each Mix Node destroy the timing signal an observer would need to correlate incoming and outgoing packets, or to connect successive packets from the same user. See [Packet Mixing](/network/mixnet-mode/mixing). +- **Unobservability**: because cover traffic is constant, an observer cannot determine when a user is active or what fraction of the traffic is real. See [Cover Traffic](/network/mixnet-mode/cover-traffic). +- **Resistance to traffic analysis**: uniform Sphinx packet sizes prevent content-type fingerprinting, and per-packet routing eliminates the long-lived circuits that make other anonymity networks susceptible to end-to-end correlation. See [Traffic Flow](/network/mixnet-mode/traffic-flow). + +## Performance + +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. + + +Updated latency measurements will be published after the Lewes Protocol release. + + +## Further reading + +The following pages cover mixnet internals in detail: + +- [Loopix Design](/network/mixnet-mode/loopix) explains the academic foundation +- [Traffic Flow](/network/mixnet-mode/traffic-flow) shows the packet journey with diagrams +- [Cover Traffic](/network/mixnet-mode/cover-traffic) explains how dummy packets provide unobservability +- [Packet Mixing](/network/mixnet-mode/mixing) covers timing delays and their importance +- [Anonymous Replies](/network/mixnet-mode/anonymous-replies) describes SURBs for bidirectional communication diff --git a/documentation/docs/pages/network/mixnet-mode/_meta.json b/documentation/docs/pages/network/mixnet-mode/_meta.json new file mode 100644 index 0000000000..84b4aa61db --- /dev/null +++ b/documentation/docs/pages/network/mixnet-mode/_meta.json @@ -0,0 +1,7 @@ +{ + "loopix": "Loopix Design", + "traffic-flow": "Traffic Flow", + "cover-traffic": "Cover Traffic", + "mixing": "Packet Mixing", + "anonymous-replies": "Anonymous Replies (SURBs)" +} diff --git a/documentation/docs/pages/network/mixnet-mode/anonymous-replies.mdx b/documentation/docs/pages/network/mixnet-mode/anonymous-replies.mdx new file mode 100644 index 0000000000..696c84fca0 --- /dev/null +++ b/documentation/docs/pages/network/mixnet-mode/anonymous-replies.mdx @@ -0,0 +1,64 @@ +--- +title: "Anonymous Replies with SURBs" +description: "How Single Use Reply Blocks (SURBs) enable anonymous bidirectional communication in the Nym mixnet without revealing the sender's address." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Anonymous Replies + +SURBs (Single Use Reply Blocks) enable anonymous bidirectional communication. A receiver can reply to a sender without learning the sender's identity or address. + +## The problem + +In a typical mixnet scenario, Alice sends a message to Bob and wants a reply. If Bob sends directly to Alice's Nym address, he learns it. This defeats the purpose of anonymous communication; Bob now knows Alice's identity for future contact, and due to how Nym's [addressing scheme](/network/reference/addressing.md) works, this means that Bob knows which Gateway node Alice's client is using. + +## How SURBs work + +Alice creates SURBs (encrypted routing headers) and includes them with her message to Bob. Each SURB contains a complete route back to Alice, encrypted so that Bob cannot read it. Bob attaches his reply to a SURB and sends the resulting packet into the mixnet. It travels through the encoded route and arrives at Alice, but Bob never learns where it went. + +A SURB contains the address of the first hop (Alice's Entry Gateway), encrypted routing headers for the path back to Alice, and a key to encrypt the reply payload. The routing headers are layered like a Sphinx packet; each hop can only see the next destination. + +## Single use + +Each SURB can only be used once. This prevents replay attacks and ensures forward secrecy. For conversations requiring multiple exchanges, Alice sends multiple SURBs with her initial message. + +SURB validity is tied to key rotation. Node keys rotate on an odd/even schedule with a default validity of 24 epochs (roughly 25 hours at the current 1-hour epoch length). After that window, the routing keys a SURB was built with are no longer accepted. Clients automatically purge stale SURBs and request fresh ones. Reply keys also expire after 24 hours independently of rotation cycles. + +## 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. + +```mermaid +--- +config: + theme: neo-dark +--- +sequenceDiagram + participant Alice + participant Mixnet + participant Bob + + Alice->>Mixnet: Message + 5 SURBs + Mixnet->>Bob: Message + 5 SURBs + Bob->>Bob: Reply needs 10 SURBs + Bob->>Mixnet: "Need more SURBs" (uses 1 SURB) + Mixnet->>Alice: SURB request + Alice->>Mixnet: 10 more SURBs + Mixnet->>Bob: Additional SURBs + Bob->>Mixnet: Reply (uses SURBs) + Mixnet->>Alice: Reply received +``` + +## 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. + +## Security considerations + +There's a known attack where a malicious receiver hoards SURBs and sends them all back simultaneously, attempting to correlate traffic patterns at the sender's Gateway. This attack requires active participation (not just passive observation), and provides limited information even if successful. It's not a passive surveillance technique; the attacker must be specifically targeting you and willing to spend resources. + +## Comparison to Tor onion addresses + +Tor's onion addresses allow indefinite replies but require the recipient to run a hidden service. SURBs are single-use but require no service; they're generated on-demand per message. SURBs also benefit from the mixnet's timing protection, which onion addresses don't have. diff --git a/documentation/docs/pages/network/mixnet-mode/cover-traffic.md b/documentation/docs/pages/network/mixnet-mode/cover-traffic.md new file mode 100644 index 0000000000..60f575bdd5 --- /dev/null +++ b/documentation/docs/pages/network/mixnet-mode/cover-traffic.md @@ -0,0 +1,54 @@ +--- +title: "Cover Traffic" +description: "How constant dummy packet streams hide real communication patterns in the Nym mixnet, achieving unobservability even against global network observers." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Cover Traffic + +Cover traffic consists of dummy packets that hide when real communication is occurring, providing unobservability: an adversary cannot determine whether a user is actively communicating. + +## The problem + +Even with perfect encryption and mixing, traffic analysis can reveal information. An adversary can see how much data you're sending, when you're sending it, and detect patterns over time. Regular silence followed by bursts of activity reveals your schedule. Consistent traffic volumes to certain destinations reveal ongoing relationships. + +## The solution + +Cover traffic maintains a constant rate of packet transmission. When you have real data to send, it replaces a cover packet in the stream. When you have nothing to send, cover packets flow anyway. To an observer, the traffic looks identical either way. + +``` +Without cover traffic: + | ||| | +Time ---------+---------+++---------+------> + Idle Activity Idle + (visible) + +With cover traffic: + |||||||||||||||||||||||||||||||||||||| +Time --------------------------------------> + Constant rate (activity hidden) +``` + +The cover packets are real Sphinx packets with valid encryption, just with empty payloads. They travel through the network exactly like real packets, get mixed at each hop, and are discarded at their destination. No node along the way can tell whether a packet contains real data or is cover traffic. + +## Loop traffic + +Cover packets follow complete routes through the network back to the sender. These "loops" serve multiple purposes: they provide traffic for mixing with others' cover traffic and they can detect active attacks. If loop packets stop returning, a network fault or active interference is likely. + +Mix nodes also generate their own cover traffic, ensuring minimum traffic levels even when few users are active. This provides baseline anonymity guarantees regardless of network load. + +## How it's generated + +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 + +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. + +The default parameters balance privacy and resource usage. Applications with heightened privacy requirements can increase the cover traffic rate; applications where unobservability is less critical can reduce it. + +## 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. diff --git a/documentation/docs/pages/network/mixnet-mode/loopix.md b/documentation/docs/pages/network/mixnet-mode/loopix.md new file mode 100644 index 0000000000..c4ca8f7a1d --- /dev/null +++ b/documentation/docs/pages/network/mixnet-mode/loopix.md @@ -0,0 +1,49 @@ +--- +title: "Loopix Design" +description: "The academic Loopix mixnet design behind Nym: stratified topology, continuous-time mixing with exponential delays, and cover traffic loops for unlinkability and unobservability." +schemaType: "TechArticle" +section: "Network" +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 insight + +Traditional mixnets focus on hiding "who messages whom," but this alone is insufficient. Adversaries observing message volume and timing over time can still infer private information. If you always message the same friend at the same time, patterns emerge. If you go silent when traveling, that's information too. + +Loopix was designed to provide both **unlinkability** (hiding who talks to whom) and **unobservability** (hiding when and how much communication occurs). The name comes from its use of "loop" cover traffic that circulates through the network. + +## Stratified topology + +The network uses a layered architecture. Traffic flows through Entry Gateways, three Mix Node layers, and Exit Gateways. Each node connects only to adjacent layers. Path selection is independent per-message, unlike Tor's per-session circuits. + +This structure prevents observations about which paths are used together and limits the damage any single compromised node can cause. + +## Continuous-time mixing + +Unlike batch mixnets that collect messages and release them periodically, Loopix uses continuous-time mixing. Each message is delayed independently according to an exponential distribution, then forwarded as soon as its delay expires. + +This approach offers optimal anonymity for a given mean latency. The exponential distribution has a key property: if two messages arrive at different times, they have equal probability of leaving in either order. An adversary watching input and output timing gains no information about which input became which output. + +Continuous mixing also means lower latency overall since messages don't wait for batches to fill. + +## Cover traffic loops + +Connected clients and nodes continuously generate dummy packets that travel in loops through the network back to the sender. These packets are indistinguishable from real traffic: same size, same encryption, same timing distribution. + +Loop traffic ensures minimum anonymity even when few users are active, hides when real communication starts and stops, and enables detection of active attacks (if loop packets fail to return, a network fault or active interference is likely). + +## 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. + +## Security guarantees + +The combination of continuous-time mixing and cover traffic provides provable guarantees. The anonymity set (the set of users who could have sent a given message) grows unboundedly over time. Even messages with short delays have large anonymity sets because of the exponential distribution. + +An adversary observing the entire network cannot determine who is communicating with whom, cannot tell when real communication is occurring, and gains no advantage from statistical analysis because the traffic patterns are designed to be indistinguishable from random. + +For the full formal analysis, see the [Loopix paper](https://arxiv.org/pdf/1703.00536) and the [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf). diff --git a/documentation/docs/pages/network/mixnet-mode/mixing.mdx b/documentation/docs/pages/network/mixnet-mode/mixing.mdx new file mode 100644 index 0000000000..1cf9890c73 --- /dev/null +++ b/documentation/docs/pages/network/mixnet-mode/mixing.mdx @@ -0,0 +1,59 @@ +--- +title: "Packet Mixing and Random Delays" +description: "How Mix Nodes use exponential random delays to reorder packets and break timing correlations, preventing traffic analysis by network observers." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Packet Mixing + +import { Callout } from 'nextra/components' + +Packet mixing breaks timing correlations by adding random delays at each Mix Node. It's the core mechanism that prevents traffic analysis. + +## The problem + +Without mixing, an observer watching a node could correlate inputs and outputs. If packets leave on a FIFO (First In First Out) basis, even with encryption hiding contents, the timing relationship reveals which input became which output. + +## The solution + +Each Mix Node adds a random delay before forwarding. Packets don't flow through in order; they're held for variable times and released in a different sequence than they arrived. An observer sees packets going in and packets coming out, but cannot match them. + +``` +Input sequence: A B C D E + | | | | | + v v v v v + [ Mixing ] + | | | | | + v v v v v +Output sequence: C A E B D +``` + +The delays follow an exponential distribution. This choice is mathematically optimal: if two packets arrive at times t₀ and t₁, they have equal probability of leaving in either order, regardless of when they arrived. The adversary gains no information from timing observations. + +## Why exponential delays + +The exponential distribution is memoryless: the probability of a packet leaving in the next moment does not depend on how long it has already waited, so an adversary cannot narrow down possibilities by noting how long packets have been in the node. + +Any other delay distribution leaks information; fixed delays would let adversaries match arrivals to departures by timing, and uniform distributions would create windows where matches become more likely. + +## Continuous vs batch mixing + +Older mixnet designs collected packets into batches and shuffled them before release. This has problems: latency is unpredictable since you wait for batches to fill, bandwidth is inefficient due to bursty traffic, and the anonymity set is limited to the batch size. + +Continuous-time mixing processes each packet independently. Latency is predictable (the mean delay is configurable), bandwidth is used efficiently, and the anonymity set is unbounded: it includes all packets that have ever passed through, weighted by time. + +## The aggregate effect + +With three Mix Node layers, each applying random delays, the overall effect is thorough reordering. Packets entering the mixnet in sequence exit in a completely different order. The timing relationship between sending and receiving is destroyed. + +These delays account for the additional latency of mixnet mode relative to dVPN mode. + + +Updated latency measurements will be published after the Lewes Protocol release. + + +## Combined with cover traffic + +Mixing and cover traffic are complementary. Cover traffic ensures there are always packets to mix, even during low activity, while mixing ensures that real and cover packets become interleaved and indistinguishable. Together they provide both unlinkability and unobservability. diff --git a/documentation/docs/pages/network/mixnet-mode/traffic-flow.mdx b/documentation/docs/pages/network/mixnet-mode/traffic-flow.mdx new file mode 100644 index 0000000000..d8f95ee405 --- /dev/null +++ b/documentation/docs/pages/network/mixnet-mode/traffic-flow.mdx @@ -0,0 +1,133 @@ +import { Callout } from 'nextra/components' + +# Traffic Flow + +This page walks through how packets travel through the mixnet, from sending client to destination. + + +This describes the 5-hop mixnet flow. For the 2-hop dVPN mode, see [dVPN Protocol](/network/dvpn-mode/protocol). + + +## Overview + +The Nym mixnet uses source routing: the sender chooses the complete route before sending. This means the sender constructs a Sphinx packet with layered encryption, where each layer contains routing information for one hop. + +## Client to Entry Gateway + +On connection, the Nym client registers with a particular Entry Gateway. This Gateway becomes part of the client's Nym address and is where incoming messages are delivered. + +The client continuously sends packets to the Entry Gateway over a WebSocket connection. This stream includes both real messages and cover traffic at a constant rate. When the application has data to send, the client encrypts it as Sphinx packets and slots them into the stream. When there is no data, cover packets flow instead. + +```mermaid +--- +config: + theme: neo-dark +--- +sequenceDiagram + box Local Machine + participant App as Application + participant Client as Nym Client + end + participant Gateway as Entry Gateway + + Gateway->>Client: Key Exchange + + loop Continuous Traffic + Client->>Gateway: Cover packet + Client->>Gateway: Cover packet + App-->>Client: Data to send + Client->>Client: Encrypt as Sphinx + Client->>Gateway: Real packet + Client->>Gateway: Cover packet + end +``` + +## Through the Mix Nodes + +The Entry Gateway forwards packets into the three Mix Node layers. At each hop, the node decrypts its layer of the Sphinx packet to learn the next destination, verifies the HMAC to ensure integrity, applies a random delay, and forwards to the next hop. + +The delay is critical. Without it, timing would correlate inputs to outputs. With exponential random delays, packets are reordered and the timing relationship is destroyed. + +```mermaid +--- +config: + theme: neo-dark +--- +sequenceDiagram + participant Entry as Entry Gateway + participant M1 as Mix Layer 1 + participant M2 as Mix Layer 2 + participant M3 as Mix Layer 3 + participant Exit as Exit Gateway + + Entry->>M1: Sphinx Packet + M1->>M1: Decrypt layer + M1->>M1: Verify HMAC + M1->>M1: Random delay + M1->>M2: Sphinx Packet + M2->>M2: Decrypt layer + M2->>M2: Verify HMAC + M2->>M2: Random delay + M2->>M3: Sphinx Packet + M3->>M3: Decrypt layer + M3->>M3: Verify HMAC + M3->>M3: Random delay + M3->>Exit: Sphinx Packet +``` + +## Exit Gateway to Destination + +The Exit Gateway handles the final hop. For traffic destined for external services, it decrypts the packet and forwards to the destination, then packages responses back into Sphinx packets for the return journey. + +For traffic destined for another Nym client, the Exit Gateway delivers to that client's registered Gateway, which holds the message until the recipient comes online. + +## The complete picture + +Putting it together, a packet travels through five hops with encryption removed and delays applied at each Mix Node layer: + +```mermaid +--- +config: + theme: neo-dark +--- +sequenceDiagram + box Sender + participant App1 as Application + participant C1 as Nym Client + end + + box Mixnet + participant Entry as Entry GW + participant M1 as Mix L1 + participant M2 as Mix L2 + participant M3 as Mix L3 + participant Exit as Exit GW + end + + box Receiver + participant C2 as Nym Client + participant App2 as Application + end + + App1->>C1: Send data + C1->>C1: Create Sphinx packet + C1->>Entry: Encrypted packet + Entry->>M1: Forward + M1->>M1: Decrypt, delay + M1->>M2: Forward + M2->>M2: Decrypt, delay + M2->>M3: Forward + M3->>M3: Decrypt, delay + M3->>Exit: Forward + Exit->>C2: Deliver + C2->>C2: Decrypt final layer + C2->>App2: Received data +``` + +## External services + +When sending to an external service rather than another Nym client, the Exit Gateway acts as a proxy. It extracts the destination from the decrypted packet, makes the request on your behalf, and routes responses back through the network. The destination service sees the Exit Gateway's IP, not yours. + +## Peer-to-peer + +For applications where all parties run Nym clients, traffic stays entirely within the mixnet. Both sides enjoy full privacy protection, and [SURBs](/network/mixnet-mode/anonymous-replies) enable anonymous bidirectional communication without either party learning the other's address. diff --git a/documentation/docs/pages/network/overview.md b/documentation/docs/pages/network/overview.md new file mode 100644 index 0000000000..45f76d5005 --- /dev/null +++ b/documentation/docs/pages/network/overview.md @@ -0,0 +1,21 @@ +--- +title: "Nym Network Overview" +description: "Introduction to the Nym Network, a privacy infrastructure that protects metadata including who communicates with whom, when, and how often." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Overview + +The Nym Network is a privacy infrastructure that protects metadata: not just message content, but who is talking to whom, when, and how often. This section explains what the network does, why it exists, and how it compares to other approaches. + +## In this section + +- [The Privacy Problem](/network/overview/privacy-problem): what metadata is, why it matters, and what adversary models Nym is designed against +- [Choosing a Mode](/network/overview/choosing-a-mode): how dVPN and Mixnet mode differ, and guidance on which fits your use case +- [Nym vs Other Systems](/network/overview/comparisons): how Nym compares to VPNs, Tor, I2P, and E2EE + +## 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. diff --git a/documentation/docs/pages/network/overview/_meta.json b/documentation/docs/pages/network/overview/_meta.json new file mode 100644 index 0000000000..55b82e474d --- /dev/null +++ b/documentation/docs/pages/network/overview/_meta.json @@ -0,0 +1,5 @@ +{ + "privacy-problem": "The Privacy Problem", + "choosing-a-mode": "Choosing a Mode", + "comparisons": "Nym vs Other Systems" +} diff --git a/documentation/docs/pages/network/overview/choosing-a-mode.md b/documentation/docs/pages/network/overview/choosing-a-mode.md new file mode 100644 index 0000000000..51ab05bd33 --- /dev/null +++ b/documentation/docs/pages/network/overview/choosing-a-mode.md @@ -0,0 +1,55 @@ +--- +title: "Choosing Between dVPN and Mixnet Mode" +description: "When to use NymVPN's dVPN mode for low-latency browsing versus Mixnet mode for metadata protection against traffic analysis." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Choosing a Mode + +Both modes run on the same Nym infrastructure but defend against different threat models. dVPN mode hides your IP and splits trust across two operators. Mixnet mode additionally protects traffic patterns against adversaries capable of observing the entire network. + +**dVPN mode** routes through 2 hops (Entry Gateway + Exit Gateway) connected via [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/), a WireGuard fork with traffic obfuscation to evade protocol-level detection. Latency is low, but there is no protection against timing analysis. + +**Mixnet mode** routes through 5 hops (Entry Gateway, three Mix Node layers, Exit Gateway). Each Mix Node adds a random delay and mixes packets with those of other users. Combined with continuous cover traffic, this makes timing correlation impractical even for an adversary watching the entire network. + +## Quick comparison + +| | dVPN Mode | Mixnet Mode | +|---|---|---| +| **Hops** | 2 (Entry + Exit Gateway) | 5 (Entry + 3 Mix Nodes + Exit) | +| **Timing protection** | No | Yes (random delays per hop) | +| **Cover traffic** | No | Yes (constant dummy packets) | +| **Protects against** | ISPs, websites, advertisers, passive observers | Global passive adversaries, timing correlation, traffic analysis | +| **Access** | [NymVPN](https://nymvpn.com) | NymVPN and [Nym SDKs](/developers) | + +## Use dVPN mode when + +- 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 + +## Use Mixnet mode when + +- Metadata exposure is dangerous: journalism, activism, whistleblowing, legal work +- Your adversary might be watching traffic across multiple network points +- Added latency is an acceptable trade for unlinkability and unobservability + +## For developers + +The [Nym SDKs](/developers) only expose **Mixnet mode**. dVPN mode is specific to the NymVPN application. + +There are two integration models: + +**Proxy** (traffic exits to the internet, analogous to Tor's exit relay model): +``` +Your App --> Entry --> Mix Nodes --> Exit --> Internet +``` + +**End-to-end** (Sphinx-encrypted the entire way, traffic stays within the Mixnet): +``` +Your App --> Entry --> Mix Nodes --> Exit --> Nym Client +``` + +See the [integration overview](/developers/integrations) for guidance on choosing between them. diff --git a/documentation/docs/pages/network/overview/comparisons.md b/documentation/docs/pages/network/overview/comparisons.md new file mode 100644 index 0000000000..5fec66afbe --- /dev/null +++ b/documentation/docs/pages/network/overview/comparisons.md @@ -0,0 +1,49 @@ +--- +title: "Nym vs VPNs, Tor, I2P, and E2EE" +description: "How the Nym Network compares to traditional VPNs, Tor, I2P, and end-to-end encryption in terms of privacy guarantees, metadata protection, and threat models." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Nym vs Other Systems + +There are several existing approaches to network privacy, each with different assumptions about who the adversary is and what they can do. + +## Nym vs VPNs + +A traditional VPN creates an encrypted tunnel between your device and a VPN server, hiding your IP from destination websites and encrypting traffic from local observers like your ISP. The fundamental limitation is that the VPN provider itself sees all your traffic (every site you visit, when you visit it, how long you stay) and can log this voluntarily or be compelled to by legal process, with your payment information linking your account directly to your activity. + +Nym's dVPN mode splits this trust across two independent operators so that the Entry Gateway knows your IP but not your destination, the Exit Gateway knows your destination but not your IP, and neither can build a complete picture. Payment is handled through [zk-nyms](/network/cryptography/zk-nym), making subscriptions unlinkable to activity. + +Nym's mixnet mode goes further by adding timing obfuscation and cover traffic, which no traditional VPN offers; see [Mixnet Mode](/network/mixnet-mode) for details. + +## Nym vs Tor + +[Tor](https://www.torproject.org/) is the best-known anonymous overlay network, routing traffic through three relays using Onion encryption so that no single relay sees both source and destination. It was designed in an era when global passive adversaries were considered unrealistic, and its [architecture](https://2019.www.torproject.org/about/overview.html.en) reflects that: packets flow through without delays and there is no cover traffic, which means an adversary watching both ends of a circuit can try and correlate timing to deanonymise users. + +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. + +## Nym vs I2P + +[I2P](https://geti2p.net/) replaces Tor's centralised directory authority with a [distributed hash table](https://geti2p.net/en/docs/how/network-database), which improves decentralisation but introduces its own attack surface: DHT-based routing is vulnerable to eclipse attacks and Sybil attacks on the routing table. Like Tor, I2P provides no timing protection, so packets flow without delays or cover traffic. + +Nym uses a blockchain-based topology registry rather than a DHT, which avoids the known attack vectors around DHT-based routing (e.g. eclipse attacks, Sybil attacks on the routing table). The mixing and cover traffic on top of that address the timing analysis gap that I2P shares with Tor. + +## Nym vs end-to-end encryption + +End-to-end encryption systems like [Signal](https://signal.org/docs/) encrypt messages on your device so that only the recipient can decrypt them, and the server never sees the content. But E2EE does nothing for metadata: the server still sees who you communicate with, when, how often, and how much, which on its own is enough to map relationships and infer sensitive activity. + +Nym and E2EE are complementary: E2EE protects message content, Nym protects the metadata around it (who, when, how much). Using Signal over the Nym mixnet, for instance, would protect both message content and the communication metadata around it. + +For a practical breakdown of when to use dVPN vs Mixnet mode, see [Choosing a Mode](/network/overview/choosing-a-mode). + +## Further reading + +- [What is WireGuard?](https://nym.com/blog/what-is-wireguard-vpn) +- [VPN Tunnels Explained](https://nym.com/blog/vpn-tunnels) +- [Tor Project: How Tor Works](https://2019.www.torproject.org/about/overview.html.en) +- [Tor Protocol Specification](https://spec.torproject.org/tor-spec/) +- [I2P: How It Works](https://geti2p.net/en/docs/how/tech-intro) diff --git a/documentation/docs/pages/network/overview/privacy-problem.md b/documentation/docs/pages/network/overview/privacy-problem.md new file mode 100644 index 0000000000..731297c65a --- /dev/null +++ b/documentation/docs/pages/network/overview/privacy-problem.md @@ -0,0 +1,29 @@ +--- +title: "The Privacy Problem: Why Metadata Matters" +description: "Why metadata exposure is a critical privacy threat, how adversaries exploit traffic patterns, and why traditional solutions like VPNs, Tor, and E2EE fall short." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# The Privacy Problem + +## Metadata is the message + +When you communicate over the internet, two types of information are in play: +- The **content** is the actual message, file, or data being sent. +- The **metadata** is everything else: who is talking to whom, when, from where, and how often. Some metadata is visible in every packet (source/destination IPs, timestamps, sizes). Other metadata only emerges from patterns over time: interaction frequency, session durations, and behavioural fingerprints that can identify users across sessions. See [Maximum Transmission Units](https://en.wikipedia.org/wiki/Maximum_transmission_unit#MTUs_for_common_media) for one example of what packet sizes reveal. + +TLS and end-to-end encryption protect content. That's often the [focus of media attention](https://wire.com/en/blog/whatsapp-end-to-end-encryption-risks). But most solutions don't protect metadata at all, and some falsely claim to. + +Metadata alone is enough to reconstruct who you talk to, when, and from where. Intelligence agencies know this. As former NSA Director Michael Hayden put it: ["We kill people based on metadata."](https://committees.parliament.uk/writtenevidence/36962/html/) + +## The adversary models + +**Mixnet mode** is designed to protect against **Global Passive Adversaries**: entities that can observe traffic across the entire network at once. Nation-state intelligence agencies, large corporations with broad network infrastructure, ISPs, or any combination sharing data. + +The assumption is worst-case: the adversary monitors all entry and exit points, correlates timing, applies machine learning to traffic patterns, and runs long-term statistical analysis. When Tor launched in 2002, this was considered unrealistic - machine learning and the increase in computation power have made this unfortunately more of a potential reality today. + +**dVPN mode** does not defend against timing analysis, but it splits trust across two independent operators and removes payment linkability, which already addresses the biggest weaknesses of traditional VPNs. + +For a comparison with VPNs, Tor, and I2P, see [Nym vs Other Systems](/network/overview/comparisons). For help picking a mode, see [Choosing a Mode](/network/overview/choosing-a-mode). diff --git a/documentation/docs/pages/network/reference.md b/documentation/docs/pages/network/reference.md new file mode 100644 index 0000000000..dbe69cad6c --- /dev/null +++ b/documentation/docs/pages/network/reference.md @@ -0,0 +1,17 @@ +--- +title: "Nym Network Reference" +description: "Technical specifications and protocol details for the Nym Network: addressing format, epoch timing, and the hop-by-hop acknowledgement system." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Reference + +Technical specifications and protocol details that apply across the Nym Network regardless of mode. + +## In this section + +- [Addressing](/network/reference/addressing): the `identity.encryption@gateway` address format and how routing works +- [Epochs](/network/reference/epochs): time divisions in the network, reward distribution, and topology reshuffling +- [Acknowledgements](/network/reference/acks): the hop-by-hop packet delivery confirmation system diff --git a/documentation/docs/pages/network/reference/_meta.json b/documentation/docs/pages/network/reference/_meta.json new file mode 100644 index 0000000000..d6a3489d76 --- /dev/null +++ b/documentation/docs/pages/network/reference/_meta.json @@ -0,0 +1,5 @@ +{ + "addressing": "Addressing System", + "epochs": "Epochs", + "acks": "Acknowledgements" +} diff --git a/documentation/docs/pages/network/reference/acks.mdx b/documentation/docs/pages/network/reference/acks.mdx new file mode 100644 index 0000000000..b35a4be1be --- /dev/null +++ b/documentation/docs/pages/network/reference/acks.mdx @@ -0,0 +1,35 @@ +--- +title: "Packet Acknowledgements" +description: "How the Nym Network uses hop-by-hop acknowledgements and retransmission to ensure reliable packet delivery despite network congestion or node failures." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +import { Callout } from 'nextra/components' + +# Acknowledgements + +The Nym Network uses acknowledgements to ensure reliable packet delivery. When a node receives a packet, it sends an ack back to the sender. If no ack arrives within a timeout, the packet is retransmitted. + +## How it works + +The sender transmits a packet and waits for acknowledgement. The receiver processes the packet and sends an ack. If the sender receives the ack, the packet is marked as delivered. If not, the sender retransmits. + +This happens automatically at each hop. If a client sends 100 packets to a Gateway and only receives 95 acks, it retransmits the 5 missing packets. The same mechanism operates between all nodes in the route. + +## Why it matters + +Network conditions can cause packet loss: congestion, temporary failures, connectivity issues. Without acks and retransmission, lost packets would mean lost messages. The acknowledgement system ensures reliable delivery despite imperfect network conditions. + +## Scope + +Acknowledgements operate hop-by-hop between adjacent nodes. They confirm that packets reached the next hop, not that they reached the final destination. End-to-end delivery confirmation for anonymous communication is handled separately through [SURBs](/network/mixnet-mode/anonymous-replies). + +## 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. + + +**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. + diff --git a/documentation/docs/pages/network/reference/addressing.md b/documentation/docs/pages/network/reference/addressing.md new file mode 100644 index 0000000000..79b0a133fb --- /dev/null +++ b/documentation/docs/pages/network/reference/addressing.md @@ -0,0 +1,41 @@ +--- +title: "Nym Network Addressing" +description: "How Nym addresses work: the identity.encryption@gateway format, key components, routing mechanics, and privacy considerations for client addressing." +schemaType: "TechArticle" +section: "Network" +lastUpdated: "2026-03-15" +--- + +# Addressing + +All clients and nodes in the Nym Network have an address that uniquely identifies them for routing. + +## Address format + +A Nym address has three parts separated by dots and an @ symbol: + +``` +.@ +``` + +The **identity key** identifies the client for routing purposes. It's derived from the client's Ed25519 keypair and base58-encoded for readability. + +The **encryption key** is the public key used to encrypt the final layer of Sphinx packets destined for this client. Only the client holding the corresponding private key can decrypt messages addressed to them. + +The **gateway key** identifies which Gateway holds messages for this client. When you connect, your client registers with a specific Entry Gateway, and that Gateway's identity becomes part of your address. + +## Example + +``` +DguTcdkWWtDyUFLvQxRdcA8qZhardhE1ZXy1YCC7Zfmq.Dxreouj5RhQqMb3ZaAxgXFdGkmfbDKwk457FdeHGKmQQ@4kjgWmFU1tcGAZYRZR57yFuVAexjLbJ5M7jvo3X5Hkcf +``` + +## How routing works + +When sending to a Nym address, the sender extracts the Gateway key and constructs a Sphinx packet with that Gateway as the final hop. The Gateway receives the packet, identifies the recipient by their identity key, and delivers the message (or stores it if the recipient is offline). + +## Privacy considerations + +The address reveals which Gateway you use and your public keys. It doesn't reveal your IP address or private keys. Multiple clients can use the same Gateway, so the Gateway key alone doesn't identify you. + +For persistent identity across sessions, store your keypairs and re-register with the same Gateway. For ephemeral identity, generate new keys each session. diff --git a/documentation/docs/pages/network/reference/epochs.md b/documentation/docs/pages/network/reference/epochs.md new file mode 100644 index 0000000000..4398e6fa22 --- /dev/null +++ b/documentation/docs/pages/network/reference/epochs.md @@ -0,0 +1,29 @@ +--- +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." +schemaType: "TechArticle" +section: "Network" +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. + +## What happens at epoch boundaries + +**Reward distribution** calculates performance metrics for each node and distributes NYM token rewards based on routing reliability and uptime. Nodes that successfully forward 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. + +## Future changes + +In upcoming releases, epochs will trigger automatic role assignment. Nodes will switch between Mix Node and Gateway roles based on network demand, without operators needing to manually configure roles. + +## SURB validity + +SURBs are tied to key rotation cycles. Node keys rotate on an odd/even schedule with a default validity of 24 epochs. A SURB remains usable for `(validity_epochs + 1) * epoch_duration`, roughly 25 hours at the current 1-hour epoch. After that, the routing keys it was built with are no longer accepted by the network. Clients automatically purge stale SURBs and request fresh ones. + +## Querying epoch information + +Current epoch data is available through Nyx blockchain queries and Nym API endpoints. diff --git a/documentation/docs/pages/network/traffic.mdx b/documentation/docs/pages/network/traffic.mdx deleted file mode 100644 index 504f79a00a..0000000000 --- a/documentation/docs/pages/network/traffic.mdx +++ /dev/null @@ -1,23 +0,0 @@ -import { Callout } from 'nextra/components' - -# Traffic Flow - -> Nym uses a source-routed decryption mixnet ... [i]n source routing, the sender of a message chooses the route that the message will traverse before reaching its final destination. -> -> ... -> -> Nym uses the Sphinx packet format to encapsulate and anonymously route data payloads. Senders prepare Sphinx messages by encrypting them multiple times in reverse routing order. First, a message is encrypted for the recipient; then, for the last mix node in the path; then, for its predecessors in the path, ending with the outermost encryption, which corresponds to the mix node in the first layer. The complete Sphinx packet is finally encrypted with a key shared with the gateway, which forwards decrypted Sphinx packets to the mixnet. -> -> [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) §4 - - -Please note that the following information is only about how traffic flows through the Mixnet, which is the **only** method that Nym Clients use, and is defined as 'anonymous' mode in the NymVPN application. DVPN 'speedy' mode is different, and you can read about that [here](https://nymcom.vercel.app/blog/decentralized-vpns). - - -The Nym mixnet re-orders encrypted, indistinguishable [Sphinx](cryptography/sphinx) packets as they travel through the network. - -Traffic to send through the mixnet is broken up into uniformly-sized packets, encrypted in the Sphinx packet format according to the route the packet will take, and sent through the mixnet to be mixed among other real traffic and fake - but identical - cover traffic. At each 'hop' (i.e. as a packet is forwarded from one node in the sequence to another) a layer of decryption is removed from the Sphinx packet, revealing the address of the next hop, and another Sphinx packet. The packet is then held by the node for a variable amount of time, before being forwarded on to the next node in the route. - -> How traffic moves through the Mixnet is one of the core mechanisms that provide the privacy properties of the network, but is also (aside from [SURBs](./traffic/anonymous-replies)) mostly abstracted away from developers. - -Whatever is on the 'other side' of the mixnet from your client, all traffic will travel this way through the mixnet. If you are sending traffic to a service external to Nym (such as a chat application's servers) then your traffic will be sent from the recieving Nym client to an application that will proxy it 'out' of the mixnet to these servers, shielding your metadata from them. P2P (peer-to-peer) applications, unlike the majority of apps, might want to keep all of their traffic entirely 'within' the mixnet, as they don't have to necessarily make outbound network requests to application servers. They would simply have their local application code communicate with their Nym clients, and not forward traffic anywhere 'outside' of the mixnet. diff --git a/documentation/docs/pages/network/traffic/_meta.json b/documentation/docs/pages/network/traffic/_meta.json deleted file mode 100644 index c4ae51a76b..0000000000 --- a/documentation/docs/pages/network/traffic/_meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "hops": { - "display": "hidden" - }, - "flow": "Step by Step Breakdown", - "acks": "Acks & Retransmission", - "addressing-system": "Addressing", - "anonymous-replies": "Anonymous Replies: SURBs" -} diff --git a/documentation/docs/pages/network/traffic/acks.md b/documentation/docs/pages/network/traffic/acks.md deleted file mode 100644 index 7647d3d581..0000000000 --- a/documentation/docs/pages/network/traffic/acks.md +++ /dev/null @@ -1,21 +0,0 @@ -# Acks & Package Retransmission -Whenever a hop is completed, the receiving node will send back an acknowledgement ('ack') so that the sending node knows that the packet was received. If it does not receive an ack after sending, it will resend the packet, as it assumes that the packet was dropped for some reason. This is done under the hood by the binaries themselves, and is never something that developers and node operators have to worry about dealing with. - -A simplified example: if a client sends 100 packets to a gateway, but only receives an acknowledgement ('ack') for 95 of them, it will resend those 5 packets to the gateway again, to make sure that all packets are received. All nodes in the mixnet support packet retransmission. - -```mermaid ---- -config: - theme: neo-dark - layout: elk ---- -sequenceDiagram - Nym Client->>Gateway: N Sphinx Packets - Gateway-->>Nym Client: N Acks - Gateway->>Mix Node 1: Packets 1..95 - Mix Node 1-->>Gateway: Acks 1..95 - Gateway-xMix Node 1: Packets 96..100 Dropped - No Acks - Gateway->>Mix Node 1: Retransmit Dropped Packets after timeout - Mix Node 1-->>Gateway: Acks 96..100 - Mix Node 1->>Mix Node 2: etc -``` diff --git a/documentation/docs/pages/network/traffic/addressing-system.md b/documentation/docs/pages/network/traffic/addressing-system.md deleted file mode 100644 index 3107373bad..0000000000 --- a/documentation/docs/pages/network/traffic/addressing-system.md +++ /dev/null @@ -1,15 +0,0 @@ -# Addressing System - -All clients and nodes in the Nym network have an address, in the format: - -``` -user-identity-key.user-encryption-key@gateway-identity-key -``` - -Which in practice, looks something like this: - -``` -DguTcdkWWtDyUFLvQxRdcA8qZhardhE1ZXy1YCC7Zfmq.Dxreouj5RhQqMb3ZaAxgXFdGkmfbDKwk457FdeHGKmQQ@4kjgWmFU1tcGAZYRZR57yFuVAexjLbJ5M7jvo3X5Hkcf -``` - -ID keys are used for routing, and encryption keys are the public keypair used to decrypt the exterior layer of Sphinx packets addressed to the node/client. diff --git a/documentation/docs/pages/network/traffic/anonymous-replies.mdx b/documentation/docs/pages/network/traffic/anonymous-replies.mdx deleted file mode 100644 index ae9c5382c9..0000000000 --- a/documentation/docs/pages/network/traffic/anonymous-replies.mdx +++ /dev/null @@ -1,78 +0,0 @@ -import { Callout } from 'nextra/components' - -# Anonymous Replies with SURBs - -> SURBs are pre-computed Sphinx packet headers encoding a mixnet route that ends in the participant that created the SURB. A sender can generate one or more SURBs and include them in their Sphinx message to a recipient. The recipient can use the SURBs as Sphinx headers to send back replies – or acknowledgements – that anonymously reach back the original sender after going through the mixnet. -> -> SURBs are the Sphinx equivalent of "onion addresses" in Tor, with the caveat that a SURB can only be used once (to prevent replay attacks) and within its epoch of validity (the mix node public keys used to prepare the SURB are only valid for a limited period). SURB headers are encrypted by the sender, so the recipient sending it back cannot infer from it any information about the message route, the per-hop latency, or the sender’s address, which is encoded in the innermost (last) routing layer of the SURB. SURBs ('Single Use Reply Blocks') allow clients to reply to incoming messages anonymously. -> -> ... -> -> A SURB effectively contains: (1) the encrypted headers of a Sphinx message that, if sent to the mixnet, will be routed back to the original sender; (2) the address of the first-layer mix node where the message should be sent; and (3) a cryptographic key to encrypt the reply payload. -> [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) §4.5 - -As outlined in the [concepts](../concepts/anonymous-replies) section, SURBs are layer encrypted sets of Sphinx headers detailing a reply path ending in the sending client's [Nym address](../traffic/addressing-system). Clients receiving messages with SURBs attached are able to write a payload to the provided headers without ever learning about anything other than the first hop back into the Mixnet - the Gateway they (the sender of the reply) are currently registered with. - -Put simply, client A sends client B a request of some kind. Before sending the request through the Mixnet, it creates a number of SURBs and sends those along with the request. Client B writes the response to the request to the payload of the SURBs, and then sends these through the Mixnet. Since Sphinx packets are multiply route-encrypted, the first destination of the packet is the `nym-node` running as an Entry Gateway that client B has registered with already, so no information regarding either the path through the Mixnet or the destination of the SURBs (aka the Nym address of client A) is revealed to client B. - -## Anatomy of a SURB -Diagramatic representation coming soon™️. - -## Sender Tags -For a session between two clients, the sending client generates a random alphanumeric string, referred to as a `sender tag` which is sent along with the SURBs to the receiver of its message(s). The `sender tag` is generated randomly, and does not refer in any way to any identifiers of the sending client. - -This is done so that receiving clients have some way of differentiating incoming SURBs from multiple clients and can split them into different 'buckets' in order to facilitate concurrent anonymous replies. - -## Replenishing SURBs -Since each SURB is just a pre-computed Sphinx packet header, and Sphinx packets only have a finite payload size, then the size of a possible reply for the amount of SURBs sent with a request is `# SURBs * payload size in bytes`. However it is often the case that replies may be variable in size and larger than the alloted payload size, and/or the sending client did not want to compute many Sphinx headers to send with its initial request. - -As such, when a client is running out of SURBs to use for replying, it will use a SURB to send a request to the initial sending client. This request is a request for more SURBs to be sent to it. - - -```mermaid ---- -config: - theme: neo-dark - layout: elk ---- -sequenceDiagram - participant Nym Client (Sender) - participant Mixnet Nodes - participant Nym Client (Receiver) - - Nym Client (Sender) ->> Nym Client (Sender): Create Sphinx packets with request payload & Sphinx packets with N SURBs - Nym Client (Sender) ->> Mixnet Nodes: Sphinx packets (with payload + with SURBs) - Mixnet Nodes ->> Nym Client (Receiver): Sphinx packets (with payload + with SURBs) - Nym Client (Receiver) ->> Nym Client (Receiver): Decrypt packets - Nym Client (Receiver) ->> Nym Client (Receiver): Perform computation - Nym Client (Receiver) ->> Nym Client (Receiver): Prepare response - problem! Response requires > N * SURB payloads - Nym Client (Receiver) ->> Mixnet Nodes: Sphinx packets (SURB request) - Mixnet Nodes ->> Nym Client (Sender): Sphinx packets (SURB request) - Nym Client (Sender) ->> Nym Client (Sender): Create more SURBs - Nym Client (Sender) ->> Mixnet Nodes: Sphinx packets (SURBs) - Mixnet Nodes ->> Nym Client (Receiver): Sphinx packets (SURBs) - Nym Client (Receiver) ->> Nym Client (Receiver): Write response to SURB payloads - Nym Client (Receiver) ->> Mixnet Nodes: SURBs with payload - Mixnet Nodes ->> Nym Client (Sender): SURBs with payload - Nym Client (Sender) ->> Nym Client (Sender): Decrypt - anonymous response received -``` - -There is a balance to be struck between the amount of SURBs to compute to send along with messages (aka the sending client A spending computation resources) and not sending enough SURBs initially, thus having to wait for a SURB to be sent from the receiving client B to client A requesting more SURBs be sent, which themselves have to be then sent through the Mixnet to client B to be written to and sent back through the Mixnet again. - -If you are able to spend the extra resources upfront and send a lot of SURBs, fewer trips through the Mixnet are required for traffic to go back and forth. However, bear in mind that SURBs in the future will have a finite lifespan (see the section below) so precomputing a very large number to send with the initial message (assuming you are expecting a large reponse, or several messages back and forth) will not work. Furthermore, sending huge amounts of SURBs might open your app up to [possible attacks](#anatomy-of-a-surb). - -## SURB Lifetimes - - At the time of writing, SURBs themselves are valid indefinitely, but clients purge their local DB of SURBs that are older than a day on restart. SURBs are valid between topology changes over epochs as `nym-nodes` have a single static publick key, so unless a node goes offline, SURBs will still work even after a topology change. - - We still have a few features to add to the Mixnet to add some extra security which will dramatically limit the amount of time SURBs are valid for, but will increase the overall security of the network, so do **not** build with the current status in mind. Instead, check out the information below, and build with this in mind. - - -Once node key rotation (part of the larger [forward secrecy](https://en.wikipedia.org/wiki/Forward_secrecy) work) and [replay protection](https://www.kaspersky.com/resource-center/definitions/replay-attack) is implemented, SURBs will only be valid for the length of the key epoch (aka for the length of time a `nym-node` retains a particular public key between rotations). The length of the key epoch is still to be decided. - -Although this means that there will probably be more back-and-forth between clients sending large volumes of traffic, this will make the network more secure overall. - -## Known Attacks Using SURBs -There is a known attack in which a malicious service provider / client continually requests that a sending client sends more and more SURBs to them, accruing a large number of them. The attacker then sends all SURBs back to the sending clients at once in order to try and see permutations in the traffic exiting the Mixnet to the sending client, in order to work out which Gateway they are using as their Entry Gateway. - -This attack however relies upon the attacker already being able to actively scan all `nym-node`s running as Gateways and capture that traffic, as well as (once the [zk-nym scheme]() is enabled, spend money to send that traffic through the Mixnet. Furthermore, this is an _active attack_ and requires a client to be either be running a malicious service, or be in a position to request multiple bundles of SURBs from clients via some service. diff --git a/documentation/docs/pages/network/traffic/flow.mdx b/documentation/docs/pages/network/traffic/flow.mdx deleted file mode 100644 index 28ae9df6bd..0000000000 --- a/documentation/docs/pages/network/traffic/flow.mdx +++ /dev/null @@ -1,232 +0,0 @@ -# Packet Flow Breakdown -## Sending Client → Entry Gateway -Nym Clients, on startup, register with a particular node to use as an Entry Gateway. This partially defines their [Client Address](./addressing-system). - -Once connected, Clients **are constantly sending traffic into the Mixnet**; as well as the packets that are sent from an application wanting to use the Mixnet, Clients send [cover traffic](../concepts/cover-traffic) at a constant rate defined by a Poisson process. - -On accepting bytes from a locally running process, Nym Clients: -- Send the data to the internal queue. -- Packetise and encrypt the data as the payload of Sphinx packets. Encryption is done according to route. Routing is done on a per-packet basis. -- Perform a Diffie Hellman Key Exchange with their Entry Gateway. -- Open a Websocket connection with their Entry Gateway. -- Slot Sphinx packets containing data payloads in between outgoing cover traffic packets and send these down the Websocket. - -```mermaid ---- -config: - theme: neo-dark ---- -sequenceDiagram - box Local Machine - participant AL as Application Logic - participant NC as Nym Client - end - - box Mixnet Infrastructure - participant EG as Entry Gateway - end - - EG->NC: Diffie Hellman Key Exchange - - Note over NC,EG: Cover Traffic Stream - loop Continuous Cover Traffic - NC->>NC: Delay - NC->>EG: Cover traffic - end - - Note over AL,EG: Mixed Traffic Stream - loop Cover + Real Traffic Processing - NC->>NC: Check internal queue + delay - NC->>EG: Cover traffic - opt Packets with Application Payload - AL-->>NC: Send(bytes): add to internal queue - NC->>NC: Check queue: bytes to send - NC->>NC: Encrypt & packetise bytes - NC->>EG: Real Packets - NC->>NC: Check queue: more bytes - NC->>NC: Encrypt & packetise bytes - NC->>EG: Real Packets - NC->>NC: Check queue: empty - end - NC->>NC: Delay - NC->>EG: Cover traffic - end - -``` - -## Entry Gateway → Mix Nodes → Exit Gateway -As packets move through the Mixnet, receiving nodes will: -- Verify the MAC address of incoming Sphinx packets. -- Forward the inner packet they have decrypted onto its next desination hop, via TCP. - -Mix Nodes, as their name suggests, perform the 'packet mixing' by adding a randomised delay before forwarding on the packets, so they no longer travel FIFO through each layer of Mix Nodes. - -```mermaid ---- -config: - theme: neo-dark ---- -sequenceDiagram - box Mixnet Infrastructure - participant EG as Entry Gateway - participant M1 as Mix Node Layer 1 - participant M2 as Mix Node Layer 2 - participant M3 as Mix Node Layer 3 - participant ExG as Exit Gateway - end - - Note over EG: Process packets - EG->>EG: Decrypt outer encryption layer - EG->>EG: Check MAC for tampering - - Note over EG,M1: Layer 1 Transmission - EG->>M1: Sphinx Packets - Note over M1: Process packets - M1->>M1: Decrypt outer encryption layer - M1->>M1: Check MAC for tampering - M1->>M1: Hold packet for variable time delay - - Note over M1,M2: Layer 2 Transmission - M1->>M2: Sphinx Packets - Note over M2: Process packets - M2->>M2: Decrypt outer encryption layer - M2->>M2: Check MAC for tampering - M2->>M2: Hold packet for variable time delay - - Note over M2,M3: Layer 3 Transmission - M2->>M3: Sphinx Packets - Note over M3: Process packets - M3->>M3: Decrypt outer encryption layer - M3->>M3: Check MAC for tampering - M3->>M3: Hold packet for variable time delay - - Note over M3,ExG: Exit Gateway Transmission - M3->>ExG: Sphinx Packets - Note over ExG: Process packets - ExG->>ExG: Decrypt outer encryption layer - ExG->>ExG: Check MAC for tampering - -``` - -## Exit Gateway → Receiving Client -The final hop of Mixnet traffic involves: -- The Exit Gateway for the packet route (the Entry Gateway that the receiving Nym Client registered with on startup) performing the decryption and MAC check. -- The Exit Gateway forwards the Sphinx packet on to the Nym Client if it is online. If the Client is not online, the Gateway holds the packet for up to 24 hours. - -The receiving Nym Client will then decrypt the final Sphinx packet layer and have access to the decrypted packet payload, and [SURB](./anonymous-replies) header information for anonymous replies. - -```mermaid ---- -config: - theme: neo-dark ---- -sequenceDiagram - box Mixnet Infrastructure - participant ExG as Exit Gateway - end - - box Remote Machine - participant RC as Nym Client - participant AR as Application Logic - end - - ExG->RC: Diffie Hellman Key Exchange - ExG->>RC: Sphinx Packets - Note over RC: Process packets - RC->>RC: Decrypt outer encryption layer - RC->>RC: Check MAC for tampering - - RC->>AR: Bytes -``` - -## Whole Flow - -```mermaid ---- -config: - theme: neo-dark ---- -sequenceDiagram - box Local Machine - participant AL as Application Logic - participant NC as Nym Client - end - - box Mixnet Infrastructure - participant EG as Entry Gateway - participant M1 as Mix Node Layer 1 - participant M2 as Mix Node Layer 2 - participant M3 as Mix Node Layer 3 - participant ExG as Exit Gateway - end - - box Remote Machine - participant RC as Nym Client - participant AR as Application Logic - end - - EG->NC: Diffie Hellman Key Exchange - - Note over NC,EG: Cover Traffic Stream - loop Continuous Cover Traffic - NC->>NC: Delay - NC->>EG: Cover traffic - end - - Note over AL,EG: Mixed Traffic Stream - loop Cover + Real Traffic Processing - NC->>NC: Check internal queue + delay - NC->>EG: Cover traffic - opt Packets with Application Payload - AL-->>NC: Send(bytes): add to internal queue - NC->>NC: Check queue: bytes to send - NC->>NC: Encrypt & packetise bytes - NC->>EG: Real Packets - NC->>NC: Check queue: more bytes - NC->>NC: Encrypt & packetise bytes - NC->>EG: Real Packets - NC->>NC: Check queue: empty - end - NC->>NC: Delay - NC->>EG: Cover traffic - end - - Note over EG: Process packets - EG->>EG: Decrypt outer encryption layer - EG->>EG: Check MAC for tampering - - Note over EG,M1: Layer 1 Transmission - EG->>M1: Sphinx Packets - Note over M1: Process packets - M1->>M1: Decrypt outer encryption layer - M1->>M1: Check MAC for tampering - M1->>M1: Hold packet for variable time delay - - Note over M1,M2: Layer 2 Transmission - M1->>M2: Sphinx Packets - Note over M2: Process packets - M2->>M2: Decrypt outer encryption layer - M2->>M2: Check MAC for tampering - M2->>M2: Hold packet for variable time delay - - Note over M2,M3: Layer 3 Transmission - M2->>M3: Sphinx Packets - Note over M3: Process packets - M3->>M3: Decrypt outer encryption layer - M3->>M3: Check MAC for tampering - M3->>M3: Hold packet for variable time delay - - Note over M3,ExG: Exit Gateway Transmission - M3->>ExG: Sphinx Packets - Note over ExG: Process packets - ExG->>ExG: Decrypt outer encryption layer - ExG->>ExG: Check MAC for tampering - - ExG->RC: Diffie Hellman Key Exchange - ExG->>RC: Sphinx Packets - Note over RC: Process packets - RC->>RC: Decrypt outer encryption layer - RC->>RC: Check MAC for tampering - - RC->>AR: Bytes -``` diff --git a/documentation/docs/pages/network/traffic/hops.mdx b/documentation/docs/pages/network/traffic/hops.mdx deleted file mode 100644 index f63d8e8704..0000000000 --- a/documentation/docs/pages/network/traffic/hops.mdx +++ /dev/null @@ -1,2 +0,0 @@ -# Traffic Hops -As highlighted in the previous page, Sphinx packets are sent between multiple nodes. Each time a packet is sent between node, it is referred to as a 'hop'. When a Mix Node receives an incoming packet, it decrypts the outer layer of the packet and applies a variable length timing delay before sending the packet out on the next hop. Gateways do not apply this delay. diff --git a/documentation/docs/pages/operators/binaries/building-nym.mdx b/documentation/docs/pages/operators/binaries/building-nym.mdx index 8a4da7b3aa..1597a52b87 100644 --- a/documentation/docs/pages/operators/binaries/building-nym.mdx +++ b/documentation/docs/pages/operators/binaries/building-nym.mdx @@ -84,6 +84,6 @@ Quite a bit of stuff gets built. The key working parts are: {/* The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages. -* [Nym Wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) +* [Nym Wallet](https://nym.com/docs/wallet/desktop-wallet.html) * [Network Explorer UI](https://nymtech.net/docs/explorers/mixnet-explorer.html) */} diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index 05b462b443..a8c7d178f1 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -778,7 +778,7 @@ cargo Profile: release - [Overhauled **developer integrations** pages](/developers/integrations) explaining the different restrictions for the different SDK options on offer -- [Fixed `mixFetch` and `WASM Client` playground + examples](/developers/typescript/start): new versions of the Typescript SDK and `mixFetch` have been published, examples and live playground have been updated accordingly +- [Fixed `mixFetch` and `WASM Client` playground + examples](/developers/typescript): new versions of the Typescript SDK and `mixFetch` have been published, examples and live playground have been updated accordingly ### Features diff --git a/documentation/docs/pages/operators/faq/general-faq.mdx b/documentation/docs/pages/operators/faq/general-faq.mdx index a31128de59..57271c1c17 100644 --- a/documentation/docs/pages/operators/faq/general-faq.mdx +++ b/documentation/docs/pages/operators/faq/general-faq.mdx @@ -10,7 +10,7 @@ lastUpdated: "2026-02-01" ## Nym Network -To see different stats about Nym Network live, we recommend you to visit [Nym Harbourmaster](https://harbourmaster.nymtech.net) and dynamic [Nym token page](https://nymtech.net/about/token). +To see different stats about Nym Network live, we recommend you to visit [Nym Harbourmaster](https://harbourmaster.nymtech.net) and dynamic [Nym token page](https://nym.com/about/token). ### Is there an explorer for Nym Mixnet? diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 0eef5ecf4c..6873349219 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -47,7 +47,7 @@ From `nym-node v1.3.0` operators can choose multiple functionalities for their ` ### Mixnet Routing -***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](../../../network/architecture)*** +***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](/network/overview)*** Nym Node has three functionalities in the Mixnet: `entry-gateway`, `mixnode` and `exit-gateway`. These are selected with a flag `--mode ` alongside `nym-node` command `run` . diff --git a/documentation/docs/pages/operators/nodes/preliminary-steps/wallet-preparation.mdx b/documentation/docs/pages/operators/nodes/preliminary-steps/wallet-preparation.mdx index be5845e983..3a758e5465 100644 --- a/documentation/docs/pages/operators/nodes/preliminary-steps/wallet-preparation.mdx +++ b/documentation/docs/pages/operators/nodes/preliminary-steps/wallet-preparation.mdx @@ -6,7 +6,7 @@ Head to our [website](https://nym.com/wallet) and download Nym wallet for your o {/* -If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nymtech.net/docs/wallet/desktop-wallet.html). +If pre-compiled binaries for your operating system aren't available, you can build the wallet yourself with instructions [here](https://nym.com/docs/wallet/desktop-wallet.html). */} If you don't already have one, please create a Nym address using the wallet, and fund it with NYM tokens. The minimum amount required to bond a node is 100 `NYM`, but make sure you have a bit more to account for gas costs. diff --git a/documentation/docs/pages/operators/performance-and-testing/_meta.json b/documentation/docs/pages/operators/performance-and-testing/_meta.json index 9d5f481d1c..77bba2c74d 100644 --- a/documentation/docs/pages/operators/performance-and-testing/_meta.json +++ b/documentation/docs/pages/operators/performance-and-testing/_meta.json @@ -1,4 +1,5 @@ -{ +{ + "ns-api-deployment": "Node Status API Deployment", "gateway-probe": "Gateway Probe", "gateway-probe-details": "Gateway Probe Details", "prometheus-grafana": "Prometheus & Grafana" diff --git a/documentation/docs/pages/apis/ns-api/ns-api-run-deploy.mdx b/documentation/docs/pages/operators/performance-and-testing/ns-api-deployment.mdx similarity index 98% rename from documentation/docs/pages/apis/ns-api/ns-api-run-deploy.mdx rename to documentation/docs/pages/operators/performance-and-testing/ns-api-deployment.mdx index 4df6a92c8d..1444892e29 100644 --- a/documentation/docs/pages/apis/ns-api/ns-api-run-deploy.mdx +++ b/documentation/docs/pages/operators/performance-and-testing/ns-api-deployment.mdx @@ -9,7 +9,7 @@ The Node Status API is made up of 3 components: - `nym-node-status-client` - `nym-node-status-agent` -The API stores its data in a local SQLite database. It periodically gets most of its data from a [NymAPI](../nym-api) instance, and for instances also running with the Gateway Probe, uses the stored topology to conduct performance probe tests. +The API stores its data in a local SQLite database. It periodically gets most of its data from a [NymAPI](/apis/nym-api) instance, and for instances also running with the Gateway Probe, uses the stored topology to conduct performance probe tests. ## Gateway Probe You can run the `nym-node-status-api` alone, but if you want to run probes for `nym-node`s running as Gateways, you have to also run the `nym-node-status-agent` (which consumes the `nym-node-status-client` lib). The probe will run a set of tests per Gateway node, and return the sort of results seen [here](https://harbourmaster.nymtech.net/gateway/23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb). diff --git a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx index 86fd14f212..06a7f67c74 100644 --- a/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx +++ b/documentation/docs/pages/operators/tokenomics/mixnet-rewards.mdx @@ -26,7 +26,7 @@ import RewardsCalculator from 'components/operators/interactive/calculators/rewa -* Nym tokenomics are based on the research paper [*Reward Sharing for Mixnets*](https://nymtech.net/nym-cryptoecon-paper.pdf) +* Nym tokenomics are based on the research paper [*Reward Sharing for Mixnets*](https://nym.com/nym-cryptoecon-paper.pdf) * For a more comprehensive overview, live data and supply graphs, visit [*explorer.nym.spectredao.net/token*](https://explorer.nym.spectredao.net/token) We are working on the final architecture of [*Fair Mixnet*](#fair-mixnet) tokenomics implementation and its detailed documentation. **The current design is called [*Naive rewarding*](#naive-rewarding).** It is an intermediate step, allowing operators to migrate to `nym-node` in Mixnet smart contract and for the first time receive delegations and earn rewards for any `nym-node` [functionality](../nodes/nym-node/setup#functionality-mode), in opposite to the past system, where only Mixnodes were able to receive delegations and rewards. diff --git a/documentation/docs/public/images/cc-icons/by.svg b/documentation/docs/public/images/cc-icons/by.svg new file mode 100644 index 0000000000..34099a3a76 --- /dev/null +++ b/documentation/docs/public/images/cc-icons/by.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/documentation/docs/public/images/cc-icons/cc.svg b/documentation/docs/public/images/cc-icons/cc.svg new file mode 100644 index 0000000000..cb08896c77 --- /dev/null +++ b/documentation/docs/public/images/cc-icons/cc.svg @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/documentation/docs/public/images/cc-icons/nc.svg b/documentation/docs/public/images/cc-icons/nc.svg new file mode 100644 index 0000000000..fcf2f4b171 --- /dev/null +++ b/documentation/docs/public/images/cc-icons/nc.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/documentation/docs/public/images/cc-icons/sa.svg b/documentation/docs/public/images/cc-icons/sa.svg new file mode 100644 index 0000000000..8d5ffde331 --- /dev/null +++ b/documentation/docs/public/images/cc-icons/sa.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/documentation/docs/public/llms-full.txt b/documentation/docs/public/llms-full.txt new file mode 100644 index 0000000000..4c38206c07 --- /dev/null +++ b/documentation/docs/public/llms-full.txt @@ -0,0 +1,23136 @@ +# Nym Documentation + +@version: 1.20.4 +@generated: 2026-03-26 +@pages: 150 +@source: https://github.com/nymtech/nym/tree/develop/documentation/docs + +--- +title: Nym Network Architecture: How the Mixnet Works +description: Deep dive into Nym network architecture, cryptographic systems, and how the mixnet provides network-level privacy against end-to-end attackers. +url: https://nym.com/docs/network +--- + +# The Nym Network + +The Nym Network is decentralized privacy infrastructure that protects against **network-level** surveillance. It does this by protecting message *metadata*—who is communicating with whom, when, how often, and how much—from being able to be captured. + +## The problem with metadata + +When you send data across the internet, observers can see that communication has occurred in the form of the source and destination IP addresses of internet packets, the timing and frequency of transmissions, packet sizes, and other bits of information that over time can be used to build up inferences about the type of [device/browser you're using](https://browserleaks.com/ip), [your connection](https://browserleaks.com/tcp), and ultimately who you are. These observers include your ISP, internet infrastructure providers, governments, and large corporations. + +Even when sending encrypted content (e.g. using messaging apps like Signal or SimpleX, or encrypted email providers), metadata can identify users by allowing observers to build up inferences and build behavioral profiles. Advances in machine learning in recent years has made these attacks increasingly practical, and spawned an entire industry dedicated to the capture and analysis of internet traffic. + +## How Nym solves this + +Every person and usecase has a different threat model - journalists in highly adversarial environments might be happy to accept higher latency and lower throughput when their safety is on the line, whereas your average user might just want to be 'private enough' to not be leaking everything they do to an ISP, passive government surveillance, or a centralised VPN provider. + +As such, there are two 'modes' for sending traffic through Nym, each serving different needs. There are also two different ways to access the network: + +### NymVPN + +[NymVPN](https://nymvpn.com) is a subscription-based application that provides access to both modes: +- **dVPN mode** routes traffic through 2 hops using WireGuard with enhanced layer encryption—fast enough for browsing and streaming while still providing strong privacy against typical adversaries. +- **Mixnet mode** routes traffic through 5 hops with packet mixing, timing delays, and cover traffic, providing maximum privacy against sophisticated adversaries capable of observing the entire network. In the Mixnet, every packet is the same size, each hop only sees the next destination, packets are delayed and reordered to destroy timing patterns, and a constant stream of 'dummy' packets hides when real communication is occurring. + +Both modes use the same underlying infrastructure. + +### Developer SDKs + +Developers can integrate mixnet functionality directly into applications using the [Nym SDKs](/developers). This provides the same privacy guarantees as NymVPN's mixnet mode and is currently free for development and testing. The SDKs do **not** provide access to dVPN mode, which is currently specific to the NymVPN application. + +## Paying for privacy without losing it + +A fundamental problem with VPNs and privacy services is that payment information can easily deanonymize users (e.g. most VPNs will link a user's session to their account ID). Nym solves this with **zk-nyms**—zero-knowledge anonymous credentials that allow you to prove you've paid for a subscription without revealing **anything else** about you. Each are used for small chunks of bandwidth, and are unlinkable to each other. + +When you pay for NymVPN access, your payment is converted to a cryptographic credential that can be split and re-randomized. Each time you connect to a new Gateway node (for example, you switch which server you want your connection to be partially routed through), you present a fresh, unlinkable proof. Gateways verify payment validity without learning your identity, and **your subscription cannot be linked to your network activity, even by infrastructure operators**. + +## Documentation structure + +This documentation covers the network architecture and protocols: +- [Overview](/network/overview): high-level concepts. +- [dVPN Mode](/network/dvpn-mode): more detail about the protocol and traffic flow of dVPN mode. +- [Mixnet Mode](/network/mixnet-mode): more detail about the protocol and traffic flow of Mixnet mode. +- [Cryptography](/network/cryptography): covers the underlying primitives (including zk-nyms). +- [Infrastructure](/network/infrastructure): blockchain and node architecture. +- [Reference](/network/reference): technical specifications. + +For building applications and integrating existing apps with the Mixnet, see the [Developer Documentation](/developers). + +If you wish to take part in the network as a Node Operator, see the [Operator Documentation](/operators/introduction). + +--- +title: Nym Network Architecture: How the Mixnet Works +description: Deep dive into Nym network architecture, cryptographic systems, and how the mixnet provides network-level privacy against end-to-end attackers. +url: https://nym.com/docs/network +--- + +# The Nym Network + +The Nym Network is decentralized privacy infrastructure that protects against **network-level** surveillance. It does this by protecting message *metadata*—who is communicating with whom, when, how often, and how much—from being able to be captured. + +## The problem with metadata + +When you send data across the internet, observers can see that communication has occurred in the form of the source and destination IP addresses of internet packets, the timing and frequency of transmissions, packet sizes, and other bits of information that over time can be used to build up inferences about the type of [device/browser you're using](https://browserleaks.com/ip), [your connection](https://browserleaks.com/tcp), and ultimately who you are. These observers include your ISP, internet infrastructure providers, governments, and large corporations. + +Even when sending encrypted content (e.g. using messaging apps like Signal or SimpleX, or encrypted email providers), metadata can identify users by allowing observers to build up inferences and build behavioral profiles. Advances in machine learning in recent years has made these attacks increasingly practical, and spawned an entire industry dedicated to the capture and analysis of internet traffic. + +## How Nym solves this + +Every person and usecase has a different threat model - journalists in highly adversarial environments might be happy to accept higher latency and lower throughput when their safety is on the line, whereas your average user might just want to be 'private enough' to not be leaking everything they do to an ISP, passive government surveillance, or a centralised VPN provider. + +As such, there are two 'modes' for sending traffic through Nym, each serving different needs. There are also two different ways to access the network: + +### NymVPN + +[NymVPN](https://nymvpn.com) is a subscription-based application that provides access to both modes: +- **dVPN mode** routes traffic through 2 hops using WireGuard with enhanced layer encryption—fast enough for browsing and streaming while still providing strong privacy against typical adversaries. +- **Mixnet mode** routes traffic through 5 hops with packet mixing, timing delays, and cover traffic, providing maximum privacy against sophisticated adversaries capable of observing the entire network. In the Mixnet, every packet is the same size, each hop only sees the next destination, packets are delayed and reordered to destroy timing patterns, and a constant stream of 'dummy' packets hides when real communication is occurring. + +Both modes use the same underlying infrastructure. + +### Developer SDKs + +Developers can integrate mixnet functionality directly into applications using the [Nym SDKs](/developers). This provides the same privacy guarantees as NymVPN's mixnet mode and is currently free for development and testing. The SDKs do **not** provide access to dVPN mode, which is currently specific to the NymVPN application. + +## Paying for privacy without losing it + +A fundamental problem with VPNs and privacy services is that payment information can easily deanonymize users (e.g. most VPNs will link a user's session to their account ID). Nym solves this with **zk-nyms**—zero-knowledge anonymous credentials that allow you to prove you've paid for a subscription without revealing **anything else** about you. Each are used for small chunks of bandwidth, and are unlinkable to each other. + +When you pay for NymVPN access, your payment is converted to a cryptographic credential that can be split and re-randomized. Each time you connect to a new Gateway node (for example, you switch which server you want your connection to be partially routed through), you present a fresh, unlinkable proof. Gateways verify payment validity without learning your identity, and **your subscription cannot be linked to your network activity, even by infrastructure operators**. + +## Documentation structure + +This documentation covers the network architecture and protocols: +- [Overview](/network/overview): high-level concepts. +- [dVPN Mode](/network/dvpn-mode): more detail about the protocol and traffic flow of dVPN mode. +- [Mixnet Mode](/network/mixnet-mode): more detail about the protocol and traffic flow of Mixnet mode. +- [Cryptography](/network/cryptography): covers the underlying primitives (including zk-nyms). +- [Infrastructure](/network/infrastructure): blockchain and node architecture. +- [Reference](/network/reference): technical specifications. + +For building applications and integrating existing apps with the Mixnet, see the [Developer Documentation](/developers). + +If you wish to take part in the network as a Node Operator, see the [Operator Documentation](/operators/introduction). + +--- +title: Nym Network Overview +description: Introduction to the Nym Network, a privacy infrastructure that protects metadata including who communicates with whom, when, and how often. +url: https://nym.com/docs/network/overview +--- + +# Overview + +The Nym Network is a privacy infrastructure that protects metadata — not just message content, but who is talking to whom, when, and how often. This section explains what the network does, why it exists, and how it compares to other approaches. + +## In this section + +- [The Privacy Problem](/network/overview/privacy-problem) — what metadata is, why it matters, and what adversary models Nym is designed against +- [Two Modes: dVPN & Mixnet](/network/overview/two-modes) — how the two modes differ in architecture and privacy guarantees +- [Choosing a Mode](/network/overview/choosing-a-mode) — guidance on which mode fits your use case +- [Network Components](/network/overview/network-components) — Entry Gateways, Mix Nodes, Exit Gateways, and the Nyx blockchain +- [Nym vs Other Systems](/network/overview/comparisons) — how Nym compares to VPNs, Tor, I2P, and E2EE + +--- +title: The Privacy Problem: Why Metadata Matters +description: Why metadata exposure is a critical privacy threat, how adversaries exploit traffic patterns, and why traditional solutions like VPNs, Tor, and E2EE fall short. +url: https://nym.com/docs/network/overview/privacy-problem +--- + +# The Privacy Problem + +## Metadata is the message + +When you communicate over the internet, you can think of two types of information being transmitted: +- The **content** is the actual message, file, or data being sent. In the context of a messaging app, this is the contents of your message. In the context of something lower level, like an HTTP packet, this is the encrypted payload of the packet itself. +- The **metadata** is information about the communication itself, some of which can be gathered immediately, such as HTTP packets have headers which show the sending and receiving IP addresses (revealing which devices are communicating), timestamps, packet sizes hinting at what type of content and what connection type (e.g. the different [Maximum Transmission Units of different media](https://en.wikipedia.org/wiki/Maximum_transmission_unit#MTUs_for_common_media)), and some which is gathered over time, by finding patterns in large amounts of traffic, such as frequency patterns indicating how often parties interact. + +Traditional encryption like TLS and end-to-end-encryption (E2EE) protect content - this is what is often the [focus of media attention](https://wire.com/en/blog/whatsapp-end-to-end-encryption-risks). However, most solutions either don't protect from metadata analysis, or falsely purport to do so. + +Even without reading a single message, metadata alone is enough to reconstruct who you talk to, when, how often, and from where — which is why intelligence agencies treat it as seriously as content. As former NSA Director Michael Hayden put it: ["We kill people based on metadata."](https://committees.parliament.uk/writtenevidence/36962/html/) + +## The adversary models + +When using the **Mixnet mode** the Nym Network is designed to protect against **Global Passive Adversaries**—entities capable of observing traffic across the entire network simultaneously. This includes nation-state intelligence agencies, large corporations with extensive network infrastructure, ISPs, and collaborative adversaries sharing data. + +The assumption is that these adversaries can monitor all entry and exit points, correlate timing across the network, apply machine learning to traffic patterns, and conduct long-term statistical analysis. When Tor was first deployed in 2002, such attacks were considered science fiction. They are now documented reality. + +**dVPN mode** offers reduced protections against E2E surveillance and timing analysis, but still offers similar protections to Tor whilst offering increased speeds. + +## Why traditional solutions fall short + +**VPNs** concentrate trust in a single provider who can see all your traffic movements, can be legally or financially compelled to log, and whose payment systems (in most cases) link your account directly to your usage — so a VPN provider can be turned into a surveillance tool with a single court order or compromise. + +**Tor** was designed in an era when global passive adversaries were considered unrealistic. It routes traffic through three relays with onion encryption, but packets flow through without delays or cover traffic, which means an adversary who can observe both ends of a circuit can correlate timing to deanonymise users. These [correlation attacks](https://www.usenix.org/conference/usenixsecurity14/technical-sessions/presentation/johnson) were once theoretical — they are now [documented in practice](https://www.vice.com/en/article/timing-attack-tor-deanonymization/). + +## Nym's approach + +**dVPN mode** splits trust across two independent operators rather than concentrating it in one, and uses [zk-nym credentials](/network/cryptography/zk-nym) so that payment cannot be linked to usage — addressing the two biggest weaknesses of traditional VPNs. + +**Mixnet mode** goes further by adding packet mixing (reordering traffic to break timing correlation), cover traffic (a constant stream of dummy packets that hides when real communication is occurring), and uniform Sphinx packet sizes (preventing content-type fingerprinting) — addressing the timing analysis weakness that Tor and dVPN mode share. + +--- +title: Two Modes: dVPN and Mixnet +description: How NymVPN's two operating modes differ: dVPN mode for fast 2-hop routing, and Mixnet mode for 5-hop traffic mixing with timing obfuscation and cover traffic. +url: https://nym.com/docs/network/overview/two-modes +--- + +# Two Modes: dVPN and Mixnet + +NymVPN has two modes, each using the same underlying network infrastructure but handling traffic very differently. + +## dVPN mode + +dVPN mode routes traffic through 2 hops—an Entry Gateway and an Exit Gateway. Traffic flows from your device to the Entry Gateway, then to the Exit Gateway, then to the destination. + +``` +User --> Entry Gateway --> Exit Gateway --> Internet +``` + +This mode uses [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/), a WireGuard fork that adds traffic obfuscation to help evade some forms of protocol detection. It creates a tunnel between you and the Entry Gateway, which then creates another tunnel to the Exit Gateway. + +dVPN mode hides your IP from destination servers and splits knowledge between two independent operators—the Entry Gateway knows your IP but not your destination, while the Exit Gateway knows your destination but not your IP. However, it does not add timing delays or cover traffic. A sophisticated adversary observing both gateways could potentially correlate entry and exit timing. + +See [Choosing a Mode](/network/overview/choosing-a-mode) for when to use dVPN vs Mixnet. + +## Mixnet mode + +Mixnet mode routes traffic through 5 hops—an Entry Gateway, three layers of Mix Nodes, and an Exit Gateway. Each Mix Node adds a random delay and mixes your traffic with other packets passing through. + +``` +User --> Entry --> Mix L1 --> Mix L2 --> Mix L3 --> Exit --> Internet + | | | + delay delay delay + + + + + mixing mixing mixing +``` + +Beyond the additional hops, Mixnet mode generates constant cover traffic—dummy packets indistinguishable from real ones. This hides not just who you're communicating with, but when you're communicating. + +Latency is higher, typically 200-500ms additional, due to the mixing delays, but this is what makes timing correlation attacks impractical even for adversaries watching the entire network. + +For practical guidance on when to use each mode — and how developers access the network via SDKs — see [Choosing a Mode](/network/overview/choosing-a-mode). + +--- +title: Choosing Between dVPN and Mixnet Mode +description: When to use NymVPN's dVPN mode for low-latency browsing versus Mixnet mode for metadata protection against sophisticated adversaries. +url: https://nym.com/docs/network/overview/choosing-a-mode +--- + +# Choosing a Mode + +Both dVPN and Mixnet mode run on the same Nym infrastructure but protect against different things — dVPN keeps your IP hidden from destinations and splits trust across two operators, while Mixnet mode goes further by trying to make your traffic patterns invisible even to someone watching the entire network. + +## Quick comparison + +| | dVPN Mode | Mixnet Mode | +|---|---|---| +| **Hops** | 2 (Entry + Exit Gateway) | 5 (Entry + 3 Mix Nodes + Exit) | +| **Additional latency** | 50–150ms | 200–500ms | +| **Timing protection** | No | Yes (random delays per hop) | +| **Cover traffic** | No | Yes (constant dummy packets) | +| **Protects against** | ISPs, websites, advertisers, passive observers | Global passive adversaries, timing correlation, traffic analysis | +| **Access** | [NymVPN](https://nymvpn.com) | NymVPN and [Nym SDKs](/developers) | + +## Use dVPN mode when + +- You need low latency for browsing, streaming, or downloads +- Your adversaries are typical: ISPs monitoring traffic, websites tracking location, advertisers building profiles +- Speed matters more than protection against sophisticated traffic analysis +- You want the decentralization and payment privacy benefits of Nym without the latency cost of mixing + +## Use Mixnet mode when + +- Metadata protection is critical: journalism, activism, whistleblowing, legal consultations +- You face sophisticated adversaries who might monitor network traffic across multiple points +- You are willing to accept higher latency (200–500ms) for stronger privacy guarantees +- You need unlinkability and unobservability, not just IP hiding + +## For developers + +Developers using the [Nym SDKs](/developers) have access to **Mixnet mode only**—dVPN mode is specific to the NymVPN application. + +There are two integration models available via the SDKs: + +**As a proxy** (traffic exits to the internet): +``` +Your App --> Entry --> Mix Nodes --> Exit --> Internet +``` + +**End-to-end** (traffic stays within the Mixnet): +``` +Your App --> Entry --> Mix Nodes --> Exit --> Nym Client +``` + +The proxy model uses the Mixnet similarly to Tor's exit relay model, whereas the end-to-end model sends Sphinx packets the entire way. See the [integration overview](/developers/integrations) for more detail on choosing between these approaches. + +--- +title: Nym Network Components +description: Architecture of the Nym Network: Entry Gateways, Mix Nodes, Exit Gateways, the Nyx blockchain, and the Nym API for credential issuance. +url: https://nym.com/docs/network/overview/network-components +--- + +# Network Components + +The Nym Network is made up of traffic-routing nodes, a Cosmos SDK blockchain for coordination, and an API layer that handles credential issuance. + +## Nym Nodes + +All traffic-routing infrastructure runs on **Nym Nodes** — a single binary that can operate as an Entry Gateway, Mix Node, or Exit Gateway depending on configuration. + +**Entry Gateways** are the user's first point of contact. They accept client connections via WebSocket, verify zk-nym credentials to confirm payment, and store messages for clients that go offline (up to 24 hours). Entry Gateways know the client's IP address but cannot see message contents or final destinations. They will either create tunnels to Exit Gateways (dVPN mode) or forward Sphinx packets to the first layer of Mix Nodes (in Mixnet mode). + +**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 network. They communicate with external internet services on behalf of users and return responses through the network (dVPN and NymVPN mode), or forward Sphinx packets to receipient Nym Clients (SDK Mixnet mode). Like Tor exit nodes, they can see destination addresses but cannot identify the original sender. + +## Nyx Blockchain + +Nyx is a Cosmos SDK blockchain that provides coordination services. It maintains the topology registry—the list of active nodes and their public keys—eliminating the need for a centralized directory server. It manages NYM token staking and distributes rewards to node operators. It also hosts the CosmWasm smart contracts that coordinate the node rewarding and credential system. + +The blockchain is secured by validators using proof-of-stake consensus. Having the topology on-chain prevents the attacks that plague peer-to-peer directory systems. + +## Nym API + +Nyx validators operate **Nym API** [instances](/apis/nym-api) which provide cached blockchain state. A subset of these also form the "Quorum", handling credential issuance—generating the partial blind signatures that form [zk-nyms](/network/cryptography/zk-nym)- and zk-nym validation. + +Credential generation relies on threshold cryptography. No single member can issue credentials alone, and the system remains functional even if some members are offline. This distributes trust across multiple independent parties. See the [zk-nym docs](/network/cryptography/zk-nym) for more on this. + +## Decentralization properties + +The architecture aims to ensure no single point of compromise: +- Entry Gateways know your IP, but not your activity +- Mix Nodes process your packets but can't trace them +- Exit Gateways see destinations but not sources +- Nyx is decentralized via its validator set, and each member of the Quorum generates partial credentials which are unlinkable to anything + +--- +title: Nym vs VPNs, Tor, I2P, and E2EE +description: How the Nym Network compares to traditional VPNs, Tor, I2P, and end-to-end encryption in terms of privacy guarantees, metadata protection, and threat models. +url: https://nym.com/docs/network/overview/comparisons +--- + +# Nym vs Other Systems + +There are several existing approaches to network privacy, each with different assumptions about who the adversary is and what they can do. + +## Nym vs VPNs + +A traditional VPN creates an encrypted tunnel between your device and a VPN server, hiding your IP from destination websites and encrypting traffic from local observers like your ISP. The fundamental limitation is that the VPN provider itself can see all your traffic — every site you visit, when you visit it, how long you stay — and can log this voluntarily or be compelled to by legal process, with your payment information linking your account directly to your activity. + +Nym's dVPN mode splits this trust across two independent operators so that the Entry Gateway knows your IP but not your destination, the Exit Gateway knows your destination but not your IP, and neither can build a complete picture. Payment is handled through [zk-nyms](/network/cryptography/zk-nym), making subscriptions unlinkable to activity. + +Nym's mixnet mode goes further by adding timing obfuscation and cover traffic, which no traditional VPN offers — see [Mixnet Mode](/network/mixnet-mode) for how this works. + +## Nym vs Tor + +[Tor](https://www.torproject.org/) is the best-known anonymous overlay network, routing traffic through three relays using [onion encryption](https://spec.torproject.org/tor-spec/relay-cells.html) so that no single relay sees both source and destination. It was designed in an era when global passive adversaries were considered unrealistic, and its [architecture](https://2019.www.torproject.org/about/overview.html.en) reflects that — packets flow through without delays and there is no cover traffic, which means an adversary watching both ends of a circuit can [correlate timing](https://spec.torproject.org/tor-spec/threat-model.html) to deanonymise users. + +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 situations where the adversary is sophisticated enough to perform traffic analysis. + +## Nym vs I2P + +[I2P](https://geti2p.net/) replaces Tor's centralised directory authority with a [distributed hash table](https://geti2p.net/en/docs/how/network-database), which improves decentralisation but introduces its own attack surface — DHT-based routing is vulnerable to eclipse attacks and Sybil attacks on the routing table. Like Tor, I2P provides no timing protection, so packets flow without delays or cover traffic. + +Nym uses a blockchain-based topology registry rather than a DHT, which avoids the known attack vectors around DHT-based routing (e.g. eclipse attacks, Sybil attacks on the routing table). The mixing and cover traffic on top of that address the timing analysis gap that I2P shares with Tor. + +## Nym vs end-to-end encryption + +End-to-end encryption systems like [Signal](https://signal.org/docs/) encrypt messages on your device so that only the recipient can decrypt them, and the server never sees the content. But E2EE does nothing for metadata — the server still sees who you communicate with, when, how often, and how much, which on its own is enough to map relationships and infer sensitive activity. + +Nym and E2EE are complementary — E2EE protects message content, Nym protects the metadata around it (who, when, how much). Using Signal over the Nym mixnet, for instance, would protect both what you're saying and the fact that you're saying it. + +For a practical breakdown of when to use dVPN vs Mixnet mode, see [Choosing a Mode](/network/overview/choosing-a-mode). + +## Further reading + +- [What is WireGuard?](https://nym.com/blog/what-is-wireguard-vpn) +- [VPN Tunnels Explained](https://nym.com/blog/vpn-tunnels) +- [Tor Project: How Tor Works](https://2019.www.torproject.org/about/overview.html.en) +- [Tor Protocol Specification](https://spec.torproject.org/tor-spec/) +- [I2P: How It Works](https://geti2p.net/en/docs/how/tech-intro) + +--- +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. +url: https://nym.com/docs/network/dvpn-mode +--- + +# 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. + +## How it works + +Unlike traditional VPNs that route traffic through a single provider's server, dVPN mode routes traffic through two independent nodes operated by different parties. + +``` +User --> Entry Gateway --> Exit Gateway --> Internet +``` + +Your traffic is encrypted in layers—a tunnel inside a tunnel. The outer layer is encrypted to the Entry Gateway, and the inner layer is encrypted to the Exit Gateway. The Entry Gateway strips the outer layer and forwards the still-encrypted packet. The Exit Gateway strips the inner layer and sends it to the destination. Responses follow the reverse path. + +This "onion" model means neither gateway ever sees both your identity and your destination simultaneously. The Entry Gateway knows your IP address but cannot see your destination or message contents. The Exit Gateway knows your destination but cannot see your IP address. + +## Privacy guarantees + +dVPN mode hides your IP from destination servers and splits trust across two operators, but it does not add timing obfuscation or cover traffic — packets are forwarded immediately without delay, which means a sophisticated adversary observing both your Entry and Exit Gateways could correlate timing to link your requests. For protection against that kind of adversary, see [Mixnet Mode](/network/mixnet-mode). + +## Performance + +Latency is typically 50-150ms additional, comparable to traditional VPNs, since WireGuard handles encryption and reconnection without much overhead. + +For help deciding between dVPN and Mixnet mode, see [Choosing a Mode](/network/overview/choosing-a-mode). + +## Technical details + +- [dVPN Protocol](/network/dvpn-mode/protocol) — protocol stack and encryption details +- [Censorship Resistance](/network/dvpn-mode/censorship-resistance) — AmneziaWG and DPI evasion + +## Further reading + +- [Introducing AmneziaWG for NymVPN](https://nym.com/blog/introducing-amneziawg-for-nymvpn) — censorship resistance +- [What Is a Double VPN?](https://nym.com/blog/double-vpn) — multi-hop privacy explained +- [Building a Decentralized WireGuard VPN](https://nym.com/blog/building-decentralized-wireguard-vpn) — architecture decisions +- [What is NymVPN?](https://nym.com/blog/what-is-nymvpn) — general overview + +--- +title: dVPN Protocol Stack and Encryption +description: Technical details of Nym dVPN mode's protocol layers, including WireGuard tunnels, AES-GCM-SIV-256 layer encryption, and packet format tradeoffs. +url: https://nym.com/docs/network/dvpn-mode/protocol +--- + +# dVPN Protocol + +This page covers the technical details of dVPN mode's protocol stack and encryption. + +## Protocol layers + +dVPN mode combines WireGuard with additional layer encryption. The client-to-Entry Gateway connection uses WireGuard, providing fast handshakes, efficient encryption, and graceful reconnection. The Entry-to-Exit Gateway connection adds another encryption layer using AES-GCM-SIV-256. + +``` ++-----------------------------------------+ +| Application Data | ++-----------------------------------------+ +| Layer Encryption (Entry -> Exit) | ++-----------------------------------------+ +| WireGuard (Client -> Entry) | ++-----------------------------------------+ +| UDP/IP | ++-----------------------------------------+ +``` + +## Encryption + +The WireGuard layer uses Curve25519 for key exchange, ChaCha20-Poly1305 for symmetric encryption, and BLAKE2s for hashing. This provides 256-bit security with modern, well-audited primitives. + +The inner layer uses AES-GCM-SIV-256, an authenticated encryption scheme with nonce-misuse resistance. Even if a nonce is accidentally reused, the scheme degrades gracefully rather than catastrophically. Keys are derived through ECDH between the client and Exit Gateway, with separate keys for each direction. + +## Packet format + +dVPN mode uses standard WireGuard packet framing — packets are not padded to a uniform size. This means 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 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. + +## Connection lifecycle + +When connecting, the client first selects Entry and Exit Gateways based on latency, location preference, or random selection. It then presents a zk-nym credential to the Entry Gateway for anonymous authentication. The credential proves payment without revealing identity—it's re-randomized for each connection and cannot be linked to previous usage. + +Once authenticated, the client establishes a WireGuard tunnel to the Entry Gateway, which establishes a link to the Exit Gateway. Traffic then flows through both hops until the session ends. + +## Security properties + +The protocol provides forward secrecy—new session keys are derived for each connection, so compromising long-term keys doesn't expose past sessions. WireGuard's key rotation provides additional forward secrecy within sessions. + +The split-knowledge architecture ensures the Entry Gateway knows your IP but not your destinations or payload content, while the Exit Gateway knows your destinations but not your IP. Neither can correlate the two. + +Replay protection comes from WireGuard's counter-based mechanism and from zk-nym serial numbers that prevent credential reuse. + +## Relationship to mixnet mode + +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, 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. + +This shared infrastructure means improvements to Gateways and credentials benefit both modes. + +--- +title: Censorship Resistance in dVPN Mode +description: How AmneziaWG obfuscation, QUIC transport mode, and Stealth API Connect help Nym dVPN users evade deep packet inspection and protocol blocking. +url: https://nym.com/docs/network/dvpn-mode/censorship-resistance +--- + +# Censorship Resistance + +dVPN mode incorporates several techniques to help users connect in restrictive network environments where VPN protocols are actively detected and blocked. + +## The problem: protocol fingerprinting + +Deep Packet Inspection (DPI) systems deployed by ISPs and governments can identify VPN protocols by their handshake patterns, packet sizes, and timing characteristics. Standard WireGuard, for instance, has a recognisable handshake initiation pattern that DPI rules can match against. Once identified, connections can be throttled or blocked entirely. + +This is not a theoretical concern — countries including China, Russia, Iran, and others actively deploy DPI to restrict VPN usage. + +## AmneziaWG + +dVPN mode uses [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/), a fork of WireGuard that adds obfuscation techniques to make the protocol harder to fingerprint. + +AmneziaWG modifies the WireGuard handshake by introducing decoy packets before the handshake initiation. These decoy packets disrupt DPI rules that rely on matching the standard WireGuard handshake sequence. The actual WireGuard protocol behaviour is preserved — the modifications sit around the handshake rather than replacing it, so all of WireGuard's security properties (Curve25519 key exchange, ChaCha20-Poly1305 encryption, forward secrecy) remain intact. + +## Limitations + +AmneziaWG raises the bar for censors relying on simple protocol fingerprinting, but it doesn't help against deeper analysis — statistical fingerprinting of packet timing and sizes, IP-based blocking of known Gateway addresses, or active probing where the censor sends packets to suspected VPN servers to confirm their identity. + +## QUIC transport mode + +QUIC transport mode wraps the WireGuard/AmneziaWG connection inside a [QUIC](https://datatracker.ietf.org/doc/html/rfc9000) layer, so the traffic looks like standard HTTPS/HTTP3 to DPI systems rather than a VPN tunnel. Since QUIC is now used by a significant portion of regular web traffic (over 30% of Cloudflare's traffic in 2023 was HTTP/3 over QUIC), blocking it outright would break large parts of the web for everyone, making it an unattractive target for censors. + +QUIC transport applies to the Entry Gateway connection only (the first hop). Not all Gateways support it yet — enabling QUIC in the NymVPN app will filter the Gateway list to those that do. Because the QUIC wrapper adds overhead, it can reduce speeds slightly, so it's worth leaving disabled unless you're in a censored environment or having connectivity issues. + +## Stealth API Connect + +Even if a user can establish a VPN tunnel, censors can also block access to the API that the NymVPN app needs to discover Gateways and fetch network state in the first place. Stealth API Connect addresses this by routing the app's API requests through a mechanism that is harder to identify and block, so the app can bootstrap its connection to the Nym network even in environments where the Nym API endpoints are actively censored. + +## Limitations + +These techniques are layered — AmneziaWG obfuscates the handshake, QUIC disguises the tunnel as regular web traffic, and Stealth API Connect protects the initial API discovery. Together they cover several common censorship methods, but none of them are guarantees. Censorship resistance is an ongoing arms race, and new techniques will be documented here as they ship. + +## Further reading + +- [Introducing AmneziaWG for NymVPN](https://nym.com/blog/introducing-amneziawg-for-nymvpn) +- [AmneziaWG documentation](https://docs.amnezia.org/documentation/amnezia-wg/) +- [What is QUIC? Censorship-Resistant Internet Connections](https://nym.com/blog/what-is-quic) +- [What is QUIC transport mode in NymVPN?](https://support.nym.com/hc/en-us/articles/39648047741457-What-is-QUIC-transport-mode-in-NymVPN) +- [What is Stealth API Connect in NymVPN?](https://support.nym.com/hc/en-us/articles/39652289741329-What-is-Stealth-API-connect-in-NymVPN) +- [NymVPN's roadmap for censorship resistance](https://nym.com/blog/NymVPN-Roadmap-for-censorship-resistance-2025) + +--- +title: Mixnet Mode +description: How Nym's Mixnet mode works: 5-hop routing through Mix Nodes with random delays, packet reordering, and cover traffic for unlinkability and unobservability. +url: https://nym.com/docs/network/mixnet-mode +--- + +# Mixnet Mode + +Mixnet mode routes traffic through 5 hops — an Entry Gateway, three layers of Mix Nodes, and an Exit Gateway — with random delays, packet reordering, and cover traffic at each mixing layer. It is available through [NymVPN](https://nymvpn.com) and the [Nym SDKs](/developers). + +## How it works + +Traffic passes through five hops: an Entry Gateway, three layers of Mix Nodes, and an Exit Gateway. Each Mix Node adds a random delay before forwarding, mixing your packets with others passing through. + +``` +User --> Entry --> Mix L1 --> Mix L2 --> Mix L3 --> Exit --> Internet + | | | + delay delay delay +``` + +Beyond the additional hops, mixnet mode generates constant cover traffic—dummy packets indistinguishable from real ones. Your client continuously sends packets into the network whether or not you're actively communicating. Real messages are slotted into this stream of cover traffic. + +The client constructs Sphinx packets with layered encryption. Each layer contains routing information for one hop plus the inner encrypted packet. As the packet travels through the network, each node removes its layer to learn the next destination, but cannot see the final destination or payload content. + +## Privacy properties + +The combination of mixing, delays, and cover traffic gives the mixnet three properties that simpler systems like VPNs and Tor don't have: + +- **Unlinkability**: an observer watching a Mix Node cannot correlate incoming packets with outgoing ones, cannot connect successive packets from the same user, and cannot link activity across different sessions — the random delays and reordering destroy the timing signal that makes this possible in other networks. +- **Unobservability**: because your client sends a constant stream of cover traffic whether or not you're actually communicating, an observer cannot tell when real communication is occurring, how much of the traffic is real versus dummy, or even whether a given user is active at all. +- **Resistance to traffic analysis**: uniform Sphinx packet sizes prevent content-type fingerprinting, per-packet routing means there are no long-lived circuits to observe (unlike Tor), and the mixing delays mean that even an adversary watching the entire network cannot correlate entry and exit timing. + +## Performance + +Latency is higher than dVPN mode, typically 200-500ms additional, due to the mixing delays at each of the three Mix Node layers. This is the cost of timing obfuscation. For most messaging applications, this latency is acceptable. For real-time applications like video calls, dVPN mode may be more appropriate. + +For help deciding between dVPN and Mixnet mode, see [Choosing a Mode](/network/overview/choosing-a-mode). + +## Further reading + +The following pages cover mixnet internals in detail: + +- [Loopix Design](/network/mixnet-mode/loopix) explains the academic foundation +- [Traffic Flow](/network/mixnet-mode/traffic-flow) shows the packet journey with diagrams +- [Cover Traffic](/network/mixnet-mode/cover-traffic) explains how dummy packets provide unobservability +- [Packet Mixing](/network/mixnet-mode/mixing) covers timing delays and their importance +- [Anonymous Replies](/network/mixnet-mode/anonymous-replies) describes SURBs for bidirectional communication + +--- +title: Loopix Design +description: The academic Loopix mixnet design behind Nym: stratified topology, continuous-time mixing with exponential delays, and cover traffic loops for unlinkability and unobservability. +url: https://nym.com/docs/network/mixnet-mode/loopix +--- + +# Loopix Design + +The Nym mixnet is based on the [Loopix](https://arxiv.org/pdf/1703.00536) academic design, with modifications for decentralized operation and economic incentives. + +## The insight + +Traditional mixnets focus on hiding "who messages whom"—but this alone is insufficient. Adversaries observing message volume and timing over time can still infer private information. If you always message the same friend at the same time, patterns emerge. If you go silent when traveling, that's information too. + +Loopix was designed to provide both **unlinkability** (hiding who talks to whom) and **unobservability** (hiding when and how much communication occurs). The name comes from its use of "loop" cover traffic that circulates through the network. + +## Stratified topology + +The network uses a layered architecture. Traffic flows through Entry Gateways, three Mix Node layers, and Exit Gateways. Each node connects only to adjacent layers. Path selection is independent per-message, unlike Tor's per-session circuits. + +This structure prevents observations about which paths are used together and limits the damage any single compromised node can cause. + +## Continuous-time mixing + +Unlike batch mixnets that collect messages and release them periodically, Loopix uses continuous-time mixing. Each message is delayed independently according to an exponential distribution, then forwarded as soon as its delay expires. + +This approach offers optimal anonymity for a given mean latency. The exponential distribution has a key property: if two messages arrive at different times, they have equal probability of leaving in either order. An adversary watching input and output timing gains no information about which input became which output. + +Continuous mixing also means lower latency overall since messages don't wait for batches to fill. + +## Cover traffic loops + +Connected clients and nodes continuously generate dummy packets that travel in loops through the network back to the sender. These packets are indistinguishable from real traffic—same size, same encryption, same timing distribution. + +Loop traffic ensures minimum anonymity even when few users are active. It hides when real communication starts and stops. And it can detect active attacks: if your loop packets don't return, something is interfering with the network. + +## Nym's modifications + +The Nym implementation extends Loopix in several ways. The original design assumed a trusted directory server; Nym uses the Nyx blockchain for decentralized topology management. The original relied on volunteers; Nym provides NYM token rewards to ensure sustainable operation. And Nym adds zk-nyms for privacy-preserving payment—something the original academic design didn't address. + +## Security guarantees + +The combination of continuous-time mixing and cover traffic provides provable guarantees. The anonymity set—the set of users who could have sent a given message—grows unboundedly over time. Even messages with short delays have large anonymity sets because of the exponential distribution. + +An adversary observing the entire network cannot determine who is communicating with whom. They cannot tell when real communication is occurring. And statistical analysis provides no advantage because the traffic patterns are designed to be indistinguishable from random. + +For the full formal analysis, see the [Loopix paper](https://arxiv.org/pdf/1703.00536) and the [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf). + +--- +title: Traffic Flow +url: https://nym.com/docs/network/mixnet-mode/traffic-flow +--- + +# Traffic Flow + +This page walks through how packets travel through the mixnet, from sending client to destination. + +This describes the 5-hop mixnet flow. For the 2-hop dVPN mode, see [dVPN Protocol](/network/dvpn-mode/protocol). + +## Overview + +The Nym mixnet uses source routing—the sender chooses the complete route before sending. This means the sender constructs a Sphinx packet with layered encryption, where each layer contains routing information for one hop. + +## Client to Entry Gateway + +When you connect, your Nym client registers with a particular Entry Gateway. This becomes part of your Nym address and is where your incoming messages are delivered. + +The client continuously sends packets to the Entry Gateway over a WebSocket connection. This stream includes both real messages and cover traffic at a constant rate. When you have data to send, it's encrypted as Sphinx packets and slotted into the stream. When you don't, cover packets flow instead. + +```mermaid +sequenceDiagram + box Local Machine + participant App as Application + participant Client as Nym Client + end + participant Gateway as Entry Gateway + + Gateway->>Client: Key Exchange + + loop Continuous Traffic + Client->>Gateway: Cover packet + Client->>Gateway: Cover packet + App-->>Client: Data to send + Client->>Client: Encrypt as Sphinx + Client->>Gateway: Real packet + Client->>Gateway: Cover packet + end +``` + +## Through the Mix Nodes + +The Entry Gateway forwards packets into the three Mix Node layers. At each hop, the node decrypts its layer of the Sphinx packet to learn the next destination, verifies the HMAC to ensure integrity, applies a random delay, and forwards to the next hop. + +The delay is critical. Without it, timing would correlate inputs to outputs. With exponential random delays, packets are reordered and the timing relationship is destroyed. + +```mermaid +--- +config: + theme: neo-dark +--- +sequenceDiagram + participant Entry as Entry Gateway + participant M1 as Mix Layer 1 + participant M2 as Mix Layer 2 + participant M3 as Mix Layer 3 + participant Exit as Exit Gateway + + Entry->>M1: Sphinx Packet + M1->>M1: Decrypt layer + M1->>M1: Verify HMAC + M1->>M1: Random delay + M1->>M2: Sphinx Packet + M2->>M2: Decrypt layer + M2->>M2: Verify HMAC + M2->>M2: Random delay + M2->>M3: Sphinx Packet + M3->>M3: Decrypt layer + M3->>M3: Verify HMAC + M3->>M3: Random delay + M3->>Exit: Sphinx Packet +``` + +## Exit Gateway to Destination + +The Exit Gateway handles the final hop. For traffic destined for external services, it decrypts the packet and forwards to the destination, then packages responses back into Sphinx packets for the return journey. + +For traffic destined for another Nym client, the Exit Gateway delivers to that client's registered Gateway, which holds the message until the recipient comes online. + +## The complete picture + +Putting it together, a packet travels through five hops with encryption removed and delays applied at each Mix Node layer: + +```mermaid +--- +config: + theme: neo-dark +--- +sequenceDiagram + box Sender + participant App1 as Application + participant C1 as Nym Client + end + + box Mixnet + participant Entry as Entry GW + participant M1 as Mix L1 + participant M2 as Mix L2 + participant M3 as Mix L3 + participant Exit as Exit GW + end + + box Receiver + participant C2 as Nym Client + participant App2 as Application + end + + App1->>C1: Send data + C1->>C1: Create Sphinx packet + C1->>Entry: Encrypted packet + Entry->>M1: Forward + M1->>M1: Decrypt, delay + M1->>M2: Forward + M2->>M2: Decrypt, delay + M2->>M3: Forward + M3->>M3: Decrypt, delay + M3->>Exit: Forward + Exit->>C2: Deliver + C2->>C2: Decrypt final layer + C2->>App2: Received data +``` + +## External services + +When sending to an external service rather than another Nym client, the Exit Gateway acts as a proxy. It extracts the destination from the decrypted packet, makes the request on your behalf, and routes responses back through the network. The destination service sees the Exit Gateway's IP, not yours. + +## Peer-to-peer + +For applications where all parties run Nym clients, traffic stays entirely within the mixnet. Both sides enjoy full privacy protection, and [SURBs](/network/mixnet-mode/anonymous-replies) enable anonymous bidirectional communication without either party learning the other's address. + +--- +title: Cover Traffic +description: How constant dummy packet streams hide real communication patterns in the Nym mixnet, achieving unobservability even against global network observers. +url: https://nym.com/docs/network/mixnet-mode/cover-traffic +--- + +# Cover Traffic + +Cover traffic is dummy packets that hide when real communication is occurring. It's a fundamental mechanism for achieving **unobservability**. + +## The problem + +Even with perfect encryption and mixing, traffic analysis can reveal information. An adversary can see how much data you're sending, when you're sending it, and detect patterns over time. Regular silence followed by bursts of activity reveals your schedule. Consistent traffic volumes to certain destinations reveal ongoing relationships. + +## The solution + +Cover traffic maintains a constant rate of packet transmission. When you have real data to send, it replaces a cover packet in the stream. When you have nothing to send, cover packets flow anyway. To an observer, the traffic looks identical either way. + +``` +Without cover traffic: + | ||| | +Time ---------+---------+++---------+------> + Idle Activity Idle + (visible) + +With cover traffic: + |||||||||||||||||||||||||||||||||||||| +Time --------------------------------------> + Constant rate (activity hidden) +``` + +The cover packets are real Sphinx packets with valid encryption—just empty payloads. They travel through the network exactly like real packets, get mixed at each hop, and are discarded at their destination. No node along the way can tell whether a packet contains real data or is cover traffic. + +## Loop traffic + +Cover packets follow complete routes through the network back to the sender. These "loops" serve multiple purposes: they test that network routes are functioning, they provide traffic for mixing with others' cover traffic, and they can detect active attacks. If your loop packets stop returning, something is wrong. + +Mix nodes also generate their own cover traffic, ensuring minimum traffic levels even when few users are active. This provides baseline anonymity guarantees regardless of network load. + +## How it's generated + +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 + +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. + +The default parameters balance privacy and resource usage. Applications with heightened privacy requirements can increase the cover traffic rate; applications where unobservability is less critical can reduce it. + +## What cover traffic defeats + +Cover traffic prevents volume analysis (how much you communicate), timing analysis (when you communicate), presence detection (whether you're online), and behavioral profiling (your communication patterns over time). Combined with packet mixing, it ensures that even an adversary watching the entire network learns nothing about your communication behavior. + +--- +title: Packet Mixing and Random Delays +description: How Mix Nodes use exponential random delays to reorder packets and break timing correlations, preventing traffic analysis by network observers. +url: https://nym.com/docs/network/mixnet-mode/mixing +--- + +# Packet Mixing + +Packet mixing breaks timing correlations by adding random delays at each Mix Node. It's the core mechanism that prevents traffic analysis. + +## The problem + +Without mixing, an observer watching a node could correlate inputs and outputs. A packet arriving at time t₀ and a packet leaving at time t₀ + δ are obviously related. Even with encryption hiding contents, the timing relationship reveals which input became which output. + +## The solution + +Each Mix Node adds a random delay before forwarding. Packets don't flow through in order—they're held for variable times and released in a different sequence than they arrived. An observer sees packets going in and packets coming out, but cannot match them. + +``` +Input sequence: A B C D E + | | | | | + v v v v v + [ Mixing ] + | | | | | + v v v v v +Output sequence: C A E B D +``` + +The delays follow an exponential distribution. This choice is mathematically optimal: if two packets arrive at times t₀ and t₁, they have equal probability of leaving in either order, regardless of when they arrived. The adversary gains no information from timing observations. + +## Why exponential delays + +The exponential distribution is "memoryless"—the probability of a packet leaving in the next moment doesn't depend on how long it's already waited. This means the adversary cannot narrow down possibilities by noting how long packets have been in the node. + +Any other delay distribution leaks information. Fixed delays would let adversaries match arrivals to departures by timing. Uniform distributions would create windows where matches become more likely. The exponential distribution maximizes uncertainty. + +## Continuous vs batch mixing + +Older mixnet designs collected packets into batches and shuffled them before release. This has problems: latency is unpredictable since you wait for batches to fill, bandwidth is inefficient due to bursty traffic, and the anonymity set is limited to the batch size. + +Continuous-time mixing processes each packet independently. Latency is predictable (the mean delay is configurable). Bandwidth is used efficiently. And the anonymity set is unbounded—it includes all packets that have ever passed through, weighted by time. + +## The aggregate effect + +With three Mix Node layers, each applying random delays, the overall effect is thorough reordering. Packets entering the mixnet in sequence exit in a completely different order. The timing relationship between sending and receiving is destroyed. + +This is why mixnet mode has higher latency than dVPN mode. The delays are the price of timing protection. Mean delays of 50-100ms per hop add up to 150-300ms average across three layers—noticeable, but worth it for the privacy gain. + +## Combined with cover traffic + +Mixing and cover traffic work together. Cover traffic ensures there's always packets to mix, even during low activity. Mixing ensures that real and cover packets become interleaved and indistinguishable. Neither mechanism alone is sufficient—together they provide both unlinkability and unobservability. + +--- +title: Anonymous Replies with SURBs +description: How Single Use Reply Blocks (SURBs) enable anonymous bidirectional communication in the Nym mixnet without revealing the sender's address. +url: https://nym.com/docs/network/mixnet-mode/anonymous-replies +--- + +# Anonymous Replies + +SURBs (Single Use Reply Blocks) enable anonymous bidirectional communication. A receiver can reply to a sender without learning the sender's identity or address. + +## The problem + +In a typical mixnet scenario, Alice sends a message to Bob and wants a reply. If Bob sends directly to Alice's Nym address, he learns it. This defeats the purpose of anonymous communication—Bob now knows Alice's identity for future contact. + +## How SURBs work + +Alice creates SURBs—encrypted routing headers—and includes them with her message to Bob. Each SURB contains a complete route back to Alice, encrypted so that Bob cannot read it. Bob attaches his reply to a SURB and sends the resulting packet into the mixnet. It travels through the encoded route and arrives at Alice, but Bob never learns where it went. + +A SURB contains the address of the first hop (Alice's Entry Gateway), encrypted routing headers for the path back to Alice, and a key to encrypt the reply payload. The routing headers are layered like a Sphinx packet—each hop can only see the next destination. + +## Single use + +Each SURB can only be used once. This prevents replay attacks and ensures forward secrecy. For conversations requiring multiple exchanges, Alice sends multiple SURBs with her initial message. + +SURB validity is tied to key rotation. Node keys rotate on an odd/even schedule with a default validity of 24 epochs (roughly 25 hours at the current 1-hour epoch length). After that window, the routing keys a SURB was built with are no longer accepted. Clients automatically purge stale SURBs and request fresh ones. Reply keys also expire after 24 hours independently of rotation cycles. + +## 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. + +```mermaid +--- +config: + theme: neo-dark +--- +sequenceDiagram + participant Alice + participant Mixnet + participant Bob + + Alice->>Mixnet: Message + 5 SURBs + Mixnet->>Bob: Message + 5 SURBs + Bob->>Bob: Reply needs 10 SURBs + Bob->>Mixnet: "Need more SURBs" (uses 1 SURB) + Mixnet->>Alice: SURB request + Alice->>Mixnet: 10 more SURBs + Mixnet->>Bob: Additional SURBs + Bob->>Mixnet: Reply (uses SURBs) + Mixnet->>Alice: Reply received +``` + +## 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. + +## Security considerations + +There's a known attack where a malicious receiver hoards SURBs and sends them all back simultaneously, attempting to correlate traffic patterns at the sender's Gateway. This attack requires active participation (not just passive observation), costs money once zk-nyms are enabled, and provides limited information even if successful. It's not a passive surveillance technique—the attacker must be specifically targeting you and willing to spend resources. + +## Comparison to Tor onion addresses + +Tor's onion addresses allow indefinite replies but require the recipient to run a hidden service. SURBs are single-use but require no service—they're generated on-demand per message. SURBs also benefit from the mixnet's timing protection, which onion addresses don't have. + +--- +title: Nym Network Cryptography +description: Overview of the cryptographic systems powering Nym: transport encryption, Sphinx packet format, per-hop encryption, and zk-nym anonymous credentials. +url: https://nym.com/docs/network/cryptography +--- + +# Cryptography + +The Nym Network relies on several cryptographic systems working together. This section covers the algorithms, packet formats, and credential systems that provide privacy guarantees. + +## Defense in depth + +There isn't a single cryptographic scheme protecting traffic — transport encryption secures connections between nodes, Sphinx packets add per-hop encryption so each node only learns where to forward rather than the full route, the payload itself is encrypted end-to-end, and zk-nyms keep payment separate from usage. + +## What's covered + +[Encryption Standards](/network/cryptography/encryption-standards) documents the specific algorithms used throughout the network—Curve25519 for key exchange, AES and ChaCha20 for symmetric encryption, Lioness for wide-block encryption in Sphinx payloads. + +[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. + +[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. + +--- +title: Encryption Standards Used in Nym +description: Cryptographic algorithms used across the Nym Network: Curve25519 key exchange, ChaCha20-Poly1305, AES-GCM-SIV, Lioness wide-block encryption, Noise protocol, and post-quantum KEM. +url: https://nym.com/docs/network/cryptography/encryption-standards +--- + +# Encryption Standards + +This page documents the cryptographic algorithms used throughout the Nym Network. + +## Key exchange + +All key exchanges use **Curve25519** via X25519. This elliptic curve provides 128-bit security with fast, constant-time implementations and compact 32-byte keys. Nym uses it for Sphinx packet key derivation (ECDH with each hop), Gateway authentication, WireGuard tunnel handshakes, and session key establishment. + +Digital signatures use **Ed25519**, the signature scheme built on Curve25519. Node identity keys, client authentication, and QUIC TLS certificate verification all use Ed25519 signatures. + +## Authenticated encryption + +**ChaCha20-Poly1305** is the primary authenticated encryption scheme. It encrypts all WireGuard data packets in dVPN mode (via the `boringtun` and `wireguard-go` implementations), and is used in the Noise protocol handshakes and the OutFox packet format. It provides 256-bit security with authentication and performs well on devices without AES hardware acceleration. + +**AES-GCM-SIV-256** is used for Gateway-client shared key encryption (protocol version 3+). The SIV (Synthetic Initialization Vector) construction degrades gracefully if a nonce is accidentally reused — important in distributed systems where nonce management is harder. + +**AES-CTR-128** is used in Sphinx header encryption, where the stream cipher combines with blinding factors to create the layered encryption that each mix node peels away. + +## Node authentication + +The **Noise protocol** framework (via the `snow` crate) provides authenticated key exchange between nodes. Two cipher suites are in use: + +- `Noise_XKpsk3_25519_AESGCM_SHA256` +- `Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s` + +These provide mutual authentication, forward secrecy, and resistance to key-compromise impersonation. + +## Wide-block encryption + +**Lioness** is a wide-block cipher used for Sphinx packet payloads. It's constructed from ChaCha20 and BLAKE2, encrypting the entire payload as a single block. This property is essential for Sphinx: modifying any part of the payload invalidates the entire payload, preventing certain manipulation attacks. + +The Lioness implementation is part of the external [`sphinx-packet`](https://github.com/nymtech/sphinx) crate used by Nym. + +## Hashing + +**BLAKE2** variants are used in the WireGuard Noise handshake (BLAKE2s) and in Lioness payload encryption (BLAKE2b via the sphinx-packet crate). + +**BLAKE3** is used for modern key derivation in the KKT protocol and data observatory components. + +**SHA-256** and **SHA-512** appear where compatibility with Cosmos SDK, HKDF, and standard tooling is required. + +## Key derivation + +**HKDF** (HMAC-based Key Derivation Function, RFC 5869) derives session keys from shared secrets. Both HKDF-SHA-256 and HKDF-SHA-512 variants are used, with HKDF-SHA-512 as the primary variant for `DerivationMaterial` in the SDK. + +**Argon2** is used for password-based key derivation when protecting locally stored keys and credentials. + +## Wallet cryptography + +**Secp256k1** (via the `k256` crate) and **ECDSA** handle transaction signing and key management for the Nyx blockchain, consistent with Cosmos SDK conventions. **BIP32** hierarchical deterministic key derivation supports hardware wallet integration via Ledger. + +## zk-nym cryptography + +The credential system uses **BLS12-381**, a pairing-friendly elliptic curve that enables threshold signatures, signature aggregation, and zero-knowledge proofs. The Nym API Quorum uses BLS for distributed key generation and threshold blind signatures. + +**Pedersen commitments** hide attribute values in credentials while allowing verification. **Zero-knowledge proofs** enable selective disclosure — proving properties about credentials without revealing the credentials themselves. + +## Post-quantum cryptography (in progress) + +The classical algorithms used today (Curve25519, BLS12-381) would be vulnerable to a sufficiently powerful quantum computer. Work is underway in the **KKT** (Key KEM Transport) module to add hybrid post-quantum key encapsulation using two NIST-standardised or finalist algorithms: + +- **ML-KEM** (formerly CRYSTALS-Kyber) — a lattice-based KEM, now a NIST standard (FIPS 203) +- **Classic McEliece** — a code-based KEM with decades of cryptanalysis behind it + +Both are available via the `libcrux` cryptographic library. The hybrid construction pairs these with classical X25519, so the system remains secure even if one primitive is broken. Post-quantum support will ship as part of the Lewes Protocol, which is currently in development. + +## References + +- [Sphinx paper](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) — Original Sphinx specification +- [Coconut paper](https://arxiv.org/pdf/1802.07344) — Credential scheme foundation +- [Offline Ecash paper](https://arxiv.org/pdf/2303.08221) — Compact ecash construction +- [WireGuard protocol](https://www.wireguard.com/protocol/) — dVPN tunnel specification +- [Noise protocol](http://www.noiseprotocol.org/) — Authenticated key exchange framework +- [Nym Whitepaper](https://nym.com/nym-whitepaper.pdf) — Full protocol description +- [Nym Trust Center: Cryptography](https://nym.com/trust-center/cryptography) — Up-to-date cryptographic overview + +--- +title: Sphinx Packet Format +description: How Sphinx packets provide layered encryption for anonymous mixnet routing, with fixed-size payloads, per-hop key derivation, and integrity verification via HMACs. +url: https://nym.com/docs/network/cryptography/sphinx +--- + +# Sphinx Packets + +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. + +## How Sphinx works + +When a client sends a message through the mixnet, it constructs a Sphinx packet with multiple encryption layers—one for each hop in the route. The outermost layer is encrypted for the first hop (Entry Gateway), the next layer for the second hop (Mix Node Layer 1), and so on until the innermost layer contains the actual payload encrypted for the recipient. + +At each hop, the node uses its private key to decrypt its layer, revealing the address of the next hop and a new Sphinx packet to forward. The node cannot see any other routing information or the payload contents. + +## Packet structure + +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. + +## Integrity verification + +Each layer includes an HMAC (Hash-based Message Authentication Code) that the receiving node verifies before processing. This prevents malicious nodes from modifying packet contents en route. If the HMAC doesn't match, the packet is dropped. + +The payload uses Lioness wide-block encryption, which means any modification to any part of the payload invalidates the entire payload. This prevents bit-flipping attacks where an adversary might try to modify specific bytes. + +## Key derivation + +For each hop, the client performs an ECDH key exchange using the node's public key and an ephemeral key embedded in the packet header. This shared secret is then used with HKDF to derive the symmetric keys for that layer's encryption and HMAC. + +The ephemeral key is "blinded" at each hop so that successive nodes cannot correlate packets by the key value. Each node sees a different ephemeral key even though they're mathematically related. + +## Message fragmentation + +Messages larger than a single Sphinx payload are split into fragments. Each fragment travels independently through the network, potentially taking different routes. The recipient reassembles the fragments into the original message. + +## External implementation + +Nym uses the [`sphinx-packet`](https://github.com/nymtech/sphinx) crate for core Sphinx operations. This crate handles packet construction, header encryption, layer processing, and the mathematical operations for key blinding. + +## References + +- [Sphinx paper](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) — Original specification and security proofs +- [Elle Mouton's Sphinx explainer](https://ellemouton.com/posts/sphinx/) — Detailed walkthrough of packet construction +- [Nym Whitepaper §4](https://nym.com/nym-whitepaper.pdf) — Sphinx in the context of Nym + +--- +title: What are zk-nyms? +url: https://nym.com/docs/network/cryptography/zk-nym +--- + +# What are zk-nyms? + +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. + +> 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. + +## Motivations +Most of the time, when we build system security, we think of _who_ questions: + +- Has Alice identified herself (authentication)? +- Is Alice allowed to take a specific action (authorisation)? + +However, _who_ is not necessarily a question we want to be asking when designing a system with anonymous access control. This scheme allows us to instead consider questions of _rights_, namely: + +- Does the entity taking this action have a right to do _X_? + +This allows a different kind of security. Many of the computer systems we talk to every day don't need to know _who we are_, they only need to know if the entity kicking off a request has the _right to use_ the system. + +The zk-nym scheme allows for this move to take place. Credentials are generated cooperatively by decentralised, trustless systems, and once the credentials are generated, they can be _re-randomized_; entirely new credentials, which no one has ever seen before, can be presented to the ingress point of the Nym Network, and validated without being linkable back to the signatures produced by the Quorum of credential signers used to generate them, or any credentials previously used by an entity wanting access. These properties allow zk-nyms to act as something like cryptographic bearer tokens generated by decentralised systems. The tokens can be mutated so that they are not traceable, but still verified with the original permissions intact. + +> TL;DR: Users present cryptographic claims encoded inside the credentials to get secure access to resources despite the systems verifying credential usage not being able to know who they are. + +### 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. + +**zk-nyms allows us to build truly private systems rather than pseudonymous ones.** + +## Features +Specifically, zk-nym is an implementation of a blinded, re-randomizable, selective disclosure threshold credential signature scheme. + +Let's say you have a `message` with the content `This credential controls X` in hand. In addition to the normal `sign(message, secretKey)` and `verify(message, publicKey)` functions present in other signature schemes like RSA, the zk-nym credential scheme adds the following: + +1. _[Blind signatures](https://en.wikipedia.org/wiki/Blind_signature)_ - disguises message content so that the signer can't see what they're signing. This defends users against signers: the entity that signed can't identify the user who created a given credential, since they've never seen the message they're signing before it's been _blinded_ (turned into seemingly random binary data). The scheme uses zero-knowledge proofs so that the signer can sign confidently without seeing the unblinded content of the message. + +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. + +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). + +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, you might think of it as a sort of supercharged decentralized privacy-friendly [JWT](https://jwt.io/). + +--- +title: Generating and using zk-nym anonymous credentials +url: https://nym.com/docs/network/cryptography/zk-nym/zk-nym-overview +--- + +# Generating and using zk-nym anonymous credentials + + The first use-case of zk-nyms is for anonymously proving the right to use the Nym mixnet for privacy. + + The Nym mixnet is - at the time of publication - free for everyone. However, soon™ it will be required for each connecting client to present a valid credential - a zk-nym - to their ingress Gateway to access the Mixnet. + + Accessing zk-nym credentials will vary depending on use: + - Individual developers building on the mixnet will be able to get zk-nym credentials via something like a faucet. + - Larger application integrations will have their own 'under the hood' credential generation and distribution scheme to generate access credentials on behalf of their users automatically. + - NymVPN users will have a variety of payment methods available to them. The vast majority, if not all of the steps outlined on this page, will happen under the hood from their perspective. _More on this soon_. + +Generation of zk-nyms involves the following actors / pieces of infrastructure: +- **Requester needing a zk-nym** for example a single user using the NymVPN app, or a company purchasing zk-nyms to distribute to their app users, in the instance of an app integrating a Mixnet client via one of the SDKs. The Requester is represented by a Bech32 address on the Nyx blockchain. +- [NymAPI](/network/infrastructure/nyx#nym-api) instances working together on signature generation and spent credential validation, referred to as the **NymAPI Quorum**. Members of the Quorum are a subset of the Nyx chain Validator set (other tasks they perform include a multisig used for triggering reward payouts to the Network Infrastructure Node Operators and maintaining the global Bloom Filter for double-spend protection). +- **OrderAPI**: an API creating crypto/fiat to `NYM` swaps and then depositing the NYM tokens in a smart contract managed by the NymAPI Quorum for payment verification. Implementation details of the API will be released in the coming months. + +Generation happens in 3 distinct stages: +- Key Generation & payment +- Issue credential +- Generate unlinkable zk-nyms for Nym Network access + +From the perspective of the Requester most of this happens under the hood, but results in the creation and usage of an **unlinkable, rerandomisable anonymous proof-of-payment credential** - a zk-nym - with which to access the Mixnet without fear of doxxing themselves via linking app usage and payment information. The user experience is further enhanced by the fact that a single credential can be split into multiple small zk-nyms, meaning that a Requester may buy a large chunk of bandwidth but 'spend' this in the form of multiple zk-nyms with different ingress Gateways. Whilst this happens under the hood, what it affords the Requester is an ease of experience in that they have to 'top up' their bandwidth less and are able to chop and change ingress points to the Nym Network as they see fit, akin to the UX of most modern day VPNs and dVPNs. + +## Key Generation & Payment +- First, a Cosmos [Bech32 address](https://docs.cosmos.network/main/build/spec/addresses/bech32) 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. +- 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. +- Payment options will trigger the OrderAPI. This will: + - Create a swap for `` to `NYM` tokens. + - Deposit these tokens with the NymAPI Quorum via a CosmWasm smart contract deployed on the Nyx blockchain. +- The Requester sends a request to each member of the Quorum requesting a zk-nym credential. This request is signed with their private key and includes the transaction hash of the NYM deposit into the deposit contract, performed either by themselves or the OrderAPI. + +## Issue zk-nym +At this point, NYM tokens have been deposited into the smart contract controlled by the Quorum's multisig and a zk-nym has been requested. Next, each member of the Quorum who responds to the Requester's request for a zk-nym checks the validity and returns a partial blinded signature - a 'partial signed credential' ('PSC') - signed with part of the master key (since this is a threshold cryptosystem, not all members of the Quorum must respond to create a zk-nym, only enough to pass the threshold). The process looks like this: + +- Members of the Quorum perform several checks to verify the request is valid: + - They verify the signature sent as part of the request is valid and that the request was made in the last 48 hours. + - They verify that the amount requested matches the amount deposited in the transaction, the hash of which was signed by the Requester's ed25519 key and sent as part of the request. +- Members then create a PSC from their fragment of the master key generated and split amongst them at the beginning of the Quorum in the initial DKG ceremony. + - The member also creates a `key:value` entry in their local cache with the transaction hash as the key, and the PSC + encrypted signature as the value. This is used later for zk-nym validation and is cleaned after a predefined timeout. +- These PSCs are given back to the Requester after setting up a secure channel via DH key exchange, with each replying Quorum member also sending their public key for verification that the returned PSC was signed by them. + +Once the Requester has received over the threshold number of PSCs they can assemble them into a 'ticketbook' of 'tickets' - spendable credentials - signed by the master key. The Requester never learns this master key (it is a private attribute) but the credential can be verified by the Quorum as being valid by checking for a proof that the credential's private attribute - the value of the master key - is valid. + +![](/images/network/deposit-generate.png) + +## Spend zk-nym to Access Mixnet +- Once the ticketbook has been aggregated from the PSCs returned from > threshold of Quorum members, smaller 'ticket' credentials can be generated from it, accounting for smaller chunks of bandwidth which can be 'spent' with ingress Gateways. This occurs entirely offline, on the device of the zk-nym Requester. See pages on the scheme's [unlinkability](unlinkability) and [rerandomisation and incremental spending](./rerandomise) features for further information on this. +- This ticket is later presented to the Quorum by the Gateway that collected it, which is used to calculate reward percentages given to Nym Network infrastructure operators by the Quorum, with payouts triggered by their multisig wallet. Both ingress Gateways and the Quorum use spent tickets when engaging in [double spending protection](./double-spend-prot). + +![](/images/network/use-zknym.png) + +--- +title: Rerandomisation & Incremental Spend +url: https://nym.com/docs/network/cryptography/zk-nym/rerandomise +--- + +# Rerandomisation & Incremental Spend + +Each ticket will not be valid for the entire amount of data that the ticketbook aggregated from the PSCs is; if the aggregated ticketbook is worth (e.g.) 10GB of Mixnet data, each ticket will be worth far less (e.g. 100MB). This amount will be globally uniform in order to avoid situations where differently sized tickets allow for patterns to emerge. + + 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 ticket sizes will be uniform across the Network. + +## 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 means that clients are not tied to particular Gateways they have 'spent' their entire subscription amount with; if the ingress Gateway goes down, or the client simply wishes to use another ingress Gateway, the user has multiple other tickets they can use that account for their remaining purchased bandwidth. + +Going back to the `nym-cli` tool to illustrate this; we can generate multiple unlinkable tickets from a single ticketbook aggregated from PSCs: + +```sh +❯ ./nym-cli ecash generate-ticket --credential-storage storage.db --provider 6qidVK21zpHD298jdDa1RRpbRozP29ENVyqcSbm6hQrG --full +TICKETBOOK DATA: +4Ys9pzUf9MPxX4s5RASyrRoY9fPk1a1kFuPBP2jm2L5PyUy535yPEfjHAfpUTC1Lf2d155TmjukvcDycQYfBSDfhEUJM4J3qPNfG3B5aQEEkefESZp3CM5AEnAu1AEyhpepbYw6BuXokiNcmaYtq3yJQbA4KicKP8FowoRzKHmXpJoUqY8wYQughGfdtXgr3rVaZmK21X51P1NL2UW1aCE512WWfy6P1LJHByWywT3qVw28Z83 + +generating payment information for 50 tickets. this might take a while!... +AVAILABLE TICKETS ++-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+ +| index | binary data | spend status | ++============================================================================================================================================================================================+ +| 0 | 4kgKyJLq1zQuk9r9AbEFHPqD8mDuxsLSjgo9XW4Lf7EqGSbgfNsWSEcTbRPEMFLzpstbX5azsA3opFh851h4g5qCG2qE3Luwqua4GG2ebJhk91rvEc5JPctbVQxL62fkfQ6svdcNp…1057bytes remaining | NOT SPENT | +|-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------| +| 1 | 4kefQqViRZd5YezMHH1FTcgUGPK2E2ivfmwgf59exvsnR8tsb5aJtGVwpA7wAJT6icPeo8jtDwDZ3WMPJxL3VRLiakAQr79zh7ixM89gowg3ChHEy6ewmHcT7T6RFkZFsMCMj1CNd…1057bytes remaining | NOT SPENT | +|-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------| +| 2 | 4kxaKdBxyFzJ8gxSZCh1v3wBfN7JvnCJuoJ4MWqkkMHtt2XgRKbDmHCv5ZxtA57Qk8LC3NDMBmqjADvY34mAPdT3tLBL4uxse9ASa227Ji96dwgxvfbpvLXSSr5o4vuPRV9K7UfpJ…1057bytes remaining | NOT SPENT | +|-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------| +| 3 | 4kdYwUJwXyxZBLQXextd4GsU2MATjzArVq5Ec459fTXyrm6q3vxurWULzBMpV5UjcmjJtnw1zFqt7f8Ydu5gyxwAVXP3Nwpn83ouguv2n4YrUewZCvFAqQYXgahhhaQGp6RxK2Arh…1057bytes remaining | NOT SPENT | +|-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------| +... + +| 46 | 4kg8bfQ7kGgq5TkkqXagpAEu95gmGT4i7NKbaxJtp2gRgWRrQZM1rxaDAzAxfghoM6PFNbYgKsnLD4MF8HtXW3p92CnPBjswzJ1EbtsMGpgDER3CYFt2ivAhMAVXFziF5UjVJXhpa…1057bytes remaining | NOT SPENT | +|-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------| +| 47 | 4kipbH5Fqt5E9hFMynm9vzFh5FkxKRdHrSEiiJWDwmg3mASctR61sXoFD5u5ZMBwGdvz9sWsRfrpR4MX2NNfRhC85aUxqtkAv3hXZiCLtE1pUC54Cq7YXHyv2XTNKpvuFZs2GmwYg…1057bytes remaining | NOT SPENT | +|-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------| +| 48 | 4kxYZ26HXvxVhh4quHXeCUyQokydeF5wkwUi8fMx6P3uoMvuiPaNP1SJTbYnaQEFFtF6U4dGop6QckUYvbtwQFoGJTJesHFHTDtHbshj5Dg8DwbyaHuAR86zGwYMUPved4XKUTMLa…1057bytes remaining | NOT SPENT | +|-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------| +| 49 | 4kb6zmPebRxjKLVicctq2whvANjWJMoohiPBMr21cT4xj78nvXmJEK8EB4PpqQVFo6ddU9uzuer5ggQZNZgETX2VXBzymBYNzXBuXjLJi1WRdAiASqWz5Hv5im1TJh4XBE4mxKo8Q…1057bytes remaining | NOT SPENT | ++-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+ +``` + +--- +title: Unlinkability +url: https://nym.com/docs/network/cryptography/zk-nym/unlinkability +--- + +# 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. + +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. + +```sh +❯ ./nym-cli ecash generate-ticket --credential-storage storage.db --provider 6qidVK21zpHD298jdDa1RRpbRozP29ENVyqcSbm6hQrG --ticket-index=3 +TICKETBOOK DATA: +4Ys9pzUf9MPxX4s5RASyrRoY9fPk1a1kFuPBP2jm2L5PyUy535yPEfjHAfpUTC1Lf2d155TmjukvcDycQYfBSDfhEUJM4J3qPNfG3B5aQEEkefESZp3CM5AEnAu1AEyhpepbYw6BuXokiNcmaYtq3yJQbA4KicKP8FowoRzKHmXpJoUqY8wYQughGfdtXgr3rVaZmK21X51P1NL2UW1aCE512WWfy6P1LJHByWywT3qVw28Z83 + +attempting to generate payment for ticket 3... + +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! + +```sh +❯ ./nym-cli ecash generate-ticket --credential-storage storage.db --provider 6qidVK21zpHD298jdDa1RRpbRozP29ENVyqcSbm6hQrG --ticket-index=3 +TICKETBOOK DATA: +4Ys9pzUf9MPxX4s5RASyrRoY9fPk1a1kFuPBP2jm2L5PyUy535yPEfjHAfpUTC1Lf2d155TmjukvcDycQYfBSDfhEUJM4J3qPNfG3B5aQEEkefESZp3CM5AEnAu1AEyhpepbYw6BuXokiNcmaYtq3yJQbA4KicKP8FowoRzKHmXpJoUqY8wYQughGfdtXgr3rVaZmK21X51P1NL2UW1aCE512WWfy6P1LJHByWywT3qVw28Z83 + +attempting to generate payment for ticket 3... + +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. + +--- +title: Double Spend Protection +url: https://nym.com/docs/network/cryptography/zk-nym/double-spend-prot +--- + +# Double Spend Protection + +Double spend protection in the context of zk-nym is a balancing act between speed, reliability, and UX. There are two possible modes for protecting against attempted double spending of zk-nyms: + +- Online: The online approach mandates that ingress Gateways instantly deposit zk-nyms received from clients to the NymAPI Quorum for verification. Once verified by the Quorum, the ingress Gateway is paid proportional to the amount of bandwidth 'spent' with the zk-nym, and proceeds to grant the client access to the network. +- Offline: In contrast, the offline approach involves the periodic submission of collected zk-nyms by ingress Gateways to the Quorum, instead of an instant check. Subsequently, the Quorum nodes perform checks to detect any instances of double-spending and identify the public key associated with such occurrences, whereas the ingress Gateways only do a simple check to check that _that particular_ zk-nym had not been spent with itself before. + +> The zk-nym system takes the **offline** approach. + +## 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. +- 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. +- Ingress Gateways accept zk-nyms without preemptively checking for instances of double spending thus making them susceptible to unknowingly accepting double-spent credentials. +- 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. + +## 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. + +### 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. + +> Since each time a zk-nym credential is rerandomised its serial number is changed, the serial number being shared in no way identifies a client or user. + +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. + +### 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. + +This identification phase involves looking for instances of double spending, identifying the id of the double-spending client, and blacklisting this client by its id. Subsequently, when this client requests a new credential, their plaintext public identifier is included in the request. The Quorum then checks if this identifier is blacklisted. If it is, a new credential is not issued. Furthermore, since the PSCs are only attainable after depositing NYM as payment, the Quorum has the authority to withhold the deposited NYMs as a punitive measure for any detected instances of double-spending. + +--- +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. +url: https://nym.com/docs/network/infrastructure +--- + +# Infrastructure + +The Nym Network runs on decentralized infrastructure — a set of independently operated nodes coordinated by the Nyx blockchain. No single party controls routing, key management, or credential issuance. + +## In this section + +- [Nyx Blockchain](/network/infrastructure/nyx) — the Cosmos SDK chain that maintains the node registry, manages token economics, and hosts the smart contracts for credentials and rewards +- [Nym Nodes](/network/infrastructure/nym-nodes) — the unified `nym-node` binary that operates as Entry Gateways, Mix Nodes, or Exit Gateways depending on network demand + +--- +title: Nyx Blockchain +description: How the Nyx Cosmos SDK blockchain coordinates the Nym Network by maintaining node topology, managing NYM token economics, and hosting smart contracts for credentials and rewards. +url: https://nym.com/docs/network/infrastructure/nyx +--- + +# Nyx Blockchain + +Nyx is a Cosmos SDK blockchain that coordinates the Nym Network. It maintains the topology of active nodes, manages token economics, and hosts the smart contracts that power the credential system. + +To interact with the chain, see [Interacting with Nyx](/developers/chain). To run a Validator, see the [Operator Documentation](/operators/nodes/validator-setup). + +## Role in the network + +The blockchain serves several functions. It maintains the **topology registry**—the list of active nodes and their public keys. This eliminates the need for a centralized directory server and prevents attacks that plague peer-to-peer directory systems. + +It manages **token economics**. The NYM token is a native token of the chain, used for staking, rewards, and credential payments. Validators secure the chain via proof-of-stake consensus. + +And it hosts **smart contracts** for mixnet coordination and the zk-nym credential system. + +## Validators + +Nyx Validators run the `nyxd` binary to maintain the blockchain. They process transactions, execute smart contracts, and participate in consensus. A subset of validators also run Nym API instances for credential issuance. + +## Nym API + +For setup instructions, see the [Nym API Operator Guide](/operators/nodes/validator-setup/nym-api). + +The Nym API is operated by a subset of validators forming the "Quorum." This group performs network monitoring—sending test packets through the mixnet and calculating reliability scores for nodes. More critically, it handles credential issuance, generating the partial blind signatures that form zk-nyms. + +The Quorum uses threshold cryptography. No single member can issue credentials alone. The system remains functional even if some members are offline. This distributes trust across multiple independent parties. + +## Smart contracts + +The Nyx chain is CosmWasm-enabled. The **Mixnet Contract** stores bonded node information, provides network topology for client routing, and tracks delegations and rewards. The **Vesting Contract** manages NYM token vesting schedules. The **zk-nym Contract** tracks deposits for credential generation and manages the blacklist for double-spend attempts. + +Contract addresses for different networks can be queried via the [Nym API](/apis/nym-api). + +## Querying the chain + +The [Nym API](/apis/nym-api) provides HTTP endpoints for querying network topology, node status, rewards, and credential information. For direct contract interaction, see the [Developer Documentation](/developers/chain). + +--- +title: Nym Nodes +url: https://nym.com/docs/network/infrastructure/nym-nodes +--- + +# Nym Nodes + +All traffic-routing infrastructure runs on the `nym-node` binary. This unified binary operates in different modes—Entry Gateway, Mix Node, or Exit Gateway—simplifying deployment and enabling future dynamic role assignment. + +To run a node, see the [Operator Documentation](/operators/introduction). + +## Node modes + +**Entry Gateways** are the user's first point of contact with the network. They accept WebSocket connections from clients, verify zk-nym credentials to confirm payment, and store messages for clients that go offline (up to 24 hours). Entry Gateways know the client's IP address but cannot see message contents or final destinations. + +**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. + +## Unified binary + +The various components were originally separate binaries. They've 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. + +## Nym clients + +For client setup, see the [Developer Documentation](/developers/clients/socks5). + +Clients are the user-side software that connects to the network. They discover network topology from the blockchain, register with an Entry Gateway, construct Sphinx packets with layered encryption, generate cover traffic, and handle acknowledgements and retransmission. + +Client types include native Rust clients, WASM clients for browsers, the SOCKS5 proxy client, and the NymVPN client. The NymVPN client supports both dVPN and mixnet modes. + +## Running infrastructure + +The current deployment includes {stats.nodes} active nodes across {stats.locations} countries, operated by independent parties worldwide. This includes {stats.mixnodes} Mix Nodes and {stats.exit_gateways} Exit Gateways. Running a node requires meeting minimum hardware specifications, bonding NYM tokens as collateral, and maintaining high uptime for rewards. + +--- +title: Nym Network Reference +description: Technical specifications and protocol details for the Nym Network: addressing format, epoch timing, and the hop-by-hop acknowledgement system. +url: https://nym.com/docs/network/reference +--- + +# Reference + +Technical specifications and protocol details that apply across the Nym Network regardless of mode. + +## In this section + +- [Addressing](/network/reference/addressing) — the `identity.encryption@gateway` address format and how routing works +- [Epochs](/network/reference/epochs) — time divisions in the network, reward distribution, and topology reshuffling +- [Acknowledgements](/network/reference/acks) — the hop-by-hop packet delivery confirmation system + +--- +title: Nym Network Addressing +description: How Nym addresses work: the identity.encryption@gateway format, key components, routing mechanics, and privacy considerations for client addressing. +url: https://nym.com/docs/network/reference/addressing +--- + +# Addressing + +All clients and nodes in the Nym Network have an address that uniquely identifies them for routing. + +## Address format + +A Nym address has three parts separated by dots and an @ symbol: + +``` +.@ +``` + +The **identity key** identifies the client for routing purposes. It's derived from the client's Ed25519 keypair and base58-encoded for readability. + +The **encryption key** is the public key used to encrypt the final layer of Sphinx packets destined for this client. Only the client holding the corresponding private key can decrypt messages addressed to them. + +The **gateway key** identifies which Gateway holds messages for this client. When you connect, your client registers with a specific Entry Gateway, and that Gateway's identity becomes part of your address. + +## Example + +``` +DguTcdkWWtDyUFLvQxRdcA8qZhardhE1ZXy1YCC7Zfmq.Dxreouj5RhQqMb3ZaAxgXFdGkmfbDKwk457FdeHGKmQQ@4kjgWmFU1tcGAZYRZR57yFuVAexjLbJ5M7jvo3X5Hkcf +``` + +## How routing works + +When sending to a Nym address, the sender extracts the Gateway key and constructs a Sphinx packet with that Gateway as the final hop. The Gateway receives the packet, identifies the recipient by their identity key, and delivers the message (or stores it if the recipient is offline). + +## Privacy considerations + +The address reveals which Gateway you use and your public keys. It doesn't reveal your IP address or private keys. Multiple clients can use the same Gateway, so the Gateway key alone doesn't identify you. + +For persistent identity across sessions, store your keypairs and re-register with the same Gateway. For ephemeral identity, generate new keys each session. + +--- +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. +url: https://nym.com/docs/network/reference/epochs +--- + +# 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. + +## What happens at epoch boundaries + +**Reward distribution** calculates performance metrics for each node and distributes NYM token rewards based on routing reliability and uptime. Nodes that successfully forward 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. + +## Future changes + +In upcoming releases, epochs will trigger automatic role assignment. Nodes will switch between Mix Node and Gateway roles based on network demand, without operators needing to manually configure roles. + +## SURB validity + +SURBs are tied to key rotation cycles. Node keys rotate on an odd/even schedule with a default validity of 24 epochs. A SURB remains usable for `(validity_epochs + 1) * epoch_duration` — roughly 25 hours at the current 1-hour epoch. After that, the routing keys it was built with are no longer accepted by the network. Clients automatically purge stale SURBs and request fresh ones. + +## Querying epoch information + +Current epoch data is available through Nyx blockchain queries and Nym API endpoints. + +--- +title: Packet Acknowledgements +description: How the Nym Network uses hop-by-hop acknowledgements and retransmission to ensure reliable packet delivery despite network congestion or node failures. +url: https://nym.com/docs/network/reference/acks +--- + +# Acknowledgements + +The Nym Network uses acknowledgements to ensure reliable packet delivery. When a node receives a packet, it sends an ack back to the sender. If no ack arrives within a timeout, the packet is retransmitted. + +## How it works + +The sender transmits a packet and waits for acknowledgement. The receiver processes the packet and sends an ack. If the sender receives the ack, the packet is marked as delivered. If not, the sender retransmits. + +This happens automatically at each hop. If a client sends 100 packets to a Gateway and only receives 95 acks, it retransmits the 5 missing packets. The same mechanism operates between all nodes in the route. + +## Why it matters + +Network conditions can cause packet loss—congestion, temporary failures, connectivity issues. Without acks and retransmission, lost packets would mean lost messages. The acknowledgement system ensures reliable delivery despite imperfect network conditions. + +## Scope + +Acknowledgements operate hop-by-hop between adjacent nodes. They confirm that packets reached the next hop, not that they reached the final destination. End-to-end delivery confirmation for anonymous communication is handled separately through [SURBs](/network/mixnet-mode/anonymous-replies). + +## 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. + +--- +title: Licensing +url: https://nym.com/docs/network/licensing +--- + +# Licensing + +As a general approach, licensing follows this pattern: + +* [Nym Documentation](https://nym.com/docs) by [Nym Technologies](https://nym.com) is licensed under [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) ![CC](/images/cc-icons/cc.svg) ![BY](/images/cc-icons/by.svg) ![NC](/images/cc-icons/nc.svg) ![SA](/images/cc-icons/sa.svg) + +* Nym applications and binaries are [GPL-3.0-only](https://www.gnu.org/licenses/) + +* Used libraries and different components are [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) or [MIT](https://mit-license.org/) + +For accurate information, please check individual files. + +--- +title: Code of Conduct +url: https://nym.com/docs/network/coc +--- + +# Code of Conduct + +We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. + +Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all. + +Please be kind and courteous. There’s no need to be mean or rude. + +Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. + +Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. + +We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behaviour. We interpret the term “harassment” as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behaviour that excludes people in socially marginalized groups. + +Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the Rust moderation team immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back. + +Likewise any spamming, trolling, flaming, baiting or other attention-stealing behaviour is not welcome. + +--- +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. +url: https://nym.com/docs/developers +--- + +# Developer Documentation + +Build applications that protect user metadata using the Nym Mixnet. This section covers SDK integration, blockchain interaction, and developer tools. + +## Where to start + +**Choosing an integration approach** — read [Integrations](./integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: + +- **[Rust SDK](./rust)** — full-featured SDK with message passing, `AsyncRead`/`AsyncWrite` streams, and client pooling. Start with the [Tour](./rust/tour). +- **[TypeScript SDK](./typescript)** — browser and Node.js SDK for mixFetch, Mixnet client, and smart contract interaction. +- **[Standalone Clients](./clients)** — language-agnostic SOCKS5 and WebSocket proxies for piping traffic through the Mixnet without an SDK. + +## Blockchain interaction + +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](./chain). + +## API reference + +Auto-generated API specs for Nym infrastructure endpoints are in the [APIs section](/apis/introduction). + +--- +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. +url: https://nym.com/docs/developers +--- + +# Developer Documentation + +Build applications that protect user metadata using the Nym Mixnet. This section covers SDK integration, blockchain interaction, and developer tools. + +## Where to start + +**Choosing an integration approach** — read [Integrations](./integrations) to understand the architectural trade-offs (native SDK vs proxy vs mixFetch), then pick your path: + +- **[Rust SDK](./rust)** — full-featured SDK with message passing, `AsyncRead`/`AsyncWrite` streams, and client pooling. Start with the [Tour](./rust/tour). +- **[TypeScript SDK](./typescript)** — browser and Node.js SDK for mixFetch, Mixnet client, and smart contract interaction. +- **[Standalone Clients](./clients)** — language-agnostic SOCKS5 and WebSocket proxies for piping traffic through the Mixnet without an SDK. + +## Blockchain interaction + +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](./chain). + +## API reference + +Auto-generated API specs for Nym infrastructure endpoints are in the [APIs section](/apis/introduction). + +--- +title: Integrating With Nym +url: https://nym.com/docs/developers/integrations +--- + +# Integrating With Nym +Any application that wants to integrate with Nym involves sending its application traffic through the Mixnet using one of the available Nym Clients. There is no single solution for this, as different environments offer different access and transport options (e.g. if operating in a web browser, you do not have access to syscalls or sockets, have to deal with content security policies, etc). + +As such, we have several solutions available for developers to choose from depending on the **environment** their application is expected to run in: native apps which are running on a desktop, or webapps running in a browser. + + The list of current options available to developers to do not cover all environments and setups - we are working on expanding this list and approaching more general solutions, but there is no one-size-fits-all approach when dealing with rerouting network traffic. + +Integration options are then further subdivided by app **architecture**; whether the application interacts with remote hosts on the public internet running independently of the app (e.g. public blockchain RPC endpoints, third-party APIs) or whether app developers have some control over the versions of the software being run on both sides of an interaction (e.g. peer to peer apps running the same software version, or client-server architectures which are running software written by the same team). + + This is because of the different security considerations each option offers. These are detailed in the following pages. + +--- +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. +url: https://nym.com/docs/developers/native +--- + +# Native / Desktop Apps + +Developers wanting to integrate into desktop apps and CLIs can use our [Rust SDK](./rust). There are two broad approaches to using the Mixnet (E2E or as a proxy), with different modules suited for each. + +## Option 1: Mixnet End-To-End +Embed Nym Clients in both sides of your app and have them send all network traffic through the Mixnet: a peer-to-peer setup, or a client and server where you control both sides. + +![](/images/developers/nym-arch-client-to-client.png) + +### Stream Module +The [Stream module](./rust/stream) provides `AsyncRead + AsyncWrite` byte streams multiplexed over the mixnet. If you're used to working with TCP sockets, this is the closest analog — open a stream, read and write bytes. + +- [docs](./rust/stream) +- [tutorial](./rust/stream/tutorial) + +### Mixnet & Client Pool Modules +The [Mixnet module](./rust/mixnet) exposes the raw message-based API and `MixnetClient`. The [Client Pool](./rust/client-pool) pre-creates clients in the background for bursty traffic patterns. + +Use these when you need full control over the communication model, or when you're building custom connection logic on top of the raw message API. + +- [docs](./rust/mixnet) +- [tutorial](./rust/mixnet/tutorial) + +### TcpProxy Module (Unmaintained) + +**This module is unmaintained.** Use the [Stream module](./rust/stream) for new projects. Existing users should plan to migrate when possible. + +A pair of abstractions that expose localhost TCP sockets for proxying traffic through the mixnet. + +- [docs](./rust/tcpproxy) + +## Option 2: Mixnet-As-Proxy +For developers who can only control the client side, and need to communicate with a 3rd party service such as a public blockchain RPC or a remote host they do not control. + +![](/images/developers/nym-arch-ip-routing.png) + +### Security Considerations + +Since traffic is only packaged as Sphinx until it gets to the Exit Gateway, where it is unwrapped into either HTTPS packets (by a Network Requester) or IP packets (by an IP Packet Router), the last hop between the Gateway and the remote host **travels as normal internet traffic**. + +This option has fewer protections than the E2E option against a global passive adversary, but still grants you timing obfuscation and sender-receiver unlinkability between your client software and whatever service it is interacting with. + +### SOCKS Client +Developers with apps that support SOCKS4, 4a, or 5 can use the Socks Client exposed by the Mixnet module. This uses the Network Requester service of the chosen Exit Gateway to interact with the remote host via the chosen SOCKS proxy protocol. The Network Requester uses SURBs to anonymously reply to the original sender. + +- [docs](./rust/mixnet) + +Development is in progress to allow for this proxy method from native Rust, C, and Go without requiring a separate SOCKS client. Stay tuned. + +--- +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. +url: https://nym.com/docs/developers/browsers +--- + +# Browser-Based Apps +Browsers are a very restricted environment to work in, with limited options for external communications (websockets, Web Transport API, WebRTC), mixed content restrictions (HTTPS-only), and no access to the file system or any syscalls. These aside, the main issue when trying to capture traffic and send it via a different transport - such as the Mixnet - is the lack of access to browser TLS negotiation from JS or the CA certificate store. + +This means that the functionality offered by our current browser-based solutions are quite restricted / specific. There are currently two options for interacting with the Mixnet from the browser: `mixFetch`, and the WASM SDK. + +![](/images/developers/nym-browser-arch.png) + +Both `mixFetch` and the WASM client are delivered to the client bundled into a web application. + +## mixFetch +Drop-in replacement for browser's `fetch` API that makes HTTP(S) requests via Exit Gateways using the SOCKS Network Requester. + +Uses an embedded CA certificate store to establish TLS session between `mixFetch` and the remote host, creating a client-host secure channel from the browser to the host over the Mixnet. + +Internally it uses the WASM client. + +- [docs](./typescript#mixfetch) +- [example](./typescript/playground/mixfetch) + + ### Current Limitations of `mixFetch` + + `mixFetch` can currently only perform 10 concurrent requests (i.e. in-flight requests where a request has been sent to a remote endpoint, but no result has been recieved). + + `mixFetchv2` - which will act more like a general-purpose userspace IP stack - is currently in development. + + It is shipped with a pre-bundled CA store. + +## WASM Client +Makes Sphinx packets and cover traffic using WASM and sent over a Websocket to the Entry Gateway and receive responses. + +This only works in messaging mode (i.e. messages sent either as text or binary data), and currently doesn’t support making IP packets that are routed to the Internet by an Exit Gateway IPR, nor does it currently expose any stream-like API. If you want to send HTTP(S) requests, use `mixFetch`. + +Note that the limitations of CSPs and Mixed Content restrictions (i.e HTTPS only) apply to the Websocket connection as normal in browsers or embedded WebViews. + +Runs in a web worker to leave UI thread free for the user. + +- [docs](./typescript#mixnet-client) +- [example](./typescript/playground/traffic) + +--- +title: Nym Client Message Queue and Cover Traffic +description: How the Nym client queues messages, sends cover traffic via Poisson processes, and manages Sphinx packet streams to prevent timing attacks. +url: https://nym.com/docs/developers/concepts/message-queue +--- + +# Message Queue + + Although good to understand how the Nym Client works under the hood, this information is only of practical use if you're 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). + +## Sphinx Packet Streams +Clients, once connected to the Mixnet, **are always sending traffic into the Mixnet**; as well as the packets that you as a developer are sending from your application logic, they send [cover traffic](/network/mixnet-mode/cover-traffic) at a constant rate defined by a Poisson process. This is part of the network's mitigation of timing attacks. + +There are two constant streams of sphinx packets leaving the client at the rate defined by the Poisson process. +- one that is solely cover traffic +- one that sends a mixture of cover and 'real' traffic + +```mermaid +--- +config: + theme: neo-dark + layout: elk + +title: Cover Traffic Stream +--- +sequenceDiagram + box Local Machine + participant App Logic + participant Nym Client + end + participant Entry Gateway + + loop Cover Traffic Stream + Nym Client->>Nym Client: Delay + Nym Client->>Entry Gateway: Cover traffic + end + +``` + +```mermaid +--- +config: + theme: neo-dark + layout: elk + +title: Mixed Stream +--- +sequenceDiagram + box Local Machine + participant App Logic + participant Nym Client + end + participant Entry Gateway + + loop Cover + Real Traffic Stream + Nym Client->>Nym Client: Check internal queue + delay + Nym Client->>Entry Gateway: Cover traffic + alt Packets with App Payload + App Logic-->>Nym Client: Send(bytes): add to internal queue + Nym Client->>Nym Client: Check internal queue: bytes to send + Nym Client->>Nym Client: Encrypt & packetise bytes + Nym Client->>Entry Gateway: Real Packets + Nym Client->>Nym Client: Check internal queue: bytes to send + Nym Client->>Nym Client: Encrypt & packetise bytes + Nym Client->>Entry Gateway: Real Packets + Nym Client->>Nym Client: Check internal queue: queue empty + end + Nym Client->>Nym Client: Delay + Nym Client->>Entry Gateway: Cover traffic + end +``` + +> Since Sphinx packets are indistinguishable to an external observer, the only difference between 'real' and cover traffic is whether the payload is empty or not. This can be only known to the eventual receiver of the packet. + +## What does `send()` do then? + +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: +- 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. + +--- +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. +url: https://nym.com/docs/developers/rust +--- + +# Rust SDK + +The Rust SDK provides high-level abstractions for building privacy-preserving applications on the Nym Mixnet. All modules share a common `MixnetClient` that handles gateway connections, Sphinx packet encryption, routing, and cover traffic under the hood. + +Full API reference, architecture documentation, and type details are available on [**docs.rs/nym-sdk**](https://docs.rs/nym-sdk/latest/nym_sdk/). + +## Modules + +- **[Mixnet](./rust/mixnet)** — Core client for sending and receiving individual message payloads through the Mixnet. This is the Mixnet's native communication model — no connections, no ordering, just individually routed payloads. + +- **[Stream](./rust/stream)** — Multiplexed `AsyncRead + AsyncWrite` byte streams over the Mixnet. This is the abstraction layer that bridges the gap between the Mixnet's message-based model and familiar socket-based networking. **If you're used to TCP sockets, start here.** + +- **[TcpProxy](./rust/tcpproxy)** *(deprecated)* — TCP socket proxying through the Mixnet with session management and message ordering. For new projects, use the Stream module instead. + +- **[Client Pool](./rust/client-pool)** — A connection pool that maintains ready-to-use `MixnetClient` instances for high-throughput applications. + +- **[FFI](./rust/ffi)** — Foreign function interface bindings for using the SDK from Go and C/C++. + +## Getting started + +New to the SDK? Start with the **[Tour](./rust/tour)** for a quick overview of what you can do, then see [Installation](./rust/importing) for how to add `nym-sdk` to your project. + +--- +title: Tour of the Rust SDK +url: https://nym.com/docs/developers/rust/tour +--- + +# Tour of the Rust SDK + +A quick walkthrough of the most important things you can do with `nym-sdk`. Each section shows working code and links to the module that covers it in depth. + +**The Mixnet is not like regular internet networking.** There are no persistent connections, no guaranteed message ordering, and no TCP underneath. At its core, the Mixnet is a message-based anonymity network — you send individual payloads that are Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination. + +This means the raw [message API](./mixnet) works differently from what most developers expect. To bridge that gap, we've built the [Stream module](./stream) — an abstraction layer that gives you familiar `AsyncRead + AsyncWrite` byte streams on top of the Mixnet. **If you're coming from socket-based networking, start with streams.** + +## Send a raw message payload + +The message API gives you direct access to the Mixnet's native communication model: individually addressed payloads with no connections and no ordering guarantees. This is useful when you want full control, but it's not how most networking code works: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +#[tokio::main] +async fn main() { + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + let addr = *client.nym_address(); + println!("Connected: {addr}"); + + // Send a message to ourselves + client + .send_plain_message(addr, "hello mixnet!") + .await + .unwrap(); + + // Receive it (filter empty SURB management messages) + if let Some(msgs) = client.wait_for_messages().await { + for msg in msgs.iter().filter(|m| !m.message.is_empty()) { + println!("Got: {}", String::from_utf8_lossy(&msg.message)); + } + } + + // Always disconnect for clean shutdown + client.disconnect().await; +} +``` + +The message is Sphinx-encrypted, mixed across 5 nodes, and reconstructed on arrival. The whole round trip takes a few seconds. + +**Next:** [Mixnet module](./mixnet) | [Tutorial: Send Your First Private Message](./mixnet/tutorial) + +## Reply anonymously with SURBs + +Every received message carries a `sender_tag` — an opaque token that lets you reply **without knowing the sender's Nym address**. Replies travel back through pre-built Single Use Reply Blocks (SURBs): + +```rust +// After receiving a message... +let tag = received_msg.sender_tag.expect("message includes sender tag"); +client.send_reply(tag, "anonymous reply!").await.unwrap(); +``` + +The replying side never learns where the reply is going. This is the foundation of anonymous communication on the Mixnet. + +## Open a bidirectional stream + +If you're used to working with TCP sockets, this is where you'll feel at home. The [Stream module](./stream) provides persistent, bidirectional byte channels that implement tokio's `AsyncRead + AsyncWrite` — so any code that works with sockets works with `MixnetStream`: + +```rust +use nym_sdk::mixnet; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[tokio::main] +async fn main() { + let mut sender = mixnet::MixnetClient::connect_new().await.unwrap(); + let mut receiver = mixnet::MixnetClient::connect_new().await.unwrap(); + let recv_addr = *receiver.nym_address(); + + // Receiver creates a listener (activates stream mode) + let mut listener = receiver.listener().unwrap(); + + // Sender opens a stream to the receiver + let mut out = sender.open_stream(recv_addr, None).await.unwrap(); + + // Receiver accepts it + let mut inc = listener.accept().await.unwrap(); + + // Standard tokio I/O — write, flush, read + out.write_all(b"hello stream").await.unwrap(); + out.flush().await.unwrap(); + + let mut buf = vec![0u8; 1024]; + let n = inc.read(&mut buf).await.unwrap(); + println!("{}", String::from_utf8_lossy(&buf[..n])); + + drop(out); + drop(inc); + sender.disconnect().await; + receiver.disconnect().await; +} +``` + +Activating stream mode (by calling `listener()` or `open_stream()`) disables message-based methods like `send_plain_message()` and `wait_for_messages()`. A single client operates in one mode at a time. + +**Next:** [Stream module](./stream) | [Tutorial: Build a Private Echo Server](./stream/tutorial) + +## Use a client pool for bursty traffic + +Creating a `MixnetClient` takes several seconds (gateway handshake, key generation, topology fetch). The [Client Pool](./client-pool) pre-creates clients in the background so they're ready when you need them: + +```rust +use nym_sdk::client_pool::ClientPool; + +#[tokio::main] +async fn main() { + let pool = ClientPool::new(3); // maintain 3 clients in reserve + let bg = pool.clone(); + tokio::spawn(async move { bg.start().await }); + + // Wait for pool to fill, then grab a ready client + tokio::time::sleep(std::time::Duration::from_secs(15)).await; + + if let Some(client) = pool.get_mixnet_client().await { + println!("Got client: {}", client.nym_address()); + client.disconnect().await; + } + + pool.disconnect_pool().await; +} +``` + +Clients are **consumed, not returned** — the pool creates replacements automatically. + +**Next:** [Client Pool module](./client-pool) | [Tutorial: Handle Bursty Traffic](./client-pool/tutorial) + +## Persist your identity + +By default, `connect_new()` creates ephemeral keys that are discarded on disconnect. To keep the same Nym address across restarts, use the builder with on-disk storage: + +```rust +use nym_sdk::mixnet::{MixnetClientBuilder, StoragePaths}; +use std::path::PathBuf; + +let storage = StoragePaths::new_from_dir( + &PathBuf::from("/tmp/my-nym-client") +).unwrap(); + +let client = MixnetClientBuilder::new_with_default_storage(storage) + .await + .unwrap() + .build() + .unwrap() + .connect_to_mixnet() + .await + .unwrap(); + +// This address is the same every time you run with the same path +println!("Persistent address: {}", client.nym_address()); +``` + +## Where to go next + +- **[Installation](./importing)** — Add `nym-sdk` to your project +- **[Mixnet Tutorial](./mixnet/tutorial)** — Full walkthrough: send, receive, reply with SURBs +- **[Stream Tutorial](./stream/tutorial)** — Build a private echo server +- **[Client Pool Tutorial](./client-pool/tutorial)** — Handle bursty traffic +- **[API Reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/)** — Type details, method signatures, architecture docs + +--- +title: Install the Nym Rust SDK +description: Add nym-sdk to your Rust project from crates.io or Git. Covers version requirements, minimum Rust version, and current feature gate status. +url: https://nym.com/docs/developers/rust/importing +--- + +# Installation + +The `nym-sdk` crate is available on [crates.io](https://crates.io/crates/nym-sdk): + +```toml +[dependencies] +nym-sdk = "1.20.4" +``` + +You can also import directly from the Git repository if you need unreleased changes: + +```toml +# development branch (latest changes, may be unstable) +nym-sdk = { git = "https://github.com/nymtech/nym", branch = "develop" } + +# latest stable release +nym-sdk = { git = "https://github.com/nymtech/nym", branch = "master" } +``` + +**Minimum Rust version:** 1.70+ + +**Feature gates are not yet implemented.** Importing `nym-sdk` currently pulls in all modules (mixnet, tcp_proxy, client_pool, etc.) and their full dependency trees. Work is planned to gate modules behind Cargo feature flags so you can import only what you need. + +--- +title: Nym Rust SDK: Mixnet Messaging Module +description: Use the Nym Rust SDK Mixnet module to send messages through the mixnet. Covers builder patterns, custom topologies, SOCKS proxy, and anonymous replies. +url: https://nym.com/docs/developers/rust/mixnet +--- + +# Mixnet Module + +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 is not like regular internet networking.** There are no persistent connections, no guaranteed message ordering, and no TCP. Each message is independently Sphinx-encrypted, routed through multiple mix nodes, and reconstructed at the destination. This is the Mixnet's native communication model — powerful for privacy, but different from what most developers expect. If you want familiar socket-like I/O (`read`/`write`), use the [Stream module](./stream) instead — it's an abstraction layer we've built to bridge the gap. + +## Two operating modes + +The client operates in one of two mutually exclusive modes: + +**Message mode** (default) — send and receive raw message payloads: +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + +// Send a message +client.send_plain_message(*client.nym_address(), "hello").await.unwrap(); + +// Receive messages +if let Some(msgs) = client.wait_for_messages().await { + for msg in msgs { + println!("Got: {}", String::from_utf8_lossy(&msg.message)); + } +} + +client.disconnect().await; +``` + +**Stream mode** — persistent `AsyncRead + AsyncWrite` channels. See the [Stream module](./stream) for details. + +Stream mode is activated by calling `open_stream()` or `listener()`. Once active, message-mode methods return `Error::StreamModeActive`. This is a one-way transition. + +## API reference + +- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/) — full architecture documentation, all types, builder methods, traits, and configuration options +- [Examples on GitHub](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) — runnable examples covering simple send/receive, builder patterns, custom topologies, SOCKS proxy, anonymous replies, and more + +Run any example with: +```sh +cargo run --example +``` + +## Next steps + +- [Tutorial: Send your first private message](./mixnet/tutorial) — step-by-step guide covering sending, receiving, SURBs, and persistent identity +- [Troubleshooting](./mixnet/troubleshooting) — common issues with logging, empty messages, and client lifecycle +- [Stream module](./stream) — if you need persistent bidirectional byte channels + +--- +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. +url: https://nym.com/docs/developers/rust/mixnet/tutorial +--- + +# Tutorial: Send Your First Private Message + +Build a program that connects to the Nym Mixnet, sends a message to yourself, receives it, and replies anonymously using SURBs. Then extend it with persistent identity and concurrent send/receive. + +## What you'll learn + +- Connecting an ephemeral client to the Nym Mixnet +- Sending and receiving Sphinx-encrypted messages +- Replying anonymously using SURBs (Single Use Reply Blocks) +- Persisting client identity to disk with `MixnetClientBuilder` +- Using `split_sender()` for concurrent send and receive tasks + +## Prerequisites + +- Rust toolchain (1.70+) +- A working internet connection (clients connect to the live Nym Mixnet) + +Code verified against `nym-sdk` v1.20.4 ([`4077717`](https://github.com/nymtech/nym/commit/4077717d3)). If the API has changed since then, check the [examples in the repo](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) for the latest usage. + +## Step 1: Set up the project + +```sh +cargo init nym-mixnet-demo +cd nym-mixnet-demo +``` + +Add dependencies to `Cargo.toml`: + +```toml +[dependencies] +nym-sdk = "1.20.4" +tokio = { version = "1", features = ["full"] } +``` + +## Step 2: Connect and send + +Replace the contents of `src/main.rs`: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +#[tokio::main] +async fn main() { + // 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 — + // encryption and mixing happen in background tasks. + client + .send_plain_message(*our_address, "hello from the mixnet!") + .await + .unwrap(); + + println!("Sent — waiting for arrival..."); +``` + +## Step 3: Receive + +```rust + // wait_for_messages() returns the next batch of incoming messages. + // 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()) { + break msg; + } + } + }; + + println!("Received: {}", String::from_utf8_lossy(&message.message)); +``` + +## Step 4: Reply anonymously + +Every message includes a `sender_tag` — an opaque `AnonymousSenderTag` that lets you reply **without knowing the sender's address**. The SDK includes SURBs (Single Use Reply Blocks) with every message by default: + +```rust + let sender_tag = message.sender_tag.expect("should have sender tag"); + + // 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 { + 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)); + + client.disconnect().await; +} +``` + +## Step 5: Run it + +```sh +cargo run +``` + +``` +Connected: 8gk4Y...@2xU4d... +Sent — waiting for arrival... +Received: hello from the mixnet! +Reply: hello back, anonymously! +``` + +## Going further: persist your identity + +The ephemeral client above generates a new address on every run. To keep the same address across restarts, use `MixnetClientBuilder` with on-disk storage: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender, StoragePaths}; + +#[tokio::main] +async fn main() { + // Keys are generated on first run, then loaded from disk on subsequent runs. + let paths = StoragePaths::new_from_dir("./my-client-data").unwrap(); + + let mut client = mixnet::MixnetClientBuilder::new_with_default_storage(paths) + .await + .unwrap() + .build() + .unwrap() + .connect_to_mixnet() + .await + .unwrap(); + + println!("Address: {}", client.nym_address()); + + // Same API as before — send, receive, reply. + client + .send_plain_message(*client.nym_address(), "persistent identity!") + .await + .unwrap(); + + 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)); + } + } + + // Always disconnect for clean shutdown — background tasks need to be + // stopped and state files flushed. + client.disconnect().await; +} +``` + +Run it twice — the address stays the same. + +## Going further: send and receive from different tasks + +Add `futures` to your `Cargo.toml`: + +```toml +futures = "0.3" +``` + +Use `split_sender()` to get a clone-able send handle for use in separate tasks: + +```rust +use futures::StreamExt; +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +#[tokio::main] +async fn main() { + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + let addr = *client.nym_address(); + + // split_sender() returns a clone-able MixnetClientSender. + let sender = client.split_sender(); + + // 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)); + } + client.disconnect().await; + }); + + // Spawn a sender on a different task. + let tx = tokio::spawn(async move { + sender.send_plain_message(addr, "hello from another task!").await.unwrap(); + }); + + tx.await.unwrap(); + rx.await.unwrap(); +} +``` + +## What's happening under the hood + +1. **`connect_new()`** generates an ephemeral identity (ed25519 + x25519 keys), fetches the current network topology, selects a gateway, and opens a persistent WebSocket connection. + +2. **`send_plain_message()`** wraps your data in **Sphinx packets** — layered encryption where each of the 5 mix nodes can only decrypt one layer and learn the next hop, never the full route. + +3. **`wait_for_messages()`** pulls from a local queue that is fed by the gateway. Messages arrive out of order (by design — this prevents timing analysis). + +4. **SURBs** (Single Use Reply Blocks) are pre-computed return routes bundled with each outgoing message. The recipient can reply without learning the sender's address. Each SURB is single-use; the SDK replenishes them automatically. + +5. **`split_sender()`** clones the send channel while the original client retains the receive side. Both can run on separate tokio tasks without locks. + +## What you've learned + +- **`MixnetClient::connect_new()`** creates an ephemeral client with in-memory keys +- **`send_plain_message(recipient, data)`** queues a Sphinx-encrypted message +- **`wait_for_messages()`** returns the next batch of received messages +- **`send_reply(sender_tag, data)`** replies anonymously via SURBs +- **`MixnetClientBuilder` + `StoragePaths`** persists identity to disk +- **`split_sender()`** enables concurrent send/receive on separate tasks +- **Always call `disconnect()`** for clean shutdown + +## Complete code + +### Ephemeral client + +New address on every run — good for quick experiments: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender}; + +#[tokio::main] +async fn main() { + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + let our_address = client.nym_address(); + println!("Connected: {our_address}"); + + client + .send_plain_message(*our_address, "hello from the mixnet!") + .await + .unwrap(); + println!("Sent — waiting for arrival..."); + + 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()) { + break msg; + } + } + }; + 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(); + + 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)); + + client.disconnect().await; +} +``` + +### Persistent identity + +Same address across restarts — use this for real applications: + +```rust +use nym_sdk::mixnet::{self, MixnetMessageSender, StoragePaths}; + +#[tokio::main] +async fn main() { + let paths = StoragePaths::new_from_dir("./my-client-data").unwrap(); + let mut client = mixnet::MixnetClientBuilder::new_with_default_storage(paths) + .await + .unwrap() + .build() + .unwrap() + .connect_to_mixnet() + .await + .unwrap(); + + let our_address = client.nym_address(); + println!("Connected: {our_address}"); + + client + .send_plain_message(*our_address, "hello from the mixnet!") + .await + .unwrap(); + println!("Sent — waiting for arrival..."); + + 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()) { + break msg; + } + } + }; + 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(); + + 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)); + + client.disconnect().await; +} +``` + +--- +title: Mixnet Module Examples +description: Runnable Rust examples for the Nym mixnet module: sending messages, SURB anonymous replies, MixnetClientBuilder, persistent storage, and parallel send/receive. +url: https://nym.com/docs/developers/rust/mixnet/examples +--- + +# Examples + +Runnable examples in [`sdk/rust/nym-sdk/examples/`](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples). Each file is self-contained with step-by-step comments. + +```bash +cargo run --example +``` + +| Example | Source | What it demonstrates | +|---|---|---| +| Simple | [`simple.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/simple.rs) | Send a message to yourself and print it | +| SURB Reply | [`surb_reply.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/surb_reply.rs) | Anonymous replies using `AnonymousSenderTag` and `send_reply()` | +| Builder | [`builder.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/builder.rs) | Using `MixnetClientBuilder` with ephemeral keys | +| Builder with Storage | [`builder_with_storage.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/builder_with_storage.rs) | Persisting keys to disk with `StoragePaths` | +| 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 | + +--- +title: Mixnet Module Troubleshooting +description: Solutions for common Nym Rust SDK issues: client disconnect errors, empty SURB messages, verbose logging, and database lock problems. +url: https://nym.com/docs/developers/rust/mixnet/troubleshooting +--- + +# Troubleshooting + +Common issues and how to resolve them. + +## Always disconnect your client + +You should always **manually disconnect your client** with `client.disconnect().await`. The client writes to a local DB and manages SURB storage, so it needs to shut down gracefully. Failing to do this can lead to the errors described below. + +## Waiting for non-empty messages + +When listening for a response, you may receive empty messages. These are SURB replenishment requests — the remote side asking for more reply SURBs. Filter them out: + +```rust +let mut message = None; +while let Some(new_message) = client.wait_for_messages().await { + if !new_message.is_empty() { + message = new_message.into_iter().next(); + break; + } +} +``` + +Prefer `client.next().await` (from the `futures::StreamExt` trait — not the Nym Stream module) over `client.wait_for_messages().await` — it returns one message at a time which is easier to work with. You'll need `use futures::StreamExt;` in scope. + +## Verbose `task client is being dropped` logging + +### On client shutdown (expected) + +When calling `client.disconnect().await`, the client logs that its background tasks are shutting down. This is normal and expected. + +Control log verbosity with `RUST_LOG`: + +```sh +RUST_LOG=warn cargo run --example simple +``` + +### Not on client shutdown (unexpected) + +If you see these messages unexpectedly, you may be killing the client process too early. See the next section. + +## Accidentally killing your client process too early + +If you see errors like `Polling shutdown failed: channel closed` or panics about `action control task has died`, your client is being dropped before it finishes sending. + +`send_plain_message()` is async, but **it only blocks until the message is placed in the client's internal queue** — not until it's actually sent into the Mixnet. After queuing, the client still needs to route-encrypt the message and interleave it with cover traffic. + +Make sure the program stays alive long enough. In practice this means awaiting a response or calling `sleep` before disconnecting: + +```rust +// Send a message +client.send_plain_message(recipient, "hello").await.unwrap(); + +// Wait for the reply (keeps the client alive) +if let Some(received) = client.wait_for_messages().await { + for r in received { + println!("Received: {}", String::from_utf8_lossy(&r.message)); + } +} + +// Always disconnect gracefully +client.disconnect().await; +``` + +## Lots of `duplicate fragment received` messages + +`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission — the original and the retransmitted copy both arrive. This is not a bug in your client logic. + +--- +title: Stream Module: AsyncRead/AsyncWrite Over the Mixnet +description: The Nym Stream module provides persistent, bidirectional byte channels over the mixnet with standard Rust AsyncRead and AsyncWrite traits. +url: https://nym.com/docs/developers/rust/stream +--- + +# Stream Module + +The Mixnet is fundamentally a message-based anonymity network — no persistent connections, no guaranteed ordering, no TCP. The default [message API](./mixnet) works at this native level: individual payloads sent independently through mix nodes. This is powerful for privacy, but it's not how most networking code works. + +The **Stream module** bridges that gap. It gives you persistent, bidirectional byte channels that look and feel like TCP sockets. Each `MixnetStream` implements Rust's standard [`AsyncRead`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncRead.html) and [`AsyncWrite`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncWrite.html) traits — use `tokio::io::copy`, codecs, `BufReader`/`BufWriter`, or any library that works with async I/O. Under the hood, the module handles framing, multiplexing, and routing so you don't have to. + +**If you're coming from socket-based networking, start here.** + +Under the hood, every stream is multiplexed over a single `MixnetClient`. A background router task decodes a small header on each incoming Mixnet message and dispatches payloads to the correct stream by ID — no extra connections or gateways needed. + +## How it works + +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()` — this 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 with a 10-byte stream header, 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 (the Mixnet doesn't guarantee message ordering, so a close could arrive before the final data). + +```mermaid +--- +config: + theme: neo-dark +--- +sequenceDiagram + participant A as Client A (opener) + participant M as Mixnet + participant B as Client B (listener) + + Note over B: listener = client.listener() + A->>M: Open message (StreamId + initial data) + M->>B: Open message delivered + Note over B: stream = listener.accept() + + A->>M: Data (StreamId + payload) + M->>B: Data delivered to stream + B->>M: Data (reply via SURBs) + M->>A: Reply delivered to stream + + Note over A: drop(stream) + Note over B: drop(stream) +``` + +## Complete example + +This is a minimal but complete example: two clients on the same machine, one opens a stream to the other, sends a message, and reads a reply. + +```rust +use nym_sdk::mixnet; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use std::time::Duration; + +const TIMEOUT: Duration = Duration::from_secs(60); + +#[tokio::main] +async fn main() { + // 1. Connect two ephemeral clients + let mut sender = mixnet::MixnetClient::connect_new().await.unwrap(); + let mut receiver = mixnet::MixnetClient::connect_new().await.unwrap(); + let receiver_addr = *receiver.nym_address(); + + // 2. The receiver creates a listener (activates stream mode) + let mut listener = receiver.listener().unwrap(); + + // 3. The sender opens a stream to the receiver's Nym address + let mut outbound = sender.open_stream(receiver_addr, None).await.unwrap(); + + // 4. The receiver accepts the incoming stream + let mut inbound = tokio::time::timeout(TIMEOUT, listener.accept()) + .await + .expect("timed out") + .expect("listener closed"); + + // 5. Send data and read it back — just like a TCP socket + outbound.write_all(b"hello from sender").await.unwrap(); + outbound.flush().await.unwrap(); + + let mut buf = vec![0u8; 1024]; + let n = tokio::time::timeout(TIMEOUT, inbound.read(&mut buf)) + .await + .expect("timed out") + .expect("read failed"); + println!("Receiver got: {}", String::from_utf8_lossy(&buf[..n])); + + // 6. Reply back through the same stream + inbound.write_all(b"hello from receiver").await.unwrap(); + inbound.flush().await.unwrap(); + + let n = tokio::time::timeout(TIMEOUT, outbound.read(&mut buf)) + .await + .expect("timed out") + .expect("read failed"); + println!("Sender got: {}", String::from_utf8_lossy(&buf[..n])); + + // 7. Clean up — streams deregister on drop, then disconnect clients + drop(outbound); + drop(inbound); + sender.disconnect().await; + receiver.disconnect().await; +} +``` + +The receiver replies via **reply SURBs** (Single Use Reply Blocks) — it never learns the sender's Nym address. This is the same anonymous reply mechanism used by the message API, applied transparently to streams. + +## When to use streams vs messages + +| | Messages | Streams | TcpProxy | +|---|---|---|---| +| **Pattern** | Raw message payloads | Persistent bidirectional channels | TCP socket proxying | +| **API** | `send_plain_message()` / `wait_for_messages()` | `AsyncRead` + `AsyncWrite` | Localhost TCP socket | +| **Multiplexing** | N/A | Multiple streams per client | One client per TCP connection | +| **Ordering** | No guarantees | No guarantees (yet) | Session-based ordering | +| **Best for** | Simple notifications, one-shot requests | Interactive protocols, streaming data, any code expecting async I/O | Wrapping existing TCP applications | +| **Status** | Stable | New | Deprecated | + +**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 [mode guard example](./stream/examples/mode-guard) for details. + +## Key types + +- [**`MixnetStream`**](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetStream.html) — a single stream implementing `AsyncRead + AsyncWrite`. Obtained from `open_stream()` (outbound) or `listener.accept()` (inbound). +- [**`MixnetListener`**](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetListener.html) — accepts inbound streams from remote peers. Created once per client via `client.listener()`. +- [**`StreamId`**](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.StreamId.html) — 8-byte random identifier (`u64`) generated by the stream opener, used to multiplex streams over a single client. + +## Next steps + +- [Tutorial: Build a private echo server](./stream/tutorial) — step-by-step guide with a server and client communicating over streams +- [Architecture](./stream/architecture) — wire protocol, router task, data flow, stream cleanup, and known limitations +- [Examples](./stream/examples) — annotated walkthroughs of the SDK examples (multi-stream, idle timeout, throughput testing) +- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/stream/) — type details and method signatures + +--- +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. +url: https://nym.com/docs/developers/rust/stream/tutorial +--- + +# Tutorial: Build a Private Echo Server + +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. + +## What you'll learn + +- Setting up a `MixnetListener` to accept incoming streams +- Opening an outbound stream with `open_stream()` +- Reading and writing with standard tokio I/O traits +- How streams are multiplexed over a single `MixnetClient` +- Clean shutdown and stream lifecycle + +Code verified against `nym-sdk` v1.20.4 ([`4077717`](https://github.com/nymtech/nym/commit/4077717d3)). If the API has changed since then, check the [examples in the repo](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) for the latest usage. + +## Prerequisites + +- Rust toolchain (1.70+) +- A working internet connection (clients connect to the live Nym Mixnet) + +## Step 1: Set up the project + +```sh +cargo init nym-echo +cd nym-echo +``` + +Add dependencies to `Cargo.toml`: + +```toml +[dependencies] +nym-sdk = "1.20.4" +tokio = { version = "1", features = ["full"] } +``` + +## Step 2: Build the echo server + +The server connects a `MixnetClient`, creates a listener, and accepts streams in a loop. Each stream gets its own task that reads data and writes it back. + +Create `src/bin/server.rs`: + +```rust +use nym_sdk::mixnet; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[tokio::main] +async fn main() { + // Connect to the Mixnet + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + println!("Echo server listening at: {}", client.nym_address()); + + // Create a listener — this activates stream mode. + // From this point, message-based methods are disabled. + let mut listener = client.listener().unwrap(); + + // Accept streams in a loop + loop { + let mut stream = match listener.accept().await { + Some(s) => s, + None => { + println!("Listener closed"); + break; + } + }; + + let stream_id = stream.id(); + println!("Accepted stream {stream_id}"); + + // Spawn a task to handle each stream concurrently + tokio::spawn(async move { + let mut buf = vec![0u8; 4096]; + + loop { + let n = match stream.read(&mut buf).await { + Ok(0) => break, // EOF — stream closed + Ok(n) => n, + Err(e) => { + eprintln!("Stream {stream_id} read error: {e}"); + break; + } + }; + + let data = &buf[..n]; + println!( + "Stream {stream_id} received {} bytes: {:?}", + n, + String::from_utf8_lossy(data) + ); + + // Echo it back + 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"); + }); + } +} +``` + +**`listener()` can only be called once per client.** It takes exclusive ownership of the inbound message channel. A second call returns `Error::ListenerAlreadyTaken`. + +## Step 3: Build the client + +The client connects, opens a stream to the server, sends a few messages, reads back the echoes, and disconnects. + +Create `src/bin/client.rs`: + +```rust +use nym_sdk::mixnet::{self, Recipient}; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +const TIMEOUT: Duration = Duration::from_secs(60); + +#[tokio::main] +async fn main() { + // Read the server's Nym address from the command line + let server_addr: Recipient = std::env::args() + .nth(1) + .expect("Usage: client ") + .parse() + .expect("Invalid Nym address"); + + // Connect to the Mixnet + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + println!("Client address: {}", client.nym_address()); + + // Open a stream to the server. + // The second argument (None) uses the default number of reply SURBs. + let mut stream = client.open_stream(server_addr, None).await.unwrap(); + println!("Stream opened: {}", stream.id()); + + // Send three messages and read back the echo for each + for i in 1..=3 { + let msg = format!("message {i}"); + println!("Sending: {msg}"); + + stream.write_all(msg.as_bytes()).await.unwrap(); + stream.flush().await.unwrap(); + + // Read the echo + let mut buf = vec![0u8; 1024]; + let n = tokio::time::timeout(TIMEOUT, stream.read(&mut buf)) + .await + .expect("timed out waiting for echo") + .expect("read failed"); + + println!("Echo: {}", String::from_utf8_lossy(&buf[..n])); + } + + // Drop the stream to deregister it from the router + drop(stream); + + // Disconnect the client + client.disconnect().await; + println!("Done!"); +} +``` + +## Step 4: Run it + +In one terminal, start the server: + +```sh +cargo run --bin server +``` + +It prints its Nym address: + +``` +Echo server listening at: 8gk4Y...@2xU4d... +``` + +In a second terminal, start the client with the server's address: + +```sh +cargo run --bin client 8gk4Y...@2xU4d... +``` + +You'll see the messages traverse the Mixnet and echo back: + +``` +Client address: F3qR7...@9nK2m... +Stream opened: 12345678 +Sending: message 1 +Echo: message 1 +Sending: message 2 +Echo: message 2 +Sending: message 3 +Echo: message 3 +Done! +``` + +On the server side: + +``` +Accepted stream 12345678 +Stream 12345678 received 9 bytes: "message 1" +Stream 12345678 received 9 bytes: "message 2" +Stream 12345678 received 9 bytes: "message 3" +Stream 12345678 closed +``` + +## What's happening under the hood + +1. The server's `listener()` activates **stream mode**, which spawns a **router task** that decodes incoming Mixnet messages and dispatches them by stream ID. + +2. The client's `open_stream()` generates a random 8-byte `StreamId`, sends an `Open` message through the Mixnet, and registers the stream in a local routing table. + +3. When the server's router receives the `Open` message, it delivers it to `listener.accept()`, which creates the inbound `MixnetStream`. + +4. Each `write_all()` prepends a 10-byte header (`[version][stream_id][message_type]`) and sends the data through the Mixnet as a Sphinx packet. + +5. On arrival, the router decodes the header, finds the matching stream, and delivers the raw payload to `read()`. + +6. The inbound stream replies via **reply SURBs** — it never learns the client's Nym address. This is the same anonymous reply mechanism used by the message API, applied transparently. + +7. When a stream is dropped, it deregisters from the local router. No close message is sent over the wire (because the Mixnet doesn't guarantee message ordering — a close could arrive before the final data). + +See the [Architecture](./architecture) page for the full technical details. + +## What you've learned + +- **`client.listener()`** activates stream mode and returns a `MixnetListener` +- **`listener.accept()`** blocks until a remote peer opens a stream +- **`client.open_stream(recipient, surbs)`** opens an outbound stream +- **`MixnetStream`** implements `AsyncRead + AsyncWrite` — standard tokio I/O +- Streams are **multiplexed** over a single client — you can open many to different peers +- **Cleanup is automatic on `drop`** — no close handshake needed +- **Reply SURBs** enable the server to respond without knowing the client's address + +## Complete code + +### Server (`src/bin/server.rs`) + +```rust +use nym_sdk::mixnet; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[tokio::main] +async fn main() { + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + println!("Echo server listening at: {}", client.nym_address()); + + let mut listener = client.listener().unwrap(); + + loop { + let mut stream = match listener.accept().await { + Some(s) => s, + None => break, + }; + + let stream_id = stream.id(); + println!("Accepted stream {stream_id}"); + + tokio::spawn(async move { + let mut buf = vec![0u8; 4096]; + loop { + let n = match stream.read(&mut buf).await { + Ok(0) | Err(_) => break, + Ok(n) => n, + }; + if let Err(_) = stream.write_all(&buf[..n]).await { + break; + } + stream.flush().await.unwrap(); + } + println!("Stream {stream_id} closed"); + }); + } +} +``` + +### Client (`src/bin/client.rs`) + +```rust +use nym_sdk::mixnet::{self, Recipient}; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +const TIMEOUT: Duration = Duration::from_secs(60); + +#[tokio::main] +async fn main() { + let server_addr: Recipient = std::env::args() + .nth(1) + .expect("Usage: client ") + .parse() + .expect("Invalid Nym address"); + + let mut client = mixnet::MixnetClient::connect_new().await.unwrap(); + println!("Client address: {}", client.nym_address()); + + let mut stream = client.open_stream(server_addr, None).await.unwrap(); + println!("Stream opened: {}", stream.id()); + + for i in 1..=3 { + let msg = format!("message {i}"); + println!("Sending: {msg}"); + + stream.write_all(msg.as_bytes()).await.unwrap(); + stream.flush().await.unwrap(); + + let mut buf = vec![0u8; 1024]; + let n = tokio::time::timeout(TIMEOUT, stream.read(&mut buf)) + .await + .expect("timed out waiting for echo") + .expect("read failed"); + + println!("Echo: {}", String::from_utf8_lossy(&buf[..n])); + } + + drop(stream); + client.disconnect().await; + println!("Done!"); +} +``` + +--- +title: Stream Module Architecture +description: Internal architecture of the Nym Stream subsystem: wire protocol, multiplexing, router task, and how concurrent byte channels share a single MixnetClient. +url: https://nym.com/docs/developers/rust/stream/architecture +--- + +# Stream Architecture + +{/* Canonical source: sdk/rust/nym-sdk/src/mixnet/stream/ARCHITECTURE.md */} + +## Overview + +The stream subsystem gives each `MixnetClient` the ability to hold many concurrent byte channels (`AsyncRead + AsyncWrite`) to different remote peers, multiplexed over a single client connection. + +```mermaid +--- +config: + theme: neo-dark +--- +flowchart TD + subgraph MixnetClient + SA["MixnetStream A"] -->|writes| CI["Client input channel"] + SB["MixnetStream B"] -->|writes| CI + CI --> MX["── Mixnet ──"] + MX --> RT["Router task"] + RT -->|Open messages| ML["MixnetListener.accept()"] + RT -->|Data messages| SM["Stream routing table"] + SM --> SA + SM --> SB + end +``` + +## Wire protocol + +Every stream message has a fixed 10-byte header prepended to the payload: + +``` +[Version: 1 byte][StreamId: 8 bytes][MessageType: 1 byte][payload ...] +``` + +- **Version** — protocol version (`1`). Unknown versions are rejected. +- **StreamId** — random `u64` generated by the opener, used to multiplex streams. +- **MessageType** — `Open` (0) or `Data` (1). + +There is no `Close` message type — see [Known Limitations](#known-limitations) for why. + +## Stream mode + +Stream mode is activated lazily on the first call to `open_stream()` or `listener()`. This is a **one-way transition**: + +1. The client's message receiver is handed off to a background router task +2. `stream_mode` flag is set to `true` +3. Message-based methods (`send_plain_message`, `wait_for_messages`) are disabled and return errors + +There is no switching back without disconnecting and creating a new client. + +## Opening and accepting streams + +**Opening (outbound):** +1. `open_stream(recipient, surbs)` generates a random `StreamId` +2. An `Open` message is sent through the Mixnet to the recipient +3. A `MixnetStream` is returned, ready for writing and reading + +**Accepting (inbound):** +1. `listener.accept()` waits for an `Open` message from a remote peer +2. A `MixnetStream` is created with the opener's `sender_tag` for anonymous replies +3. The stream is ready for bidirectional I/O + +## Cleanup + +- **On `drop`** — the stream deregisters from the routing table. No close message is sent over the wire. +- **Idle timeout** — streams idle for longer than the configured timeout (default: 30 minutes) are automatically cleaned up. Configure with [`MixnetClientBuilder::with_stream_idle_timeout()`](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/struct.MixnetClientBuilder.html). + +## Known limitations + +**No message ordering.** The Mixnet does not guarantee message ordering. Messages on a stream can arrive out of order. This means: +- Large writes that span multiple Sphinx packets may arrive shuffled +- There is no `Close` message — a close could race ahead of in-flight data +- Protocols that depend on byte ordering (HTTP, TLS, protobuf) may not work correctly over streams yet + +Sequencing (similar to the `MessageBuffer` in the TcpProxy module) is planned for a future release. + +**No protocol discriminator.** There is currently no way to distinguish stream messages from regular Mixnet messages. Sending to a non-stream client will deliver bytes with the stream header prepended. A protocol discriminator is planned for a future release. + +## Internal details + +For the full implementation details (router task, `StreamMap`, `PollSender` usage, base-client type rationale), see the [architecture documentation in the source tree](https://docs.rs/nym-sdk/latest/nym_sdk/mixnet/stream/) or the `ARCHITECTURE.md` file next to the module code. + +--- +title: Stream Module Examples +description: Runnable Rust examples for the Nym Stream module: bidirectional read/write, idle timeouts, mode guards, and throughput benchmarks. +url: https://nym.com/docs/developers/rust/stream/examples +--- + +# Examples + +Runnable examples in [`sdk/rust/nym-sdk/examples/`](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples). Each file is self-contained with step-by-step comments. + +```bash +cargo run --example +``` + +| Example | Source | What it demonstrates | +|---|---|---| +| Simple Read/Write | [`stream_simple_read_write.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_simple_read_write.rs) | Multiple concurrent streams, bidirectional communication | +| Idle Timeout | [`stream_idle_timeout.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_idle_timeout.rs) | Configuring `with_stream_idle_timeout`, observing EOF after cleanup | +| Mode Guard | [`stream_mode_guard.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_mode_guard.rs) | Mutual exclusion between stream and message modes | +| Throughput | [`stream_throughput.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/stream_throughput.rs) | Sending 1 MB over a single stream, verifying data integrity | + +--- +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. +url: https://nym.com/docs/developers/rust/tcpproxy +--- + +# TcpProxy Module + + **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. + +The Stream module offers the same key benefit (familiar I/O patterns on top of the Mixnet) with a simpler API, multiplexed connections on a single client, and no localhost socket overhead. The one feature TcpProxy has that streams don't yet have is **message ordering** — see the [stream architecture](./stream/architecture#known-limitations) for details. If your application requires guaranteed byte ordering today, TcpProxy still works. + +--- + +This module exposes `NymProxyClient` and `NymProxyServer` for proxying TCP traffic through the Mixnet. Both are initialised and run in a background thread, exposing a configurable `localhost` socket which you can read/write to without worrying about the Mixnet's message-based internals. + +> Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](../tools/standalone-tcpproxy). + +## Examples + +| Example | Source | +|---|---| +| Single connection | [`tcp_proxy_single_connection.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_single_connection.rs) | +| Multiple connections | [`tcp_proxy_multistream.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_multistream.rs) | + +## 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 + +## Troubleshooting + +### Lots of `duplicate fragment received` messages + +`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission — the original and the retransmitted copy both arrive at the destination. This is not a bug in your client logic or the TcpProxy module. + +--- +title: TcpProxy Tutorial: Tunnel TCP Through the Mixnet +description: Build a proxy server and client that tunnel TCP traffic through the Nym mixnet using the TcpProxy module. Includes NymProxyServer and NymProxyClient setup. +url: https://nym.com/docs/developers/rust/tcpproxy/tutorial +--- + +# Tutorial: Tunnel TCP Through the Mixnet + +The TcpProxy module is **unmaintained**. For new projects, use the [Stream module](../stream) instead. This tutorial exists for users working with existing TcpProxy-based code. + +Build two programs — a proxy server that forwards TCP traffic to a local service, and a proxy client that tunnels connections through the Mixnet. + +## What you'll learn + +- Setting up a `NymProxyServer` that forwards Mixnet traffic to a local TCP service +- Setting up a `NymProxyClient` that tunnels localhost TCP connections through the Mixnet +- How TcpProxy differs from the Stream module + +## Prerequisites + +- Rust toolchain (1.70+) +- A working internet connection + +Code verified against `nym-sdk` v1.20.4 ([`4077717`](https://github.com/nymtech/nym/commit/4077717d3)). If the API has changed since then, check the [examples in the repo](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) for the latest usage. + +## Step 1: Set up the project + +```sh +cargo init nym-tcp-proxy +cd nym-tcp-proxy +``` + +Add dependencies to `Cargo.toml`: + +```toml +[dependencies] +nym-sdk = "1.20.4" +tokio = { version = "1", features = ["full"] } +anyhow = "1" +``` + +This tutorial creates two binaries (server and client). Add them to `Cargo.toml`: + +```toml +[[bin]] +name = "proxy_server" +path = "src/bin/proxy_server.rs" + +[[bin]] +name = "proxy_client" +path = "src/bin/proxy_client.rs" +``` + +Create the `src/bin/` directory: + +```sh +mkdir -p src/bin +``` + +## Step 2: Build the server + +The server connects to the Mixnet and forwards incoming traffic to a local TCP service (e.g. a web server on port 3000). + +Create `src/bin/proxy_server.rs`: + +```rust +use nym_sdk::tcp_proxy::NymProxyServer; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Forward traffic to localhost:3000 (your upstream service). + // The second argument is a directory for persistent key storage. + let mut server = NymProxyServer::new( + "127.0.0.1:3000", // upstream address (host:port) + "./proxy-server-config", // config directory for persistent keys + None, // env file (None = mainnet) + None, // gateway (None = auto-select) + ).await?; + + // Print the Nym address — the client needs this to connect. + println!("Proxy server address: {}", server.nym_address()); + + // Blocks until shutdown. Traffic from the Mixnet is forwarded to + // localhost:3000, and responses are sent back via reply SURBs. + server.run_with_shutdown().await?; + Ok(()) +} +``` + +## Step 3: Build the client + +The client opens a localhost TCP socket and tunnels all traffic through the Mixnet to the server. + +Create `src/bin/proxy_client.rs`: + +```rust +use nym_sdk::tcp_proxy::NymProxyClient; +use nym_sdk::mixnet::Recipient; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Parse the server's Nym address from the command line. + let server_addr: Recipient = std::env::args() + .nth(1).expect("Usage: proxy_client ") + .parse()?; + + // Create the proxy client — listens on localhost:1080. + let client = NymProxyClient::new( + server_addr, + "127.0.0.1", // listen host + "1080", // listen port + 60, // close timeout (seconds) + None, // env file (None = mainnet) + 2, // client pool size + ).await?; + + // Spawn the proxy in the background. + let proxy = tokio::spawn(async move { client.run().await }); + + // Give the proxy a moment to start listening. + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + + // Connect to the local proxy socket — traffic goes through the Mixnet. + let mut stream = TcpStream::connect("127.0.0.1:1080").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(()) +} +``` + +## Step 4: Run it + +Start a simple upstream service (e.g. Python's HTTP server): + +```sh +cd /tmp && echo "hello from upstream" > index.html +python3 -m http.server 3000 +``` + +Terminal 1 — start the proxy server: +```sh +cargo run --bin proxy_server +# Proxy server address: 8gk4Y...@2xU4d... +``` + +Terminal 2 — start the proxy client: +```sh +cargo run --bin proxy_client 8gk4Y...@2xU4d... +# Response: +# HTTP/1.0 200 OK +# ... +# hello from upstream +``` + +The HTTP request travelled through the Mixnet — the upstream server only sees a connection from `localhost`, not the client's real IP. + +## How it differs from streams + +TcpProxy handles **message ordering** internally using session IDs and sequence numbers, which the [Stream module](../stream) does not yet provide. This means TcpProxy can work with protocols that depend on byte ordering (HTTP, TLS). The trade-off is higher overhead: each side runs a localhost TCP socket, and ordering adds latency. For new code, the stream API is simpler and more efficient. + +## What you've learned + +- **`NymProxyServer::new(upstream, config_dir, env, gateway)`** creates a server that forwards Mixnet traffic to a local TCP service +- **`NymProxyClient::new(recipient, host, port, timeout, env, pool_size)`** creates a client that tunnels localhost TCP through the Mixnet +- **The server uses persistent keys** (stored in `config_dir`) so its Nym address stays the same across restarts +- **The client uses ephemeral keys** from a `ClientPool` — one per TCP connection +- **TcpProxy handles message ordering** — unlike the Stream module, it can work with order-dependent protocols like HTTP + +--- +title: TcpProxy Architecture +description: Architecture of the Nym TcpProxy module: client and server design, byte framing, session management, and message ordering over the mixnet. +url: https://nym.com/docs/developers/rust/tcpproxy/architecture +--- + +# Architecture + +**This module is unmaintained.** See the [Stream module](../stream) for the actively developed replacement. Existing users should plan to migrate when possible. + +## Motivations +The motivation behind the creation of the `TcpProxy` module is to allow developers to interact with the Mixnet in a way that is far more familiar to them: simply setting up a connection with a transport, being returned a socket, and then being able to stream data to/from it, similar to something like the Tor [`arti`](https://gitlab.torproject.org/tpo/core/arti/-/tree/main/crates/arti-client) client. + +## Clients +Each of the sub-modules exposed by the `TcpProxy` deal with Nym clients in a different way. +- the `NymProxyClient` relies on the [`Client Pool`](../client-pool) to create clients and keep a certain number of them in reserve. If the amount of incoming TCP connections rises quicker than the Client Pool can create clients, or you have the pool size set to `0`, the `TcpProxyClient` creates an ephemeral client per new TCP connection, which is closed according to the configurable timeout: we map one ephemeral client per TCP connection. This is to deal with multiple simultaneous streams. +- the `NymProxyServer` has a single Nym client with a persistent identity. + +## Framing +We are currently relying on the [`tokio::Bytecodec`](https://docs.rs/tokio-util/latest/tokio_util/codec/struct.BytesCodec.html) and [`framedRead`](https://docs.rs/tokio-util/latest/tokio_util/codec/struct.Framed.html) to frame bytes moving through the `NymProxyClient` and `NymProxyServer`. + +> For those interested, under the hood the client uses our own [`NymCodec`](https://github.com/nymtech/nym/blob/27ac34522cf0f8bfe1ca265e0b57ee52f2ded0d2/common/nymsphinx/framing/src/codec.rs) to frame message bytes as Sphinx packet payloads. + +## Sessions & Message Ordering +We have implemented session management and message ordering, where messages are wrapped in a session ID per connection, with individual messages being given an incrementing message ID. Once all the messages have been sent, the `NymProxyClient` then sends a `Close` message as the last outgoing message. This is to notify the `NymProxyServer` that there are no more outbound messages for this session, and that it can trigger the session timeout. + +> 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 gRPC parser before the 1st half, even if it is received first. + +Lets step through a full request/response path between a client process communicating with a remote host via the proxies: + +### Outgoing Client Request +The `NymProxyClient` instance, once initialised and running, listens out for incoming TCP connections on its localhost port. + +On receiving one, it will create a new session ID and packetise the incoming bytes into messages of the following structure: + +```rust +pub struct ProxiedMessage { + message: Payload, + session_id: Uuid, + message_id: u16, +} +``` + +> This code can be found [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/tcp_proxy/utils.rs#L147C1-L152C2) + +And then send these to the Nym address of the `NymProxyServer` instance. Not much to see here regarding message ordering, as the potential for reordering only starts once packets are travelling through the Mixnet. + +```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->>EntryGateway: Sphinx Packets: Message 1 + EntryGateway-->>NymProxyClient: Acks + NymProxyClient->>EntryGateway: Sphinx Packets: Message 2 + EntryGateway-->>NymProxyClient: Acks + NymProxyClient->>EntryGateway: Sphinx Packets: Message 3 + EntryGateway-->>NymProxyClient: Acks + NymProxyClient->>EntryGateway: Sphinx Packets: Close Message + NymProxyClient->>NymProxyClient: Start Client Close timeout + EntryGateway-->>NymProxyClient: Acks +``` + +### Server Receives Request & Responds + +Here is a diagrammatic representation of a situation in which the request arrives out of order, and how the message buffer deals with this so as not to pass a malformed request upstream to the process running on the same remote host: + +```mermaid +--- +config: + theme: neo-dark + layout: elk +--- +sequenceDiagram + Exit Gateway->>NymProxyServer: Sphinx Packets: Message 2 + NymProxyServer-->>Exit Gateway: Acks + Exit Gateway->>NymProxyServer: Sphinx Packets: Message 3 + 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: Message Close + 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 +``` + +> Note that this is per-session, with a session mapped to a single TCP connection. Both the `NymProxyClient` and `Server` are able to handle multiple concurrent connections. + +### Client Receives Response + +The `ProxyClient` deals with incoming traffic in the same way as the `ProxyServer`, with a per-session message queue: + +```mermaid +--- +config: + theme: neo-dark + layout: elk +--- +sequenceDiagram + box Local Machine + participant Client Process + participant NymProxyClient + end + Entry Gateway--xNymProxyClient: Sphinx Packets: Reply Message 1 dropped: No Ack! + Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 2 + NymProxyClient-->Entry Gateway: Ack + Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 3 + 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** +``` + +After receiving the packets, it can then forward the recorded bytes to the requesting process. + +### Full Flow Diagram +```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: Message 3 + 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 + Exit Gateway->>NymProxyServer: Sphinx Packets: Message 3 + 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--xNymProxyClient: Sphinx Packets: Reply Message 1 dropped: No Ack! + Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 2 + NymProxyClient-->Entry Gateway: Ack + Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 3 + 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** +``` + +--- +title: Client Pool: Pre-Connected Mixnet Clients +description: The Nym ClientPool maintains ready-to-use MixnetClient instances, eliminating connection latency for bursty traffic patterns. +url: https://nym.com/docs/developers/rust/client-pool +--- + +# Client Pool + +The `ClientPool` maintains a configurable number of connected ephemeral `MixnetClient` instances, ready for immediate use. This eliminates the connection latency (gateway handshake, key generation, topology fetch) that comes with creating a new client on each request. + +## How it works + +```mermaid +--- +config: + theme: neo-dark +--- +flowchart LR + BG["Background loop"] -->|creates clients| P["Pool (Vec)"] + P -->|"get_mixnet_client()"| APP["Your application"] + APP -->|uses and disconnects| D["Done"] + 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 + +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). + +The `NymProxyClient` (TcpProxy) uses a `ClientPool` internally — one client per incoming TCP connection. + +## Quick example + +```rust +use nym_sdk::client_pool::ClientPool; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let pool = ClientPool::new(5); // maintain 5 clients in reserve + + let pool_clone = pool.clone(); + tokio::spawn(async move { pool_clone.start().await }); + + // Get a client when needed + if let Some(client) = pool.get_mixnet_client().await { + println!("Got client: {}", client.nym_address()); + client.disconnect().await; + } + + pool.disconnect_pool().await; + Ok(()) +} +``` + +## Further reading + +- [Tutorial: Handle bursty traffic](./client-pool/tutorial) — step-by-step guide covering pool creation, burst handling, and fallback logic +- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/client_pool/) — type details, method signatures, and architecture docs +- [Example source on GitHub](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs) — complete working example + +--- +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. +url: https://nym.com/docs/developers/rust/client-pool/tutorial +--- + +# Tutorial: Handle Bursty Traffic with Client Pool + +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. + +## What you'll learn + +- Creating and starting a `ClientPool` +- Popping clients from the pool for concurrent operations +- Falling back to on-demand client creation when the pool is empty +- Observing pool replenishment +- Graceful shutdown + +Code verified against `nym-sdk` v1.20.4 ([`4077717`](https://github.com/nymtech/nym/commit/4077717d3)). If the API has changed since then, check the [examples in the repo](https://github.com/nymtech/nym/tree/develop/sdk/rust/nym-sdk/examples) for the latest usage. + +## Prerequisites + +- Rust toolchain (1.70+) +- A working internet connection + +## Step 1: Set up the project + +```sh +cargo init nym-pool-demo +cd nym-pool-demo +``` + +Add dependencies to `Cargo.toml`: + +```toml +[dependencies] +nym-sdk = "1.20.4" +tokio = { version = "1", features = ["full"] } +``` + +## Step 2: Create and start the pool + +The pool is created with a **reserve size** — the number of connected clients it tries to maintain at all times. The `start()` method runs a background loop that creates clients whenever the pool drops below the reserve. + +Create `src/main.rs`: + +```rust +use nym_sdk::client_pool::ClientPool; +use nym_sdk::mixnet::MixnetMessageSender; +use std::time::Duration; + +#[tokio::main] +async fn main() { + // Create a pool that maintains 3 clients in reserve + let pool = ClientPool::new(3); + + // Start the pool in a background task. + // It immediately begins connecting clients. + 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; + + // Check how many are ready + let count = pool.get_client_count().await; + println!("Pool has {count} clients ready"); +``` + +Creating a `MixnetClient` takes several seconds (gateway handshake, key generation, topology fetch). The pool does this work ahead of time so your application doesn't block when it needs a client. + +## Step 3: Pop clients and use them + +When you call `get_mixnet_client()`, the pool removes a client and returns it. The background loop notices the shortfall and starts creating a replacement. + +```rust + // Simulate a burst of 3 concurrent tasks, each needing a client + let mut handles = vec![]; + + for i in 1..=3 { + let pool = pool.clone(); + + let handle = tokio::spawn(async move { + // Pop a client from the pool + let client = match pool.get_mixnet_client().await { + Some(c) => { + println!("Task {i}: got client {} from pool", c.nym_address()); + c + } + None => { + // 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 + let addr = *client.nym_address(); + client + .send_plain_message(addr, format!("hello from task {i}")) + .await + .unwrap(); + + // Wait for the message to arrive + if let Some(msgs) = client.wait_for_messages().await { + for msg in msgs { + if !msg.message.is_empty() { + println!( + "Task {i}: received {:?}", + String::from_utf8_lossy(&msg.message) + ); + } + } + } + + // Disconnect when done — the pool will create a replacement + client.disconnect().await; + println!("Task {i}: done"); + }); + + handles.push(handle); + } + + // Wait for all tasks to finish + for h in handles { + h.await.unwrap(); + } +``` + +## Step 4: Observe replenishment + +After popping all 3 clients, the pool background loop starts creating replacements. Give it time and check: + +```rust + // Pool should be replenishing + println!("\nWaiting for pool to replenish..."); + tokio::time::sleep(Duration::from_secs(15)).await; + + let count = pool.get_client_count().await; + println!("Pool has {count} clients ready again"); +``` + +## Step 5: Shut down gracefully + +```rust + // Disconnect all remaining clients and stop the background loop + pool.disconnect_pool().await; + println!("Pool shut down"); +} +``` + +## Step 6: Run it + +```sh +cargo run +``` + +You'll see output like: + +``` +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 +Task 3: got client A7bN2...@4pL8w... from pool +Task 1: received "hello from task 1" +Task 2: received "hello from task 2" +Task 3: received "hello from task 3" +Task 1: done +Task 2: done +Task 3: done + +Waiting for pool to replenish... +Pool has 3 clients ready again +Pool shut down +``` + +## When to use the pool + +The pool is most useful when: + +- **You have bursty traffic** — many concurrent operations that each need their own client +- **Latency matters** — you can't afford the several-second delay of creating a client on each request +- **You're building a service** — an API endpoint that creates a client per request would benefit from pre-warmed clients + +If your application only ever needs one client at a time, just use `MixnetClient::connect_new()` directly. + +The `NymProxyClient` (TcpProxy module) uses a `ClientPool` internally — one client per incoming TCP connection. + +## What you've learned + +- **`ClientPool::new(n)`** creates a pool targeting `n` reserve clients +- **`pool.start()`** runs a background loop that creates clients whenever the pool is below reserve +- **`pool.get_mixnet_client()`** pops a client — returns `None` if the pool is empty +- **Clients are consumed, not returned** — the pool automatically creates replacements +- **`pool.disconnect_pool()`** shuts down all remaining clients and stops the background loop +- **Fall back to on-demand creation** when the pool is empty for resilience + +## Complete code + +```rust +use nym_sdk::client_pool::ClientPool; +use nym_sdk::mixnet::MixnetMessageSender; +use std::time::Duration; + +#[tokio::main] +async fn main() { + 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..."); + tokio::time::sleep(Duration::from_secs(15)).await; + println!("Pool has {} clients", pool.get_client_count().await); + + let mut handles = vec![]; + for i in 1..=3 { + let pool = pool.clone(); + handles.push(tokio::spawn(async move { + let client = match pool.get_mixnet_client().await { + Some(c) => c, + None => nym_sdk::mixnet::MixnetClient::connect_new().await.unwrap(), + }; + + let addr = *client.nym_address(); + client + .send_plain_message(addr, format!("hello from task {i}")) + .await + .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)); + } + } + + client.disconnect().await; + })); + } + + for h in handles { + h.await.unwrap(); + } + + println!("Waiting for replenishment..."); + tokio::time::sleep(Duration::from_secs(15)).await; + println!("Pool has {} clients", pool.get_client_count().await); + + pool.disconnect_pool().await; + println!("Done"); +} +``` + +--- +title: FFI Bindings: Go and C/C++ +description: Use the Nym SDK from Go and C/C++ via FFI bindings. Covers mixnet messaging, anonymous replies, and TcpProxy lifecycle from non-Rust languages. +url: https://nym.com/docs/developers/rust/ffi +--- + +# FFI Bindings + +The SDK exposes FFI bindings for Go and C/C++. The source lives in [`sdk/ffi`](https://github.com/nymtech/nym/tree/develop/sdk/ffi): + +``` +ffi +├── cpp # C/C++ bindings (manual C FFI) +├── go # Go bindings (via uniffi-bindgen-go) +└── 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. + +## What's exposed + +**Mixnet** (Go and C/C++) — ephemeral and persistent client creation, sending messages, anonymous replies via SURBs, and listening for incoming messages. + +**TcpProxy** (Go only) — client and server creation and lifecycle. + +The TcpProxy module is deprecated. For new projects, use the [Stream module](./stream) instead. + +**Client Pool and Stream** — no standalone FFI bindings yet. The TcpProxy bindings use the Client Pool internally. + +## Quick example (Go) + +```go +// Initialize an ephemeral client +bindings.InitEphemeral() + +// Get our Nym address +addr, _ := bindings.GetSelfAddress() + +// Send a message through the Mixnet +bindings.SendMessage(addr, "hello from Go") + +// Listen for incoming messages +msg, _ := bindings.ListenForIncoming() +fmt.Println("Received:", msg.Message) + +// Reply anonymously via SURBs +bindings.Reply(msg.Sender, "reply from Go") +``` + +## Quick example (C++) + +The C++ bindings use callbacks for return values and a `ReceivedMessage` struct for incoming data: + +```cpp +extern "C" { + struct ReceivedMessage { + const uint8_t* message; + size_t size; + const char* sender_tag; + }; + + void init_logging(); + char init_ephemeral(); + char get_self_address(void (*callback)(const char*)); + char send_message(const char*, const char*); + char listen_for_incoming(void (*callback)(ReceivedMessage)); + char reply(const char*, const char*); +} + +// Get address via callback +char addr[134]; +void on_address(const char* s) { strcpy(addr, s); } + +// Receive message via callback +char sender_tag[22]; +void on_message(ReceivedMessage msg) { + std::cout << "Received: " << msg.message << std::endl; + strcpy(sender_tag, msg.sender_tag); +} + +int main() { + init_ephemeral(); + get_self_address(on_address); + send_message(addr, "hello from C++"); + listen_for_incoming(on_message); + reply(sender_tag, "reply from C++"); +} +``` + +## Building + +Each language has a `build.sh` script that compiles the Rust shared library and generates bindings. See the README in each directory for prerequisites. + +## Examples and source + +- [Go mixnet example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/go/example.go) — full client lifecycle: init, send, receive, SURB reply +- [Go TcpProxy example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/go/proxy_example.go) — proxy client and server with TCP echo +- [C++ example](https://github.com/nymtech/nym/blob/develop/sdk/ffi/cpp/src/main.cpp) — same flow using Boost threads +- [`sdk/ffi` source](https://github.com/nymtech/nym/tree/develop/sdk/ffi) — full source and build scripts + +--- +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. +url: https://nym.com/docs/developers/typescript +--- + +# 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 Nym mixnet is not like regular internet networking.** There are no persistent connections, no guaranteed message ordering, and no TCP underneath. Traffic is Sphinx-encrypted, mixed through multiple nodes, and independently reconstructed at the destination. This means sending data through the mixnet works differently from what web developers typically expect. The SDK abstracts the complexity, but understanding the underlying model helps when debugging. + +## Packages + + **mixFetch** + + 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 + +
+
+
+ + **Mixnet Client** + + Send and receive text and binary messages over the Nym mixnet + +
+
+
+ + **Nym Smart Contracts** + + Query and execute methods on the smart contracts that run the Nym mixnet + +### Which variant should I use? + +All packages (except Contract Clients) come in four variants: + +- **ESM** — For new projects with current tooling. You may need to [configure your bundler](./typescript/bundling) to handle WASM and web worker components. +- **ESM full-fat** — Pre-bundled with inline WASM and web workers. No bundler config needed. +- **CommonJS** — For older projects using CommonJS. WASM and web workers need to be [bundled](./typescript/bundling/webpack). +- **CommonJS full-fat** — Pre-bundled, works without additional configuration. + +All `*-full-fat` variants have large bundle sizes because they include WASM and web workers as inline Base64 strings. Use the standard ESM variant if bundle size matters. + +## Installation + +### mixFetch + +```bash +npm install @nymproject/mix-fetch-full-fat +``` + +### Mixnet Client + +```bash +npm install @nymproject/sdk-full-fat +``` + +### Nym Smart Contracts + +```bash +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing +``` + +### Install everything + +```bash +npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @nymproject/sdk-full-fat @nymproject/mix-fetch-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 + +// 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: + +```js + +const nym = await createNymMixnetClient(); +const nymApiUrl = 'https://validator.nymtech.net/api'; + +// Subscribe to incoming messages +nym.events.subscribeToTextMessageReceivedEvent((e) => { + console.log('Got a message: ', e.args.payload); +}); + +// Connect to the mixnet +await nym.client.start({ clientId: 'my-app', nymApiUrl }); + +// Send a message to yourself +const recipient = nym.client.selfAddress(); +nym.client.send({ payload: 'Hello mixnet', recipient }); +``` + +### Nym Smart Contracts + +Use the [Contract Clients](https://www.npmjs.com/package/@nymproject/contract-clients) to query or execute on Nym smart contracts: + +```js + +const signer = await DirectSecp256k1HdWallet.fromMnemonic("..."); +const accounts = await signer.getAccounts(); + +const cosmWasmSigningClient = await SigningCosmWasmClient.connectWithSigner( + "https://rpc.nymtech.net:443", signer +); +const client = new contracts.Mixnet.MixnetClient( + cosmWasmSigningClient, + accounts[0].address, + 'n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr' +); + +// Delegate 1 NYM to mixnode with id 100 +const result = await client.delegateToMixnode( + { mixId: 100 }, 'auto', undefined, + [{ amount: `${1_000_000}`, denom: 'unym' }] +); +console.log(`Tx Hash = ${result.transactionHash}`); +``` + +## Next steps + +- **[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 + +--- +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. +url: https://nym.com/docs/developers/typescript/examples/mix-fetch +--- + +# mixFetch + +An easy way to secure parts or all of your web app is to replace calls to [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with `mixFetch`. It works the same as vanilla `fetch` — it's a proxied wrapper around the original function. + +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. + +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/). + +## Environment Setup + +Create a new project with Vite: + +```bash +npm create vite@latest +``` + +Choose React + TypeScript, then: + +```bash +cd +npm i +npm run dev +``` + +## Installation + +```bash +npm install @nymproject/mix-fetch-full-fat +``` + +## Configuration + +```ts + +const mixFetchOptions: SetupMixFetchOps = { + clientId: "docs-mixfetch-demo", + preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", + mixFetchOverride: { + requestTimeoutMs: 60_000, + }, + forceTls: true, // force WSS +}; +``` + +## 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. + +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. + +```tsx + +const defaultUrl = + "https://nymtech.net/.wellknown/network-requester/exit-policy.txt"; +const args = { mode: "unsafe-ignore-cors" }; +const mixFetchOptions: SetupMixFetchOps = { + clientId: "docs-mixfetch-demo", + preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", + mixFetchOverride: { + requestTimeoutMs: 60_000, + }, + forceTls: true, +}; + +// Log entry type for the visible log panel +type LogLevel = "info" | "error" | "send" | "receive"; +type LogEntry = { timestamp: string; message: string; level: LogLevel }; + +const logColors: Record = { + info: "gray", + error: "red", + send: "blue", + receive: "green", +}; + +const logLabels: Record = { + info: "INFO", + error: "ERROR", + send: "SEND", + receive: "RECV", +}; + +export const MixFetch = () => { + // MixFetch initialization state + const [status, setStatus] = useState<"idle" | "starting" | "ready" | "error">("idle"); + const [errorMsg, setErrorMsg] = useState(null); + + // Log panel state + const [logs, setLogs] = useState([]); + const logEndRef = useRef(null); + + // Single fetch state + const [url, setUrl] = useState(defaultUrl); + const [html, setHtml] = useState(); + const [busy, setBusy] = useState(false); + + // Concurrent fetch state + const [concurrentResults, setConcurrentResults] = useState([]); + const [concurrentBusy, setConcurrentBusy] = useState(false); + + // Auto-scroll log panel to bottom + useEffect(() => { + logEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [logs]); + + // Helper to add a timestamped log entry + const addLog = (message: string, level: LogLevel) => { + const timestamp = new Date().toISOString().substring(11, 23); + setLogs((prev) => [...prev, { timestamp, message, level }]); + }; + + // Initialize MixFetch explicitly via createMixFetch + const handleStart = async () => { + try { + setStatus("starting"); + setErrorMsg(null); + addLog("Starting MixFetch...", "info"); + await createMixFetch(mixFetchOptions); + setStatus("ready"); + addLog("MixFetch is ready!", "info"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + setStatus("error"); + setErrorMsg(msg); + addLog(`Error: ${msg}`, "error"); + } + }; + + // Single URL fetch — reuses the existing MixFetch singleton + const handleFetch = async () => { + try { + setBusy(true); + setHtml(undefined); + addLog(`Sending request to ${url}...`, "send"); + const response = await mixFetch(url, args, mixFetchOptions); + const resHtml = await response.text(); + setHtml(resHtml); + addLog(`Response received (${resHtml.length} bytes)`, "receive"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + addLog(`Fetch error: ${msg}`, "error"); + } finally { + setBusy(false); + } + }; + + // Send 5 concurrent requests to different URLs on the same domain + const handleConcurrentFetch = async () => { + const baseUrl = "https://jsonplaceholder.typicode.com/posts/"; + const count = 5; + try { + setConcurrentBusy(true); + setConcurrentResults([]); + addLog( + `Starting ${count} concurrent requests to ${baseUrl}1-${count}...`, + "send", + ); + const requests = Array.from({ length: count }, (_, i) => { + const targetUrl = `${baseUrl}${i + 1}`; + return mixFetch(targetUrl, args, mixFetchOptions) + .then((res) => res.json()) + .then((json: { id: number; title: string }) => { + const entry = `[${json.id}] ${json.title}`; + addLog(entry, "receive"); + return entry; + }); + }); + const results = await Promise.all(requests); + setConcurrentResults(results); + addLog(`All ${count} concurrent requests completed!`, "info"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + addLog(`Concurrent fetch error: ${msg}`, "error"); + } finally { + setConcurrentBusy(false); + } + }; + + const isReady = status === "ready"; + + return ( + + {/* Start MixFetch */} + + Start MixFetch + + {status === "starting" && } + + {status === "idle" ? "Not started" : + status === "starting" ? "Starting..." : + status === "ready" ? "Ready" : + `Error: ${errorMsg}`} + + {/* Fetch controls — disabled until MixFetch is ready */} + + {/* Single fetch */} + + setUrl(e.target.value)} + /> + + Fetch + + {busy && } + {html && ( + <> + Response + + {html} + + + )} + + {/* Concurrent fetch */} + + Concurrent Requests + + Send 5 Concurrent Requests (posts/1-5) + + {concurrentBusy && } + {concurrentResults.length > 0 && ( + + {concurrentResults.map((result, i) => ( + {result} + ))} + + )} + + {/* Log Panel */} + {logs.length > 0 && ( + + Log + {logs.map((entry, i) => ( + + {entry.timestamp} [{logLabels[entry.level]}] {entry.message} + + ))} + + )} + + ); +}; +``` + +--- +title: TypeScript Mixnet Client Example +description: Send and receive private messages in the browser using the Nym TypeScript SDK. Includes setup, SURB anonymous replies, and environment configuration. +url: https://nym.com/docs/developers/typescript/examples/mixnet +--- + +# Mixnet Client + +The [`SDK Client`](https://www.npmjs.com/package/@nymproject/sdk) lets you send and receive messages over the Nym mixnet. + +The client is message-based — it sends one-way messages to another client's address. Replying can be achieved in two ways: +- Reveal the sender's address to the recipient (as part of the payload) +- Use a SURB (single use reply block) that lets the recipient reply without compromising the identity of either party + +## Environment Setup + +Create a new project with Vite: + +```bash +npm create vite@latest +``` + +Choose React + TypeScript, then: + +```bash +cd +npm i +npm run dev +``` + +## Installation + +```bash +npm install @nymproject/sdk-full-fat +``` + +## Full Example + +This example creates a Mixnet client, connects to a gateway, and provides a UI for sending and receiving messages through the mixnet. + +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. + +```ts copy filename="App.tsx" + + createNymMixnetClient, + NymMixnetClient, + Payload, +} from "@nymproject/sdk-full-fat"; + +const nymApiUrl = "https://validator.nymtech.net/api"; + +export const Traffic = () => { + const [nym, setNym] = useState(); + const [selfAddress, setSelfAddress] = useState(); + const [recipient, setRecipient] = useState(); + const [payload, setPayload] = useState(); + const [receivedMessage, setReceivedMessage] = useState(); + const [buttonEnabled, setButtonEnabled] = useState(false); + + const init = async () => { + const client = await createNymMixnetClient(); + setNym(client); + + // start the client and connect to a gateway + await client?.client.start({ + clientId: crypto.randomUUID(), + nymApiUrl, + forceTls: true, // force WSS + }); + + // check when is connected and set the self address + client?.events.subscribeToConnected((e) => { + const { address } = e.args; + setSelfAddress(address); + }); + + // show whether the client is ready or not + client?.events.subscribeToLoaded((e) => { + console.log("Client ready: ", e.args); + }); + + // show message payload content when received + client?.events.subscribeToTextMessageReceivedEvent((e) => { + console.log(e.args.payload); + setReceivedMessage(e.args.payload); + }); + }; + + const stop = async () => { + await nym?.client.stop(); + }; + + const send = () => + payload && recipient && nym?.client.send({ payload, recipient }); + + useEffect(() => { + init(); + return () => { + stop(); + }; + }, []); + + useEffect(() => { + if (recipient && payload) { + setButtonEnabled(true); + } else { + setButtonEnabled(false); + } + }, [recipient, payload]); + + if (!nym || !selfAddress) { + return ( + + ); + } + + return ( + + My self address is: + {selfAddress || "loading"} + Communication through the Mixnet + setRecipient(e.target.value)} + size="small" + /> + + setPayload({ message: e.target.value, mimeType: "text/plain" }) + } + size="small" + /> + + +

Delegate

+ (mixId = +e.target.value)} /> + (amountToDelegate = e.target.value)} /> + + + + ); +} +``` + +--- +title: Cosmos Kit +url: https://nym.com/docs/developers/typescript/examples/cosmos-kit +--- + +# Cosmos Kit + +The wonderful people of Cosmology have made some [fantastic components](https://cosmoskit.com/) that can be used with +Nym. These include: + +- 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); + +##### Environment Setup +Begin by creating a directory and configuring your application 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: +```bash +cd < YOUR_APP > +npm i +npm run dev +``` + +##### Installation +Install the required package: +```bash +npm install @cosmos-kit/react @cosmos-kit/keplr @cosmos-kit/ledger chain-registry +``` + +You need to polyfill some nodejs modules in order to use keplr and ledger wallets by modifying your `vite.config.js` file: +```bash +npm install @esbuild-plugins/node-globals-polyfill +``` + +```js +// vite.config.js + +export default defineConfig({ + plugins: [react()], + optimizeDeps: { + esbuildOptions: { + define: { + global: 'globalThis' + }, + plugins: [ + NodeGlobalsPolyfillPlugin({ + buffer: true + }) + ] + } + } +}) +``` + +Your components have to be wrapped into a [ChainProvider](https://docs.cosmoskit.com/chain-provider), +in order to use the `useChain('nyx')` hook. The nyx chain is provided in the 'chain-registry' NPM package by default. + +Now, go to the `src` folder and open your `App.tsx` file to replace all the code with the following, which will allow you to connect and disconnect a Ledger or Keplr wallet to Nyx: + +```ts + +export const getDoc = (address: string) => { + const chainId = 'nyx'; + const msg: AminoMsg = { + type: '/cosmos.bank.v1beta1.MsgSend', + value: MsgSend.fromPartial({ + fromAddress: address, + toAddress: 'n1nn8tghp94n8utsgyg3kfttlxm0exgjrsqkuwu9', + amount: [{ amount: '1000', denom: 'unym' }], + }), + }; + const fee = { + amount: [{ amount: '2000', denom: 'ucosm' }], + gas: '180000', // 180k + }; + const memo = 'Use your power wisely'; + const accountNumber = 15; + const sequence = 16; + const doc = makeSignDoc([msg], fee, chainId, memo, accountNumber, sequence); + return doc +}; + +function MyComponent() { + const {wallet, address, connect, disconnect, getOfflineSignerAmino } = + useChain('nyx'); + + React.useEffect(() => { + connect(); + disconnect(); + }, []); + + const sign = async () => { + if (!address) return + const doc = getDoc(address); + return getOfflineSignerAmino().signAmino(address, doc); + }; + + return ( + + {wallet && + +
Connected to {wallet?.prettyName}
+
Address: {address}
+ } + + {wallet ? ( + + + + ) : ( + + + + )} + + ); +} + +export default function App() { + const assetsFixedUp = React.useMemo(() => { + const nyx = assets.find((a) => a.chain_name === 'nyx'); + if (nyx) { + const nyxCoin = nyx.assets.find((a) => a.name === 'nyx'); + if (nyxCoin) { + nyxCoin.coingecko_id = 'nyx'; + } + nyx.assets = nyx.assets.reverse(); + } + return assets; + }, [assets]); + + return ( + c.chain_id === 'nyx')!]} + assetLists={assetsFixedUp} + wallets={[...ledger, ...keplr]} + signerOptions={{ + preferredSignType: () => 'amino', + }} + > + + ) +} +``` + +--- +title: TypeScript SDK Bundling Troubleshooting +description: Fix common bundling issues with the Nym TypeScript SDK: WASM files missing from output, web worker configuration for Webpack and other bundlers. +url: https://nym.com/docs/developers/typescript/bundling/bundling +--- + +# Troubleshooting + +## Bundling issues + +### WASM and web worker not included in output bundle (Webpack) + +You might need to use the CopyPlugin by adding this to your Webpack config: + +```js +const CopyPlugin = require('copy-webpack-plugin'); + +module.exports = { + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*.wasm'), + to: '[name][ext]', + }, + { + from: path.resolve(path.dirname(require.resolve('@nymproject/mix-fetch/package.json')), '*worker*.js'), + to: '[name][ext]', + }, + ], + }), + ], +} +``` + +`require.resolve('@nymproject/mix-fetch/package.json')` finds the disk location of the Nym SDK package. `path.dirname` resolves the directory, and the `*.wasm` glob matches the WASM files. Use `[name][ext]` to preserve the output filename, because the package expects it to stay the same. + +### ESM not supported + +If your bundler does not support ECMAScript Modules (ESM), CommonJS packages are supported for most parts of the SDK. + +For those that don't have ESM versions, you will need to use a tool like [Babel](https://babeljs.io/) to convert ESM to CommonJS. + +### CSP prevents loading + +If you are using a `*-full-fat` package, or if you inline WASM or web workers, you may not be able to load them if the [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) prevents WASM from being instantiated from a string. + +You'll have to experiment with either adjusting the CSP or use another variant that is unbundled. + +## Mixnet client issues + +### Insufficient topology error + +The mixnet client will complain about insufficient topology in the following cases: +- There are empty mix layers (rare) +- The gateway you've registered with does not appear in the network topology — it is either unbonded or was blacklisted +- The gateway you want to send packets to does not appear in the network topology — it is either unbonded or was blacklisted + +To avoid the last two, make sure the gateway you are using is bonded and whitelisted. + +### Checking gateway status + +Your client address has the format: `client-id.client-dh@gateway-id` + +For example: `DpB3cHAchJi...suko.ANNWrvHq...U2Vx@2BuMSfMW...3SEh` + +- First part: client's identity key +- Second part: client's Diffie-Hellman key +- After `@`: gateway's identity key — search for this in the [Nym Explorer](https://nym.com/explorer) to check its status + +--- +title: Troubleshooting bundling with ESbuild +url: https://nym.com/docs/developers/typescript/bundling/esbuild +--- + +# Troubleshooting bundling with ESbuild + +If you've been following the steps outlined 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: +```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: +```bash +cd < YOUR_APP > +npm i +npm run dev +``` + +##### Installation +Install the required package: +```bash +npm install @nymproject/< PACKAGE_NAME > +``` + + Remember that the CosmosKit example will require you to make use of polyfills. + +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! + +--- +title: Troubleshooting bundling with Webpack +url: https://nym.com/docs/developers/typescript/bundling/webpack +--- + +# Troubleshooting bundling with Webpack + +## Webpack > 5 ESM + +For any project using Webpack, you´ll need the following rule in your `webpack.config.js` above version 5: +```json +{ + test: /\.(m?js)$/, + resolve: { + fullySpecified: false + } +} +``` + +### Create-react-app + +#### 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: + +```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. + +#### Contract client + + 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: + +##### Install contract-clients dependencies +```bash +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. + +##### Polyfilling + +Copy the following to your terminal and run: + +```bash +npm install react-app-rewired +npm install --save-dev crypto-browserify stream-browserify assert stream-http https-browserify os-browserify url buffer process +cat < config-overrides.js +const webpack = require('webpack'); +const path = require('path') + +module.exports = function override(config) { + const fallback = config.resolve.fallback || {}; + Object.assign(fallback, { + "crypto": require.resolve("crypto-browserify"), + "stream": require.resolve("stream-browserify"), + "assert": require.resolve("assert"), + "http": require.resolve("stream-http"), + "https": require.resolve("https-browserify"), + "os": require.resolve("os-browserify"), + "url": require.resolve("url") + }) + config.resolve.fallback = fallback; + config.plugins = (config.plugins || []).concat([ + new webpack.ProvidePlugin({ + process: 'process/browser', + Buffer: ['buffer', 'Buffer'] + }) + ]) + config.module.rules = (config.module.rules || []).concat([ + { + test: /\.(m?js)$/, + resolve: { + fullySpecified: false + } + } + ]) + return config; +} +EOF +``` + +#### Edit the `package.json` file as follows: + +```json + "scripts": { + "start": "react-app-rewired start", + "build": "react-app-rewired build", + "test": "react-app-rewired test", + "eject": "react-scripts eject" + }, +``` + +--- +title: NymVPN CLI: Run NymVPN from the Command Line +description: Install and run NymVPN from the terminal on Linux, macOS, and Windows. Includes ARM64 .deb packages, account setup, tunnel configuration, and gateway selection. +url: https://nym.com/docs/developers/nymvpncli +--- + +# Nym VPN CLI + +This is a short guide to setting up and using the `nym-vpnc` tool, which is used in conjunction with the `nym-vpnd` daemon. + +Download and run instructions for the GUIs can be found [here](https://nymvpn.com/en/download/linux). + +## Download & Extract Binary +Check the [release page](https://github.com/nymtech/nym-vpn-client/releases/) page for the latest release version and modify the instructions accordingly. These instructions use the latest as of the time of writing. +```sh +wget -q https://github.com/nymtech/nym-vpn-client/releases/download/nym-vpn-core-v1.27.0-beta/nym-vpn-core-v1.27.0-beta_.tar.gz && +tar -xzf nym-vpn-core-v1.27.0-beta_.tar.gz && +cd nym-vpn-core-v1.27.0-beta_/ && +chmod u+x * +``` + +### Linux ARM64 (.deb) + +ARM64 `.deb` packages are available for Linux distributions that support them (e.g. Ubuntu/Debian on Raspberry Pi or ARM servers). Install both the daemon and the client: + +```sh +sudo dpkg -i nym-vpnd__arm64.deb +sudo dpkg -i nym-vpnc__arm64.deb +``` + +The `.deb` package installs a systemd service that starts `nym-vpnd` automatically. Verify the service is running: + +```sh +service nym-vpnd status +``` + +You should see output similar to: + +```sh +● nym-vpnd.service - nym-vpnd daemon + Loaded: loaded (/usr/lib/systemd/system/nym-vpnd.service; enabled; preset: enabled) + Active: active (running) +``` + +Verify the installed version with `nym-vpnc info`: + +```sh +nym-vpnc info +``` +```sh +nym-vpnd: + version: 1.25.0 + build_timestamp (utc): 2026-03-02 16:25:31.229479864 +00:00:00 + triple: aarch64-unknown-linux-gnu + platform: Ubuntu; Linux (Ubuntu 24.04); aarch64 + git_commit: fce7a84e612b8d2cb48b66695cdaf023d7f9a42b +``` + +## Build from Source +### Prerequisites +All operating systems require both [Rust](https://www.rust-lang.org/tools/install) and [Go](https://go.dev/doc/install). + +**Arch specific packages:** +```sh +yay -S gcc make protobuf base-devel clang +``` + +**Ubuntu24 specific packages:** +```sh +apt install gcc make protobuf-compiler pkconfig libdbus-1-dev build-essential clang +``` + + Older Debian/Ubuntu versions need to manually install `protobuf-compiler` >= v3.21.12 + +### Clone & `make` +```sh +git clone https://github.com/nymtech/nym-vpn-client.git +cd nym-vpn-client/ +make +``` + +## Start the Daemon + +If you installed via `.deb` packages, the daemon is already running as a systemd service. You can check its status with: + +```sh +service nym-vpnd status +``` + +If you are running from pre-built binaries or a source build, start the daemon manually: + +```sh +sudo ./PATH/TO/nym-vpnd +``` + + Leave the daemon running and run `nym-vpnc` commands in a separate terminal window. + +## Account Setup + +### Create an Account + +Head to [https://nym.com/account/create](https://nym.com/account/create) and obtain a passphrase (mnemonic). + +### Log In + +Store your account passphrase on this device: + +```sh +nym-vpnc account set "" +``` + +### Check Account Status + +Verify that the device is logged in and view account details: + +```sh +nym-vpnc account get +``` + +Example output: + +```sh +Account identity: n1wlmrpa7ts7znz7nxvmxevaw65796cr6q6pht69 +Canonical Account identity: n1wlmrpa7ts7znz7nxvmxevaw65796cr6q6pht69 +Account mode: Some(Api) +Account state: Error(BandwidthExceeded { context: "SYNCING_STATE" }) +``` + +### Account Summary & Balance + +```sh +nym-vpnc account summary +nym-vpnc account balance +``` + +### Account Links + +Get URLs for managing your NymVPN account: + +```sh +nym-vpnc account links +``` + +### Forget Account + +Remove the stored passphrase, device keys, and local credentials from this device: + +```sh +nym-vpnc account forget +``` + +### Device Information + +View the current device identity: + +```sh +nym-vpnc device get +``` + +## Tunnel Configuration + +Print current tunnel configuration: + +```sh +nym-vpnc tunnel get +``` + +Enable two-hop mode (WireGuard) — traffic jumps directly from entry gateway to exit gateway: + +```sh +nym-vpnc tunnel set --two-hop on +``` + +Enable Mixnet (5-hop) — disable two-hop to route traffic through the full mixnet for maximum privacy: + +```sh +nym-vpnc tunnel set --two-hop off +``` + +Enable or disable IPv6: + +```sh +nym-vpnc tunnel set --ipv6 on +``` + +Enable censorship circumvention transports (currently QUIC): + +```sh +nym-vpnc tunnel set --circumvention-transports on +``` + + Run `nym-vpnc tunnel set --help` for all available tunnel options including mixnet timing parameters. + +## Gateway Configuration + +Set entry and exit gateways bound to specific countries using [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes: + +```sh +nym-vpnc gateway set --entry-country US --exit-country JP +``` + +Print current gateway configuration: + +```sh +nym-vpnc gateway get +``` + +Example output: + +```sh +Entry point: Country { two_letter_iso_country_code: "US" } +Exit point: Country { two_letter_iso_country_code: "JP" } +Residential exit: off +``` + +Only use residential exit nodes: + +```sh +nym-vpnc gateway set --residential-exit on +``` + +### List Available Gateways + +List available WireGuard gateways (use a wide terminal window for the table output): + +```sh +nym-vpnc gateway list wg +``` + +You can also list mixnet entry and exit gateways: + +```sh +nym-vpnc gateway list mixnet-entry +nym-vpnc gateway list mixnet-exit +``` + +## Connect & Disconnect + +Connect using the settings stored in `nym-vpnd`: + +```sh +nym-vpnc connect +``` + +Disconnect: + +```sh +nym-vpnc disconnect +``` + +Reconnect: + +```sh +nym-vpnc reconnect +``` + +Print the current tunnel status: + +```sh +nym-vpnc status +``` + +Continuously stream tunnel status in real time: + +```sh +nym-vpnc status --listen +``` + +## Ad-Block + +NymVPN includes a built-in ad-blocker (Brave ad-engine). Ad-blocking is only active while the tunnel is connected. + +Enable ad-block: + +```sh +nym-vpnc ad-block set enabled +``` + +Disable ad-block: + +```sh +nym-vpnc ad-block set disabled +``` + + You can test ad-blocking with [adblock.turtlecute.org](https://adblock.turtlecute.org/). Some browsers cache DNS internally, so toggling ad-block on/off at runtime may not have an immediate effect — a browser restart may be needed. Use `nslookup` or `dig` to verify that domains are being blocked. + +## DNS + +View current DNS configuration: + +```sh +nym-vpnc dns get +nym-vpnc dns get-default +``` + +Set custom DNS servers: + +```sh +nym-vpnc dns set 1.1.1.1 9.9.9.9 +nym-vpnc dns enable +``` + +Disable custom DNS and revert to defaults: + +```sh +nym-vpnc dns disable +``` + +Clear custom DNS servers: + +```sh +nym-vpnc dns clear +``` + +## Local Network Access + +Control whether local network (LAN) traffic is allowed while the tunnel is active: + +```sh +nym-vpnc lan get +nym-vpnc lan set allow +nym-vpnc lan set block +``` + +## SOCKS5 Proxy + +NymVPN can expose a local SOCKS5 proxy: + +```sh +nym-vpnc socks5 enable +nym-vpnc socks5 disable +nym-vpnc socks5 status +``` + +## Network + +View or change the Nym network (requires a daemon restart): + +```sh +nym-vpnc network get +nym-vpnc network set mainnet +``` + +## Diagnostic + +Run connectivity diagnostics: + +```sh +nym-vpnc diagnostic run +``` + +## Getting Help + +Any `nym-vpnc` command has built-in help. Add `--help` to the end of any command to view available options: + +```sh +nym-vpnc --help +nym-vpnc connect --help +nym-vpnc tunnel set --help +``` + +## Default Config Directories +Configurations are stored in `/etc/nym`. State stored between runs (keys, mnemonic, etc) are stored in `/var/lib/nym-vpnd`. + +--- +title: Nyx Blockchain & Nym Smart Contracts +description: Developer guide for interacting with the Nyx blockchain via Cosmos SDK. Covers CLI wallet setup, Cosmos Registry, Ledger Live, and RPC node deployment. +url: https://nym.com/docs/developers/chain +--- + +# Interacting with Nyx Chain and Smart Contracts + +There are two options for interacting with the blockchain to send tokens or interact with deployed smart contracts: +* [`Nym-CLI`](./tools/nym-cli) tool +* `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. + +Instructions on how to do so can be found on the [`nym-cli` docs page](./tools/nym-cli) + +## 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. + +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). + +--- +title: CLI Wallet +url: https://nym.com/docs/developers/chain/cli-wallet +--- + +# 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. + +### 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. + +--- +title: Cosmos Registry +url: https://nym.com/docs/developers/chain/cosmos-registry +--- + +# Cosmos Registry +You can find all relevant information (chain info, RPC endpoints, etc) on the [Cosmos Chain Registry](https://github.com/cosmos/chain-registry/tree/master/nyx). + +--- +title: Ledger Live Support +url: https://nym.com/docs/developers/chain/ledger-live +--- + +# 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. + +## Prerequisites +* Download and install [Ledger Live](https://www.ledger.com/ledger-live). +* Compile the `nyxd` binary as per the instructions [here](../../operators/nodes/validator-setup). Stop after you can successfully run `nyxd` and get the helptext in your console output. + +## 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. +* On the device, navigate to the Cosmos app and open it + +## Create a keypair +Add a reference to the ledger device on your local machine by running the following command in the same directory as your `nyxd` binary: + +``` +nyxd keys add ledger_account --ledger +``` + +## Command help with `nyxd` +More information about each command is available by consulting the help section (`--help`) at each layer of `nyxd`'s commands: + +``` +# logging top level command help +nyxd --help + +# logging top level command help for transaction commands +nyxd tx --help + +# logging top level command help for transaction commands utilising the 'bank' module +nyxd tx bank --help +``` + +## Sending tokens between addresses +Perform a transaction from the CLI with `nyxd`, appending the `--ledger` option to the command. + +As an example, the below command will send 1 `NYM` from the ledger account to the `$DESTINATION_ACCOUNT`: + +``` +nyxd tx bank send ledger_account $DESTINATION_ACCOOUNT 1000000unym --ledger --node https://rpc.dev.nymte.ch:443 +``` + +> When a command is run, the transaction will appear on the Ledger device and will require physical confirmation from the device before being signed. + +## Nym-specific transactions +Nym-specific commands and queries, like bonding a mix node or delegating unvested tokens, are available in the `wasm` module, and follow the following pattern: + +``` +# Executing commands +nyxd tx wasm execute $CONTRACT_ADDRESS $JSON_MSG + +# Querying the state of a smart contract +nyxd query wasm contract-state smart $CONTRACT_ADDRESS $JSON_MSG +``` + +You can find the value of `$CONTRACT_ADDRESS` in the [`network defaults`](https://github.com/nymtech/nym/blob/master/common/network-defaults/src/mainnet.rs) file. + +The value of `$JSON_MSG` will be a blog of `json` formatted as defined for each command and query. You can find these definitions for the mixnet smart contract [here](https://github.com/nymtech/nym/blob/master/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs) and for the vesting contract [here](https://github.com/nymtech/nym/blob/master/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs) under `ExecuteMsg` and `QueryMsg`. + +### Example command execution: +#### Delegate to a mix node +You can delegate to a mix node from the CLI using `nyxd` and signing the transaction with your ledger by filling in the values of this example: +``` +CONTRACT_ADDRESS=mixnet_contract_address + +./nyxd tx wasm execute $CONTRACT_ADDRESS '{"delegate_to_mixnode":{"mix_identity":"MIX_NODE_IDENTITY","amount":{"amount":"100000000000","denom":"unym"}}}' --ledger --from admin --node https://rpc.dev.nymte.ch:443 --gas-prices 0.025unymt --gas auto -b block +``` + +> By replacing the value of `CONTRACT_ADDRESS` with the address of the vesting contract, you could use the above command to use tokens held in the vesting contract. + +#### Query a vesting schedule +You can query for (e.g.) seeing the current vesting period of an address by filling in the values of the following: +``` +CONTRACT_ADDRESS=vesting_contract_address + +nyxd query wasm contract-state smart $CONTRACT_ADDRESS '{"get_current_vesting_period"}:{"address": "address_to_query_for"}' --ledger --from admin --node https://rpc.dev.nymte.ch:443 --chain-id qa-net --gas-prices 0.025unymt --gas auto -b block +``` + +--- +title: Run a Nyx RPC Node for the Nym Network +description: Set up and run a dedicated RPC node for the Nyx blockchain. Query network state, serve chain data, and interact with Nym smart contracts programmatically. +url: https://nym.com/docs/developers/chain/rpc-node +--- + +# 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. + +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. + +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**. + +If you want to fast-sync your node, check out the Polkachu snapshot and their other [resources](https://polkachu.com/seeds/nym). + +--- +title: Nym Developer Tools: CLI, Diagnostics & TcpProxy +description: Overview of Nym developer tools including nym-cli for blockchain interaction, diagnostic tool for troubleshooting, and standalone TcpProxy binary downloads. +url: https://nym.com/docs/developers/tools +--- + +# Tools + +Standalone binaries for development and testing. These 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. | + +--- +title: Nym CLI: Mixnet & Blockchain Commands +description: Use nym-cli to interact with the Nym mixnet and Nyx blockchain. Manage nodes, delegate stake, and query network state from the command line. +url: https://nym.com/docs/developers/tools/nym-cli +--- + +# Nym-CLI + +This is a CLI tool for interacting with: + +* the Nyx blockchain (account management, querying the chain state, etc) +* the smart contracts deployed on Nyx (bonding and un-bonding mixnodes, collecting rewards, etc) + +It provides a convenient wrapper around the `nymd` client, and has similar functionality to the `nyxd` binary for querying the chain or executing smart contract methods. + +--- +title: Usage +url: https://nym.com/docs/developers/tools/nym-cli/usage +--- + +# Usage + +## Building +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. + +## 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: + +- Bond on the gateway's or mix node's behalf. +- Delegate or Un-delegate (to a mix node in order to begin receiving rewards) +- Claiming the rewards on the account + +```admonish note title="" +The staking address has no ability to withdraw any coins from the parent's account. +``` + +The staking address must maintain the same level of security as the parent mnemonic; while the parent mnemonic's delegations and bonding events will be visible to the parent owner, the staking address will be the only account capable of undoing the bonding and delegating from the mix nodes or gateway. + +Query for staking on behalf of someone else +``` +./nym-cli --mnemonic mixnet delegators delegate --mix-id --identity-key --amount +``` + +--- +title: `nym-cli` Binary Commands (Autogenerated) +url: https://nym.com/docs/developers/tools/nym-cli/commands +--- + +# `nym-cli` Binary Commands (Autogenerated) + +These docs are autogenerated by the [`autodocs`](https://github.com/nymtech/nym/tree/max/new-docs-framework/documentation/autodoc) script. +```sh +A client for interacting with Nym smart contracts and the Nyx blockchain + +Usage: nym-cli [OPTIONS] + +Commands: + account Query and manage Nyx blockchain accounts + signature Sign and verify messages + ecash Ecash related stuff + block Query chain blocks + cosmwasm Manage and execute WASM smart contracts + tx Query for transactions + vesting-schedule Create and query for a vesting schedule + mixnet Manage your mixnet infrastructure, delegate stake or query the directory + generate-fig Generates shell completion + help Print this message or the help of the given subcommand(s) + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `account` +```sh +Query and manage Nyx blockchain accounts + +Usage: nym-cli account [OPTIONS] + nym-cli account + +Commands: + create Create a new mnemonic - note, this account does not appear on the chain until the account id is used in a transaction + balance Gets the balance of an account + pub-key Gets the public key of an account + send Sends tokens to another account + send-multiple Batch multiple token sends + help Print this message or the help of the given subcommand(s) + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `account create` +```sh +Create a new mnemonic - note, this account does not appear on the chain until the account id is used in a transaction + +Usage: nym-cli account create [OPTIONS] + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + --word-count + + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `account balance` +```sh +Gets the balance of an account + +Usage: nym-cli account balance [OPTIONS] [ADDRESS] + +Arguments: + [ADDRESS] The account address to get the balance for + +Options: + --denom + Optional currency to show balance for + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --hide-denom + Optionally hide the denom + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --raw + Show as a raw value + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `account pub-key` +```sh +Gets the public key of an account + +Usage: nym-cli account pub-key [OPTIONS] [ADDRESS] + +Arguments: + [ADDRESS] Optionally, show the public key for this account address, otherwise generate the account address from the mnemonic + +Options: + --from-mnemonic + If set, get the public key from the mnemonic, rather than querying for it + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `account send` +```sh +Sends tokens to another account + +Usage: nym-cli account send [OPTIONS] + +Arguments: + The recipient account address + Amount to transfer in micro denomination (e.g. unym or unyx) + +Options: + --denom + Override the denomination + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --memo + + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `account send-multiple` +```sh +Batch multiple token sends + +Usage: nym-cli account send-multiple [OPTIONS] --input + +Options: + --memo + + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --input + Input file path (CSV format) with account/amount pairs to send + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --output + An output file path (CSV format) to create or append a log of results to + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `signature` +```sh +Sign and verify messages + +Usage: nym-cli signature [OPTIONS] + nym-cli signature + +Commands: + sign Sign a message + verify Verify a message + help Print this message or the help of the given subcommand(s) + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `signature sign` +```sh +Sign a message + +Usage: nym-cli signature sign [OPTIONS] + +Arguments: + The message to sign + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `signature verify` +```sh +Verify a message + +Usage: nym-cli signature verify [OPTIONS] + +Arguments: + The public key of the account, or the account id to query for a public key (NOTE: the account must have signed a message stored on the chain for the public key record to exist) + The signature to verify as hex + The message to verify as a string + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `ecash` +```sh +Ecash related stuff + +Usage: nym-cli ecash [OPTIONS] + nym-cli ecash + +Commands: + issue-ticket-book + recover-ticket-book + import-ticket-book + generate-ticket + import-coin-index-signatures + import-expiration-date-signatures + import-master-verification-key + help Print this message or the help of the given subcommand(s) + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `ecash issue-ticket-book` +```sh +Usage: nym-cli ecash issue-ticket-book [OPTIONS] <--client-config |--output-file > + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + --ticketbook-type + Specify which type of ticketbook should be issued [default: v1-mixnet-entry] + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --client-config + Config file of the client that is supposed to use the credential + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --output-file + Output file for the ticketbook + --bs58-output + Specifies whether the output file should use binary or bs58 encoded data + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --include-expiration-date-signatures + Specifies whether the file output should contain expiration date signatures + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --include-coin-index-signatures + Specifies whether the file output should contain coin index signatures + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + --include-master-verification-key + Specifies whether the file output should contain master verification key + --bs58-encoded-client-secret + Secret value that's used for deriving underlying ecash keypair + -h, --help + Print help +``` + +## `ecash recover-ticket-book` +```sh +Usage: nym-cli ecash recover-ticket-book [OPTIONS] --client-config + +Options: + --client-config + Config file of the client that is supposed to use the credential + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `ecash import-ticket-book` +```sh +Usage: nym-cli ecash import-ticket-book [OPTIONS] --credentials-store <--credential-data |--credential-path > <--standalone|--full> + +Options: + --credentials-store + Config file of the client that is supposed to use the credential + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --credential-data + Explicitly provide the encoded credential data (as base58) + --credential-path + Specifies the path to file containing binary credential data + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --standalone + Specifies whether we're attempting to import a standalone ticketbook (i.e. serialised `IssuedTicketBook`) + --full + Specifies whether we're attempting to import full ticketboot (i.e. one that **might** contain required global signatures; that is serialised `ImportableTicketBook`) + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `coconut` + +## `coconut generate-freepass` + +## `coconut issue-credentials` + +## `coconut recover-credentials` + +## `coconut import-credential` + +## `block` +```sh +Query chain blocks + +Usage: nym-cli block [OPTIONS] + nym-cli block + +Commands: + get Gets a block's details and prints as JSON + time Gets the block time at a height + current-height Gets the current block height + help Print this message or the help of the given subcommand(s) + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `block get` +```sh +Gets a block's details and prints as JSON + +Usage: nym-cli block get [OPTIONS] + +Arguments: + The block height + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `block time` +```sh +Gets the block time at a height + +Usage: nym-cli block time [OPTIONS] + +Arguments: + The block height + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `block current-height` +```sh +Gets the current block height + +Usage: nym-cli block current-height [OPTIONS] + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `cosmwasm` +```sh +Manage and execute WASM smart contracts + +Usage: nym-cli cosmwasm [OPTIONS] + nym-cli cosmwasm + +Commands: + upload Upload a smart contract WASM blob + init Init a WASM smart contract + generate-init-message Generate an instantiate message + migrate Migrate a WASM smart contract + execute Execute a WASM smart contract method + raw-contract-state Obtain raw contract state of a cosmwasm smart contract + help Print this message or the help of the given subcommand(s) + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `cosmwasm upload` +```sh +Upload a smart contract WASM blob + +Usage: nym-cli cosmwasm upload [OPTIONS] --wasm-path + +Options: + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + --wasm-path + + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --memo + + --nyxd-url + Overrides the nyxd URL provided either as an environment variable NYXD_VALIDATOR or in a config file + --nym-api-url + Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file + --mixnet-contract-address + Overrides the mixnet contract address provided either as an environment variable or in a config file + --vesting-contract-address + Overrides the vesting contract address provided either as an environment variable or in a config file + -h, --help + Print help +``` + +## `cosmwasm init` +```sh +Init a WASM smart contract + +Usage: nym-cli cosmwasm init [OPTIONS] --init-message + +Arguments: + +Options: + --memo + + --mnemonic + Provide the mnemonic for your account. You can also provide this is an env var called MNEMONIC. + -c, --config-env-file + Overrides configuration as a file of environment variables. Note: individual env vars take precedence over this file. + --label