Depositing into SolMask is free, and the protocol fee is charged when you withdraw — in two parts, both enforced on-chain by the program. Deposit 10 SOL and your shielded note is worth the full 10 SOL; deposit 10,000 USDC and your note is worth 10,000 USDC. Nothing is skimmed on the way in. The amount you can later spend privately is exactly what you put in.
First part: a percentage fee on withdrawal, taken in the asset you withdraw. It lives in the on-chain Config (withdraw_fee_bps), is admin-tunable without a redeploy, and is capped at 100 basis points (1.00%). At the recommended 23 bps (0.23%), withdrawing 10 SOL costs ~0.023 SOL, routed to a per-pool token fee vault owned by the protocol. Crucially, this fee is bound inside the withdraw zero-knowledge proof — the circuit proves your spent notes equal the released output plus the fee plus change, and the program recomputes the expected fee on-chain and rejects any proof that doesn't match. The relayer cannot tamper with it.
Second part: a flat withdraw fee that defaults to 0.003 SOL, paid to the fee collector on every withdraw. The amount lives in the on-chain Config (withdraw_fee_lamports) and is admin-tunable without a redeploy. It is fixed in SOL regardless of which asset you are withdrawing or how much. On a 1 SOL transfer it is 0.3% of the move; on a 100 SOL transfer it is 0.003%. This is the right shape for privacy-as-utility: large transfers — where privacy matters most — pay the smallest percentage. The relayer pays that SOL fee to the fee collector up front and recoups exactly it by keeping a small tip slice in the withdrawn asset. The Solana network gas — compute units plus account rent for the new nullifier PDA — is a cost the relayer absorbs; it is not reimbursed out of this fee.
Why charge on the way out? Deposits cost you nothing, so funding the pool is frictionless and you only ever pay when value actually leaves the shielded set. It also keeps the fee proportional to what you withdraw rather than what you parked.
The fees were chosen for simplicity. No minimum, no maximum beyond the on-chain caps, no slab structure, no surge pricing, no different rate per asset, no time-of-day premium. Whether you withdraw 0.05 SOL or 5,000 SOL, the percentage is the same. The model is meant to be predictable in your head; you should be able to estimate the cost without a calculator.
For withdraws into non-SOL assets — USDC, USDT, anything Jupiter-routable — you never need to hold SOL to pay the fee. The relayer pays the flat SOL fee from its own wallet and recoups it by keeping a small tip slice of the withdrawn asset (a fraction of a USDC, say) before forwarding the rest. The recipient still ends up with the asset they expected, net of the percentage fee and that tip. That's why you can withdraw USDC to a brand-new wallet that holds zero SOL.
Fees accumulate in per-pool fee vaults and are swept to the configured fee destination permissionlessly. Any signer can call the sweep instruction once the per-pool balance exceeds its configured threshold; if an automated job stops tomorrow, the funds are not stuck. The fee destination is published on-chain and reachable from any block explorer. Every cent of SolMask's revenue is on-chain, traceable to a single fee-destination address, and was deducted by code the verifier can replay.
FAQ
Does it really cost nothing to deposit? Yes — the full amount you deposit enters the pool and backs your note. The only Solana network gas on a deposit is the normal priority fee that goes to validators, not to SolMask.
Does the relayer pay gas for me? Yes — the relayer is the transaction's fee payer on a withdraw, so it covers the Solana network gas and account rent out of its own SOL, and it fronts the flat protocol fee (0.003 SOL by default). It recoups only the protocol fees, via a tip slice in the withdrawn asset; the network gas it absorbs. That's why your recipient never needs SOL.
What does the withdrawal fee actually go toward? Protocol revenue. Audit costs, the ceremony, ongoing development, infrastructure (indexer, relayer, RPC). It is not a network gas fee.
Is the percentage fee taken in the asset I withdraw, or in SOL? In the asset you withdraw. A SOL withdraw pays the percentage in SOL; a USDC withdraw pays it in USDC. The flat fee is always in SOL.
What if I self-relay? You sign and submit the withdraw yourself, so you pay the network gas and the protocol fees (the current withdraw_fee_bps and withdraw_fee_lamports) directly. There is no asset-denominated tip in that case — you are the relayer — but you take on the gas the hosted relayer would otherwise have absorbed.
Where can I verify all of this? Deposits are free, the withdraw percentage is Config.withdraw_fee_bps, and the flat withdraw fee is Config.withdraw_fee_lamports — both defined and enforced on-chain in the program's deposit and withdraw instructions. The full breakdown is at /docs/fees.