Babysitter openzeppelin
Expert usage of OpenZeppelin Contracts library for secure smart contract development. Covers access control, token standards, governance, upgrades, and security utilities.
install
source · Clone the upstream repo
git clone https://github.com/a5c-ai/babysitter
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/a5c-ai/babysitter "$T" && mkdir -p ~/.claude/skills && cp -r "$T/library/specializations/cryptography-blockchain/skills/openzeppelin" ~/.claude/skills/a5c-ai-babysitter-openzeppelin && rm -rf "$T"
manifest:
library/specializations/cryptography-blockchain/skills/openzeppelin/SKILL.mdsource content
OpenZeppelin Contracts Skill
Expert usage of OpenZeppelin Contracts, the standard library for secure smart contract development.
Capabilities
- Access Control: Ownable, AccessControl, Governor patterns
- Upgradeable Contracts: UUPS and Transparent Proxy plugins
- Token Standards: ERC-20, ERC-721, ERC-1155 implementations
- Security Utilities: ReentrancyGuard, Pausable, SafeERC20
- Governance: Governor and TimelockController
- Metatransactions: ERC-2771 support
- Cryptographic Utilities: ECDSA, MerkleProof, EIP712
Installation
# Standard contracts npm install @openzeppelin/contracts # Upgradeable contracts npm install @openzeppelin/contracts-upgradeable # Hardhat upgrades plugin npm install @openzeppelin/hardhat-upgrades
Access Control
Ownable
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; contract MyContract is Ownable { constructor(address initialOwner) Ownable(initialOwner) {} function protectedFunction() external onlyOwner { // Only owner can call } }
AccessControl (Role-Based)
import "@openzeppelin/contracts/access/AccessControl.sol"; contract MyContract is AccessControl { bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); constructor() { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(ADMIN_ROLE, msg.sender); } function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { // Minting logic } function pause() external onlyRole(PAUSER_ROLE) { // Pausing logic } }
AccessControlEnumerable
import "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; contract MyContract is AccessControlEnumerable { function getAllAdmins() external view returns (address[] memory) { uint256 count = getRoleMemberCount(DEFAULT_ADMIN_ROLE); address[] memory admins = new address[](count); for (uint256 i = 0; i < count; i++) { admins[i] = getRoleMember(DEFAULT_ADMIN_ROLE, i); } return admins; } }
Token Standards
ERC-20 with Extensions
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; contract MyToken is ERC20, ERC20Burnable, ERC20Pausable, ERC20Permit, ERC20Votes, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(MINTER_ROLE, msg.sender); _grantRole(PAUSER_ROLE, msg.sender); } function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { _mint(to, amount); } function pause() public onlyRole(PAUSER_ROLE) { _pause(); } function unpause() public onlyRole(PAUSER_ROLE) { _unpause(); } // Required overrides function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Pausable, ERC20Votes) { super._update(from, to, value); } function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) { return super.nonces(owner); } }
ERC-721 with Extensions
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol"; contract MyNFT is ERC721, ERC721Enumerable, ERC721URIStorage, ERC721Royalty { uint256 private _tokenIdCounter; constructor() ERC721("MyNFT", "MNFT") { _setDefaultRoyalty(msg.sender, 500); // 5% royalty } function safeMint(address to, string memory uri) public { uint256 tokenId = _tokenIdCounter++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); } // Required overrides function _update(address to, uint256 tokenId, address auth) internal override(ERC721, ERC721Enumerable) returns (address) { return super._update(to, tokenId, auth); } function _increaseBalance(address account, uint128 value) internal override(ERC721, ERC721Enumerable) { super._increaseBalance(account, value); } function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable, ERC721URIStorage, ERC721Royalty) returns (bool) { return super.supportsInterface(interfaceId); } }
Governance
Governor Setup
import "@openzeppelin/contracts/governance/Governor.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; contract MyGovernor is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl { constructor( IVotes _token, TimelockController _timelock ) Governor("MyGovernor") GovernorSettings( 1 days, // Voting delay 1 weeks, // Voting period 100e18 // Proposal threshold ) GovernorVotes(_token) GovernorVotesQuorumFraction(4) // 4% quorum GovernorTimelockControl(_timelock) {} // Required overrides... }
Upgradeable Contracts
UUPS Pattern
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; contract MyContractV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable { uint256 public value; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize() public initializer { __Ownable_init(msg.sender); __UUPSUpgradeable_init(); } function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} }
Deployment Script
const { ethers, upgrades } = require("hardhat"); async function main() { // Deploy const MyContract = await ethers.getContractFactory("MyContractV1"); const proxy = await upgrades.deployProxy(MyContract, [], { initializer: "initialize", kind: "uups", }); await proxy.waitForDeployment(); console.log("Proxy:", await proxy.getAddress()); // Upgrade const MyContractV2 = await ethers.getContractFactory("MyContractV2"); await upgrades.upgradeProxy(await proxy.getAddress(), MyContractV2); }
Security Utilities
SafeERC20
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract MyContract { using SafeERC20 for IERC20; function deposit(IERC20 token, uint256 amount) external { token.safeTransferFrom(msg.sender, address(this), amount); } function withdraw(IERC20 token, uint256 amount) external { token.safeTransfer(msg.sender, amount); } }
ReentrancyGuard
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; contract MyContract is ReentrancyGuard { function withdraw() external nonReentrant { // Safe from reentrancy } }
MerkleProof
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; contract Airdrop { bytes32 public merkleRoot; function claim(bytes32[] calldata proof, uint256 amount) external { bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount)); require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof"); // Process claim } }
Process Integration
| Process | Purpose |
|---|---|
| All token processes | Token development |
| Governance |
| Upgrades |
| Staking |
Best Practices
- Always use latest stable version
- Understand inheritance order
- Use Wizard for starting templates
- Audit custom extensions
- Test upgrade paths thoroughly
See Also
- Solidity developmentskills/solidity-dev/SKILL.md- OpenZeppelin Docs
- Contracts Wizard