Deployment Guide

This guide covers deploying zero-knowledge applications to Kusama's testnet and mainnet environments.

Prerequisites

Required Tools

# Rust and Cargo
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Node.js (v18+)
nvm install 18

# Foundry (for Solidity)
curl -L https://foundry.paradigm.xyz | bash
foundryup

# snarkJS
npm install -g snarkjs

# Circom
git clone https://github.com/iden3/circom.git
cd circom && cargo build --release
sudo cp target/release/circom /usr/local/bin/

# Ethers.js / Hardhat
npm install -g hardhat @nomicfoundation/hardhat-toolbox

Wallet Setup

# Create wallet with cast
cast wallet new

# Save private key securely
export PRIVATE_KEY="your_private_key_here"

# Fund wallet from faucet
# Visit: https://faucet.polkadot.io/?parachain=1111

Testnet Deployment

Paseo Asset Hub Configuration

ParameterValue
RPC URLhttps://testnet-passet-hub-eth-rpc.polkadot.io
Chain ID420420422
Explorerhttps://blockscout-passet-hub.parity-testnet.parity.io/
Faucethttps://faucet.polkadot.io/?parachain=1111

Step 1: Deploy Circuit Verifier

# Generate verifier from circuit
snarkjs zkey export solidityverifier circuit_final.zkey verifier.sol

# Deploy using Foundry
forge create src/verifier.sol:Groth16Verifier \
  --rpc-url https://testnet-passet-hub-eth-rpc.polkadot.io \
  --private-key $PRIVATE_KEY

# Save the deployed address
export VERIFIER_ADDRESS="0x..."

Step 2: Deploy Application Contract

// src/shielded_pool.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {FixedIlop} from "./FixedIlop.sol";

// Deploy this contract
forge create src/shielded_pool.sol:FixedIlop \
  --constructor-args $VERIFIER_ADDRESS \
  --rpc-url https://testnet-passet-hub-eth-rpc.polkadot.io \
  --private-key $PRIVATE_KEY

export POOL_ADDRESS="0x..."

Step 3: Verify Contract Source

# Get API key from Blockscout
export BLOCKSCOUT_API_KEY="your_api_key"

forge verify-contract \
  $POOL_ADDRESS \
  src/shielded_pool.sol:FixedIlop \
  --chain-id 420420422 \
  --verifier-url https://blockscout-passet-hub.parity-testnet.parity.io/api \
  --etherscan-api-key $BLOCKSCOUT_API_KEY \
  --constructor-args $(cast abi-encode "constructor(address)" $VERIFIER_ADDRESS)

Step 4: Deploy Poseidon Library (if needed)

# For projects using external Poseidon
forge create src/Poseidon.sol:Poseidon \
  --rpc-url https://testnet-passet-hub-eth-rpc.polkadot.io \
  --private-key $PRIVATE_KEY

export POSEIDON_ADDRESS="0x..."

Mainnet Deployment

Kusama Asset Hub

ParameterValue
RPC URLhttps://kusama-asset-hub-rpc.polkadot.io
Chain ID(Check current value)
Explorerhttps://blockscout.kusama.network/

Pre-Mainnet Checklist

  • All contracts tested on testnet
  • Security audit completed
  • Emergency pause mechanism implemented
  • Time-lock for upgrades
  • Monitoring and alerting setup
  • Documentation complete

Deploy to Mainnet

# Update RPC URL
export KUSAMA_RPC_URL="https://kusama-asset-hub-rpc.polkadot.io"

# Deploy with same process as testnet
forge create src/verifier.sol:Groth16Verifier \
  --rpc-url $KUSAMA_RPC_URL \
  --private-key $PRIVATE_KEY

# Record all addresses in deployment log

Hardhat Deployment

Configuration

// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");

module.exports = {
  solidity: "0.8.28",
  networks: {
    paseo: {
      url: "https://testnet-passet-hub-eth-rpc.polkadot.io",
      accounts: [process.env.PRIVATE_KEY],
      chainId: 420420422
    },
    kusama: {
      url: "https://kusama-asset-hub-rpc.polkadot.io",
      accounts: [process.env.PRIVATE_KEY],
      chainId: 420420 // Verify actual chain ID
    }
  },
  etherscan: {
    apiKey: {
      paseo: process.env.BLOCKSCOUT_API_KEY
    },
    customChains: [
      {
        network: "paseo",
        chainId: 420420422,
        urls: {
          apiURL: "https://blockscout-passet-hub.parity-testnet.parity.io/api",
          browserURL: "https://blockscout-passet-hub.parity-testnet.parity.io/"
        }
      }
    ]
  }
};

Deployment Script

// scripts/deploy.js
async function main() {
  const [deployer] = await ethers.getSigners();
  console.log("Deploying with:", deployer.address);

  // Deploy verifier
  const Verifier = await ethers.getContractFactory("Groth16Verifier");
  const verifier = await Verifier.deploy();
  await verifier.waitForDeployment();
  console.log("Verifier:", await verifier.getAddress());

  // Deploy pool
  const Pool = await ethers.getContractFactory("FixedIlop");
  const pool = await Pool.deploy(await verifier.getAddress());
  await pool.waitForDeployment();
  console.log("Pool:", await pool.getAddress());

  // Save addresses
  const fs = require('fs');
  fs.writeFileSync('deployment.json', JSON.stringify({
    network: network.name,
    verifier: await verifier.getAddress(),
    pool: await pool.getAddress(),
    deployer: deployer.address,
    timestamp: new Date().toISOString()
  }, null, 2));
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Run Deployment

npx hardhat run scripts/deploy.js --network paseo

Post-Deployment

Fund the Pool

// scripts/fund-pool.js
async function main() {
  const pool = await ethers.getContractAt("FixedIlop", POOL_ADDRESS);
  
  // Deposit initial liquidity
  const tx = await pool.deposit(
    TOKEN_ADDRESS,  // or address(0) for native
    ethers.parseEther("1000"),
    initialCommitment
  );
  await tx.wait();
  
  console.log("Pool funded!");
}

Register on Block Explorer

  1. Visit Blockscout explorer
  2. Navigate to your contract
  3. Click "Verify & Publish"
  4. Submit source code and settings

Set Up Monitoring

# Monitor contract events
cast logs \
  --rpc-url https://testnet-passet-hub-eth-rpc.polkadot.io \
  --address $POOL_ADDRESS \
  --from-block latest \
  --follow

CI/CD Integration

GitHub Actions Example

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1
      
      - name: Install Node
        uses: actions/setup-node@v4
        with:
          node-version: 18
      
      - name: Install dependencies
        run: npm ci
      
      - name: Deploy to Paseo
        env:
          PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
        run: |
          forge create src/verifier.sol:Groth16Verifier \
            --rpc-url https://testnet-passet-hub-eth-rpc.polkadot.io \
            --private-key $PRIVATE_KEY

Troubleshooting

"Insufficient funds"

# Check balance
cast balance $YOUR_ADDRESS \
  --rpc-url https://testnet-passet-hub-eth-rpc.polkadot.io

# Get funds from faucet
# https://faucet.polkadot.io/?parachain=1111

"Gas estimation failed"

# Try with explicit gas limit
forge create Contract \
  --rpc-url $RPC \
  --private-key $KEY \
  --gas-limit 5000000

"Contract creation failed"

# Check constructor arguments
cast abi-encode "constructor(address)" $VERIFIER_ADDRESS

# Verify contract compiles
forge build

"Transaction reverted"

# Use cast to trace
cast trace <TX_HASH> \
  --rpc-url https://testnet-passet-hub-eth-rpc.polkadot.io

# Check logs
cast logs --rpc-url $RPC --from-block <BLOCK>

Security Best Practices

1. Use Multi-Sig for Production

// Deploy with Gnosis Safe as owner
constructor(address _safeAddress) {
    owner = _safeAddress;
}

2. Implement Emergency Pause

bool public paused = false;

function pause() external onlyOwner {
    paused = true;
}

function unpause() external onlyOwner {
    paused = false;
}

modifier notPaused() {
    require(!paused, "Contract paused");
    _;
}

3. Time-Lock Upgrades

uint256 public constant TIMELOCK = 2 days;
mapping(bytes32 => uint256) public proposals;

function propose(bytes32 id) external onlyOwner {
    proposals[id] = block.timestamp + TIMELOCK;
}

function execute(bytes32 id) external onlyOwner {
    require(proposals[id] != 0, "Not proposed");
    require(block.timestamp >= proposals[id], "Timelock active");
    // Execute upgrade
}

4. Rate Limiting

mapping(address => uint256) public lastWithdraw;
uint256 public constant COOLDOWN = 1 hours;

function withdraw(...) external {
    require(
        block.timestamp >= lastWithdraw[msg.sender] + COOLDOWN,
        "Cooldown active"
    );
    lastWithdraw[msg.sender] = block.timestamp;
    // ...
}

Deployment Checklist

Pre-Deployment

  • Circuit tested with multiple inputs
  • Verifier key generated correctly
  • Contract tests passing
  • Gas optimization complete
  • Security audit (for mainnet)

Deployment

  • Correct network selected
  • Sufficient funds in wallet
  • Contract addresses recorded
  • Source verified on explorer

Post-Deployment

  • Contract interaction tested
  • Events emitting correctly
  • Monitoring configured
  • Documentation updated

Resources


Previous: Circom Guide | Next: Further Reading