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
| Parameter | Value |
|---|---|
| RPC URL | https://testnet-passet-hub-eth-rpc.polkadot.io |
| Chain ID | 420420422 |
| Explorer | https://blockscout-passet-hub.parity-testnet.parity.io/ |
| Faucet | https://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
| Parameter | Value |
|---|---|
| RPC URL | https://kusama-asset-hub-rpc.polkadot.io |
| Chain ID | (Check current value) |
| Explorer | https://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
- Visit Blockscout explorer
- Navigate to your contract
- Click "Verify & Publish"
- 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