| name | smart-contract-generator |
| description | Generates Solidity smart contracts with security best practices (ERC-20, ERC-721, ERC-1155, custom). Use when user asks to "create smart contract", "solidity contract", "erc20 token", "nft contract", or "web3 contract". |
| allowed-tools | Write, Read |
Smart Contract Template Generator
Generates secure Solidity smart contracts following OpenZeppelin standards and best practices.
When to Use
- "Create an ERC-20 token"
- "Generate NFT contract"
- "Smart contract template"
- "Solidity contract with security"
- "Create DAO contract"
Instructions
1. Determine Contract Type
Ask user which type:
- ERC-20 (Fungible Token)
- ERC-721 (NFT - Non-Fungible Token)
- ERC-1155 (Multi-Token)
- ERC-4626 (Tokenized Vault)
- Custom contract
2. Generate Contracts
ERC-20 Token
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
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/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract MyToken is ERC20, ERC20Burnable, ERC20Pausable, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
ERC20Permit("MyToken")
{
_mint(msg.sender, 1000000 * 10 ** decimals());
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
// Required override
function _update(address from, address to, uint256 value)
internal
override(ERC20, ERC20Pausable)
{
super._update(from, to, value);
}
}
ERC-721 NFT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
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/ERC721Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
contract MyNFT is ERC721, ERC721Enumerable, ERC721URIStorage, ERC721Pausable, Ownable, ERC721Burnable {
uint256 private _nextTokenId;
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.05 ether;
constructor(address initialOwner)
ERC721("MyNFT", "MNFT")
Ownable(initialOwner)
{}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function safeMint(address to, string memory uri) public payable {
require(_nextTokenId < MAX_SUPPLY, "Max supply reached");
require(msg.value >= MINT_PRICE, "Insufficient payment");
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
function withdraw() public onlyOwner {
uint256 balance = address(this).balance;
payable(owner()).transfer(balance);
}
// Required overrides
function _update(address to, uint256 tokenId, address auth)
internal
override(ERC721, ERC721Enumerable, ERC721Pausable)
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)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
ERC-1155 Multi-Token
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
contract MyMultiToken is ERC1155, Ownable, ERC1155Pausable, ERC1155Supply {
constructor(address initialOwner)
ERC1155("https://api.example.com/token/{id}.json")
Ownable(initialOwner)
{}
function setURI(string memory newuri) public onlyOwner {
_setURI(newuri);
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function mint(address account, uint256 id, uint256 amount, bytes memory data)
public
onlyOwner
{
_mint(account, id, amount, data);
}
function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)
public
onlyOwner
{
_mintBatch(to, ids, amounts, data);
}
// Required overrides
function _update(address from, address to, uint256[] memory ids, uint256[] memory values)
internal
override(ERC1155, ERC1155Pausable, ERC1155Supply)
{
super._update(from, to, ids, values);
}
}
3. Security Patterns
Reentrancy Protection:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureContract is ReentrancyGuard {
function withdraw() public nonReentrant {
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Access Control:
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MyContract is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function mint(address to) public onlyRole(MINTER_ROLE) {
// Minting logic
}
}
Pull Over Push:
// ❌ BAD: Push pattern (vulnerable)
function distribute() public {
for (uint i = 0; i < recipients.length; i++) {
recipients[i].transfer(amounts[i]);
}
}
// ✅ GOOD: Pull pattern (secure)
mapping(address => uint) public pendingWithdrawals;
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];
pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
4. Gas Optimization
// Use uint256 instead of smaller uints (saves gas)
uint256 public count; // ✅
// Cache array length
for (uint256 i = 0; i < array.length; i++) // ❌
uint256 length = array.length;
for (uint256 i = 0; i < length; i++) // ✅
// Use unchecked for gas savings (when safe)
unchecked {
counter++;
}
// Immutable for constants
uint256 public immutable MAX_SUPPLY;
5. Testing Setup
Hardhat:
// test/MyToken.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyToken", function () {
let token;
let owner;
let addr1;
beforeEach(async function () {
[owner, addr1] = await ethers.getSigners();
const MyToken = await ethers.getContractFactory("MyToken");
token = await MyToken.deploy(owner.address);
});
it("Should assign total supply to owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
it("Should transfer tokens", async function () {
await token.transfer(addr1.address, 50);
expect(await token.balanceOf(addr1.address)).to.equal(50);
});
});
6. Deployment Script
// scripts/deploy.js
const hre = require("hardhat");
async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying with account:", deployer.address);
const MyToken = await hre.ethers.getContractFactory("MyToken");
const token = await MyToken.deploy(deployer.address);
await token.waitForDeployment();
console.log("Token deployed to:", await token.getAddress());
// Verify on Etherscan
if (network.name !== "hardhat") {
await hre.run("verify:verify", {
address: await token.getAddress(),
constructorArguments: [deployer.address],
});
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
7. Configuration Files
hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
},
mainnet: {
url: process.env.MAINNET_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};
8. Best Practices
- Use latest Solidity version
- Import from OpenZeppelin
- Add comprehensive tests (>90% coverage)
- Use Slither for static analysis
- Get audited before mainnet
- Use multi-sig for ownership
- Implement pause mechanism
- Follow checks-effects-interactions pattern
- Document all functions with NatSpec
- Version control and CI/CD
9. Audit Checklist
- Reentrancy protection
- Integer overflow/underflow (use 0.8.0+)
- Access control properly implemented
- No unchecked external calls
- Gas limits considered
- Front-running mitigation
- Timestamp dependence avoided
- Randomness source secure
- Upgrade mechanism (if proxy)
- Emergency pause function
10. Documentation Template
/**
* @title MyToken
* @dev Implementation of ERC-20 token with additional features
* @custom:security-contact security@example.com
*/
/**
* @notice Mints new tokens
* @dev Only callable by owner
* @param to Address to receive tokens
* @param amount Amount of tokens to mint
*/
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
Installation
# Initialize project
npm init -y
npm install --save-dev hardhat @openzeppelin/contracts
# Initialize Hardhat
npx hardhat init
# Install dependencies
npm install --save-dev @nomicfoundation/hardhat-toolbox
# Run tests
npx hardhat test
# Deploy
npx hardhat run scripts/deploy.js --network sepolia