THE ESSENTIALS
A guide to zkPay.
zkPay uses zero-knowledge proofs to let you spend your private balance without revealing which deposit you are spending.
YOUR FIRST PAYMENT
Two steps, one private balance.
Deposit SOL
Connect a supported wallet, unlock your private balance with a message signature, then deposit SOL. Confirm the deposit transaction in your wallet.
Send privately
Enter a recipient’s Solana address and an amount. Review what the recipient will receive, then send. Your browser creates a proof and the relayer submits the payment.
The recipient needs only a Solana address. They do not need to connect to zkPay.
STEP 1
Deposit SOL.
Before you start
Use OKX Wallet, Solflare, Jupiter, or Backpack. Your wallet needs to support the selected Solana network, message signing, and versioned transactions.
Keep some SOL in your external wallet for transaction fees and account rent.
- Open app.zkpay.sh and check the network shown in the top-right corner.
- Select Connect Wallet. Approve the connection and carefully review the message-signing request that unlocks your private balance.
- Choose Deposit and enter the amount of SOL to move into the pool.
- Select Deposit SOL, review the transaction, and approve it in your wallet.
- Wait for confirmation before using the deposited balance on Send.
Wallet balance is not private balance
Wallet shows the SOL still in your external wallet. Available on Send shows your private pool balance. A deposit of 0.1 SOL adds 0.1 SOL to the pool; the external wallet also pays transaction costs and any required account rent.
STEP 2
Send privately.
You can send to a Solana address without the recipient connecting a wallet to zkPay. The payment is made from the pool through the relayer, rather than directly from your depositing wallet.
- Choose Send and enter the amount to deduct from your private balance. Max selects the available balance, including fees.
- Paste the Recipient address. Check the complete address and make sure the recipient accepts SOL on the selected Solana network.
- Review Recipient receives.
- Select Send privately. Keep the tab open while your browser generates the proof and the relayer submits the transaction.
- Look for Payment sent, then use View transaction to inspect the onchain result.
There is one recipient per payment. Once your balance is unlocked, sending does not normally require another wallet transaction approval. Any unspent change remains in your private balance.
There is no protocol-enforced waiting period after a confirmed deposit. This does not mean an immediate send is unlinkable: timing and similar amounts can reveal a relationship.
LEARN THE CONCEPTS
What is zero-knowledge?
A zero-knowledge proof lets someone demonstrate that a statement is true without revealing the private information used to prove it.
Think of proving that you know the combination to a safe without telling anyone the combination. The verifier needs evidence that you know the secret, not the secret itself. This is an intuition for the idea, not the exact protocol zkPay uses.
A statement, a witness, and a proof
- Statement: what is being checked. For a private payment, this includes authorization to spend valid funds without creating money.
- Witness: the private information that makes the statement true, such as spending-key material and the private notes being spent.
- Proof: cryptographic evidence the verifier can check without being given that witness.
Some values are deliberately public. A proof can hide the private note behind a payment while still binding that payment to a public recipient and amount. “Zero-knowledge” does not mean “zero public information.”
The three properties
- Completeness
- An honest prover with a valid witness can produce a proof the verifier accepts.
- Soundness
- A dishonest prover should not be able to convince the verifier of a false statement, assuming the system’s cryptographic assumptions hold.
- Zero-knowledge
- The proof should reveal no additional information about the witness beyond what the public statement already reveals.
Zero-knowledge is not encryption
Encryption hides a message so that someone with the right key can read it later. A zero-knowledge proof shows that a computation or statement is valid without revealing its private inputs. A private payment system can use both: encryption to protect note details, and a proof to validate spending.
What does zk-SNARK mean?
Zero-Knowledge Succinct Non-Interactive Argument of Knowledge. “Succinct” means the proof is compact and comparatively cheap to verify. “Non-interactive” means the verifier can check the proof without a back-and-forth conversation with the prover. “Argument of knowledge” refers to the computational guarantee that a successful prover knows an appropriate witness.
zkPay uses a SNARK system called Groth16. Your browser does the proof-generation work; the Solana program verifies the proof before accepting a payment. Not every zero-knowledge system uses Groth16 or requires the same kind of setup.
Further reading: Circom’s official background guide explains circuits, private inputs, and the proof workflow.
CONNECT THE IDEAS
Inside a private payment.
1. Notes, commitments, and encrypted records
A note represents a private claim on SOL in the pool. It is not another coin or token. It contains an amount, a spending public key, and random blinding data.
A commitment is a cryptographic fingerprint of that note. zkPay uses the Poseidon hash function to combine the amount, spending public key, blinding data, and asset identifier. The random blinding matters: simply hashing a predictable amount like 1 SOL would be easy to guess.
The pool records commitments publicly. Separately, encrypted notes let the owner recover the private details and reconstruct a balance. On scanning, the client decrypts a note and checks that it matches the public commitment. Encryption and proof verification perform different jobs.
The deposit itself is still public. A hidden note does not hide which wallet deposited SOL or the deposit amount.
2. Merkle trees: membership without naming the note
A Merkle tree combines many commitments into a compact summary called a root. A membership path connects one commitment to that root. In zkPay, the path and selected note are private inputs to the proof.
The checked-in circuit uses a tree of depth 26. The program checks an accepted root, while the proof establishes that a nonzero input note belongs to that tree without revealing its selected leaf. The commitment history remains public; it is the membership choice that the proof conceals.
3. Nullifiers: preventing a second spend
A nullifier is a public spent marker derived from a note and its private spending information. The proof establishes that it was derived correctly. The program separately rejects a nullifier already used onchain.
These checks work together: zero knowledge alone does not maintain a spent-note database, and membership in a Merkle tree does not mean a note is still unspent. A nullifier is not a refund code or a secret you should enter into a website.
4. Circuits: the rules a proof must satisfy
A circuit expresses a set of mathematical constraints. zkPay’s circuit checks ownership of input notes, valid commitments and nullifiers, membership for nonzero inputs, output amount ranges, and conservation of value.
A simplified way to read the conservation rule is: value entering a transaction must equal value leaving it. A withdrawal consumes private notes, pays a recipient and fee, and creates private change if needed.
Recipient: 0.3932 SOL
Fee: 0.0068 SOL
Private change: 0.6 SOL
The circuit has two input slots and two output slots. Unused slots can be filled with zero-value dummy notes. This does not mean the app sends to two recipients: the current Send flow has one external recipient.
5. Public inputs: binding the payment
The proof has seven public scalar inputs: the Merkle root, public amount, external-data hash, two input nullifiers, and two output commitments.
The external-data hash binds the recipient, amount, fee, fee recipient, and encrypted output bytes to the proof. The contract recomputes it before accepting a payment. A relayer cannot simply swap the destination while keeping an unchanged valid proof.
A hash here authenticates the relationship between fields; it does not make those fields secret. The recipient, external amount, and fees are still supplied to the program and visible onchain.
6. Local proving and onchain verification
Your browser computes the private witness and Groth16 proof. The relayer receives the proof and payment data, then submits a Solana transaction. The program verifies the proof, checks the root, rejects spent nullifiers, enforces fees, and processes the payment.
The proving key downloaded by the app is a public cryptographic parameter file, not your wallet private key. Downloading it does not let someone spend your funds. Your private witness and spending key must still stay secret.
7. Trusted setup: a separate security assumption
Groth16 uses proving and verification parameters created through a setup ceremony. Secret randomness from that ceremony must not be retained in a way that enables forged proofs. Security depends on correct ceremony execution and at least one honest secret contribution whose randomness was destroyed.
Keep learning
- Circom: proving circuits — how setup, proving, and verification fit together.
- Zcash Protocol Specification — a deeper reference for shielded notes, commitments, and nullifiers. Zcash and zkPay are different protocols; their features and security properties are not interchangeable.
BEFORE USING REAL SOL
Security & recovery.
Security audit
Protect your wallet and unlock signature
Your wallet signature is used to derive your private spending key. Anyone who obtains that signature may be able to spend the corresponding private funds. Never share it, your seed phrase, or your private key with a website, support contact, or chat.
Check app.zkpay.sh in the address bar before signing. A malicious site can copy the legitimate signing message; a familiar-looking message is not proof that a site is trustworthy.
Recover access to your private balance
Reconnect the same wallet account on the official app, choose the same network, and sign the same unlock message. The app derives the key again and scans the pool’s encrypted notes to reconstruct the available balance.
This depends on retaining access to the original wallet key and compatible message signing. A different wallet account, network, pool, or signing behavior can produce a different key. There is no zkPay password reset, and zkPay cannot recover a lost wallet key for you.
VERIFY WHERE YOU ARE
Networks & contracts.
| Network | Asset | App |
|---|---|---|
| Mainnet Beta | Real SOL | Open Mainnet |
| Devnet | Test SOL, no monetary value | Open Devnet |
The pools and private balances are separate. Changing the network does not move funds between them. zkPay currently supports SOL, not SPL tokens, USDC, or cross-chain transfers.
- Mainnet program
- 98Bj9K8iPV1JiVqBWXzY4bX4wsrm2x5DgEbiToybm9hx
- Devnet program
- 79EUG9jBTvcLenrTTYaHBzX6dqM9osaUXhLs3hVf4vBk
Use the deposit flow in the app. Do not send SOL directly to a program address expecting it to credit your private balance.
KNOW THE AMOUNT
Fees & amounts.
Deposits have no fee. Your wallet separately pays the Solana transaction fee and any required account rent.
Each Send currently costs 0.2% of the entered amount + 0.006 SOL. The fee comes out of that amount; it is not added on top. The relayer submits the payment and pays its network transaction costs.
Send fee = 0.2% × Entered amount + 0.006 SOL
Amounts are calculated in lamports: 1 SOL = 1,000,000,000 lamports. The exact fee is floor(amount × 20 ÷ 10,000) + 6,000,000 lamports.
WHEN SOMETHING LOOKS WRONG
Troubleshooting.
My wallet is missing or cannot sign.
Use a supported wallet: OKX Wallet, Solflare, Jupiter, or Backpack. Unlock the extension, check the selected network, and allow it to connect to the official app. The wallet must support message signing and versioned Solana transactions. Do not paste a private key into the app as a workaround.
I deposited SOL, but Wallet shows less.
Wallet is your external wallet balance, not your private balance. The deposited SOL has moved into the pool, and your wallet also paid transaction costs and possibly account rent. Check Available on Send after confirmation.
I reconnected and my private balance is zero.
Check the wallet account, Mainnet versus Devnet, and the exact official hostname. Allow the balance scan to finish. A different account or network derives a different key. If the original deposit was pending, inspect its transaction first. Do not make a second deposit just to try to restore access.
The recipient received less than I entered.
The entered amount is the total deduction from your private balance. The recipient receives it minus the send fee, currently 0.2% + 0.006 SOL. For example, entering 1 SOL sends 0.992 SOL. See the fee details.
Proof generation or confirmation is taking a long time.
Keep the tab open. Proof generation depends on your device, while submission and confirmation depend on the relayer and network. A timeout is not proof of failure. Check any available transaction link and refresh the balance before trying again. Never share the unlock signature when describing an error.
Can a payment be cancelled or recovered?
A confirmed Solana payment cannot be reversed by zkPay. If you sent to the wrong address, zkPay cannot take the funds back. Check the complete recipient address before sending.
Do I need to wait after depositing?
There is no protocol-enforced waiting period after confirmation. Waiting alone does not guarantee privacy; timing, amounts, a small pool, and address reuse can still allow correlation.