Private swaps are now live on AVNU ↗

Getting Started

These pages are for teams building privacy wallets on Starknet or advanced integrations that manage their own account, keys, note discovery, and proving. If you are building a private dapp on top of an existing wallet, use the Starknet Wallet API instead - it keeps viewing keys inside the wallet. Everything here goes through one factory: createPrivateTransfers.

Key-management requirement: a production wallet must give users a secure, account-specific way to export and recover the viewing key. A seed phrase is not a safe substitute unless your recovery design guarantees the exact key already registered on-chain. See Viewing Keys & Backups.

Install

npm install @starkware-libs/starknet-privacy-sdk@0.14.3-rc.8

The SDK requires Node.js >= 24 (its ohttp-ts dependency needs modern WebCrypto).

Getting a 404? The package is not published on npmjs.com yet. Until npm publication is live, install it from GitHub Packages, which needs a GitHub token even for public packages. With the GitHub CLI:

gh auth refresh -h github.com -s read:packages
npm config set @starkware-libs:registry https://npm.pkg.github.com
npm config set '//npm.pkg.github.com/:_authToken' "$(gh auth token)"

npm install @starkware-libs/starknet-privacy-sdk@0.14.3-rc.8

Or skip the registry entirely and install from git at a specific commit:

npm install "starkware-libs/starknet-privacy#<commit-sha>"

Wire it up

The factory needs a Starknet account plus three things: a viewing key (your privacy key), a proving service (generates validity proofs) and a discovery service (finds your notes and channels). Pass the last two as plain config objects and the SDK constructs the production providers for you.

import { Account, RpcProvider, constants } from "starknet"
import { createPrivateTransfers } from "@starkware-libs/starknet-privacy-sdk"

const provider = new RpcProvider({ nodeUrl: process.env.RPC_URL! })

// cairoVersion "1" is required for accounts sending v3 transactions
const account = new Account({
  provider,
  address: process.env.ACCOUNT_ADDRESS!,
  signer: process.env.ACCOUNT_PRIVATE_KEY!,
  cairoVersion: "1",
})

const transfers = createPrivateTransfers({
  account,
  // The viewing key MUST be a bigint. A hex string silently misbehaves
  // downstream (wrong channel-key derivation).
  viewingKeyProvider: {
    getViewingKey: async () => BigInt(process.env.VIEWING_KEY!),
  },
  provingProvider: {
    url: process.env.PROVING_SERVICE_URL!,
    chainId: constants.StarknetChainId.SN_SEPOLIA,
  },
  discoveryProvider: { url: process.env.INDEXER_URL! },
  poolContractAddress: process.env.POOL_ADDRESS!,
  // Required only when using transfers.build().shadowAccounts(...).
  shadowAccountAnonymizerAddress: process.env.SHADOW_ACCOUNT_ANONYMIZER_ADDRESS,
})

If you need to configure a provider beyond what the config object exposes, ProvingServiceProofProvider and IndexerDiscoveryProvider are both exported from the package root and can be passed as instances instead — see Discovery Providers.

On Sepolia, POOL_ADDRESS is the privacy pool (v2.0) deployed at 0x0254a6b2997ef52e9f830ce1f543f6b29768295e8d17e2267d672c552cfe0d91. See Deployed Contract Addresses for the Mainnet pool and the verified anonymizer deployments on both networks.

If you configured shadowAccountAnonymizerAddress, continue with Shadow Accounts for commitment derivation and invocation.

Going deeper

The SDK is open source (Apache 2.0): starkware-libs/starknet-privacy. Its sdk/README.md covers material these pages do not — transaction sequencing against finalized state, the Open note API, and classifyTransaction for reading history.

Your first transaction

Every operation follows the same shape: build() a batch of operations, then execute() it and submit the resulting call.

// Prove against a slightly older block: notes mature 10 blocks after
// creation, and proving at the chain head risks reorg invalidation.
const provingBlockId = (await provider.getBlockNumber()) - 10

const { callAndProof } = await transfers.build().register().execute({ provingBlockId })

// Omit proof keys entirely when there are no proof facts - passing
// empty arrays serializes an invalid v3 transaction.
const proofDetails = callAndProof.proof.proofFacts?.length
  ? { proofFacts: callAndProof.proof.proofFacts, proof: callAndProof.proof.data }
  : {}

// tip is mandatory for v3 transactions in starknet.js
const tx = await account.execute(callAndProof.call, { tip: 0n, ...proofDetails })

await provider.waitForTransaction(tx.transaction_hash)
console.log(`registered in tx ${tx.transaction_hash}`)

This submission tail - back off provingBlockId, conditionally spread proofDetails, pass tip: 0n, wait - is identical for every operation in the following pages. We will not repeat the explanation, just the code.

What each provider does

Provider Role
viewingKeyProvider Supplies the private viewing key k used to decrypt notes and derive nullifiers
provingProvider Sends your signed invocation to a proving service, which executes it in a virtual Starknet environment and returns a STARK proof
discoveryProvider Scans your channels for incoming notes. Backed by IndexerDiscoveryProvider (HTTP discovery service)

Never log, sync in plaintext, or send a viewing key to analytics or support systems. A leak reveals the account's past and future private activity, although the Starknet account signature is still required to spend.

Next: register your viewing key so you can receive private transfers.