Skip to main content
This guide walks through deploying the zkp2p-contracts system to Base networks using Hardhat Deploy.

Prerequisites

Required Software

  • Node.js: Version 18 or higher
  • Yarn: Version 4+ (managed via Corepack)
  • Foundry: For Forge testing (optional)

Installation

# Clone the repository
git clone https://github.com/zkp2p/zkp2p-v2-contracts.git
cd zkp2p-v2-contracts

# Install dependencies
yarn install

Environment Setup

1. Create Environment File

Copy the default environment template:
cp .env.default .env

2. Configure Environment Variables

Edit .env with your configuration:
# Required for deployment
ALCHEMY_API_KEY=your_alchemy_api_key_here
BASE_DEPLOY_PRIVATE_KEY=your_mainnet_deployer_private_key
TESTNET_DEPLOY_PRIVATE_KEY=your_testnet_deployer_private_key

# Required for contract verification
BASESCAN_API_KEY=your_basescan_api_key_here
ETHERSCAN_KEY=your_etherscan_api_key_here

# Optional: For legacy networks
INFURA_TOKEN=your_infura_token_here

API Key Sources

ServicePurposeGet API Key
AlchemyRPC provider for Base networksalchemy.com
BasescanContract verification on Basebasescan.org/apis
EtherscanContract verification (legacy)etherscan.io/apis
InfuraAlternative RPC providerinfura.io
Never commit your .env file to version control. It contains sensitive private keys. The .env.default file uses test private keys only.

Deployment Process

Local Development

1. Start Local Hardhat Node

yarn chain
This starts a local Ethereum node at http://127.0.0.1:8545 with:
  • Deterministic accounts
  • Fast block mining
  • No deployment delay

2. Deploy to Local Node

In a separate terminal:
yarn deploy:localhost
This executes all deployment scripts in sequence:
  1. 00_deploy_system.ts - Core registries, escrow, and orchestrator
  2. 01_deploy_unified_verifier.ts - Payment verification contracts
  3. 02-13_add_*_payment_method.ts - Payment platform configurations
  4. 14_deploy_v2_system.ts - V2 contracts (if applicable)

Base Sepolia Testnet

1. Fund Deployer Account

Get testnet ETH from a Base Sepolia faucet: Your deployer address is derived from TESTNET_DEPLOY_PRIVATE_KEY.

2. Deploy Contracts

yarn deploy:base_sepolia
Deployment artifacts are saved to:
deployments/base_sepolia/
├── Escrow.json
├── Orchestrator.json
├── UnifiedPaymentVerifier.json
└── ...
Exported contract data:
deployments/outputs/baseSepoliaContracts.ts

3. Verify Contracts

yarn etherscan:base_sepolia
This verifies all deployed contracts on Basescan with:
  • 5 second delay between verifications (avoids rate limiting)
  • Automatic constructor argument encoding
  • Retry logic for network issues

Base Mainnet Production

Production deployments are permanent and handle real funds. Triple-check all parameters before deploying.

1. Review Deployment Parameters

Check deployments/parameters.ts for production values:
export const INTENT_EXPIRATION_PERIOD = {
  "base": SIX_HOURS_IN_SECONDS,  // 6 hours
};

export const PROTOCOL_TAKER_FEE = {
  "base": ZERO,  // No protocol fee
};

export const MULTI_SIG = {
  "base": "0x0bC26FF515411396DD588Abd6Ef6846E04470227",
};

export const USDC = {
  "base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
};

2. Deploy to Base

yarn deploy:base
This command:
  1. Deploys all contracts
  2. Configures permissions and ownership
  3. Transfers ownership to multisig
  4. Exports contract addresses to deployments/outputs/baseContracts.ts

3. Verify on Basescan

yarn etherscan:base
Uses 600ms delay between verifications for production rate limits.

Deployment Scripts

The deployment system uses numbered scripts that execute in order:

Core System (00-01)

00_deploy_system.ts - Deploys foundational contracts:
  • PaymentVerifierRegistry
  • PostIntentHookRegistry
  • RelayerRegistry
  • NullifierRegistry
  • EscrowRegistry
  • Escrow
  • Orchestrator
  • ProtocolViewer
01_deploy_unified_verifier.ts - Deploys verification layer:
  • SimpleAttestationVerifier
  • UnifiedPaymentVerifier

Payment Methods (02-13)

Each script adds a payment platform:
  • 02_add_venmo_payment_method.ts
  • 03_add_revolut_payment_method.ts
  • 04_add_cashapp_payment_method.ts
  • 05_add_wise_payment_method.ts
  • 06_add_mercadopago_payment_method.ts
  • 07_add_zelle_payment_methods.ts (Chase, BofA, Citi)
  • 08_add_paypal_payment_method.ts
  • 09_add_monzo_payment_method.ts
  • 10_add_n26_payment_method.ts
  • 11_add_alipay_payment_method.ts
  • 12_add_chime_payment_method.ts
  • 13_add_luxon_payment_method.ts

V2 System (14-16)

14_deploy_v2_system.ts - V2 architecture:
  • EscrowV2
  • OrchestratorV2
  • OrchestratorRegistry
  • RateManagerV1
  • ProtocolViewerV2
15_deploy_v2_periphery.ts - V2 utilities:
  • SignatureGatingPreIntentHook
  • AcrossBridgeHookV2
16_configure_v2_payment_methods.ts - V2 payment config

External Integrations (17)

17_deploy_pyth_oracle.ts - Price oracle:
  • PythOracleAdapter
  • ChainlinkOracleAdapter

Deployment Configuration

Network-Specific Parameters

Defined in deployments/parameters.ts:
// Intent expiration periods
export const INTENT_EXPIRATION_PERIOD = {
  "localhost": ONE_DAY_IN_SECONDS,
  "base": SIX_HOURS_IN_SECONDS,
  "base_sepolia": ONE_HOUR_IN_SECONDS,
};

// Protocol fees
export const PROTOCOL_TAKER_FEE = {
  "localhost": ether(.001),
  "base": ZERO,
  "base_sepolia": ZERO,
};

// Dust collection
export const ESCROW_DUST_THRESHOLD = {
  "localhost": usdc(0.1),
  "base": usdc(0.1),
  "base_sepolia": usdc(0.1),
};

// Concurrent intents
export const MAX_INTENTS_PER_DEPOSIT = {
  "localhost": 100,
  "base": 200,
  "base_sepolia": 200,
};

Deployment Delays

To avoid nonce conflicts and ensure transaction confirmation:
export const DEPLOY_TX_DELAY_MS = {
  localhost: 0,           // No delay needed
  base: 8000,            // 8 seconds
  base_staging: 5000,    // 5 seconds
  base_sepolia: 5000,    // 5 seconds
};

Verifying Deployment

1. Check Contract Addresses

View deployed addresses:
cat deployments/base/Escrow.json | jq '.address'
cat deployments/base/Orchestrator.json | jq '.address'

2. Verify on Block Explorer

Navigate to Basescan:
  • Mainnet: https://basescan.org/address/<CONTRACT_ADDRESS>
  • Testnet: https://sepolia.basescan.org/address/<CONTRACT_ADDRESS>
Confirm:
  • Contract is verified (green checkmark)
  • Constructor arguments match
  • Owner is set correctly (multisig for production)

3. Test Core Functionality

Run deployment tests:
yarn test:deploy
This executes:
test/deploy/00_system.spec.ts
test/deploy/01_unifiedVerifier.spec.ts
test/deploy/02_venmoPaymentMethod.spec.ts
...

4. Verify Ownership

Check that ownership was transferred:
npx hardhat console --network base

> const escrow = await ethers.getContractAt('Escrow', '<ESCROW_ADDRESS>')
> await escrow.owner()
// Should return multisig: 0x0bC26FF515411396DD588Abd6Ef6846E04470227

Troubleshooting

Deployment Fails with “Nonce too low”

Cause: Transaction submitted with incorrect nonce Solution: Increase deployment delay in parameters.ts
export const DEPLOY_TX_DELAY_MS = {
  base: 10000,  // Increase from 8000 to 10000
};

”Insufficient funds” Error

Cause: Deployer account lacks ETH for gas Solution: Fund the deployer address:
# Get deployer address
npx hardhat console --network base_sepolia
> const [deployer] = await ethers.getSigners()
> deployer.address
Send ETH to this address from a faucet or your wallet.

Verification Fails with “Already verified”

Cause: Contract was verified in a previous run Solution: This is informational only. The contract is already verified.

”Contract source code already verified” on Basescan

Cause: Another deployment of identical code exists Solution: Basescan automatically verifies matching bytecode. No action needed.

Deployment Script Skipped

Cause: Script has skip function returning true Solution: Check the script’s skip conditions:
func.skip = async (hre: HardhatRuntimeEnvironment): Promise<boolean> => {
  const network = hre.network.name;
  if (network === "localhost" || network === "hardhat") {
    return false;  // Don't skip for local networks
  }
  return true;  // Skip for other networks (already deployed)
};
Modify the skip logic if you need to redeploy.

Post-Deployment Steps

1. Update Frontend Configuration

Update your frontend with new contract addresses:
// config/contracts.ts
export const CONTRACTS = {
  base: {
    escrow: '0x2f121CDDCA6d652f35e8B3E560f9760898888888',
    orchestrator: '0x88888883Ed048FF0a415271B28b2F52d431810D0',
    // ... other contracts
  }
};

2. Configure Subgraph

Update subgraph configuration with new addresses and start block numbers.

3. Set Up Monitoring

Configure monitoring for:
  • Contract events
  • Transaction failures
  • Ownership changes
  • Registry modifications

4. Document Deployment

Record deployment details:
  • Network and chain ID
  • Deployer address
  • Contract addresses
  • Deployment timestamp
  • Git commit hash
  • Constructor parameters

Advanced: Custom Deployment

Deploy Individual Contracts

# Deploy only the core system
npx hardhat deploy --network base_sepolia --tags system

# Deploy only payment verifiers
npx hardhat deploy --network base_sepolia --tags verifiers

Skip Specific Scripts

Edit the script’s skip function:
func.skip = async (hre: HardhatRuntimeEnvironment): Promise<boolean> => {
  return true;  // Always skip this script
};

Test Deployment Locally First

# Start local node
yarn chain

# Deploy locally
yarn deploy:localhost

# Run integration tests
yarn test:integration

Security Considerations

  • Private Keys: Never commit private keys. Use hardware wallets for production.
  • Multisig: Always transfer ownership to a multisig for production deployments.
  • Verification: Verify all contracts on Basescan before use.
  • Testing: Thoroughly test on Base Sepolia before mainnet deployment.
  • Audit: Have contracts audited before handling significant value.

Next Steps

After successful deployment:
  1. Verify contracts on Basescan
  2. Configure payment methods
  3. Set up relayers
  4. Monitor contract events
For production deployments, coordinate with the zkp2p team to ensure proper attestation service configuration and multisig setup.

Build docs developers (and LLMs) love