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.
CRITICAL FOR AGENTS: Frontend applications frequently encounter gas estimation issues when sending transactions to Polkadot networks. Always implement these strategies:
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.
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);
}
}
// 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
});
// 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
});
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;
}
}
}
}