DEV Community

agentzeny
agentzeny

Posted on • Originally published at agentzeny.ai

How We Built Private Agent Payments on Solana with Zero-Knowledge Proofs

The AI agent economy is scaling fast. Agents pay for APIs, buy data from other agents, settle trades, and manage resources autonomously. But running these agents on public blockchains introduces a critical flaw: surveillance.

When every on-chain agent payment is public, an agent's entire financial graph becomes visible. Anyone can see who an agent paid, how much, and when. This reveals the agent's strategy, its vendor partnerships, and its vulnerabilities.

For AI agents to function effectively in a competitive economy, they need the digital equivalent of cash.

That's why I built SNAP (Shield Network Agent Payments) — a privacy protocol purpose-built for AI agent-to-agent payments on Solana. This is a deep dive into how it works, the architecture choices I made, and the challenges of putting zero-knowledge proofs on Solana.

The Problem: Payment Graphs Leak Strategy

Consider an autonomous trading agent. It sources price data from Agent A, executes trades through Agent B, and pays for compute from Agent C. On a public blockchain, any observer can reconstruct this entire supply chain just by watching the payment graph.

A competitor doesn't need to hack your agent. They just need a block explorer.

This isn't hypothetical. MEV bots already exploit transaction visibility on Solana and Ethereum. As agents become larger economic actors, payment graph analysis becomes the next attack vector.

The Approach: Commitment-Nullifier Scheme

To break the link between sender and receiver, SNAP uses a commitment-nullifier scheme powered by Groth16 zero-knowledge proofs.

Instead of Agent A sending SOL directly to Agent B, the transaction goes through a shielded pool:

  1. Deposit: Agent A deposits a fixed denomination (e.g., 0.1 SOL) into the pool alongside a cryptographic commitment — Poseidon(secret, nullifier).
  2. Transfer: Agent A sends a "secret note" (the commitment pre-image) to Agent B through any private channel.
  3. Withdraw: Agent B generates a ZK proof demonstrating they possess a valid note for some commitment in the pool, without revealing which commitment.

The key is the nullifier — a unique hash derived from the secret note. When Agent B withdraws, the on-chain program records the nullifier hash to prevent double-spending. Because the commitment and nullifier are Poseidon hashes, observers cannot link the nullifier back to the 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)"
Enter fullscreen mode Exit fullscreen mode

An observer sees deposits going in and withdrawals going out, but cannot connect any specific withdrawal to any specific deposit.

Architecture

The system has four components: 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 the user's funds and a 32-byte commitment. Inserts the commitment into the pool's Merkle tree.
  • withdraw_zk — Takes a Groth16 proof, nullifier hash, recipient, and Merkle root. Verifies the proof on-chain using BN254 pairing operations and transfers funds to the recipient.
  • withdraw_zk_relayed — Same verification, but the relayer submits the transaction and takes a 0.25% fee from the withdrawal amount.

Solana's native alt_bn128 precompiles make Groth16 verification possible directly on-chain. The challenge was fitting the pairing operations within Solana's 1.4M compute unit limit per transaction — this required careful optimization of the verifier code.

ZK Circuit (circom/Groth16)

The withdrawal circuit (withdraw_20.circom) proves:

  • The prover knows a secret and a nullifier
  • Poseidon(secret, nullifier) equals a commitment that exists in the Merkle tree
  • Poseidon(nullifier) equals the publicly submitted nullifierHash
  • The Merkle path is valid for the given root
  • The proof is bound to a specific recipient address (preventing front-running)

The circuit uses a depth-20 Merkle tree, supporting over 1 million deposits per pool (1,048,576 leaves). Poseidon is the hash function throughout — it's ZK-friendly (low constraint count) and collision-resistant.

On-Chain State: Commitment Pages

Storing a depth-20 Merkle tree on Solana is non-trivial. A naive approach would require a single account holding all 1M+ commitments, which exceeds Solana's 10MB account size limit.

SNAP uses CommitmentPage accounts — paginated storage where each page holds a slice of the tree's leaves. When a deposit occurs, the commitment is inserted into the current page. For verification, the SDK reconstructs the Merkle path client-side from the commitment pages and passes the path as proof inputs.

NullifierRecord PDAs track spent nullifiers. Each nullifier maps to a PDA derived from [pool_address, nullifier_hash]. The program checks if the PDA exists (already spent) before allowing a withdrawal.

The Relayer: Solving the Gas Problem

Even with ZK proofs breaking the payment link, a privacy leak remains: gas fees.

If Agent B withdraws to a fresh wallet, how does it pay the Solana transaction fee? If it funds the wallet from an existing account, the privacy is compromised — an observer can link the funding transaction to the withdrawal.

The SNAP Relayer solves this. It's an Express service that:

  1. Receives a withdrawal request (ZK proof + parameters) from the agent
  2. Verifies the proof off-chain (fast sanity check)
  3. Builds and submits the Solana transaction, paying the gas fee
  4. Deducts a 0.25% protocol fee from the withdrawal amount

This means agents can withdraw to completely fresh, unfunded wallets with zero on-chain footprint connecting the recipient to any 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 }
Enter fullscreen mode Exit fullscreen mode

SDK: 5 Lines to Private Payments

A privacy protocol that requires a PhD to use is a protocol nobody uses. The SDK (snap-solana-sdk) wraps the entire 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
);
Enter fullscreen mode Exit fullscreen mode

The SDK handles commitment generation, Merkle path reconstruction, proof generation (WASM-based snarkjs), and transaction building. The developer never touches circom constraints or BN254 math.

Agent Framework Integrations

Privacy should plug into whatever agent framework you're already using:

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, {});
Enter fullscreen mode Exit fullscreen mode

LangChain / LangGraph

npm install snap-langchain-tools @langchain/core
Enter fullscreen mode Exit fullscreen mode
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" }],
});
Enter fullscreen mode Exit fullscreen mode

MCP Server (Claude Code, Cursor, etc.)

SNAP also ships as an MCP server, so any MCP-compatible AI coding assistant can execute private payments as part of its tool set.

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 are a privacy feature, not a limitation. When every deposit is the same size, deposits become indistinguishable — the anonymity set is the entire pool.

What I Learned Building This

ZK artifact management is harder than ZK math. Bundling WASM files, zkeys, and verification keys into an npm package that works in Node.js environments required more engineering than the circuit itself. Agents run in server environments, not browsers — the artifact loading pipeline had to work with require(), not fetch().

Agents need API-first privacy. Agents don't click buttons in web wallets. They execute scripts. Reducing the integration surface to 5 lines of code was harder than building the smart contract, but it's what makes the protocol actually usable.

Solana's compute limits are tight but workable. Groth16 verification on BN254 fits within the 1.4M compute unit budget, but just barely. Every unnecessary operation in the on-chain verifier had to go.

The relayer is the most underrated component. Without gas abstraction, ZK proofs alone don't provide 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 ceremony — Expanding beyond the current single-contributor setup
  • Larger denomination pools — As the protocol hardens
  • More framework integrations — ElizaOS, Coinbase AgentKit, and others in progress

SNAP is fully open source. If you're building AI agents on Solana and want private payments:

Your agent's payment graph is a map of your business.

Top comments (0)