Deployment strategies, scripts, and best practices for Solidity smart contracts. Use when deploying contracts to testnets or mainnet.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill provides deployment strategies, scripts, and best practices for deploying Solidity smart contracts with Foundry and Hardhat.
Use this skill when:
Code Complete
Configuration
Documentation
Testing
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Script} from "forge-std/Script.sol";
import {MyContract} from "../src/MyContract.sol";
contract DeployScript is Script {
function run() external {
// Load deployer private key
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
// Start broadcasting transactions
vm.startBroadcast(deployerPrivateKey);
// Deploy contract
MyContract myContract = new MyContract(
vm.envAddress("INITIAL_OWNER")
);
// Log deployment address
console.log("MyContract deployed to:", address(myContract));
// Stop broadcasting
vm.stopBroadcast();
}
}
contract DeployScript is Script {
function run() external {
// Use --ledger flag when running script
// No private key in environment
vm.startBroadcast();
MyContract myContract = new MyContract(msg.sender);
vm.stopBroadcast();
}
}
Run with Ledger:
forge script script/Deploy.s.sol --rpc-url $SEPOLIA_RPC_URL --ledger --broadcast --verify
contract DeployScript is Script {
function run() external returns (MyContract) {
vm.startBroadcast();
// Get parameters from environment or hardcode
address owner = vm.envAddress("OWNER");
uint256 initialSupply = vm.envUint("INITIAL_SUPPLY");
string memory name = vm.envString("TOKEN_NAME");
MyContract myContract = new MyContract(
owner,
initialSupply,
name
);
vm.stopBroadcast();
return myContract;
}
}
# Deploy to testnet with verification
forge script script/Deploy.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--broadcast \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY
# Deploy to mainnet
forge script script/Deploy.s.sol \
--rpc-url $MAINNET_RPC_URL \
--broadcast \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--slow # Use for mainnet to avoid nonce issues
contract DeployScript is Script {
function run() external {
vm.startBroadcast();
// Deploy in order
Token token = new Token();
console.log("Token:", address(token));
Oracle oracle = new Oracle();
console.log("Oracle:", address(oracle));
Vault vault = new Vault(address(token), address(oracle));
console.log("Vault:", address(vault));
// Configure relationships
token.setVault(address(vault));
oracle.addAuthorized(address(vault));
vm.stopBroadcast();
}
}
contract DeployScript is Script {
function run() external {
vm.startBroadcast();
bytes32 salt = bytes32(uint256(1)); // Choose salt for deterministic address
MyContract myContract = new MyContract{salt: salt}();
console.log("Deployed to:", address(myContract));
vm.stopBroadcast();
}
}
// scripts/deploy.ts
import { ethers } from "hardhat";
async function main() {
// Get signer
const [deployer] = await ethers.getSigners();
console.log("Deploying with account:", deployer.address);
// Check balance
const balance = await ethers.provider.getBalance(deployer.address);
console.log("Account balance:", ethers.formatEther(balance), "ETH");
// Deploy contract
const MyContract = await ethers.getContractFactory("MyContract");
const myContract = await MyContract.deploy();
await myContract.waitForDeployment();
const address = await myContract.getAddress();
console.log("MyContract deployed to:", address);
// Wait for block confirmations before verification
console.log("Waiting for block confirmations...");
await myContract.deploymentTransaction()?.wait(5);
// Verify on Etherscan
if (process.env.ETHERSCAN_API_KEY) {
console.log("Verifying contract...");
await run("verify:verify", {
address: address,
constructorArguments: [],
});
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
async function main() {
const [deployer] = await ethers.getSigners();
// Get parameters
const initialOwner = process.env.INITIAL_OWNER || deployer.address;
const initialSupply = ethers.parseEther("1000000");
// Deploy
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy(initialOwner, initialSupply);
await token.waitForDeployment();
const address = await token.getAddress();
console.log("Token deployed to:", address);
// Verify with constructor args
await run("verify:verify", {
address: address,
constructorArguments: [initialOwner, initialSupply],
});
}
// ignition/modules/MyContract.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
export default buildModule("MyContractModule", (m) => {
const initialOwner = m.getParameter("initialOwner");
const initialSupply = m.getParameter("initialSupply", 1000000n);
const token = m.contract("Token", [initialOwner, initialSupply]);
return { token };
});
Deploy with Ignition:
npx hardhat ignition deploy ignition/modules/MyContract.ts --network sepolia
async function main() {
const [deployer] = await ethers.getSigners();
// Deploy Token
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy();
await token.waitForDeployment();
console.log("Token:", await token.getAddress());
// Deploy Oracle
const Oracle = await ethers.getContractFactory("Oracle");
const oracle = await Oracle.deploy();
await oracle.waitForDeployment();
console.log("Oracle:", await oracle.getAddress());
// Deploy Vault with dependencies
const Vault = await ethers.getContractFactory("Vault");
const vault = await Vault.deploy(
await token.getAddress(),
await oracle.getAddress()
);
await vault.waitForDeployment();
console.log("Vault:", await vault.getAddress());
// Configure relationships
await token.setVault(await vault.getAddress());
await oracle.addAuthorized(await vault.getAddress());
// Save deployment addresses
const fs = require("fs");
const deployment = {
token: await token.getAddress(),
oracle: await oracle.getAddress(),
vault: await vault.getAddress(),
deployer: deployer.address,
network: (await ethers.provider.getNetwork()).name,
timestamp: new Date().toISOString(),
};
fs.writeFileSync(
"deployment.json",
JSON.stringify(deployment, null, 2)
);
}
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract DeployUpgradeable is Script {
function run() external {
vm.startBroadcast();
// Deploy implementation
MyContract implementation = new MyContract();
console.log("Implementation:", address(implementation));
// Encode initialize call
bytes memory initData = abi.encodeWithSelector(
MyContract.initialize.selector,
msg.sender
);
// Deploy proxy
ERC1967Proxy proxy = new ERC1967Proxy(
address(implementation),
initData
);
console.log("Proxy:", address(proxy));
vm.stopBroadcast();
}
}
import { ethers, upgrades } from "hardhat";
async function main() {
const MyContract = await ethers.getContractFactory("MyContract");
// Deploy upgradeable contract
const myContract = await upgrades.deployProxy(
MyContract,
[initialOwner], // initializer args
{ initializer: "initialize", kind: "uups" }
);
await myContract.waitForDeployment();
console.log("Proxy deployed to:", await myContract.getAddress());
console.log("Implementation:", await upgrades.erc1967.getImplementationAddress(
await myContract.getAddress()
));
}
# Deploy to multiple networks
networks=("sepolia" "goerli" "mumbai")
for network in "${networks[@]}"; do
echo "Deploying to $network..."
forge script script/Deploy.s.sol \
--rpc-url $(eval echo \$${network^^}_RPC_URL) \
--broadcast \
--verify
done
// hardhat.config.ts
export default {
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY!],
},
polygon: {
url: process.env.POLYGON_RPC_URL,
accounts: [process.env.PRIVATE_KEY!],
},
arbitrum: {
url: process.env.ARBITRUM_RPC_URL,
accounts: [process.env.PRIVATE_KEY!],
},
},
};
Deploy to each:
npx hardhat run scripts/deploy.ts --network sepolia
npx hardhat run scripts/deploy.ts --network polygon
npx hardhat run scripts/deploy.ts --network arbitrum
# Verify contract
forge verify-contract \
--chain-id 11155111 \
--num-of-optimizations 200 \
--watch \
--compiler-version v0.8.30 \
--verification-method standard-json-input \
--etherscan-api-key $ETHERSCAN_API_KEY \
0x1234... \
src/MyContract.sol:MyContract
# Verify with constructor args
forge verify-contract \
--chain-id 1 \
--constructor-args $(cast abi-encode "constructor(address,uint256)" 0x... 1000) \
--verification-method standard-json-input \
0x1234... \
src/MyContract.sol:MyContract
# Verify contract
npx hardhat verify --network sepolia 0x1234...
# Verify with constructor args
npx hardhat verify --network sepolia 0x1234... "arg1" 123
# Verify upgradeable (verify implementation)
npx hardhat verify --network sepolia IMPLEMENTATION_ADDRESS
# Set gas price
forge script Deploy.s.sol --gas-price 50gwei
# Set priority fee
forge script Deploy.s.sol --priority-gas-price 2gwei
# Legacy gas pricing
forge script Deploy.s.sol --legacy
// hardhat.config.ts
export default {
networks: {
mainnet: {
url: process.env.MAINNET_RPC_URL,
gasPrice: 50000000000, // 50 gwei
},
},
};
// Or in script
const tx = await myContract.deploy({
gasPrice: ethers.parseUnits("50", "gwei"),
});
Test First
Security
Documentation
Verification
Gas Management
Multi-Sig for Critical Functions
// Save to JSON
const deployment = {
network: network.name,
contractAddress: await contract.getAddress(),
deployer: deployer.address,
blockNumber: contract.deploymentTransaction()?.blockNumber,
transactionHash: contract.deploymentTransaction()?.hash,
timestamp: new Date().toISOString(),
constructorArgs: [owner, initialSupply],
};
fs.writeFileSync(
`deployments/${network.name}.json`,
JSON.stringify(deployment, null, 2)
);
const config = {
development: {
initialSupply: ethers.parseEther("1000000"),
fee: 100, // 1%
},
production: {
initialSupply: ethers.parseEther("100000000"),
fee: 30, // 0.3%
},
};
const env = process.env.NODE_ENV || "development";
const params = config[env];
# Deploy
forge script Deploy.s.sol --rpc-url $RPC_URL --broadcast
# Deploy with verification
forge script Deploy.s.sol --rpc-url $RPC_URL --broadcast --verify
# Deploy with hardware wallet
forge script Deploy.s.sol --rpc-url $RPC_URL --ledger --broadcast
# Verify existing contract
forge verify-contract ADDRESS Contract --chain-id 1
# Deploy
npx hardhat run scripts/deploy.ts --network sepolia
# Verify
npx hardhat verify --network sepolia ADDRESS "arg1" 123
# Deploy upgradeable
npx hardhat run scripts/deployUpgradeable.ts --network sepolia
Remember: Always test deployments on testnets first. Use hardware wallets or secure key management for mainnet deployments. Verify all contracts on block explorers.