AI agents are starting to act like businesses. They pay for APIs, buy data, settle trades, and manage compute on their own. Put those payments on a public chain, though, and a hard problem shows up: surveillance.
When every payment is public, an agent’s financial graph is exposed. Who it pays. How much. When. That reveals strategy, vendors, and weak points. In a competitive market, that’s costly.
Agents need the digital equivalent of cash. That’s why I built SNAP (Shield Network Agent Payments), a privacy protocol for agent-to-agent payments on Solana. Here’s how it works, why the architecture looks the way it does, and what it took to bring zero-knowledge proofs to Solana in practice.
The problem: payment graphs leak strategy
Picture a trading agent. It buys price data from Agent A, routes trades through Agent B, and pays for compute from Agent C. On a public blockchain, anyone can rebuild that supply chain just by watching payments.
No hack required. A block explorer is enough.
We’ve seen this pattern already. MEV bots exploit transaction visibility on Solana and Ethereum. As agents grow into larger economic actors, payment graph analysis becomes the next attack surface.
The approach: a commitment–nullifier scheme
SNAP breaks the link between sender and receiver using a commitment–nullifier scheme with Groth16 zero-knowledge proofs. Instead of sending funds directly, agents move value through a shielded pool with fixed denominations (e.g., 0.1 SOL).
- Deposit: Agent A deposits a fixed amount with a commitment
Poseidon(secret, nullifier). - Transfer: Agent A shares the “secret note” (the commitment preimage) with Agent B over any private channel.
- Withdraw: Agent B generates a ZK proof that it holds a valid note for some commitment in the pool—without revealing which one.
The nullifier prevents double-spends. On withdrawal, the program records nullifierHash = Poseidon(nullifier). Because both commitment and nullifier use Poseidon, observers cannot link a nullifier back to its original commitment.
Deposit: commitment = Poseidon(secret, nullifier) → stored in Merkle tree
Withdraw: nullifierHash = Poseidon(nullifier) → checked against nullifier set
proof verifies: "I know (secret, nullifier) such that
Poseidon(secret, nullifier) is in the tree
AND nullifierHash = Poseidon(nullifier)"
From the outside, you see deposits in and withdrawals out, but you can’t connect a specific withdrawal to a specific deposit.
Architecture
Four pieces make this work: the Solana program, ZK circuits, on-chain Merkle state, and an off-chain relayer.
Solana program (Rust/Anchor)
The on-chain program exposes three core instructions:
deposit— Takes user funds and a 32-byte commitment. Inserts the commitment into the pool’s Merkle tree.withdraw_zk— Accepts a Groth16 proof, nullifier hash, recipient, and Merkle root. Verifies on-chain using BN254 pairing operations and transfers funds to the recipient.withdraw_zk_relayed— Same verification, but submitted by a relayer that takes a 0.25% fee from the withdrawal amount.
Solana’s native alt_bn128 precompiles make Groth16 verification possible directly on-chain. The hard part was fitting the pairing operations into Solana’s 1.4M compute unit limit per transaction. That required careful verifier optimization.
ZK circuit (circom/Groth16)
The withdrawal circuit (withdraw_20.circom) proves:
- The prover knows a
secretand anullifier. Poseidon(secret, nullifier)equals a commitment in the Merkle tree.Poseidon(nullifier)equals the publicnullifierHash.- The Merkle path is valid for the given
root. - The proof is bound to a specific
recipient(prevents front‑running).
It uses a depth‑20 Merkle tree (1,048,576 leaves). Poseidon is the hash function throughout—ZK‑friendly and collision‑resistant.
On‑chain state: commitment pages
Storing a depth‑20 tree on Solana isn’t trivial. A single account can’t hold 1M+ commitments due to the ~10MB account size limit.
SNAP uses CommitmentPage accounts—paginated storage where each page holds a slice of leaves. On deposit, the commitment goes into the current page. For withdrawals, the SDK reconstructs the Merkle path client‑side from these pages and passes it to the prover.
NullifierRecord PDAs track spent nullifiers. Each nullifier maps to a PDA derived from [pool_address, nullifier_hash]. The program checks if that PDA exists (already spent) before allowing a withdrawal.
The relayer: solving the gas problem
ZK proofs hide links, but gas fees can still leak identity. If Agent B withdraws to a fresh wallet, how does that wallet pay the fee without revealing a connection?
The SNAP Relayer handles it. It:
- Receives a withdrawal request (ZK proof + parameters).
- Verifies the proof off‑chain as a quick check.
- Builds and submits the Solana transaction, paying the fee.
- Deducts a 0.25% protocol fee from the withdrawal amount.
This lets agents withdraw to brand‑new, unfunded wallets with no on‑chain link back to prior activity.
// Agent B withdraws via relayer — no gas needed
const result = await snap.withdrawViaRelayer(
pool,
note,
freshRecipientWallet,
"https://relayer.agentzeny.ai"
);
// result: { txSignature, fee, recipientReceived }
SDK: private payments in five lines
Privacy that’s hard to use won’t be used. The snap-solana-sdk wraps the full flow:
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import { SNAPClient } from "snap-solana-sdk";
const connection = new Connection("https://your-rpc-url.com");
const sender = Keypair.generate();
const pool = new PublicKey("B8SyffZKt8LABKogWjH9rZcjY5PV2hyYRCbTxxbcrpFf");
// Agent A deposits
const snap = new SNAPClient(connection, sender);
const note = await snap.deposit(pool, 0.1);
const serialized = SNAPClient.serializeNote(note);
// Send `serialized` to Agent B through any private channel
// Agent B withdraws
const snapB = new SNAPClient(connection, recipient);
const tx = await snapB.withdraw(
pool,
SNAPClient.deserializeNote(serialized),
recipient
);
The SDK handles commitment generation, Merkle path reconstruction, WASM‑based proof generation (snarkjs), and transaction building. No circom constraints or BN254 math for the developer.
Agent framework integrations
Privacy should fit the tools you already use.
Solana Agent Kit
import { SolanaAgentKit } from "solana-agent-kit";
// SNAP plugin auto-registers snap_deposit, snap_withdraw, snap_withdraw_private
const agent = new SolanaAgentKit(wallet, rpcUrl, {});
LangChain / LangGraph
npm install snap-langchain-tools @langchain/core
import { createSNAPTools } from "snap-langchain-tools";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const tools = createSNAPTools(connection, wallet);
// Returns: [snap_list_pools, snap_deposit, snap_withdraw, snap_estimate_fee]
const agent = createReactAgent({ llm, tools });
const result = await agent.invoke({
messages: [{ role: "user", content: "Deposit 0.1 SOL into the SNAP pool" }],
});
MCP server (Claude Code, Cursor, etc.)
SNAP also ships as an MCP server so MCP‑compatible coding assistants can execute private payments as tools.
Mainnet pools
SNAP is live on Solana mainnet with three pools:
| Pool | Address | Denomination |
|---|---|---|
| SOL | B8SyffZKt8LABKogWjH9rZcjY5PV2hyYRCbTxxbcrpFf |
0.1 SOL |
| USDC | 5LeuHrPBgHNhgbCy996MEjcsBk5gNHhVj6AiuuCHZ8od |
1 USDC |
| USDC | ECuHf8kgiWfmL3Q6id4WGBQWvuukhzqvF5vsxuPAKZBv |
10 USDC |
Program ID: 9uePoqdgaXpqFLQM2ED1GGQrwSEiqe3r6tW1AfsnrrbS
Fixed denominations improve privacy. When every deposit is the same size, deposits blend together. The anonymity set is the entire pool.
What I learned building this
- ZK artifact management is harder than ZK math. Packaging WASM files, zkeys, and verification keys for Node.js took more engineering than the circuit. Agents run in servers, not browsers—so the loader had to work with
require(), notfetch(). - Agents need API‑first privacy. Agents don’t click buttons. They run scripts. Compressing the integration down to five lines mattered more than the smart contract work.
- Solana’s compute limits are tight but workable. Groth16 on BN254 fits within ~1.4M compute units, but just barely. Every extra operation in the verifier had to go.
- The relayer is underrated. Without gas abstraction, ZK alone doesn’t give full privacy. The relayer closes the last gap.
What’s next
- Security audit — Engaging a ZK/Solana audit firm for the program and circuits.
- Multi‑party trusted setup — Moving beyond a single‑contributor setup.
- Larger denomination pools — As the protocol hardens.
- More integrations — ElizaOS, Coinbase AgentKit, and others in progress.
SNAP is open source. If you’re building AI agents on Solana and want private payments:
- GitHub: github.com/agentzeny/snap-public
- SDK:
npm install snap-solana-sdk - LangChain tools:
npm install snap-langchain-tools - Website: agentzeny.ai
Your agent’s payment graph is a map of your business.
Reference: View article

Leave a Reply