Developers
Step-by-step Examples
2. Mixnet Client

Mixnet Client

The SDK Client (opens in a new tab) 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:

npm create vite@latest

Choose React + TypeScript, then:

cd <YOUR_APP>
npm i
npm run dev

Installation

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 copies the WASM and web worker files to the output bundle.

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 const Traffic = () => {
  const [nym, setNym] = useState<NymMixnetClient>();
  const [selfAddress, setSelfAddress] = useState<string>();
  const [recipient, setRecipient] = useState<string>();
  const [payload, setPayload] = useState<Payload>();
  const [receivedMessage, setReceivedMessage] = useState<string>();
  const [buttonEnabled, setButtonEnabled] = useState<boolean>(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 (
      <Box sx={{ display: "flex" }}>
        <CircularProgress />
      </Box>
    );
  }
 
  return (
    <Box padding={3}>
      <Paper style={{ marginTop: "1rem", padding: "2rem" }}>
        <Stack spacing={3}>
          <Typography variant="body1">My self address is:</Typography>
          <Typography variant="body1">{selfAddress || "loading"}</Typography>
          <Typography variant="h5">Communication through the Mixnet</Typography>
          <TextField
            type="text"
            placeholder="Recipient Address"
            onChange={(e) => setRecipient(e.target.value)}
            size="small"
          />
          <TextField
            type="text"
            placeholder="Message to send"
            multiline
            rows={4}
            onChange={(e) =>
              setPayload({ message: e.target.value, mimeType: "text/plain" })
            }
            size="small"
          />
          <Button
            variant="outlined"
            onClick={() => send()}
            disabled={!buttonEnabled}
            sx={{ width: "fit-content" }}
          >
            Send
          </Button>
        </Stack>
        {receivedMessage && (
          <Stack spacing={3} style={{ marginTop: "1rem" }}>
            <Typography variant="h5">Message Received!</Typography>
            <Typography fontFamily="monospace">{receivedMessage}</Typography>
          </Stack>
        )}
      </Paper>
    </Box>
  );
};

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".