writing-skills
writing-skills
Use when creating new skills, editing existing skills, or verifying skills work before deployment
soliditySolidity is the programming language for smart contracts on Ethereum and other EVM chains. This skill covers contracts, ERC-20 tokens, Hardhat and Foundry. Use when a user asks to create a smart contract, build an ERC-20 token, deploy to Ethereum, write NFT contracts, or develop DeFi protocols.
Independent GitHub snapshot · Automated classification · Original documentation governs
Solidity is the primary language for Ethereum smart contracts. It compiles to EVM bytecode that runs on Ethereum and all EVM-compatible chains. This skill covers contract structure, common patterns (ERC-20, ERC-721), security, and deployment with Hardhat 3 or Foundry. Examples were built and deployed on Hardhat 3.18 with solc 0.8.34 and OpenZeppelin 5.
// contracts/SimpleStorage.sol — Basic smart contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleStorage {
uint256 private value;
address public owner;
event ValueChanged(uint256 newValue, address changedBy);
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
constructor(uint256 initialValue) {
owner = msg.sender;
value = initialValue;
}
function setValue(uint256 newValue) external onlyOwner {
value = newValue;
emit ValueChanged(newValue, msg.sender);
}
function getValue() external view returns (uint256) {
return value;
}
}
// contracts/MyToken.sol — Standard ERC-20 token
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor() ERC20("My Token", "MTK") Ownable(msg.sender) {
_mint(msg.sender, 1_000_000 * 10 ** decimals()); // 1M tokens
}
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
}
Hardhat 3 needs Node.js 22.13+ and is ESM ("type": "module"). Create a project in an empty directory:
npm init -y
npm install --save-dev hardhat
npx hardhat --init # interactive; pick the Mocha + ethers template
# non-interactive: npx hardhat --init --template mocha-ethers --install
npm install @openzeppelin/contracts # OpenZeppelin 5.x, used by MyToken above
Put the contracts in contracts/, then:
npx hardhat build # `compile` is an alias; writes artifacts
npx hardhat test
Deploy with a Hardhat Ignition module (the recommended way, resumable if a transaction fails):
// ignition/modules/MyToken.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
export default buildModule("MyTokenModule", (m) => {
const token = m.contract("MyToken");
return { token };
});
npx hardhat ignition deploy ignition/modules/MyToken.ts # in-process test network
npx hardhat keystore set SEPOLIA_PRIVATE_KEY # encrypted keystore, not a .env file
npx hardhat keystore set SEPOLIA_RPC_URL
npx hardhat ignition deploy ignition/modules/MyToken.ts --network sepolia
The template's hardhat.config.ts already defines a sepolia network that reads both values with configVariable("SEPOLIA_RPC_URL") and configVariable("SEPOLIA_PRIVATE_KEY"). A plain script also works:
// scripts/deploy-token.ts — run with: npx hardhat run scripts/deploy-token.ts
import { network } from "hardhat";
const { ethers } = await network.create(); // network.connect() is deprecated
const token = await ethers.deployContract("MyToken");
await token.waitForDeployment();
console.log("Token deployed to:", await token.getAddress());
Running it prints Token deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 on the in-process network. Verify a deployed contract with npx hardhat verify etherscan 0x5FbDB2315678afecb367f032d93F642f64180aa3.
forge init token-vault
cd token-vault
forge install OpenZeppelin/openzeppelin-contracts
forge build
forge test
Map the import in foundry.toml (remappings = ["@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/"]) and add an RPC alias:
[rpc_endpoints]
sepolia = "${SEPOLIA_RPC_URL}"
forge script script/Deploy.s.sol # simulation only
forge script script/Deploy.s.sol --rpc-url sepolia --account deployer --broadcast
--broadcast is what sends transactions, and --account deployer uses a keystore created with cast wallet import deployer --interactive. --chain only sets block.chainid; it does not pick the network, so always pass --rpc-url. Add --verify --etherscan-api-key $ETHERSCAN_API_KEY to verify while deploying.
Request: "Create an ERC-20 token with a fixed initial supply that I can deploy to Sepolia."
Use MyToken from Step 2, build with npx hardhat build (output Compiled 1 Solidity file with solc 0.8.34), and deploy with the Ignition module; the last lines are MyTokenModule#MyToken - 0x5FbD...0aa3 on the local network.
Request: "Write a contract that only I can update, and test it."
Use SimpleStorage from Step 1, add a Mocha test (or a Solidity .t.sol test with Foundry) that calls setValue from a second account and expects the revert Not owner, then run npx hardhat test.
pragma solidity ^0.8.20 in a file only sets the minimum.Ownable(msg.sender)).error NotOwner(); and revert NotOwner();) to require strings: cheaper to deploy and call.ReentrancyGuard), access control mistakes, front-running, unchecked external calls, tx.origin auth. Overflow reverts by default since 0.8, except inside unchecked blocks.npx hardhat test solidity).