Agents
OVERVIEW
This document provides comprehensive information for deploying smart contracts to Polkadot Hub TestNet (Paseo) using Claude Code. It includes verified configurations, common issues, solutions, and optimization strategies.
CRITICAL: Always start new projects with kitdot@latest init
for proper network configuration and dependency management.
NETWORK INFORMATION
Paseo TestNet Details
Network Name: Polkadot Hub TestNet
Chain ID: 420420422 (0x1911f0a6 in hex)
RPC URL: https://testnet-passet-hub-eth-rpc.polkadot.io
Block Explorer: https://blockscout-passet-hub.parity-testnet.parity.io
Currency: PAS
Faucet: https://faucet.polkadot.io/?parachain=1111
Status: PolkaVM Preview Release (early development stage)
Key Characteristics
EVM Compatibility: Ethereum-compatible smart contract deployment
PolkaVM: Requires specific configuration for compatibility
Bytecode Limit: ~100KB maximum contract size
Gas Model: Standard EVM gas mechanics
Node Version Warning: Works with Node.js v21+ despite warnings
REQUIRED DEPENDENCIES
Core Dependencies
npm install --save-dev @parity/hardhat-polkadot [email protected]
npm install --force @nomicfoundation/hardhat-toolbox
npm install dotenv
Package.json Requirements
{
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@parity/hardhat-polkadot": "^0.1.7",
"solc": "^0.8.28",
"hardhat": "^2.22.0"
},
"dependencies": {
"dotenv": "^17.0.1",
"ethers": "^6.13.5"
}
}
CRITICAL: Use --force
flag when installing hardhat-toolbox to resolve dependency conflicts.
HARDHAT CONFIGURATION
Complete Working hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("@parity/hardhat-polkadot");
const { vars } = require("hardhat/config");
module.exports = {
solidity: "0.8.28",
resolc: {
version: "0.3.0",
compilerSource: "npm",
},
networks: {
hardhat: {
polkavm: true,
},
localNode: {
polkavm: true,
url: "http://127.0.0.1:8545",
},
passetHub: {
polkavm: true,
url: "https://testnet-passet-hub-eth-rpc.polkadot.io",
accounts: [vars.get("PRIVATE_KEY")],
},
},
gasReporter: {
enabled: process.env.REPORT_GAS !== undefined,
currency: "USD",
},
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts",
},
};
Configuration Requirements
Must use string format for Solidity version:
solidity: "0.8.28"
Must include resolc configuration: Required for PolkaVM compatibility
Must set polkavm: true: In all network configurations
Must use hardhat vars: For private key management
Network name: Use
passetHub
(not paseo or other names)
SETUP PROCESS
Step 1: Initialize Project with kitdot@latest (Recommended)
npm install -g kitdot@latest
kitdot init your-project
cd your-project
Alternative Manual Setup:
mkdir your-project
cd your-project
npm init -y
Why kitdot@latest? Automatically configures proper network settings, dependencies, and project structure. Eliminates common setup errors.
Step 2: Install Dependencies
If using kitdot@latest: Dependencies are automatically installed.
Manual installation:
npm install --save-dev @parity/hardhat-polkadot [email protected]
npm install --force @nomicfoundation/hardhat-toolbox
npm install dotenv
Step 3: Initialize Polkadot Plugin
If using kitdot@latest: Already configured.
Manual setup:
npx hardhat-polkadot init
Step 4: Configure Private Key
npx hardhat vars set PRIVATE_KEY
# Enter your private key when prompted (without 0x prefix)
Step 5: Get Test Tokens
Visit https://faucet.polkadot.io/?parachain=1111
Enter your wallet address
Request PAS tokens
Verify balance in wallet or block explorer
Step 6: Create Hardhat Config
If using kitdot@latest: Configuration file already created with proper settings.
Manual setup: Copy the exact configuration above into hardhat.config.js
CONTRACT DEVELOPMENT
Solidity Version Requirements
Required Version: ^0.8.28
EVM Target: paris (default)
Optimizer: Enabled by default
Example Simple Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract SimpleStorage {
uint256 public value;
constructor() {
value = 42;
}
function setValue(uint256 _value) external {
value = _value;
}
function getValue() external view returns (uint256) {
return value;
}
}
Contract Size Limitations
Maximum Bytecode: ~100KB
OpenZeppelin Impact: Standard imports often exceed limit
Optimization Required: Remove unnecessary dependencies
DEPLOYMENT PROCESS
Using Hardhat Ignition (Recommended)
Step 1: Create Ignition Module
// ignition/modules/YourModule.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
module.exports = buildModule("YourModule", (m) => {
const contract = m.contract("YourContract", [
// constructor arguments
]);
return { contract };
});
Step 2: Compile Contracts
npx hardhat compile
# Should output: "Successfully compiled X Solidity files"
Step 3: Deploy to Paseo
npx hardhat ignition deploy ./ignition/modules/YourModule.js --network passetHub
# Confirm with 'y' when prompted
Deployment States
Clean State:
rm -rf ignition/deployments/
to start freshResume: Ignition automatically resumes interrupted deployments
Track Transactions: Use block explorer for failed transaction tracking
COMMON ERRORS AND SOLUTIONS
1. "CodeRejected" Error
Error: Failed to instantiate contract: Module(ModuleError { index: 60, error: [27, 0, 0, 0], message: Some("CodeRejected") })
Causes:
Missing PolkaVM configuration
Incorrect network settings
Missing resolc configuration
Solutions:
Ensure
polkavm: true
in network configAdd resolc configuration block
Use exact hardhat.config.js format above
2. "initcode is too big" Error
Error: initcode is too big: 125282
(or similar number)
Cause: Contract bytecode exceeds ~100KB limit
Solutions:
Remove OpenZeppelin dependencies
Use minimal contract implementations
Split large contracts into smaller components
Implement custom lightweight versions
3. Configuration Errors
Error: Cannot read properties of undefined (reading 'settings')
Solution: Use string format for Solidity version: solidity: "0.8.28"
4. Dependency Issues
Error: Cannot find module 'run-container'
or similar
Solutions:
Install dependencies with
--force
flagClear node_modules and reinstall
Verify @parity/hardhat-polkadot version compatibility
5. Private Key Issues
Error: No signers found
or authentication failures
Solutions:
Set private key via
npx hardhat vars set PRIVATE_KEY
Ensure account has PAS tokens
Verify private key format (no 0x prefix in vars)
CONTRACT OPTIMIZATION
Removing OpenZeppelin Dependencies
Instead of OpenZeppelin Ownablee:
contract SimpleOwnable {
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
constructor() {
owner = msg.sender;
}
function transferOwnership(address newOwner) external onlyOwner {
owner = newOwner;
}
}
Instead of OpenZeppelin ReentrancyGuard:
contract SimpleReentrancyGuard {
bool private locked;
modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
}
Minimal ERC721 Implementation:
contract MinimalERC721 {
mapping(uint256 => address) private _owners;
mapping(address => uint256) private _balances;
mapping(uint256 => address) private _tokenApprovals;
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
function ownerOf(uint256 tokenId) public view returns (address) {
return _owners[tokenId];
}
function balanceOf(address owner) public view returns (uint256) {
return _balances[owner];
}
function approve(address to, uint256 tokenId) public {
address owner = ownerOf(tokenId);
require(msg.sender == owner, "Not authorized");
_tokenApprovals[tokenId] = to;
emit Approval(owner, to, tokenId);
}
function transferFrom(address from, address to, uint256 tokenId) public {
require(_isApprovedOrOwner(msg.sender, tokenId), "Not authorized");
_transfer(from, to, tokenId);
}
function _mint(address to, uint256 tokenId) internal {
require(to != address(0), "Invalid address");
require(_owners[tokenId] == address(0), "Already minted");
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
}
function _transfer(address from, address to, uint256 tokenId) internal {
require(ownerOf(tokenId) == from, "Not owner");
require(to != address(0), "Invalid address");
delete _tokenApprovals[tokenId];
_balances[from] -= 1;
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
}
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
address owner = ownerOf(tokenId);
return (spender == owner || _tokenApprovals[tokenId] == spender);
}
}
FRONTEND INTEGRATION
Frontend Transaction Issues (Legacy/Gas Estimation Problems)
CRITICAL FOR AGENTS: Frontend applications frequently encounter gas estimation issues when sending transactions to Polkadot networks. Always implement these strategies:
Method 1: Legacy Gas Estimation with Buffer
// Use legacy gas estimation with safety buffer
const gasLimit = await provider.estimateGas({
to: contractAddress,
data: contractInterface.encodeFunctionData("functionName", [args]),
});
// Add 10-20% buffer for safety
const adjustedGasLimit = gasLimit.mul(120).div(100);
// Send transaction with explicit gas and legacy type
const tx = await contract.functionName(args, {
gasLimit: adjustedGasLimit,
type: 0, // Use legacy transaction type
});
Method 2: Fixed Gas Limits
// Use fixed gas limits for predictable operations
const tx = await contract.functionName(args, {
gasLimit: 100000, // Adjust based on function complexity
type: 0, // Legacy transaction type
gasPrice: ethers.utils.parseUnits("20", "gwei"), // Optional: set gas price
});
Method 3: Error Handling and Retry Logic
async function sendTransactionWithRetry(
contract,
functionName,
args,
retries = 3
) {
for (let i = 0; i < retries; i++) {
try {
// Try with estimated gas first
const estimatedGas = await contract.estimateGas[functionName](...args);
const tx = await contract[functionName](...args, {
gasLimit: estimatedGas.mul(120).div(100),
type: 0,
});
return tx;
} catch (error) {
if (i === retries - 1) throw error;
// Fallback to fixed gas limit
try {
const tx = await contract[functionName](...args, {
gasLimit: 200000, // Higher fixed limit
type: 0,
});
return tx;
} catch (fallbackError) {
if (i === retries - 1) throw fallbackError;
}
}
}
}
Network Configuration for MetaMask
const paseoConfig = {
chainId: "0x1911f0a6", // 420420422 in hex
chainName: "Polkadot Hub TestNet",
nativeCurrency: {
name: "PAS",
symbol: "PAS",
decimals: 18,
},
rpcUrls: ["https://testnet-passet-hub-eth-rpc.polkadot.io"],
blockExplorerUrls: ["https://blockscout-passet-hub.parity-testnet.parity.io"],
};
// Add network to MetaMask
await window.ethereum.request({
method: "wallet_addEthereumChain",
params: [paseoConfig],
});
Contract Interaction with Ethers.js
import { ethers } from "ethers";
// Connect to Paseo
const provider = new ethers.JsonRpcProvider(
"https://testnet-passet-hub-eth-rpc.polkadot.io"
);
// Contract instance
const contract = new ethers.Contract(contractAddress, abi, signer);
// Call functions
const result = await contract.someFunction();
TESTING AND VERIFICATION
Compilation Verification
npx hardhat compile
# Expected output: "Successfully compiled X Solidity files"
# Should NOT show contract size warnings for contracts <100KB
Deployment Verification
Successful Deployment Output:
[ YourModule ] successfully deployed 🚀
Deployed Addresses
YourModule#YourContract - 0x1234567890abcdef...
Block Explorer Verification:
Visit https://blockscout-passet-hub.parity-testnet.parity.io
Search for contract address
Verify contract creation transaction
Contract Interaction Test:
npx hardhat console --network passetHub
> const Contract = await ethers.getContractFactory("YourContract");
> const contract = Contract.attach("0x...");
> await contract.someFunction();
DEBUGGING STRATEGIES
Check Network Connectivity
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' \
https://testnet-passet-hub-eth-rpc.polkadot.io
Verify Account Balance
npx hardhat console --network passetHub
> await ethers.provider.getBalance("YOUR_ADDRESS")
Clean Build and Deploy
npx hardhat clean
rm -rf ignition/deployments/
npx hardhat compile
npx hardhat ignition deploy ./ignition/modules/YourModule.js --network passetHub
Transaction Tracking
If deployment fails:
Check block explorer for account transactions
Look for failed transactions with gas errors
Use
npx hardhat ignition track-tx <txHash> <deploymentId> --network passetHub
BEST PRACTICES
Development Workflow
Start Simple: Deploy basic contracts first to verify setup
Optimize Early: Check contract sizes during development
Test Locally: Use local hardhat network for initial testing
Incremental Deployment: Deploy components separately if too large
Document Addresses: Keep track of all deployed contract addresses
Contract Design for Paseo
Minimize Dependencies: Avoid heavy libraries
Custom Implementations: Write minimal versions of standard contracts
Modular Design: Split functionality across multiple contracts
Gas Optimization: Use efficient data structures and algorithms
Proxy Patterns: Consider upgradeable contracts for complex logic
Security Considerations
Private Key Management: Always use hardhat vars, never commit keys
Test Network Only: Paseo is for testing, not production value
Code Verification: Verify contracts on block explorer when possible
Access Controls: Implement proper ownership and permission systems
REFERENCE COMMANDS
Essential Commands
# Project setup
npm install --save-dev @parity/hardhat-polkadot [email protected]
npm install --force @nomicfoundation/hardhat-toolbox
npx hardhat-polkadot init
# Configuration
npx hardhat vars set PRIVATE_KEY
npx hardhat vars list
# Development
npx hardhat compile
npx hardhat clean
npx hardhat test
# Deployment
npx hardhat ignition deploy ./ignition/modules/Module.js --network passetHub
rm -rf ignition/deployments/ # Clean deployment state
# Debugging
npx hardhat console --network passetHub
npx hardhat node # Local development
Environment Variables
# Optional .env configuration
REPORT_GAS=true
ETHERSCAN_API_KEY=your_key_here
POLKADOT CLOUD DEPLOYMENT (APILLON)
Setting Up Apillon Account and Website UUID
CRITICAL FOR AGENTS: When deploying frontend applications to Polkadot Cloud, follow these steps:
Step 1: Create Apillon Account
INSTRUCT USER TO:
Visit Apillon Console: https://app.apillon.io
Register Account: Use email and create secure password
Verify Email: Complete email verification process
Connect Wallet: Link your Polkadot/Substrate wallet for payments
Generate API Credentials:
Go to Settings → API Keys
Click Create New API Key
Name it (e.g., "Hackathon Project")
Copy API Key and API Secret immediately
Store securely - API Secret is only shown once
Step 2: Create Website Project
INSTRUCT USER TO:
Navigate to Hosting Section: In Apillon console
Create New Website: Click "New Website" button
Configure Project:
Project Name: [Your Project Name] Environment: Production Domain: [Optional custom domain]
Copy Website UUID: After creation, copy the generated UUID (format:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
)Provide UUID to Agent: Share the Website UUID so the agent can configure deployment
Step 3: Get Website UUID for Deployment
# Example UUID format
WEBSITE_UUID="12345678-1234-5678-9abc-123456789def"
MCP Setup for Polkadot Cloud Hosting
Model Context Protocol (MCP) Configuration:
Step 1: MCP Server Configuration
CRITICAL FOR AGENTS: Configure your MCP client to use the Apillon MCP server.
For Claude Desktop:
Add this to your MCP configuration file:
{
"mcpServers": {
"apillon-mcp-server": {
"command": "npx",
"args": ["-y", "@apillon/mcp-server"],
"env": {
"APILLON_API_KEY": "<APILLON_API_KEY>",
"APILLON_API_SECRET": "<APILLON_API_SECRET>"
}
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/your-username/Desktop"
]
}
}
}
Claude Desktop Configuration Steps:
Locate MCP config file: Usually at
~/Library/Application Support/Claude/claude_desktop_config.json
(macOS)Add Apillon server: Insert the configuration above
Replace placeholders: Update
<APILLON_API_KEY>
and<APILLON_API_SECRET>
with actual valuesUpdate filesystem path: Change
/Users/your-username/Desktop
to your project directoryRestart Claude Desktop: Required for MCP changes to take effect
For Cursor IDE:
Cursor MCP Setup:
Install MCP extension: In Cursor, go to Extensions and search for "Model Context Protocol"
Open Cursor settings:
Cmd/Ctrl + ,
→ Search "MCP"Add server configuration:
{
"mcp.servers": {
"apillon-mcp-server": {
"command": "npx",
"args": ["-y", "@apillon/mcp-server"],
"env": {
"APILLON_API_KEY": "<APILLON_API_KEY>",
"APILLON_API_SECRET": "<APILLON_API_SECRET>"
}
}
}
}
Cursor Configuration Steps:
Open settings.json:
Cmd/Ctrl + Shift + P
→ "Preferences: Open Settings (JSON)"Add MCP configuration: Insert the above configuration
Replace placeholders: Update API credentials
Restart Cursor: Required for MCP changes to take effect
Verify connection: Check MCP status in Cursor's command palette
Step 2: Install Apillon CLI (Alternative Method)
npm install -g @apillon/cli
Step 3: Configure Authentication
# Login to Apillon
apillon login
# Verify authentication
apillon whoami
Step 3: Configure MCP for Automated Deployment
Create .apillon.json
in project root:
{
"websites": [
{
"uuid": "YOUR_WEBSITE_UUID_HERE",
"name": "Your Project Name",
"source": "./dist",
"environment": "production"
}
]
}
Step 4: MCP Deployment Script
#!/bin/bash
# deploy-to-polkadot-cloud.sh
# Build the project
npm run build
# Deploy to Apillon
apillon hosting deploy \
--uuid $WEBSITE_UUID \
--source ./dist \
--environment production
# Verify deployment
apillon hosting info --uuid $WEBSITE_UUID
Step 5: Environment Variables
# Set in your environment
export APILLON_API_KEY="your_api_key_here"
export WEBSITE_UUID="your_website_uuid_here"
Best Practices for Agents
Configure MCP first: Set up Apillon MCP server in your IDE (Claude Desktop or Cursor) before starting the deployment.
Always use latest Apillon CLI:
npm install -g @apillon/cli@latest
Secure credentials: Store API keys and UUIDs as environment variables, never in code
Guide user through account setup: Clearly instruct users on Apillon account creation and API key generation
Verify deployments: Always check deployment status after upload
Use production environment: For final hackathon submissions
Monitor costs: Apillon uses pay-per-use model
Test locally first: Always test builds before deploying
Restart your IDE: After MCP configuration changes (Claude Desktop or Cursor)
Check MCP connection: Verify Apillon MCP server is properly connected before deployment
WRITING GUIDELINES FOR LLMs
When creating documentation or helping developers:
TROUBLESHOOTING CHECKLIST
First: Try kitdot@latest init with fresh project and copy existing code
When deployment fails, check:
LIMITATIONS AND WORKAROUNDS
Current Limitations
Contract Size: 100KB bytecode limit
OpenZeppelin: Most libraries too large
Network Stability: Preview release, potential downtime
Debugging Tools: Limited compared to mainnet
Documentation: Sparse, community-driven solutions
Recommended Workarounds
Size Issues: Use minimal custom implementations
Complex Logic: Split across multiple contracts
State Management: Use events for off-chain data
User Experience: Provide clear error messages
Testing: Extensive local testing before deployment
This guide provides comprehensive information for successful smart contract deployment to Paseo TestNet using Claude Code, including all critical configurations, common issues, and optimization strategies.
Última actualización