THE ESSENTIALS
A guide to zkPay.
Deposit SOL into a private balance. Send it to a Solana address without a direct wallet-to-wallet transfer.
zkPay uses zero-knowledge proofs to let you spend your private balance without revealing which deposit you are spending. Your wallet unlocks that balance; a relayer submits your payment to the network.
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. Phantom is not currently supported.
Keep some SOL in your external wallet for transaction fees and account rent. Mainnet and Devnet have separate balances; test SOL cannot be used on Mainnet.
- 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. This is the entered amount minus the send fee.
- 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. See privacy boundaries.
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.
The following describes the design implemented in zkPay’s checked-in source. It is not an independent audit or a claim that the complete source-to-deployed-artifact build has been independently reproduced.
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.
zkPay’s recorded setup contains four upstream secret contributions, one project secret contribution, and a public beacon. A public beacon is not another secret contributor. Secret destruction and the complete source-to-parameters build have not been independently verified for this deployment.
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.
SET THE RIGHT EXPECTATIONS
Privacy has boundaries.
zkPay is designed to avoid an explicit onchain link between a deposited private note and its later spend. It does not make Solana activity invisible or guarantee anonymity.
| Protected by the proof | Still observable |
|---|---|
| The private witness used to authorize a spend. | Depositing wallets, deposit amounts, transaction times, and pool activity. |
| Which valid private note and membership path are used. | Send amounts, fees, recipients, and public nullifiers. |
| No explicit note-to-spend linkage is required for verification. | Request metadata visible to services handling your connection. |
Why payments may still be linked
An observer can compare timing, unusual amounts, repeated addresses, and other onchain activity. A small pool gives fewer plausible deposits to choose from. Offchain information may also identify a participant.
There is no waiting duration that guarantees privacy. A new recipient address alone does not guarantee privacy either. Do not treat a successful proof as proof that an observer cannot link your activity.
Hosting, RPC, and relayer services can observe network requests and related metadata. These docs do not make a no-logs promise. The documentation page itself does not connect a wallet or ask you to sign anything.
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.
Understand the trust and operational risks
- No independent audit: successful tests and upstream audits are not an audit of this deployed system.
- Upgradeable program: the upgrade authority can replace the program’s rules. Current restrictions are not immutable.
- Pool administration: the pool authority can change limits and permitted fee settings, and initiate shutdown.
- Shutdown: a shutdown notice stops new deposits. Under the current Mainnet rules, remaining funds can be swept by the authority after the 30-day withdrawal period. Do not assume an automatic refund.
- Availability: relay, RPC, indexer, website, or network outages can delay access. Lost keys, malicious software, incorrect addresses, and contract bugs can cause loss.
- Cryptography: proof-system assumptions and the trusted setup are part of the security model, not a substitute for independent review.
Start with a small amount on Mainnet and consider the risks before increasing it. Devnet is also available for practice with test SOL.
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 zkPay platform 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
| You enter | Send fee | Recipient receives |
|---|---|---|
| 0.1 SOL | 0.0062 SOL | 0.0938 SOL |
| 1 SOL | 0.008 SOL | 0.992 SOL |
| 3 SOL | 0.012 SOL | 2.988 SOL |
| 5 SOL | 0.016 SOL | 4.984 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.
Small payments and leftover balances
The fixed fee is significant for small payments. Your send amount must cover the fee and leave a positive amount for the recipient. A partial send can leave a remainder too small to send by itself. Consider this before depositing or choosing an amount.
These are the current settings, not a promise that fees will never change. The current program allows the pool authority to change the percentage fee up to 1%; the program itself is upgradeable. Always check the recipient estimate in the app.
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. Phantom is not currently supported. 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 examples.
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.
The app says the balance is too small.
A Send must cover both the percentage fee and fixed fee and leave a positive amount for the recipient. A tiny remainder may be too small to send on its own. Review the current fees before deciding whether to add more SOL.
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.