Smart contracts for the ZKP2P fiat on/off-ramp, with the current repository centered on the v2 system.
The pristine deployed pre-cut OrchestratorV2 source is archived outside the compilation tree.
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, and unrestricted account-level concurrency.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
- V3 Lifecycle Risk and Maker Groups
- 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,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. Active registries define which escrows, orchestrators, hooks, and payment methods are valid.RelayerRegistrybacks the deployed legacy V1 stack and the deployed prodOrchestratorV2. - 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.
The deposit whitelist stack is available through the OrchestratorV2 per-deposit whitelist hook:
AddressGroupRegistry: anyone may create a curator-managed group. Curators can add or remove members, transfer control, configure an optional membership resolver, and opt into self-service membership.WhitelistPolicy: each deposit owns anenabledswitch, a direct address whitelist, and a bounded list of up to 10 allowed groups. Only the escrow's recorded depositor may configure a deposit, andconfigureDepositsets all three in one transaction. The policy keeps this configuration independently of theOrchestratorV2hook assignment. A governance owner may rotate the escrow registry that gates those writes viasetEscrowRegistry. The owner cannot admit or reject a taker: whitelist enforcement allows a taker when enforcement is disabled for the intent's deposit, the taker is directly whitelisted on that deposit, or the taker belongs to at least one group allowed by that deposit. Enabled policies with no matching address or group fail closed.
Group IDs are derived from the curator and registry group counter, and offchain consumers must key them by
chain, registry address, and group ID. All three admission settings are scoped to the (escrow, depositId)
pair, so one maker can run gated and open deposits at the same time and nothing is shared across a maker's
deposits.
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.
- Allows every account to hold multiple concurrent intents; V3 admission is governed only by the selected lifecycle hook.
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.
- 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: backs deployed legacy V1 orchestrators and the deployed prodOrchestratorV2NullifierRegistry: 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
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/
archive/
orchestrator-v2-pre-relayer-cut/ # pristine deployed legacy V2 source, excluded from compilation
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-foundry/
deterministic/
fuzz/
invariant/
- Node.js 20.20.2 (the version pinned in CI)
- Yarn 4
- Foundry v1.7.1
- a
.envcopied from.env.default
corepack yarn install --immutable
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 compiler, coverage, and generated build artifactsyarn typechain: generate TypeChain bindingsyarn transpile: runtsc
corepack yarn test: run the complete always-on Foundry suitecorepack yarn test:deterministic: run deterministic unit, integration, and deployment testscorepack yarn test:fuzz: run the real-contract property suite at 512 cases per propertycorepack yarn test:invariant: run handler-driven invariants at 128 runs × 64 callsforge test --match-path '<path>' --match-test '<name>': isolate a file or named test
See TESTING.md for suite design, seed reproduction, coverage mechanics, and contribution rules.
corepack yarn coverage: run deterministic coverage once, build accurate Foundry LCOV, and enforce the permanent Foundry baseline floors
Coverage is deliberately outside the pull-request critical path. Code pull requests run the complete deterministic,
fuzz, invariant, integration, and deployment Foundry suite. The Release readiness workflow runs package and
localhost-deployment checks for release-surface pull requests, every relevant push to main, and manual dispatches;
its four coverage lanes run on main and manual dispatches only. Markdown, agent guidance, audits, and deployment
logs do not start these workflows when the entire PR or push is limited to those paths. A docs-only follow-up inside
a mixed PR still reruns CI because GitHub evaluates pull-request path filters against the complete PR diff.
Before publishing the contracts package, promoting a release branch, or deploying to Base staging or Base, verify
that the exact release SHA has a green complete Foundry suite and a green Release readiness run including package,
localhost-deployment, and coverage jobs. A green contract-only pull request is not release-readiness evidence because
the deferred workflow runs after merge to main; if the release SHA has not run there, dispatch Release readiness
on its release ref and wait for it. Never reuse coverage or deployment evidence from another SHA.
An ignored documentation-only head may inherit the immediately preceding green runtime SHA only after its complete
intervening diff is proven to contain no executable, package, configuration, or deployment input.
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:
WhitelistPreIntentHookSignatureGatingPreIntentHookRateManagerV1ChainlinkOracleAdapterProtocolViewerV2
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
- 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 hookdeploy/32_deploy_deposit_creation_guard.ts: deploys the statelessDepositCreationGuardfor atomic pre/post assertions aroundEscrowV2.createDeposit
The canonical DepositCreationGuard was deployed once on Base at
0x0D765BD7322b9E8C85F66Cc3353dBc6B6d602e2f in transaction
0xaa732525f07f9919e3e5ac991430f7f5e7a88e945a4204a23571c14d1a63cfaf. Its canonical production artifact is
recorded under deployments/base_prod/DepositCreationGuard.json. Because the guard is stateless and receives the
target escrow on each call, staging intentionally reuses this production Base deployment. No separate staging
contract deployment or duplicate staging artifact is required.
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_staging
The lifecycle rollout is split into explicit lanes. Lane 30 supports the whitelist-only groups deployment on Base staging and Base. Lane 31 verifies and cuts over the V3 payment-binding pair, and lane 32 deploys, wires, transfers, and activates the fresh dispute/staking stack.
deploy/30_deploy_v3_lifecycle_stack.ts is the groups-only lane. Its lane-29 dependency supplies a
fresh WhitelistPolicy; lane 30 deploys a fresh WhitelistLifecycleHook and OrchestratorV3, sets
the hook after O3 construction, registers the new O3, and removes the two drained staging
predecessors. It reuses the existing registries, UPV3, NullifierRegistryV2, dispute stack, and
payment routing without mutating them. On Base it leaves existing orchestrators registered and queues
exactly one Safe transaction to add the fresh O3 to OrchestratorRegistry.
Before a separately authorized live cutover, move these three artifacts aside for the selected environment in the deployment worktree:
deployments/<environment>/WhitelistPolicy.jsondeployments/<environment>/WhitelistLifecycleHook.jsonwhen presentdeployments/<environment>/OrchestratorV3.json
Do not commit the temporary removals: the authorized deployment must replace all three artifacts with fresh
creation records. Keep AddressGroupRegistry and every other deployment artifact intact. After an
external read-only check proves both registered predecessor O3s have no unresolved intents, run
--tags V3LifecycleStack. Base staging requires ENABLE_STAGING_V3_GROUPS_CUTOVER=true and
CONFIRM_STAGING_V3_PREDECESSORS_DRAINED=true; Base requires ENABLE_BASE_V3_GROUPS_CUTOVER=true.
The staging confirmation is an operator acknowledgement; the deploy script intentionally contains no
indexer client or drain-query implementation. Base fails unless O2 and EscrowV2 are already registered,
the existing stack remains intact, and the generated Safe batch contains only the fresh O3 registration.
deploy/31_deploy_v3_payment_binding_stack.ts is the state-aware payment-binding and hard-cutover lane.
On Base staging and Base it pins the existing NullifierRegistryV2 and UnifiedPaymentVerifierV3
addresses, runtime code hashes, immutable dependencies, owners, active method order, exact currency arrays,
and writer set. Missing, partial, or mismatched production-like artifacts fail closed; only local networks
may deploy the pair. Base staging is already fully cut over and is verification-only: its registries are
EOA-owned, so the script refuses to approximate an atomic cutover with 22 independent transactions. On Base,
ENABLE_BASE_V3_PAYMENT_BINDING_CUTOVER=true removes all ten methods in reverse order, re-adds them in
original order against UPV3, and revokes UPV1 and UPV2 from the legacy nullifier registry. This preserves
the registry order and produces exactly 22 atomic Safe calls. Never route a method back to a retired verifier
after this one-way cutover.
deploy/32_deploy_and_activate_dispute_lifecycle_stack.ts is the combined dispute/staking lane. Run
--tags V3DisputeLifecycleStack with ENABLE_STAGING_V3_DISPUTE_DEPLOYMENT=true. It deploys a fresh
DisputeNullifierRegistry,
DisputeVerifier, StakeVault, DisputeProtectionPolicy, and IntentLifecycleHookV1; initializes the vault
controller; applies non-zero risk windows only to PayPal, Venmo, and Cash App; authorizes the combined
hook; grants the policy sole nullifier-writer permission; and transfers ownership. A partial deployment is
resumable only when every persisted dependency, owner, writer, and risk window remains recognizable.
Staging deliberately uses two runs of this one lane. First,
ENABLE_STAGING_V3_DISPUTE_DEPLOYMENT=true deploys and prepares the five contracts without changing the
active O3 hook. Commit and propagate the fresh addresses, verify the live predecessor is drained, then run
again with ENABLE_STAGING_V3_DISPUTE_ACTIVATION=true,
CONFIRM_STAGING_V3_DISPUTE_DOWNSTREAM_READY=true, and
CONFIRM_STAGING_V3_DISPUTE_PREDECESSOR_DRAINED=true. Base requires
ENABLE_BASE_V3_DISPUTE_DEPLOYMENT=true, transfers the plain registry immediately, and prepares an
unexecuted Safe batch that accepts the three two-step ownership transfers and activates the hook. Both
production-like paths pin the expected O3 address, runtime hash, owner, registry membership, unpaused state,
and predecessor hook before preparing activation.
Commit the five newly generated deployment artifacts so their addresses can flow through the package, indexer, curator, and attestation-service releases. Production Safe execution remains blocked until every compatible downstream release is deployed:
-
zkp2p-indexerindexes the fresh contract addresses and the renamedDispute*events. -
curatorrecognizesIntentLifecycleHookV1as the enforcement hook and enables dispute enforcement. -
@zkp2p/contracts-v2publishes the hard-renamed dispute ABI and staging addresses, and its consumers upgrade. - The production
attestation-servicerelease remains on the ratified UPV3 address; do not promote an independently diverged release branch that restores UPV2.
Dispute-evidence issuance in attestation-service remains a separate follow-up and is intentionally not implemented
by these contract lanes. Only PayPal, Venmo, and Cash App receive non-zero onchain risk windows, matching the
explicitly ratified chargebackable-platform set.
Commit the newly generated canonical artifacts after the authorized deployment.
yarn whitelist:bootstrap discovers active deposits from a configurable raw GraphQL endpoint and
selects only deposits with an active Venmo, Cash App, or PayPal payment method. It deduplicates the
matching method rows by deposit and simulates canonical WhitelistPolicy.bootstrapDeposits batches
for the explicitly supplied PRO, PLUS, Peer Pay, and Peer Makers group IDs. It imports no indexer
schema package, so the contracts and indexer packages remain acyclic. Discovery is a dry-run by
default; mutation and Safe output require both the exact expected deposit count and the printed
selection digest, and all discovery modes enforce a configurable maximum.
- Staging execution requires
BOOTSTRAP_EXECUTE=trueand the current policy owner's private key. - Production Safe preparation requires
BOOTSTRAP_SAFE_OUTPUT_FILE; it emits unsigned Transaction Builder JSON owned by the policy's onchain owner, and never signs or submits it. - Direct execution and Safe output are mutually exclusive. Every batch is simulated and its calldata decoded and checked before either execution or file output.
- Base execution is pinned to the canonical production indexer, deployment artifacts, and exact four
production group IDs. It also requires
BOOTSTRAP_CONFIRM_PRODUCTION=true. BOOTSTRAP_ALLOW_COMPLETED=trueresumes only batches whose deposits are still enabled and contain every requested group. The script rechecks policy ownership before each submitted batch.- Direct execution uses the receipt RPC for confirmation and bounded post-receipt state reads. It computes
EIP-1559 fees from the latest base fee with a
0.001gwei priority fee and refuses to submit above the0.02gwei default max-fee ceiling; both values are configurable through the documented env. - Run
yarn whitelist:bootstrap --self-testfor the embedded calldata/Safe validation andyarn whitelist:bootstrap --helpfor the complete environment-variable reference.
yarn etherscan:baseyarn etherscan:base_staging
The repository contains deployment coverage for the following payment rails in the current v2 configuration flow:
- Venmo
- Revolut
- Cash App
- Wise
- Mercado Pago
- Zelle
- PayPal
- Monzo
- N26
- Alipay
- Chime
- Luxon
Payment-method specific provider configuration lives under deployments/verifiers/.
Foundry is the sole contract test system. The suite is separated by assurance type:
test-foundry/deterministic/: unit behavior, integration, deployment topology, events, reverts, state, balances, and authorization boundariestest-foundry/fuzz/: bounded real-contract properties that add input breadth beyond deterministic casestest-foundry/invariant/: multi-actor handlers, ghost accounting, lifecycle conservation, and nullifier uniqueness
The default corepack yarn test command runs every layer with the centrally configured run counts. The PR suite has
no reduced fuzz counts, pending cases, or ignored test failures, but non-runtime paths are skipped and coverage is
deferred. During development, run the affected file first and rely on the exact commit's PR suite for the complete
gate. Run or confirm the deferred coverage and release-readiness workflow only when preparing a package, release
branch, or deployment, or when changing its configuration.
The repo is configured primarily for:
- Base
- 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
- 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. - 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

