Skip to main content

Disclose your viewing key

What this lets you do

Provide your viewingPrivateKey to a third-party recipient like an auditor, accountant, or regulator. That key lets them decrypt every incoming note ever published on-chain to your address, past and future, for as long as you use that wallet. This is the broadest disclosure mode, for when finer-grained options aren't sufficient.

Constraints & limits

  • Receive-only access. The viewing private key cannot spend, cannot derive nullifiers, cannot move funds. Compromise of this key alone doesn't put your funds at risk, because spending requires the privateNullifyingKey which stays with you.
  • Irreversible in scope. Once disclosed, that key reveals every incoming note that's ever existed for that wallet. There is no "un-share." If you later wish you hadn't, you need to migrate to a new wallet for future activity.
  • Doesn't disclose outgoing transfers as receipts do. The viewing private key is for decrypting what's encrypted to you. To disclose individual outgoing payments, use receipts instead, which don't require disclosing this key.
  • Future notes too. If you keep using the wallet after sharing, every new incoming note also gets exposed to whoever holds the key.

What it unlocks next

  • Wholesale compliance review: a regulator can build a read-only session with your viewing key and your wallet address and run discoverNotes() to get the full receive history.
  • Accountant onboarding for an organization that wants ongoing full-stream visibility into incoming shielded payments (rather than per-payment receipts).

How to use it

import { PoolSessionBuilder, type RelayerInfo } from "@privacy-pools-v2/sdk";

const HOSTED_RELAYER = {
url: "https://relayer.example",
name: "placeholder",
chainId: 11155111,
chainType: "evm",
status: "active",
address: ZERO_HEX_20,
processorAddress: ZERO_HEX_20,
} satisfies RelayerInfo;

const auditSession = await PoolSessionBuilder.fromConfig({
chainId: 11155111,
rpcUrl,
ownerAddress: orgAddress,
protocolKeys: {
privateNullifyingKey: ZERO_HEX_32,
privateRevocableKey: ZERO_HEX_32,
revocableKeyIndex: "0x0",
viewingPrivateKey: DISCLOSED_VIEWING_KEY,
viewingPublicKey: DISCLOSED_VIEWING_PUBKEY,
},
aspUrl,
relayers: [HOSTED_RELAYER],
circuitArtifactsDir: process.env.CIRCUIT_ARTIFACTS_DIR,
}).create();

await auditSession.discoverNotes();
const history = (await auditSession.exportAccount()).notes;

Share viewingPrivateKey and ownerAddress, and optionally a discovery cursor to avoid rescanning old events. The builder still requires a relayer-shaped entry and circuit artifacts even though discovery does not relay transactions or build spend proofs.

Reach for this last. Viewing-key disclosure is broad and reveals all past and future incoming history for your wallet. For most "prove this single payment" needs, a narrower tool fits. From least to most disclosure:

  1. One payment, outgoing: generate a receipt.
  2. One payment, incoming: share single-note secret.
  3. All incoming forever: disclose viewing private key.

Pick the narrowest one that meets the disclosure requirement.

Behind the scenes

What you share viewingPrivateKey (32-byte hex) plus ownerAddress
What the auditor does Builds a read-only PoolSession with the disclosed key in the protocolKeys slot. The SDK's discovery code decrypts incoming notes via that key
What you keep privateNullifyingKey and privateRevocableKey: spending and auth-policy control stay with you