Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Send a Transaction

This guide sends one private USDC transaction. It assumes you already created the viem clients and selected a Mirage network in Installation.

The flow has two application-level phases: prepare a quote, then execute it after the user confirms.

Define the transaction

Token amounts are bigint values in the token's smallest unit. Read the token decimals and convert the amount at your UI boundary.

import {
  getTokenMetadata,
  networks,
  prepareTransfer,
} from "@mirageprivacy/sdk";
import { parseUnits } from "viem";
 
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const recipient = "0x...";
 
const token = await getTokenMetadata(USDC, publicClient);
const amount = parseUnits("100", token.decimals);

Prepare and display the quote

Preparation requests pricing and escrow bytecode, but it does not ask the wallet to send a transaction.

const prepared = await prepareTransfer({
  tokenAddress: USDC,
  recipientAddress: recipient,
  amount,
  walletClient,
  publicClient,
  network: networks.ethereum,
});

The quote is available on prepared.fees. Show the amount, recipient, and service fee before asking the user to continue.

import { formatUnits } from "viem";
 
const serviceFee = formatUnits(
  prepared.fees.serviceFee.amount,
  token.decimals,
);

See Quotes and Fees for balances, funding requirements, and quote refresh rules.

Execute after confirmation

execute is an async generator. Each event tells your interface which stage is active without requiring it to reproduce protocol logic.

for await (const event of prepared.execute()) {
  switch (event.step) {
    case "fees":
      setStatus("Quote confirmed");
      break;
    case "approve":
      setStatus("Token approved");
      break;
    case "deploy":
      await saveSecrets(event.secrets);
      setStatus("Transfer submitted");
      break;
    case "compliance":
      setStatus("Transfer authorized");
      break;
    case "signal":
      setStatus("Waiting for delivery");
      break;
    case "transfer":
      setStatus(`Delivered: ${event.transfer.transactionHash}`);
      break;
    case "complete":
      setStatus("Complete");
      break;
  }
}

The wallet may request an ERC-20 approval followed by escrow deployment. After deployment, Mirage completes compliance authorization, submits the encrypted signal, and watches for delivery.

Saving transfer secrets

The saved value contains bigint fields, which standard JSON.stringify cannot serialize directly.

async function saveSecrets(secrets) {
  const serialized = JSON.stringify(secrets, (_, value) =>
    typeof value === "bigint" ? `${value}n` : value,
  );
 
  await durableStorage.put(secrets.escrowAddress, serialized);
}

Treat this value as key material. Do not put it in analytics, logs, or error-reporting tools. Keep it until the complete event confirms delivery.

What to build in your interface

A clear first integration usually has four screens or states:

  1. Transaction form
  2. Quote review
  3. Wallet and delivery progress
  4. Completed receipt or recoverable error

This keeps the product model aligned with the SDK model. Preparation powers the review state, and execution events power everything after confirmation.

You now have the main path working. Continue to Advanced Transfers for multiple recipients, separate execution stages, cancellation, and recovery.