Skip to main content
Answers to frequently asked questions about the ZKP2P protocol, smart contracts, and integration.

General Protocol

ZKP2P is a decentralized protocol enabling trustless peer-to-peer exchanges between fiat and cryptocurrency. It uses zero-knowledge proofs and attestations to verify off-chain payments without exposing sensitive financial data.Key features:
  • Direct P2P fiat-to-crypto exchanges without intermediaries
  • Support for 8+ major payment platforms (Venmo, PayPal, Wise, etc.)
  • Privacy-preserving payment verification using zkTLS attestations
  • Intent-based architecture for efficient liquidity matching
The protocol achieves trustlessness through several mechanisms:
  1. Escrow Locking: Maker’s USDC is locked in the Escrow contract when a taker signals intent
  2. Payment Verification: Off-chain payments are verified using EIP-712 signed attestations from trusted witnesses
  3. Nullifier Registry: Prevents double-spending by tracking used payment IDs
  4. Atomic Settlement: Funds are only released after successful payment verification
  5. Smart Contract Enforcement: All rules are enforced on-chain with no trusted intermediary
  • On-ramp: Buy USDC with fiat through supported payment platforms (Venmo, PayPal, Wise, etc.)
  • Off-ramp: Sell USDC for fiat with guaranteed settlement
  • Cross-border: Access global liquidity through multiple payment rails
  • P2P Trading: Direct peer-to-peer exchanges without centralized exchanges
The protocol is deployed on:
  • Base (Production): Fully operational mainnet deployment
  • Base Sepolia (Testnet): For testing and development
  • Base Staging: Internal staging environment
All deployment addresses are available in the deployments/ directory.

Payment Methods

Currently supported platforms:North America:
  • Venmo (USD)
  • PayPal (USD)
  • CashApp (USD, GBP)
  • Zelle - Citibank, Chase, Bank of America (USD)
  • Chime (USD)
Europe:
  • Wise (30+ currencies)
  • Revolut (20+ currencies)
  • Monzo (GBP, EUR, USD)
  • N26
Latin America:
  • MercadoPago (7 countries)
Asia:
  • Alipay
See the Supported Payment Methods page for complete details.
Payment verification uses the UnifiedPaymentVerifier contract with the following flow:
  1. Off-chain Payment: Taker sends fiat payment through their chosen platform
  2. Attestation Generation: Payment receipt is converted to a zkTLS proof
  3. Witness Signing: Trusted witness signs the payment attestation (EIP-712)
  4. On-chain Verification: UnifiedPaymentVerifier validates:
    • Witness signature authenticity
    • Payment details match intent (amount, payee, method, currency)
    • Payment timestamp within acceptable buffer
    • Payment ID hasn’t been used before (nullifier check)
  5. Settlement: If valid, USDC is released to taker
See contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:128 for implementation details.
Yes! The protocol is designed to be extensible. To add a new payment method:
  1. Create a verifier configuration in deployments/verifiers/your-method.ts
  2. Define the payment method hash using calculatePaymentMethodHash("method-name")
  3. Specify supported currencies
  4. Create deployment script in deploy/
  5. Register with UnifiedPaymentVerifier and PaymentVerifierRegistry
Refer to the Supported Payment Methods guide for detailed steps.
A payment method hash is a unique identifier for each payment platform, computed as:
keccak256(toUtf8Bytes("method-name"))
Examples:
  • Venmo: 0x90262a3db0edd0be2369c6b28f9e8511ec0bac7136cefbada0880602f87e7268
  • Wise: 0x554a007c2217df766b977723b276671aee5ebb4adaea0edb6433c88b3e61dac5
  • Revolut: 0x617f88ab82b5c1b014c539f7e75121427f0bb50a4c58b187a238531e7d58605d
These hashes are used throughout the protocol to identify payment methods in intents and verifications.

Architecture & Components

The Escrow contract manages liquidity deposits from makers (liquidity providers):
  • Deposit Management: Makers create deposits with USDC and specify accepted payment methods
  • Fund Custody: Securely holds liquidity until trades are completed
  • Configuration: Enforces intent limits, expiry periods, dust thresholds
  • Fee Collection: Collects maker fees on successful trades
  • Lock/Unlock: Orchestrator can lock/unlock funds during intent processing
Key file: contracts/Escrow.sol
The Orchestrator is the central coordinator for the intent lifecycle:
  • Intent Management: Handles signaling, fulfilling, and canceling intents
  • Gating: Validates optional gating signatures for access control
  • Fund Coordination: Locks/unlocks funds on Escrow during processing
  • Verification Routing: Routes verification requests to appropriate verifiers via registry
  • Fee Distribution: Collects and distributes protocol/referrer fees
  • Hook Execution: Executes optional post-intent hooks for custom logic
Key file: contracts/Orchestrator.sol
The UnifiedPaymentVerifier is a single contract that verifies payments for all supported payment methods:Key Features:
  • Replaces individual payment verifiers (VenmoVerifier, PayPalVerifier, etc.)
  • Validates EIP-712 signed attestations from off-chain services
  • Configurable per payment method (currencies, timestamp buffers)
  • Nullifies payments to prevent double-spending
  • Enforces timestamp buffers for L2 flexibility (max 48 hours)
Design Benefits:
  • Single contract to upgrade/manage
  • Consistent verification logic across all methods
  • Easy to add new payment methods
  • Reduced deployment and maintenance costs
Key file: contracts/unifiedVerifier/UnifiedPaymentVerifier.sol
The protocol uses multiple registries for permissions and configuration:PaymentVerifierRegistry: Maps payment methods to verifiers and supported currenciesEscrowRegistry: Whitelist of valid escrow implementationsOrchestratorRegistry: Whitelist of valid orchestrator contractsRelayerRegistry: Authorizes relayers for gasless transactionsPostIntentHookRegistry: Manages approved post-intent hooksNullifierRegistry: Tracks used payment proofs globally to prevent double-spendingAll registries are in contracts/registries/
The Protocol Viewer is a read-only contract that aggregates state from Escrow and Orchestrator:
  • Optimized for frontend queries and analytics
  • Batched data fetching for UI performance
  • No state modification, only view functions
  • Reduces number of RPC calls needed by frontends
Useful for building UIs and dashboards.

Integration

import { Escrow } from "@typechain/Escrow";

// Approve USDC first
await usdcToken.approve(escrowAddress, amount);

// Create deposit with payment method configuration
const depositId = await escrow.createDeposit({
  token: USDC_ADDRESS,
  amount: ethers.utils.parseUnits("1000", 6),
  paymentMethods: [venmoHash, paypalHash],
  minAmounts: [
    ethers.utils.parseUnits("10", 6),
    ethers.utils.parseUnits("10", 6)
  ],
  conversionRates: [100, 100], // 1:1 rates
});
See contracts/Escrow.sol for complete function signatures.
import { Orchestrator } from "@typechain/Orchestrator";

// Signal intent to trade
const tx = await orchestrator.signalIntent({
  escrow: escrowAddress,
  depositId: depositId,
  amount: ethers.utils.parseUnits("100", 6),
  recipient: takerAddress,
  paymentMethod: venmoHash,
  payeeDetails: ethers.utils.keccak256(
    ethers.utils.toUtf8Bytes("payee-id")
  ),
  data: "0x", // Additional data if needed
});

const receipt = await tx.wait();
// Extract intentHash from events
See contracts/Orchestrator.sol:signalIntent for details.
// Build payment attestation (typically done by attestation service)
const attestation = {
  intentHash: intentHash,
  releaseAmount: ethers.utils.parseUnits("100", 6),
  dataHash: ethers.utils.keccak256(paymentData),
  signatures: [witnessSignature],
  data: encodedPaymentData,
  metadata: "0x"
};

const paymentProof = ethers.utils.defaultAbiCoder.encode(
  ["tuple(bytes32,uint256,bytes32,bytes[],bytes,bytes)"],
  [attestation]
);

// Submit for verification
await orchestrator.fulfillIntent({
  intentHash: intentHash,
  paymentProof: paymentProof,
  data: "0x",
});
See test/unifiedVerifier/unifiedPaymentVerifier.spec.ts for examples.
Use the Protocol Viewer for efficient querying:
import { ProtocolViewer } from "@typechain/ProtocolViewer";

// Get deposits for specific payment method
const deposits = await protocolViewer.getDepositsByPaymentMethod(
  escrowAddress,
  venmoHash
);

// Get specific deposit details
const deposit = await protocolViewer.getDeposit(
  escrowAddress,
  depositId
);
The project uses Typechain to generate TypeScript bindings from contracts:
import {
  Escrow,
  Orchestrator,
  UnifiedPaymentVerifier,
  PaymentVerifierRegistry,
  NullifierRegistry,
  // ... other contracts
} from "@utils/contracts";
Generate types with:
yarn build  # Compiles contracts and generates typechain
Types are in typechain/ directory.

Security & Privacy

The protocol uses several privacy-preserving techniques:
  1. Hashed Identifiers: Payment IDs and payee details are hashed on-chain
  2. Off-chain Proofs: Sensitive payment details stay off-chain, only proofs are submitted
  3. Zero-Knowledge: zkTLS proofs verify payments without revealing full data
  4. Event Emissions: Only necessary data is emitted in events for reconciliation
Example from UnifiedPaymentVerifier.sol:67-72:
struct PaymentDetails {
    bytes32 method;
    bytes32 payeeId;     // Hashed for privacy
    uint256 amount;
    bytes32 currency;
    uint256 timestamp;
    bytes32 paymentId;   // Hashed for privacy
}
The NullifierRegistry prevents double-spending:
  1. Each payment creates a unique nullifier: keccak256(abi.encodePacked(paymentMethod, paymentId))
  2. Nullifier is checked before verification
  3. If already used, transaction reverts
  4. If new, nullifier is added to registry after successful verification
  5. Nullifiers are permanent and cannot be removed
See contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:242 for implementation.
Key security assumptions:
  1. Trusted Witnesses: Attestation signers are trusted to accurately verify off-chain payments
  2. Smart Contract Security: Contracts have been audited but use at your own risk
  3. Payment Platform Integrity: Payment platforms (Venmo, PayPal, etc.) accurately report transaction data
  4. Timestamp Accuracy: L2 timestamps are reasonably accurate (within buffer window)
  5. Registry Permissions: Only authorized addresses can modify critical registries
Always verify witness addresses and ensure they’re trustworthy before relying on attestations.
If payment verification fails:
  1. Transaction reverts with specific error message
  2. Intent remains locked (can be canceled after expiry)
  3. No USDC is transferred
  4. No nullifier is created (payment can be attempted again with valid proof)
  5. Taker can retry with corrected proof or cancel intent
Common verification failures:
  • Invalid witness signature
  • Payment details don’t match intent
  • Payment ID already used (double-spend attempt)
  • Timestamp outside acceptable buffer
  • Payment amount insufficient

Development

Prerequisites:
  • Node.js 18+
  • Yarn 4
  • Foundry (for forge)
Setup:
# Install dependencies
yarn

# Copy environment template
cp .env.default .env

# Configure .env with your keys:
# - ALCHEMY_API_KEY
# - BASE_DEPLOY_PRIVATE_KEY
# - TESTNET_DEPLOY_PRIVATE_KEY
# - BASESCAN_API_KEY
# - ETHERSCAN_KEY
# - INFURA_TOKEN

# Start local node
yarn chain

# Deploy to local node (in another terminal)
yarn deploy:localhost
# All Hardhat tests
yarn test

# Fast tests only (skip slow integration tests)
yarn test:fast

# Specific test suite
yarn test test/escrow/
yarn test test/orchestrator/
yarn test test/unifiedVerifier/

# Foundry tests
yarn test:forge              # All Foundry tests
yarn test:forge:fuzz         # Fuzz testing
yarn test:forge:invariant    # Invariant testing

# Coverage
yarn coverage                # Hardhat coverage
yarn test:forge:coverage     # Foundry coverage
Test files are in test/ (Hardhat) and test-foundry/ (Foundry).
# Deploy to Base
yarn deploy:base

# Deploy to Base Sepolia (testnet)
yarn deploy:base_sepolia

# Verify contracts on Basescan
yarn etherscan:base
yarn etherscan:base_sepolia
Deployment scripts are numbered in deploy/ directory (00-17). They run sequentially.Deployment artifacts saved to deployments/{network}/.
The project uses:
  • Hardhat: Primary development framework
  • Foundry: Fuzz and invariant testing
  • Typechain: TypeScript bindings generation
  • Ethers.js: Ethereum library
  • Waffle: Testing utilities
  • Hardhat Deploy: Deployment management
Build commands:
yarn build      # Full build: clean → compile → typechain → tsc
yarn compile    # Compile contracts only
yarn clean      # Clean build artifacts

Fees & Economics

The protocol has three types of fees:
  1. Maker Fee: Fee charged to liquidity providers on successful trades (configured per deposit)
  2. Protocol Fee: Fee collected by the protocol (configured in Orchestrator)
  3. Referrer Fee: Optional fee for referrers who bring users
Fees are deducted from the release amount:
releaseAmount = verifiedAmount - protocolFee - referrerFee
makerReceives = depositAmount - releaseAmount + makerFee
See contracts/Orchestrator.sol for fee distribution logic.
Conversion rates are set by makers in their deposits:
conversionRates: [100, 105, 98]  // Basis points (100 = 1:1)
  • 100 = 1:1 (1 USD = 1 USDC)
  • 105 = 1.05:1 (1 USD = 1.05 USDC)
  • 98 = 0.98:1 (1 USD = 0.98 USDC)
Takers see and accept the rate when signaling intent. Final rate is locked in the intent and verified during fulfillment.See contracts/Escrow.sol for rate configuration.
Minimum Amounts:
  • Configured per deposit and payment method by maker
  • Prevents dust trades and ensures economic viability
  • Typically 10 USDC or equivalent
Maximum Amounts:
  • Limited by deposit available liquidity
  • No protocol-level maximum
  • Payment platforms may have their own limits
Dust Threshold:
  • Escrow has a global dust threshold (typically 10 USDC)
  • Deposits must be above dust threshold
  • Prevents griefing with tiny deposits
See contracts/Escrow.sol constructor for dust threshold configuration.

Need More Help?

If your question isn’t answered here:

Build docs developers (and LLMs) love