Smart contracts for the ZKP2P fiat on/off-ramp, with the current repository centered on the v2 system:
EscrowV2: maker liquidity, per-deposit payment configuration, oracle-backed pricing, delegated rate managers.OrchestratorV2: intent lifecycle, fee handling, pre-intent hooks, whitelist hooks, post-intent execution.UnifiedPaymentVerifierV2: shared attestation-based verifier registered across supported payment methods.ProtocolViewerV2: batched read model for deposits, intents, supported payment methods, and effective rates.
The repository still contains legacy v1 contracts and deploy scripts because v2 is deployed on top of shared protocol infrastructure, but the active development work over the last month has been concentrated in the v2 contracts, periphery, and deployment pipeline.
- What Landed Recently
- System Overview
- V2 Contract Inventory
- Core Lifecycle
- Rate Management Model
- Hooks and Extensibility
- Payment Verification Model
- Repository Layout
- Getting Started
- Build, Test, and Development Commands
- Deployment Model
- Supported Payment Methods
- Testing Strategy
- Networks and Deployment Artifacts
- Security Notes
The v2 surface was built out rapidly between February 20, 2026 and March 11, 2026. The main additions in that window are:
2026-03-02:EscrowV2,OrchestratorV2,ProtocolViewerV2,RateManagerV1,AcrossBridgeHookV2,SignatureGatingPreIntentHook,WhitelistPreIntentHook,ChainlinkOracleAdapter,OrchestratorRegistry, and the supporting v2 interfaces/mocks/tests landed.2026-03-02: the dedicated v2 deployment pipeline landed indeploy/14_deploy_v2_system.ts,deploy/15_deploy_v2_periphery.ts, anddeploy/16_configure_v2_payment_methods.ts, with matching deployment tests.2026-03-03:PythOracleAdapterand its deployment/test coverage were added for Pyth FX feeds.2026-03-04: mainnet deployment scripts forEscrowV2,OrchestratorV2, andRateManagerV1landed.2026-03-06: the rate-floor model was refactored soEscrowV2enforces the final floor whileRateManagerV1acts as a pure delegated rate registry.2026-03-11:EscrowV2gained batch currency/oracle configuration setters, includingsetOracleRateConfigBatch,updateCurrencyConfigBatch, anddeactivateCurrenciesBatch.2026-03-11:EscrowV2added support for negative oracle spreads, allowing makers to quote below the oracle market rate while still preserving a positive multiplier invariant.2026-03-11:OrchestratorV2added support for multi-recipient referral fees, withReferralFeeLiband updated interfaces/tests.2026-03-11: a staging redeploy script forEscrowV2,OrchestratorV2, andSignatureGatingPreIntentHooklanded indeploy/19_redeploy_escrowv2_orchestratorv2_staging.ts.
In practice, "the latest contracts we have added in the past month" means the README should be read as a v2-first document.
ZKP2P is a non-custodial fiat-to-crypto settlement protocol. Makers deposit on-chain liquidity, takers lock a portion of that liquidity by signaling an intent, a payment proof is verified on-chain against an off-chain attestation, and settlement completes either directly to the taker or through a post-intent hook.
The v2 system is built around four layers:
- Liquidity custody and pricing.
EscrowV2stores deposits, payment methods, payee hashes, supported fiat currencies, fixed floors, oracle configs, rate-manager delegation, and outstanding intents. - Intent coordination and settlement.
OrchestratorV2validates whether a taker can lock liquidity, snapshots fee terms and min intent size, verifies payments through the registry-selected verifier, and releases funds. - Verification and registries.
UnifiedPaymentVerifiervalidates EIP-712 attestations and nullifies payments. Shared registries define which escrows, orchestrators, relayers, hooks, and payment methods are valid. - Read models and periphery.
ProtocolViewerV2, oracle adapters, bridge hooks, and pre-intent hooks provide the ergonomic layer used by frontends, routing systems, and privileged operators.
This section covers the current v2 contracts and why each exists.
EscrowV2 is the v2 liquidity layer. Each deposit stores:
- depositor and optional delegate
- deposit token
- min/max per-intent amount range
- whether the deposit is currently accepting intents
- optional intent guardian
- optional
retainOnEmptybehavior so the deposit configuration can survive empty liquidity - per-payment-method verification data
- per-payment-method supported currencies
- per-currency fixed minimum conversion rates
- optional per-currency oracle rate configuration
- optional delegated rate manager configuration
Notable v2 behavior:
- Supports
createDepositanddepositTo, so contracts can fund deposits on behalf of makers. - Tracks outstanding intents inside escrow and reclaims liquidity when expired intents are pruned.
- Allows a delegate to manage deposit configuration without ownership transfer.
- Supports oracle-based pricing through
OracleRateConfigwith:adapter- normalized
adapterConfig - signed
spreadBps maxStaleness
- Supports negative oracle spreads as of March 11, 2026.
- Supports delegated pricing via
RateManagerV1. - Exposes batch management APIs for currency/oracle updates.
Important v2 management APIs include:
setOracleRateConfigsetOracleRateConfigBatchupdateCurrencyConfigBatchdeactivateCurrenciesBatchsetRateManagerclearRateManagergetEffectiveRategetManagerFee
OrchestratorV2 is the settlement coordinator. It owns the intent lifecycle:
signalIntentcancelIntentfulfillIntentreleaseFundsToPayercleanupOrphanedIntents- escrow-driven
pruneIntents
Key v2 behavior:
- Validates escrow and payment-method support through registries.
- Locks liquidity on signal and unlocks/releases on cancel or fulfill.
- Snapshots the deposit minimum at signal time to prevent sub-minimum fulfillments later.
- Snapshots delegated manager fee terms at signal time.
- Supports a generic pre-intent hook and a dedicated whitelist hook per deposit.
- Supports optional post-intent hooks through
IPostIntentHookV2. - Distributes protocol fees, manager fees, and multiple referral fees.
- Supports relayer-authorized flows through
RelayerRegistry.
Recent addition:
- Multi-recipient referral fee support landed on March 11, 2026.
ProtocolViewerV2 is the read-model contract for apps and indexers that need a single call to return:
- a deposit plus all configured payment methods
- effective per-currency rates after floor/oracle/manager logic
- outstanding intent hashes
- reclaimable liquidity from expired intents
- intents joined with their backing deposits
It is stateless and exists to make frontend queries cheaper and simpler.
RateManagerV1 is the delegated rate registry used by EscrowV2.
It stores manager-owned pricing state keyed by a rateManagerId:
- manager address
- fee recipient
- fee and max fee
- minimum liquidity requirement
- display metadata (
name,uri) - per-payment-method / per-currency rates
Important semantics:
- As of March 6, 2026,
RateManagerV1is a pure registry; floor enforcement is handled insideEscrowV2. - Escrows opt into a manager via
setRateManager. - Managers can update one rate or batches of rates.
- The manager fee is snapshotted in
OrchestratorV2when the taker signals an intent.
A depositor-managed private-orderbook mechanism:
- whitelist specific taker addresses per
escrow + depositId - only authorized orchestrators may invoke it
- used through the dedicated whitelist hook slot in
OrchestratorV2
An off-chain allowlist / RFQ gate:
- the deposit owner or delegate sets an authorized signer per deposit
- the taker includes ephemeral hook data with a signature and expiration
- the hook verifies a payload bound to:
- orchestrator
- escrow
- deposit
- amount
- taker
- recipient
- payment method
- fiat currency
- conversion rate
- referral fee hash
- expiration
- chain id
This is useful for private liquidity, per-trade approval, or off-chain risk checks.
A post-intent hook that bridges fulfilled tokens through Across depositNow.
Design constraints:
- intended for stablecoin-to-stablecoin routes
- the signal step commits destination chain, output token, recipient, and minimum output amount
- the fulfill step supplies just-in-time Across route data
- if the bridge cannot proceed, the hook falls back to a direct source-chain transfer instead of reverting the entire settlement
That fallback behavior is important because the user may already have made the off-chain fiat payment.
- Accepts
abi.encode(address feed, bool invert)raw config. - Validates the feed and normalizes config to a compact packed format.
- Normalizes output to
1e18precise units. - Supports
feed == address(0)as a constant1.0base rate, useful for USD-denominated USDC deposits.
- Added on March 3, 2026.
- Accepts
abi.encode(bytes32 feedId, bool invert)raw config. - Uses
Pyth.getPriceUnsafeand returns a normalized1e18precise-unit rate plus publish time. - Delegates staleness enforcement to
EscrowV2viamaxStaleness.
The deployed v2 verifier uses the same contract implementation as UnifiedPaymentVerifier, but is deployed under the deployment name UnifiedPaymentVerifierV2.
Responsibilities:
- maintain the set of supported payment methods
- verify standardized EIP-712 payment attestations
- validate that the attested intent snapshot matches the live orchestrator intent
- nullify
(paymentMethod, paymentId)combinations throughNullifierRegistry - emit normalized
PaymentVerifiedevents for off-chain reconciliation
Shared base contract for:
- payment method configuration
- attestation verifier rotation
- orchestrator authorization
- nullifier writes
The v2 stack reuses and extends the existing registry model:
EscrowRegistry: whitelists escrow contractsOrchestratorRegistry: whitelists both v1 and v2 orchestrators for escrow/verifier authorizationPaymentVerifierRegistry: maps payment method hash to verifier and supported currenciesPostIntentHookRegistry: whitelists post-intent hooksRelayerRegistry: whitelists relayersNullifierRegistry: stores consumed payment nullifiers
The maker funds EscrowV2 and specifies:
- deposit token and amount
- min/max intent size
- supported payment methods
- per-method payee and verifier data
- per-method supported fiat currencies
- optional delegate
- optional intent guardian
- whether the deposit should remain configured after going empty
For each (paymentMethod, currency) pair, the deposit can use:
- a fixed floor only
- an oracle-backed rate with a spread
- a delegated manager rate through
RateManagerV1
ProtocolViewerV2 surfaces the final effective rate used by clients.
OrchestratorV2.signalIntent:
- validates the escrow and deposit
- verifies payment-method and currency support
- runs the generic pre-intent hook, if present
- runs the dedicated whitelist hook, if present
- snapshots deposit min amount and manager fee terms
- stores the intent
- locks funds on
EscrowV2
The taker pays the maker using the selected payment rail. The off-chain attestation layer turns the payment evidence into a standardized signed payload.
OrchestratorV2.fulfillIntent:
- loads the stored intent
- resolves the correct verifier from
PaymentVerifierRegistry - verifies the payment proof
- checks the attested intent snapshot against on-chain data
- enforces the min-at-signal guarantee
- prunes the intent
- unlocks and transfers escrowed funds
- distributes protocol, referral, and manager fees
- optionally executes a post-intent hook
If an intent expires:
- the escrow owner can reclaim liquidity during fund removal or withdrawal
- the orchestrator can prune orphaned intents
ProtocolViewerV2exposes reclaimable liquidity so off-chain systems can model real availability
Pricing is one of the biggest differences between the old system and v2.
Every configured currency can carry a fixed minimum conversion rate. This is the hard floor that the settlement rate cannot violate.
EscrowV2 can derive rates from an oracle adapter plus a spread:
- Chainlink or Pyth supplies the base market rate.
spreadBpsadjusts the market rate.maxStalenessbounds stale data.- The effective rate is returned in precise units.
Recent change:
spreadBpsis signed, so makers can quote above or below market. The implementation still requires the final multiplier to remain strictly positive.
For programmatic market making, a deposit can delegate pricing to RateManagerV1:
- the deposit opts into a
rateManagerId - the manager updates rates off the critical settlement path
EscrowV2combines manager-side rates with escrow-side floor enforcementOrchestratorV2snapshots the manager fee when the taker signals
This split keeps settlement safety inside escrow while letting pricing move quickly.
v2 introduces a clearer extension model around the intent lifecycle.
Executed during signalIntent, before funds are locked:
- generic hook slot: arbitrary eligibility or policy checks
- whitelist hook slot: dedicated private-liquidity control
These hooks are configured per deposit by the depositor or delegate.
Executed during fulfillIntent, after verification and escrow release:
- direct recipient settlement remains the default
- hooks can route fulfilled funds into external workflows
AcrossBridgeHookV2is the canonical example
The hook and orchestrator registry model prevents arbitrary external contracts from being inserted into the settlement path without explicit whitelisting.
The verification path is intentionally standardized across payment methods.
Each payment method is registered in two places:
UnifiedPaymentVerifierV2must recognize the payment method.PaymentVerifierRegistrymust map the method to the deployed verifier and its supported currencies.
The payment proof decodes into:
- payment details
- an intent snapshot
- witness signatures
- attested data and metadata
The verifier:
- reconstructs the EIP-712 digest
- checks the attestation through the configured attestation verifier
- validates the on-chain intent snapshot
- nullifies the payment ID
- returns the final release amount to
OrchestratorV2
Payment IDs are nullified as keccak256(paymentMethod, paymentId) so the same raw ID cannot be replayed across methods.
contracts/
Escrow.sol
EscrowV2.sol
Orchestrator.sol
OrchestratorV2.sol
ProtocolViewer.sol
ProtocolViewerV2.sol
RateManagerV1.sol
hooks/
interfaces/
lib/
mocks/
oracles/
registries/
unifiedVerifier/
deploy/
00_deploy_system.ts
01_deploy_unified_verifier.ts
02_add_venmo_payment_method.ts
...
14_deploy_v2_system.ts
15_deploy_v2_periphery.ts
16_configure_v2_payment_methods.ts
17_deploy_pyth_oracle.ts
18_redeploy_escrowv2_ratemanager.ts
19_redeploy_escrowv2_orchestratorv2_staging.ts
deploy_summary.ts
test/
deploy/
escrow/
escrowV2/
hooks/
libs/
orchestrator/
orchestratorV2/
periphery/
rateManager/
registries/
unifiedVerifier/
test-foundry/
fuzz/
invariant/
- Node.js 18+
- Yarn 4
- Foundry for Solidity-native tests
- a
.envcopied from.env.default
yarn
cp .env.default .envFill the relevant environment variables, including the ones used by deployment and verification flows:
ALCHEMY_API_KEYBASE_DEPLOY_PRIVATE_KEYTESTNET_DEPLOY_PRIVATE_KEYBASESCAN_API_KEYETHERSCAN_KEYINFURA_TOKEN
Start a chain:
yarn chainDeploy locally:
yarn deploy:localhostFor wallet-based local testing, import Hardhat account #0 into your wallet.
yarn compile: compile Solidity contractsyarn build: clean, compile, generate typechain bindings, and transpile TypeScriptyarn clean: remove Hardhat, coverage, and generated build artifactsyarn typechain: generate TypeChain bindingsyarn transpile: runtsc
foundry-mainmigration rule: new contract tests should be written in Foundry undertest-foundry/; existing Hardhat suites undertest/are being preserved only until their parity ports are verifiedyarn test: run the main Hardhat suite across libs, hooks, periphery, unified verifier, registries, escrow, escrowV2, orchestrator, orchestratorV2, and rate manageryarn test:fast: run the same Hardhat suite without recompilingyarn test:deploy: run deployment-script testsyarn test:forge: run Foundry testsyarn test:forge:fuzz: run Foundry fuzz contractsyarn test:forge:invariant: run Foundry invariant contractsyarn test:forge:fork: run the fork profile testsyarn test:all: run Hardhat plus Foundry
yarn coverageyarn test:forge:coverage
Coverage is intentionally heavy in this repo. On foundry-main, it should not sit on the every-push critical path; use focused unit/integration suites for normal iteration and run coverage in the slower dedicated lane.
yarn pkg:extractyarn pkg:buildyarn pkg:cleanyarn pkg:test
The deployment pipeline is layered because v2 reuses shared protocol infrastructure.
Scripts 00 through 13 deploy and configure the original shared system and payment method scaffolding. These remain relevant because v2 builds on:
- existing registries
- the shared attestation verifier
- shared payment-method config artifacts
Deploys and wires:
OrchestratorRegistryEscrowV2OrchestratorV2UnifiedPaymentVerifierV2(same implementation asUnifiedPaymentVerifier)
Then it:
- adds both v1 and v2 orchestrators to
OrchestratorRegistry - adds
EscrowV2toEscrowRegistry - grants the v2 verifier write permissions in
NullifierRegistry - transfers ownership of the ownable v2 contracts to the configured multisig
Deploys:
WhitelistPreIntentHookSignatureGatingPreIntentHookAcrossBridgeHookV2RateManagerV1ChainlinkOracleAdapterProtocolViewerV2
Then registers AcrossBridgeHookV2 in PostIntentHookRegistry.
Configures the v2 verifier and registry for all supported methods:
- add payment methods to
UnifiedPaymentVerifierV2 - repoint
PaymentVerifierRegistryentries to the v2 verifier - save payment method snapshots
- emit Safe batch calldata when the deployer is not the registry owner
This script currently covers:
- Venmo
- Revolut
- Cash App
- Wise
- Mercado Pago
- Zelle Citi
- Zelle Chase
- Zelle Bank of America
- PayPal
- Monzo
- N26
- Alipay
- Chime
- Luxon
deploy/17_deploy_pyth_oracle.ts: deploys the Pyth adapterdeploy/18_redeploy_escrowv2_ratemanager.ts: redeploysEscrowV2andRateManagerV1after the rate-floor refactordeploy/19_redeploy_escrowv2_orchestratorv2_staging.ts: staging-specific redeploy forEscrowV2,OrchestratorV2, and the signature-gating hook
deploy/deploy_summary.ts prints both legacy and v2 addresses and writes Safe Transaction Builder batch files when multisig actions are pending.
yarn deploy:localhostyarn deploy:baseyarn deploy:base_stagingyarn deploy:base_sepolia
yarn etherscan:baseyarn etherscan:base_stagingyarn etherscan:base_sepolia
The repository contains deployment coverage for the following payment rails in the current v2 configuration flow:
- Venmo
- Revolut
- Cash App
- Wise
- Mercado Pago
- Zelle:
- Citi
- Chase
- Bank of America
- PayPal
- Monzo
- N26
- Alipay
- Chime
- Luxon
Payment-method specific provider configuration lives under deployments/verifiers/.
The repo uses both Hardhat tests and Foundry tests today, but foundry-main is the active migration branch toward a Foundry-only end state.
test/escrowV2/: v2 deposit lifecycle, delegated rates, oracle rates, Pyth rates, and legacy/branch coveragetest/orchestratorV2/: v2 signaling, fulfillment, fee behavior, and cleanup logictest/hooks/: whitelist, signature-gating, and Across hook behaviortest/rateManager/: delegated pricing and oracle adapterstest/periphery/:ProtocolViewerV2and related read-model behaviortest/deploy/: deployment scripts for the unified verifier, v2 system, v2 periphery, payment-method configuration, and Pyth deploymenttest/unifiedVerifier/: attestation validation and nullifier logictest/registries/: whitelist and registry integrity
Foundry suites live in test-foundry/ and are intended for:
- deterministic replacements for the current Hardhat suites as parity ports land
- fuzzing
- invariants
- fork-based testing where needed
For work on foundry-main:
yarn compile- add or update the relevant Foundry suite under
test-foundry/ - run the focused Foundry suite for the touched area
- run the corresponding legacy Hardhat suite only when checking parity during migration
- avoid coverage unless explicitly needed
The repo is configured primarily for:
- Base
- Base Sepolia
- Base staging
- localhost / hardhat
Artifacts and exports live in:
deployments/<network>/deployments/outputs/
The deploy scripts also reference current network parameters such as:
- USDC addresses
- multisig address
- protocol fee recipient
- dust recipient
- Across SpokePool address
- Pyth contract address
These parameters live in deployments/parameters.ts.
- The contracts are designed around explicit registry-based authorization rather than open-ended plugin injection.
EscrowV2andOrchestratorV2both use reentrancy guards on state-changing flows that interact with external contracts.- Payment replay protection depends on
NullifierRegistry. - Oracle safety depends on both adapter validation and escrow-side
maxStaleness. AcrossBridgeHookV2is intentionally biased toward successful settlement; it falls back to a direct transfer instead of reverting bridge failures.- Manager fees are capped and snapshotted at signal time.
- The README is not a full audit report. Contract behavior should be read alongside the source and tests before making deployment or integration assumptions.
MIT

