Introduction
The blockchain and Web3 ecosystem has matured significantly since the early days of Bitcoin. Today, building decentralized applications (dApps) requires not only a deep understanding of consensus mechanisms but also proficiency in specialized programming languages like Solidity (for Ethereum Virtual Machine), Rust (for Solana and Near), and Vyper (a Pythonic alternative for EVM). However, even experienced developers often hit roadblocks: debugging a reentrancy attack, optimizing gas costs, or writing secure token contracts. This is where structured prompts come in. They act as mental frameworks—or even AI-assisted templates—that guide you through complex tasks, from initial design to security audits. In this article, I present 10 carefully crafted prompts, organized by expertise level, that cover smart contract development, DeFi mechanics, and NFT standards. Each prompt includes a real-world example, code snippets, and explanations grounded in official documentation (e.g., Solidity docs, Solana Cookbook, Vyper documentation). Whether you are a beginner or an expert, these prompts will help you write cleaner, safer, and more efficient blockchain code.
Basic Prompts
1. Prompt for Writing a Simple ERC-20 Token Contract in Solidity
Task: Write a minimal, secure ERC-20 token with mint and burn functions, following OpenZeppelin standards.
Prompt: "Generate a Solidity smart contract for an ERC-20 token called 'MyToken' with symbol 'MTK'. Include a constructor that mints an initial supply to the deployer, a mint function with onlyOwner modifier, and a burn function that any holder can call. Use OpenZeppelin v5.0.0 contracts for security."
Example result:
// 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(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
{
_mint(msg.sender, 1000000 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
}
Explanation: This prompt leverages OpenZeppelin’s battle-tested implementations. The Ownable contract restricts minting to the owner, preventing unauthorized inflation. The burn function is public, allowing any token holder to reduce supply. Always verify the OpenZeppelin version because APIs change between major releases (see OpenZeppelin Contracts documentation).
2. Prompt for Deploying a Smart Contract Using Hardhat
Task: Create a deployment script and a simple test for the ERC-20 contract above.
Prompt: "Write a Hardhat deployment script for MyToken that deploys to Sepolia testnet. Also write a unit test using Chai and ethers.js that checks the initial supply and mint function."
Example result (deploy script):
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("MyToken deployed to:", await token.getAddress());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Example test:
const { expect } = require("chai");
describe("MyToken", function () {
it("Should mint initial supply to deployer", async function () {
const [owner] = await ethers.getSigners();
const MyToken = await ethers.getContractFactory("MyToken");
const token = await MyToken.deploy(owner.address);
const balance = await token.balanceOf(owner.address);
expect(balance).to.equal(ethers.parseEther("1000000"));
});
});
Explanation: Hardhat is the de facto development environment for EVM-based chains. The prompt forces you to handle network configuration and test logic. Always include a waitForDeployment() call in newer Hardhat versions (v2.19+). Refer to Hardhat documentation for network setup.
3. Prompt for a Simple NFT Minting Contract in Solidity
Task: Write an ERC-721 contract with a public mint function and a max supply limit.
Prompt: "Create an ERC-721 smart contract named 'MyNFT' using OpenZeppelin. Include a public safeMint function that requires payment of 0.01 ETH, a max supply of 10,000 tokens, and a withdraw function for the owner."
Example result:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, Ownable {
uint256 public totalSupply;
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.01 ether;
constructor(address initialOwner) ERC721("MyNFT", "MNFT") Ownable(initialOwner) {}
function safeMint(address to) public payable {
require(totalSupply < MAX_SUPPLY, "Max supply reached");
require(msg.value == MINT_PRICE, "Incorrect payment");
totalSupply++;
_safeMint(to, totalSupply);
}
function withdraw() public onlyOwner {
uint256 balance = address(this).balance;
payable(owner()).transfer(balance);
}
}
Explanation: The safeMint function increments a counter and uses _safeMint to prevent accidental transfers to non-receivable contracts. The withdraw function protects owner funds. Always use require for input validation—this prevents common exploits like reentrancy in payment logic. See EIP-721 Standard.
Advanced Prompts
4. Prompt for a Reentrancy Guard in a DeFi Withdrawal Function
Task: Secure a withdrawal function against reentrancy attacks using OpenZeppelin’s ReentrancyGuard.
Prompt: "Write a Solidity contract for a simple DeFi vault that accepts ETH deposits and allows users to withdraw. Use OpenZeppelin's ReentrancyGuard to prevent reentrancy. Include a deposit and withdraw function, with a user balance mapping."
Example result:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SimpleVault is ReentrancyGuard, Ownable {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Transfer failed");
}
}
Explanation: The nonReentrant modifier prevents recursive calls. The pattern "checks-effects-interactions" is critical: update state (balances) before external calls. The call method is preferred over transfer because it forwards all gas. For official guidance, see Solidity Reentrancy Attacks.
5. Prompt for a Liquidity Pool Contract in Vyper
Task: Write a minimal constant product automated market maker (AMM) in Vyper, similar to Uniswap V2.
Prompt: "Create a Vyper contract for a liquidity pool with two tokens (token0, token1). Implement addLiquidity and removeLiquidity functions that maintain the constant product formula k = x * y. Use Vyper 0.3.10 syntax."
Example result (simplified):
# @version 0.3.10
interface ERC20:
def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable
def transfer(_to: address, _value: uint256) -> bool: nonpayable
token0: public(ERC20)
token1: public(ERC20)
reserve0: public(uint256)
reserve1: public(uint256)
@external
def __init__(_token0: address, _token1: address):
self.token0 = ERC20(_token0)
self.token1 = ERC20(_token1)
@external
def addLiquidity(amount0: uint256, amount1: uint256) -> bool:
assert amount0 > 0 and amount1 > 0
self.token0.transferFrom(msg.sender, self, amount0)
self.token1.transferFrom(msg.sender, self, amount1)
self.reserve0 += amount0
self.reserve1 += amount1
return True
Explanation: Vyper’s explicit interface declarations and lack of inheritance make it harder to write, but safer for simple contracts. The constant product formula is not fully implemented here (no price calculation), but the structure shows how to handle token transfers. For a full AMM, study Uniswap V2 Whitepaper.
6. Prompt for Cross-Contract Call in Solidity (DeFi Aggregator)
Task: Write a contract that calls an external DEX to swap tokens.
Prompt: "Create a Solidity contract SwapAggregator that takes a token address, an amount, and a DEX router address. It should approve the router to spend the token, then call the router's swapExactTokensForETH function. Use interface for the router."
Example result:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IUniswapV2Router02 {
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
}
interface IERC20 {
function approve(address spender, uint256 amount) external returns (bool);
}
contract SwapAggregator {
function swap(address tokenIn, uint256 amountIn, address router) external {
IERC20(tokenIn).approve(router, amountIn);
address[] memory path = new address[](2);
path[0] = tokenIn;
path[1] = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // ETH placeholder
IUniswapV2Router02(router).swapExactTokensForETH(
amountIn, 1, path, msg.sender, block.timestamp + 300
);
}
}
Explanation: The contract uses an interface to interact with an external DEX. The approve call is necessary because the router needs allowance. Always use a deadline and a minimum output amount (amountOutMin) to protect against slippage and front-running. For production, consider using a library like 1inch for aggregation. Source: Uniswap V2 Router docs.
Expert Prompts
7. Prompt for an Upgradeable Proxy Contract (UUPS Pattern) in Solidity
Task: Implement an upgradeable contract using the UUPS (Universal Upgradeable Proxy Standard) pattern.
Prompt: "Write a UUPS upgradeable contract CounterV1 with a count variable and an increment function. Include a _authorizeUpgrade function that restricts upgrades to the owner. Use OpenZeppelin's UUPSUpgradeable and OwnableUpgradeable."
Example result:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract CounterV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable {
uint256 public count;
function initialize() initializer public {
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
}
function increment() external {
count++;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
Explanation: UUPS stores upgrade logic in the implementation contract, reducing deployment costs compared to the Transparent Proxy pattern. The _authorizeUpgrade function must be overridden to enforce access control. Always initialize storage variables in the initialize function, not in the constructor. Reference: OpenZeppelin Upgradeable Contracts.
8. Prompt for a Flash Loan Contract in Solidity (Aave V3 Style)
Task: Write a contract that executes a flash loan to perform arbitrage.
Prompt: "Create a Solidity contract FlashLoanArbitrage that inherits from Aave V3's IFlashLoanSimpleReceiver. Implement executeOperation to borrow DAI, swap it for USDC on a DEX, repay the loan, and keep the profit."
Example result (skeleton):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
contract FlashLoanArbitrage is FlashLoanSimpleReceiverBase {
constructor(address _addressProvider)
FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider))
{}
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external override returns (bool) {
// Arbitrage logic: swap borrowed DAI for USDC on Uniswap
// Then repay loan + premium
_approveAndSwap(asset, amount);
uint256 amountOwed = amount + premium;
IERC20(asset).approve(address(POOL), amountOwed);
return true;
}
function _approveAndSwap(address token, uint256 amount) internal {
// Implementation omitted for brevity
}
}
Explanation: Flash loans require the borrowed amount plus a small fee (premium) to be returned in the same transaction. The executeOperation function is called by the Aave pool. This pattern is used in real arbitrage bots. For production, use audited libraries and test on a fork. Source: Aave V3 Flash Loan docs.
9. Prompt for a Solana Program (Smart Contract) in Rust
Task: Write a Solana program that stores a user's favorite number on-chain.
Prompt: "Create a Solana program in Rust using the solana-program crate. It should have an instruction SetFavoriteNumber(u64) that stores the number in a user's PDA (Program Derived Address). Include an entrypoint and a test using solana-program-test."
Example result (simplified lib.rs):
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg,
pubkey::Pubkey,
};
entrypoint!(process_instruction);
fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
msg!("Set favorite number instruction");
// Parse instruction data, derive PDA, store number
Ok(())
}
Explanation: Solana programs are stateless; data is stored in accounts. The PDA is derived from the program ID and user's public key. This prompt is essential for understanding Solana's account model. Full implementation requires handling of borsh serialization and CPI calls. See Solana Program Library for examples.
10. Prompt for a Gas Optimization Audit of a Solidity Contract
Task: Analyze a given contract and suggest gas optimizations using specific patterns.
Prompt: "Given the following contract, identify three gas inefficiencies and rewrite it with optimizations: use calldata instead of memory, use unchecked blocks, and pack structs."
Example input contract:
function transfer(address[] memory recipients, uint256[] memory amounts) external {
for (uint256 i = 0; i < recipients.length; i++) {
_transfer(msg.sender, recipients[i], amounts[i]);
}
}
Optimized version:
function transfer(address[] calldata recipients, uint256[] calldata amounts) external {
uint256 len = recipients.length;
for (uint256 i = 0; i < len; ) {
_transfer(msg.sender, recipients[i], amounts[i]);
unchecked { i++; }
}
}
Explanation: calldata avoids copying to memory (saves ~300 gas per parameter). unchecked skips overflow checks in loops (saves ~20 gas per iteration). Struct packing reduces storage slots. These techniques are documented in Solidity Gas Optimization Guide.
Conclusion
These 10 prompts cover the essential building blocks of modern blockchain development—from writing your first ERC-20 token to performing flash loans and optimizing gas. The key takeaway is that a well-structured prompt forces you to think about security, efficiency, and best practices from the start. Whether you are coding in Solidity, Rust, or Vyper, always consult official documentation and use audited libraries. The Web3 space evolves rapidly; the prompts above are based on stable versions as of July 2026. I encourage you to adapt them to your specific use case, test on testnets, and never deploy without a professional audit. Happy coding, and may your transactions always be final!
Comments