[AUTO-INVOKE] MUST be invoked BEFORE writing or modifying any Solidity contract (.sol files). Covers pragma version, naming conventions, project layout, OpenZeppelin library selection standards, oracle integration, and anti-patterns. Trigger: any task involving creating, editing, or reviewing .sol source files.
pragma solidity ^0.8.19; — keep consistent across all files in the projectremappings.txtrequire strings — saves gas and is more expressive
error InsufficientBalance(uint256 available, uint256 required);if (balance < amount) revert InsufficientBalance(balance, amount);public / external functions must have NatSpec (@notice, @param, @return)indexed to address type parameters — add comment if indexing other typesimmutable / constant / unchecked / assembly must have inline comment explaining whyWhen a contract involves state initialization (external addresses, business parameters, role assignments), DO NOT default to any single approach. Follow this workflow:
| | Scheme A: Constructor Only | Scheme B: Initializer Only | Scheme C: Hybrid |
|---|---|---|---|
| Approach | All init in constructor params | Deploy empty, call initialize() after | Constructor handles known params; initialize() handles post-deploy dependencies |
| Atomicity | Deploy fails = nothing exists, no dangerous half-init state | Risk of "deployed but not initialized" window | Partially atomic — constructor part is safe, initializer part has the window |
| Front-run risk | None — constructor cannot be front-run | initialize() can be front-run if unprotected | Only the initializer portion has risk |
| Gas | One tx for deploy + init | Deploy tx + N init txs | Deploy tx + 1 init tx |
| Flexibility | Low — all params must be known at deploy time | High — can set dependencies after other contracts deploy | Medium — split between deploy-time and post-deploy |
| Upgradeable | Not compatible with proxy patterns | Required for UUPS / Transparent Proxy | Partial — constructor sets proxy-safe params, initializer for the rest |
| Scenario | Suggested Scheme | |----------|-----------------| | Non-upgradeable, all dependencies known at deploy | A | | Upgradeable contract (UUPS / Transparent Proxy) | B | | Non-upgradeable, but some dependencies deploy later (e.g., LP pair created after token deploy) | C | | Multi-contract system with circular references | C | | Simple utility / library-style contract | A | | Contract needs post-deploy configuration by multisig / DAO | B or C |
require(currentValue == address(0)) or a bool initialized flag — prevent re-initializationinitialize() functions must have access control (onlyOwner, initializer modifier, or deployer-only check)immutable over regular storage — saves gas on every readinitialize() in the same transaction batch or immediately after — document the required call sequenceWhen an event represents a conditional/branching outcome (the same action can happen for different reasons), add a string reason parameter to make on-chain data self-documenting:
// GOOD — on-chain data is self-explanatory
event WithdrawalFailed(address indexed user, uint256 amount, string reason);
emit WithdrawalFailed(user, amount, "insufficient balance");
emit WithdrawalFailed(user, amount, "cooldown not expired");
event ProposalRejected(uint256 indexed proposalId, string reason);
emit ProposalRejected(id, "quorum not reached");
emit ProposalRejected(id, "voting period ended");
// BAD — must read contract source to understand why
event WithdrawalFailed(address indexed user, uint256 amount);
适用场景:
"security incident", "parameter tuning")不适用场景:
uint8 reasonCode 代替| 场景 | 方式 | Gas 成本 |
|------|------|----------|
| 原因种类少且固定(≤5种) | string reason 字面量 | 低(编译器优化短字符串) |
| 原因种类多或动态 | uint8 reasonCode + 前端映射表 | 最低 |
| 需要附带动态数据 | string reason 拼接 | 较高,慎用 |
// 方式 A:string literal(推荐,可读性最好)
emit WithdrawalFailed(user, amount, "cooldown not expired");
// 方式 B:uint8 code(高频场景省 gas)
event SwapRejected(address indexed user, uint256 amount, uint8 reasonCode);
// 0 = slippage, 1 = insufficient liquidity, 2 = paused
emit SwapRejected(user, amount, 0);
external / public 函数必须 emit 至少一个事件@param 必须为 reason 参数列出所有可能的值| Element | Convention | Example |
|---------|-----------|---------|
| Contract / Library | PascalCase | MyToken, StakingPool |
| Interface | I + PascalCase | IMyToken, IStakingPool |
| State variable / Function | lowerCamelCase | totalSupply, claimDividend |
| Constant / Immutable | UPPER_SNAKE_CASE | MAX_SUPPLY, ROUTER_ADDRESS |
| Event | PascalCase (past tense) | TokenTransferred, PoolCreated |
| Custom Error | PascalCase | InsufficientBalance, Unauthorized |
| Function parameter | prefix _ for setter | function setFee(uint256 _fee) |
i/j/k in loops), excessive abbreviationsis / can / has / current / total) instead of appending _ — keeps ABI clean for frontend consumers| Situation | Rule |
|-----------|------|
| Cross-contract constants | Place in src/common/Const.sol |
| Interface definitions | Place in src/interfaces/I<Name>.sol, separate from implementation |
| Simple on-chain queries | Use Foundry cast CLI (call / send) |
| Complex multi-step operations | Use Foundry script (*.s.sol) |
| Import style | Use named imports: import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; |
src/ — Contract source code
interfaces/ — Interface definitions (I*.sol)
common/ — Shared constants, types, errors (Const.sol, Types.sol)
test/ — Test files (*.t.sol)
script/ — Deployment & interaction scripts (*.s.sol)
config/ — Network config, parameters (*.json)
deployments/ — Deployment records (latest.env)
docs/ — Documentation, changelogs
lib/ — Dependencies (managed by Foundry)
config/*.json — network RPC URLs, contract addresses, business parametersdeployments/latest.env — latest deployed contract addresses, must update after each deploymentfoundry.toml — compiler version, optimizer settings, remappingsWhen writing Solidity contracts, prioritize using battle-tested OpenZeppelin libraries over custom implementations. Select the appropriate library based on the scenario:
| Scenario | Library | Import Path |
|----------|---------|-------------|
| Single owner management | Ownable | @openzeppelin/contracts/access/Ownable.sol |
| Owner transfer needs safety | Ownable2Step | @openzeppelin/contracts/access/Ownable2Step.sol |
| Multi-role permission (admin/operator/minter) | AccessControl | @openzeppelin/contracts/access/AccessControl.sol |
| Need to enumerate role members | AccessControlEnumerable | @openzeppelin/contracts/access/AccessControlEnumerable.sol |
| Governance with timelock delay | TimelockController | @openzeppelin/contracts/governance/TimelockController.sol |
Rule: Single owner → Ownable2Step; 2+ roles → AccessControl; governance/DAO → TimelockController
| Scenario | Library | Usage |
|----------|---------|-------|
| External call / token transfer | ReentrancyGuard | Add nonReentrant modifier |
| Emergency pause needed | Pausable | Add whenNotPaused to user-facing functions; keep admin functions unpaused |
| ERC20 token interaction | SafeERC20 | Use safeTransfer / safeTransferFrom / safeApprove instead of raw calls |
Rule: Any contract that transfers tokens or ETH MUST use ReentrancyGuard + SafeERC20
| Scenario | Library | Notes |
|----------|---------|-------|
| Fungible token | ERC20 | Base standard |
| Token with burn mechanism | ERC20Burnable | Adds burn() and burnFrom() |
| Token with max supply cap | ERC20Capped | Enforces totalSupply <= cap |
| Gasless approval (EIP-2612) | ERC20Permit | Saves users approve tx gas |
| Governance voting token | ERC20Votes | Snapshot-based voting power |
| NFT | ERC721 | Base NFT standard |
| NFT with enumeration | ERC721Enumerable | Supports tokenOfOwnerByIndex queries |
| Multi-token (FT + NFT mixed) | ERC1155 | Game items, batch operations |
| Scenario | Library | Usage |
|----------|---------|-------|
| Whitelist / airdrop verification | MerkleProof | Gas-efficient Merkle tree verification |
| Signature verification | ECDSA + EIP712 | Off-chain sign + on-chain verify |
| Auto-increment IDs | Counters | Token ID, order ID generation |
| Batch function calls | Multicall | Multiple operations in one tx |
| Address set / uint set | EnumerableSet | Iterable sets with O(1) add/remove/contains |
| Revenue sharing | PaymentSplitter | Split ETH/token payments by shares |
| Standardized yield vault | ERC4626 | DeFi vault standard |
| Scenario | Library | Notes |
|----------|---------|-------|
| Upgradeable contract (gas efficient) | UUPSUpgradeable | Upgrade logic in implementation contract |
| Upgradeable contract (admin separated) | TransparentUpgradeableProxy | Upgrade logic in proxy, higher gas |
| Initializer (replace constructor) | Initializable | Use initializer modifier instead of constructor |
Rule: New projects prefer UUPSUpgradeable; always use Initializable for upgradeable contracts
| Scenario | Library | Notes |
|----------|---------|-------|
| Token price data | AggregatorV3Interface | Only for tokens with supported oracle data feeds |
| Verifiable randomness (lottery/NFT) | VRFConsumerBaseV2 | On-chain provably fair random numbers |
| Automated execution (cron jobs) | AutomationCompatible | Replace centralized keepers |
| Cross-chain messaging | CCIP | Cross-chain token/message transfer |
Does contract handle user funds/tokens?
├── YES → Add ReentrancyGuard + SafeERC20
│ Does it need emergency stop?
│ ├── YES → Add Pausable
│ └── NO → Skip
└── NO → Skip
How many admin roles needed?
├── 1 role → Ownable2Step
├── 2+ roles → AccessControl
└── DAO/governance → TimelockController
Does contract need price data?
├── Token has oracle feed → AggregatorV3Interface
├── No oracle feed → Custom TWAP with min-liquidity check
└── No price needed → Skip
Will contract need upgrades?
├── YES → UUPSUpgradeable + Initializable
└── NO → Standard deployment (immutable)
transfer wrappers — use SafeERC20Ownable / AccessControlPausableSafeMath on Solidity >= 0.8.0 — overflow checks are built-inrequire(token.transfer(...)) — use token.safeTransfer(...) via SafeERC20tx.origin for auth — use msg.sender with Ownable / AccessControlWhen OpenZeppelinContracts MCP is configured, prefer using it to generate base contracts instead of writing from scratch:
| Contract Type | MCP Tool | When to Use |
|---|---|---|
| Fungible token | solidity-erc20 | Any new ERC20 token contract |
| NFT | solidity-erc721 | Any new NFT contract |
| Multi-token | solidity-erc1155 | Game items, batch operations |
| Stablecoin | solidity-stablecoin | Stablecoin with ERC20 compliance |
| Real-world assets | solidity-rwa | Asset tokenization |
| Smart account | solidity-account | ERC-4337 account abstraction |
| Governance | solidity-governor | DAO voting and proposals |
| Custom | solidity-custom | Non-standard contracts with OZ patterns |
Workflow: MCP generates base → apply this skill's naming/structure rules → customize business logic → apply /solidity-security rules
Why MCP over manual: MCP output is validated against the same rule-set as OZ Contracts Wizard — imports, modifiers, security checks are guaranteed correct. Manual coding risks missing imports or using wrong OZ versions.
When NOT to use MCP: Heavily custom contracts with non-standard patterns, contracts that don't fit any OZ template, or when you need fine-grained control from line 1.
Graceful degradation: If MCP is not configured, fall back to the Library Selection Standards above and write contracts manually following all rules in this skill.
| Operation | Command |
|-----------|---------|
| Create new project | forge init <project-name> |
| Install dependency | forge install openzeppelin-contracts |
| Build contracts | forge build |
| Format code | forge fmt |
| Update remappings | forge remappings |
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer