원클릭으로
deploy-contracts
Deploy smart contracts to Polkadot Asset Hub. Triggers: deploy, deployment, paseo, mainnet
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Deploy smart contracts to Polkadot Asset Hub. Triggers: deploy, deployment, paseo, mainnet
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Deploy frontend to Bulletin Chain + DotNS. Triggers: deploy frontend, bulletin, dotns, .dot domain, decentralized hosting
dot.li universal resolver - the fourth host. Triggers: dot.li, smoldot, helia, link sharing, client-side resolution
Foundry testing patterns for Solidity contracts. Triggers: test, testing, forge test, coverage, solidity test
| name | deploy-contracts |
| description | Deploy smart contracts to Polkadot Asset Hub. Triggers: deploy, deployment, paseo, mainnet |
What this repo actually uses: Hardhat +
@parity/hardhat-polkadot(PolkaVM via theresolcbinary), not Foundry. There is noforge/anvil, nofoundry.toml, no*.s.solscripts. The two contracts —P2PMarket.sol(VERSION 7.0.0) andZKPassportRegistry.sol(1.0.0) — are non-upgradeable: no proxy, no UUPS, no initializer, noOwnable. Deploy with plainhardhat runscripts. The Foundry / proxy / upgrade material below is kept only as generic reference and does not apply here.
| Rule | Enforcement |
|---|---|
Use Hardhat (hardhat run) for all deploys | REQUIRED — no Foundry in this repo |
PRIVATE_KEY set in packages/contracts/.env | REQUIRED before deployment |
| Deployer account holds native PAS (faucet on testnet) | REQUIRED — no gas sponsorship wired |
| Log all deployed addresses | REQUIRED (scripts also write them to env files) |
| Contracts are non-upgradeable | A new deploy = new address; update env/CI accordingly |
| Network | EVM RPC (ethers) | Chain ID | Use Case |
|---|---|---|---|
| Paseo Asset Hub Next (testnet) | https://eth-rpc-paseo-next.polkadot.io | 420420417 | Integration / pre-prod |
| Local Hardhat | (in-process) | 31337 | Unit tests |
| Mainnet | TBD | TBD | Production |
Block explorer (testnet): https://blockscout-paseo-next.polkadot.io
Native token: PAS (10 decimals). Escrow uses the chain-native token via msg.value, not an ERC-20.
# Set PRIVATE_KEY (and optional PASEO_RPC_URL) in packages/contracts/.env
# Verify it's present
grep -q '^PRIVATE_KEY=' packages/contracts/.env && echo 'PRIVATE_KEY set' || echo 'PRIVATE_KEY MISSING'
# Download the resolc binary once (required for PolkaVM compilation)
pnpm download:binaries
# Build + test contracts
pnpm contracts:compile
pnpm contracts:test
scripts/deploy.ts deploys only P2PMarket,
verifies its initial state (VERSION, getOfferCount), and writes the new address +
VITE_CHAIN_ID + VITE_RPC_URL into both apps/web/.env.local and .github/env.
# from packages/contracts
pnpm deploy
# i.e. hardhat run scripts/deploy.ts --network paseo
Or from the repo root:
pnpm contracts:deploy
The registry is not deployed by deploy.ts. It has its own script,
scripts/deploy-zkpassport.ts, which
writes VITE_ZKPASSPORT_REGISTRY_ADDRESS into apps/web/.env.local and .github/env.
# from packages/contracts
hardhat run scripts/deploy-zkpassport.ts --network paseo
scripts/seed.ts registers 2 demo agents and 10
offers against the deployed P2PMarket. It uses the AGENT1_KEY / AGENT2_KEY /
PROVIDER1_KEY / PROVIDER2_KEY keys from packages/contracts/.env.
# from packages/contracts
pnpm seed
# or from root:
pnpm contracts:seed
The real config lives in
packages/contracts/hardhat.config.ts: solc
0.8.28, optimizer runs 200, viaIR: true, resolc.compilerSource: 'binary' with
resolcPath: './bin/resolc'. The paseo network targets PolkaVM (polkadot.target: 'pvm').
// hardhat.config.ts (excerpt)
networks: {
hardhat: { chainId: 31337, allowUnlimitedContractSize: true },
paseo: {
url: process.env.PASEO_RPC_URL || 'https://eth-rpc-paseo-next.polkadot.io',
chainId: 420420417,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
polkadot: { target: 'pvm' }, // Required for PolkaVM
},
},
resolc: {
compilerSource: 'binary',
settings: { resolcPath: './bin/resolc' },
},
Blockscout verification is configured via the etherscan customChains entry pointing at
https://blockscout-paseo-next.polkadot.io/api.
Check that artifacts carry PVM bytecode (starts with 0x50564d00):
jq -r '.bytecode' packages/contracts/artifacts/contracts/P2PMarket.sol/P2PMarket.json | head -c 20
# Should output: 0x50564d0000...
| Issue | Solution |
|---|---|
| Transaction is temporarily banned | Wait and retry; or deploy via ethers with explicit gas params (below) |
| Transaction underpriced | Set explicit gasPrice: feeData.gasPrice * 2n |
| Nonce too low | Wait and retry, or check pending transactions |
| Out of gas | Increase gas limit: gasLimit: 10_000_000n |
| Signature invalid | Check PRIVATE_KEY format (with 0x prefix) |
| Contract write reverts / "insufficient funds" | Deployer/product account needs native PAS — no gas sponsorship is wired |
The RPC proxy can temporarily ban a tx whose hash it just saw. Retrying after a short delay usually clears it. If the Hardhat deploy still fails, deploy the contract with ethers directly, using explicit gas params:
import { ethers } from 'ethers';
import fs from 'fs';
const provider = new ethers.JsonRpcProvider('https://eth-rpc-paseo-next.polkadot.io');
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const artifact = JSON.parse(
fs.readFileSync('./artifacts/contracts/P2PMarket.sol/P2PMarket.json', 'utf8'),
);
const factory = new ethers.ContractFactory(artifact.abi, artifact.bytecode, wallet);
const feeData = await provider.getFeeData();
const contract = await factory.deploy({
gasLimit: 10_000_000n,
gasPrice: feeData.gasPrice ? feeData.gasPrice * 2n : 1_000_000_000n,
});
await contract.waitForDeployment();
console.log('Contract deployed at:', await contract.getAddress());
Because the contracts are non-upgradeable, there is no proxy upgrade path. To ship a contract change you deploy a fresh instance and point the frontend at the new address:
pnpm contracts:compilepnpm contracts:deploy (writes the new VITE_P2PMARKET_ADDRESS into apps/web/.env.local + .github/env)deploy-zkpassport.ts only if the registry changedThe sections below describe Foundry-based, upgradeable (UUPS proxy) deployment. This project uses none of it — it is retained only as generic background. For LocalDOT, follow the Hardhat workflow above.
# NOTE: this repo has no foundry.toml / *.s.sol. Reference only.
source .env
forge script script/Deploy.s.sol --rpc-url paseo --broadcast --slow -vvvv
// NOTE: LocalDOT contracts are non-upgradeable — no proxy, no initializer, no Ownable.
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
ERC1967Proxy proxy = new ERC1967Proxy(
address(impl),
abi.encodeCall(MyContract.initialize, (admin))
);
// NOTE: not applicable here — a LocalDOT redeploy creates a brand-new address.
MyContract(proxyAddress).upgradeToAndCall(address(newImpl), "");
| Pattern | Status | Reason |
|---|---|---|
Deploy without checking PRIVATE_KEY | FORBIDDEN | Missing key fails silently |
| Forget to fund the deployer with native PAS | FORBIDDEN | No gas sponsorship — writes revert |
Forget to update apps/web/.env.local / .github/env | FORBIDDEN | Frontend points at stale address |
(Foundry-specific) Deploy without --slow | N/A | Foundry not used here |
| (Proxy-specific) Initialize in constructor | N/A | No proxies/initializers here |