Skip to main content
OpaqueClient in @opaquecash/opaque is the single integration surface for stealth payments, PSR admin, reputation proofs, and cross-chain announcements.

Construction

Pass the connected wallet(s) in the unified signer shape. The client prompts for the one-time SETUP_MESSAGE signature (or reuses a cached one), derives the stealth keys, and wires each wallet as that chain’s write signer:
import { OpaqueClient } from "@opaquecash/opaque";

// Browser, Ethereum wallet
const client = await OpaqueClient.fromWallet({
  wallets: { chain: "ethereum", address: userAddress, provider: window.ethereum },
  chainId: 11155111,
  rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
  wasmModuleSpecifier: "/pkg/cryptography.js",
});

// Both chains at once (Solana wallet-adapter + viem WalletClient)
const dual = await OpaqueClient.fromWallet({
  wallets: [
    { chain: "ethereum", address: userAddress, walletClient },
    { chain: "solana", publicKey, signMessage, signTransaction },
  ],
  chainId: 11155111,
  rpcUrl,
  solana: { cluster: "devnet" },
});
The FIRST wallet in the list signs SETUP_MESSAGE (key derivation) unless you pass a cached walletSignature, in which case no prompt happens at all:
const restored = await OpaqueClient.fromWallet({
  wallets: [],                  // allowed when walletSignature is provided
  walletSignature: cachedSignature,
  chainId: 11155111,
  rpcUrl,
});
See Unified signer below for the wallet shapes.

OpaqueClient.create(config) (manual)

Use create when you already hold the setup signature and want to wire signers yourself:
const client = await OpaqueClient.create(config);
create is async: it optionally loads WASM and derives viewing/spending keys from walletSignature.

OpaqueClient.createViewOnly(config, keys)

Build a scan-only client from the viewing private key and spending public key (no walletSignature). It scans and reads balances but cannot spend — sweep and key reconstruction throw. This is the safe shape for a server-side scanner. See Server-side scanning.
const viewer = await OpaqueClient.createViewOnly(
  { chainId, rpcUrl, ethereumAddress, wasmModuleSpecifier },
  { viewingKey, spendPublicKey },
);
viewer.isViewOnly; // true

OpaqueClientConfig

FieldTypeRequiredDescription
chainIdnumberYesEVM chain id (11155111 Sepolia). Must be in getSupportedChainIds() unless contracts overridden.
rpcUrlstringYesHTTP RPC for EVM reads and sweep
walletSignatureHexYesHKDF entropy from the SETUP_MESSAGE signature; never sent on-chain
ethereumAddressAddressYesConnected EOA (registrant context, PSR issuer on EVM)
wasmModuleSpecifierstringNo*URL to cryptography.js. *Required for scan/sweep/prove
trackedTokensTrackedToken[]NoExtra ERC-20s for balance aggregation
contractsPartial<{...}>NoOverride registry, announcer, verifier, UAB addresses
solanaSolanaAdapterConfigNo****Required when scanning/sending on Solana
ethereumProviderEIP1193ProviderNo******For EVM writes if no ethereumWalletClient
ethereumWalletClientWalletClientNo***Precedence over provider for EVM writes
solanaWallet{ publicKey, signTransaction }No********Required for Solana writes
ens{ client?, getText? }NoENS read access for resolveRecipient of *.eth names (an ENS-capable viem client, or a custom text-record reader)
ipfs{ gateways?, fetch? }NoGateway list + fetch override for resolveRecipient of ipfs:// DID documents
ons{ parentName?, registry?, mirrorProgram? }NoONS overrides for *.opq.eth-style names; defaults from @opaquecash/deployments (testnet parent opqtest.eth)
sns{ getRecord? }NoCustom .sol record reader; defaults to the Records V2 TXT reader over the solana connection

Unified signer

UnifiedSigner is one shape over every supported wallet type. Pass it to fromWallet, or use requestSetupSignature directly when you manage sessions yourself:
import { requestSetupSignature, type UnifiedSigner } from "@opaquecash/opaque";

// Ethereum: EIP-1193 provider or a viem WalletClient
const eth: UnifiedSigner = { chain: "ethereum", address, provider: window.ethereum };
const ethViem: UnifiedSigner = { chain: "ethereum", address, walletClient };

// Solana: wallet-adapter shape
const sol: UnifiedSigner = { chain: "solana", publicKey, signMessage, signTransaction };

const signature = await requestSetupSignature(eth);  // personal_sign over SETUP_MESSAGE
FieldEthereumSolana
IdentityaddresspublicKey (base58 string or PublicKey)
Setup signatureprovider (personal_sign) or walletClientsignMessage
Transaction signingsame provider / walletClientsignTransaction

Static helpers

MethodReturnsDescription
OpaqueClient.supportedChainIds()number[]Bundled EVM chain ids
OpaqueClient.chainDeployment(chainId)OpaqueChainDeployment | undefinedContract addresses + default tokens
OpaqueClient.buildReputationActionScope({ chainId, module, actionId })stringAction scope string
OpaqueClient.reputationExternalNullifierFromScope(scope)bigintCircuit external nullifier

Method index

Stealth API

Registry, recipient resolution, send, scan, sweep, anonymity utilities

PSR API

Schemas, attestations, delegates

Reputation API

Traits, proofs, verifier

Cross-chain API

UAB relay and cross-chain scan

React hooks

OpaqueProvider, useScan, useStealthBalance

Utilities

Exported helpers, types, adapters

Instance getters

client.getChainId()           // number
client.getEthereumAddress()   // Address
client.getMetaAddressHex()    // Hex, the 66-byte meta-address
client.getContracts()         // { stealthMetaAddressRegistry, stealthAddressAnnouncer, opaqueReputationVerifier? }

Signer requirements summary

CategoryMethodsEthereum signerSolana signerWASM
Read-only stealthresolveRecipient, resolveRecipientMetaAddress, isMetaAddressRegisterednosolana for Solana checkno
Stealth writesregisterMetaAddress, sendStealthPaymentyesyesno
Scan / sweepscan, sweep, filterOwnedAnnouncements, balancesnosolanayes
PSR admincreateSchema, issueAttestation, …yes (EVM)yes (Solana)no
ReputationdiscoverTraitsV2, discoverTraits (legacy V1), generateReputationProof, submitReputationVerificationyes (submit EVM)yes (submit Solana)yes

Error: WASM not configured

If wasmModuleSpecifier is omitted and you call a WASM-backed method:
Opaque: this method requires the cryptography WASM module. Pass `wasmModuleSpecifier` to OpaqueClient.create.
PSR admin methods work without WASM.