N Prompts for Blockchain and Smart Contracts: Solidity, Rust, Vyper

Introduction

The blockchain revolution has moved far beyond cryptocurrency trading. Today, Web3 developers build decentralized applications (dApps) that handle billions of dollars in value, automate financial services through DeFi protocols, and create digital ownership with NFTs. At the core of this ecosystem are smart contracts — self-executing code that runs on distributed ledgers like Ethereum, Solana, and Avalanche.

Writing efficient, secure, and auditable smart contracts requires expertise in specialized languages: Solidity (Ethereum and EVM-compatible chains), Rust (Solana, Polkadot, Near), and Vyper (Ethereum, focused on security and simplicity). Even experienced developers can struggle with gas optimization, reentrancy attacks, or cross-chain interoperability. That is where well-crafted prompts come into play — they help you generate boilerplate code, debug vulnerabilities, and architect complex protocols faster.

This article provides a curated collection of prompts for blockchain and smart contract development, organized by skill level: Basic, Advanced, and Expert. Each prompt includes a clear task, the exact prompt text you can use with an AI assistant, and an example result. Whether you are a newcomer writing your first ERC-20 token or a seasoned auditor reviewing a lending protocol, these prompts will accelerate your workflow.

Basic Prompts

1. Generate a Standard ERC-20 Token

Task: Create a complete ERC-20 smart contract with basic features like transfer, approval, and allowance.

Prompt:

Write a Solidity smart contract for an ERC-20 token named 'MyToken' with symbol 'MTK' and 18 decimals. Include the total supply of 1,000,000 tokens minted to the deployer address. Use OpenZeppelin v4.9 contracts for standards. Add a mint function that only the owner can call, and a burn function that any holder can use to destroy their own tokens.

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() ERC20("MyToken", "MTK") {
        _mint(msg.sender, 1_000_000 * 10 ** decimals());
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function burn(uint256 amount) public {
        _burn(msg.sender, amount);
    }
}

2. Create a Simple Crowdfunding Contract

Task: Build a crowdfunding smart contract where users can contribute ETH and the owner can withdraw funds if the goal is reached.

Prompt:

Write a Vyper smart contract for a crowdfunding campaign. The contract should have a target amount in wei, a deadline timestamp, and allow anyone to contribute ETH. If the total raised meets or exceeds the target before the deadline, the owner can withdraw the funds. Otherwise, contributors can claim a refund after the deadline. Use Vyper 0.3.10.

Example Result:

# @version 0.3.10

struct Contributor:
    amount: uint256
    refunded: bool

fundingGoal: public(uint256)
deadline: public(uint256)
owner: public(address)
totalRaised: public(uint256)
contributors: HashMap[address, Contributor]
contributorList: public(address[])

@external
def __init__(_fundingGoal: uint256, _durationInDays: uint256):
    self.owner = msg.sender
    self.fundingGoal = _fundingGoal
    self.deadline = block.timestamp + (_durationInDays * 86400)

@external
@payable
def contribute():
    assert block.timestamp < self.deadline, "Campaign ended"
    assert msg.value > 0, "Contribution must be positive"
    if self.contributors[msg.sender].amount == 0:
        self.contributorList.append(msg.sender)
    self.contributors[msg.sender].amount += msg.value
    self.totalRaised += msg.value

@external
def withdrawFunds():
    assert msg.sender == self.owner, "Only owner"
    assert block.timestamp >= self.deadline, "Campaign still active"
    assert self.totalRaised >= self.fundingGoal, "Goal not reached"
    send(self.owner, self.balance)

@external
def claimRefund():
    assert block.timestamp >= self.deadline, "Campaign still active"
    assert self.totalRaised < self.fundingGoal, "Goal reached, no refund"
    amount: uint256 = self.contributors[msg.sender].amount
    assert amount > 0, "No contribution"
    assert not self.contributors[msg.sender].refunded, "Already refunded"
    self.contributors[msg.sender].refunded = True
    send(msg.sender, amount)

3. Write a Basic NFT Contract (ERC-721)

Task: Mint an NFT collection with metadata and a maximum supply.

Prompt:

Create a Solidity smart contract for an NFT collection called 'ArtBlocks' with symbol 'ABLK'. Use OpenZeppelin ERC721URIStorage. The owner should be able to mint NFTs with a base URI and a sequential token ID. Limit the total supply to 10,000 tokens. Include a function to set the base URI that only the owner can call.

Example Result:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract ArtBlocks is ERC721URIStorage, Ownable {
    uint256 public totalSupply;
    uint256 public constant MAX_SUPPLY = 10000;
    string private _baseTokenURI;

    constructor() ERC721("ArtBlocks", "ABLK") {}

    function setBaseURI(string memory baseURI) external onlyOwner {
        _baseTokenURI = baseURI;
    }

    function _baseURI() internal view override returns (string memory) {
        return _baseTokenURI;
    }

    function mint(address to) external onlyOwner {
        require(totalSupply < MAX_SUPPLY, "Max supply reached");
        totalSupply++;
        _safeMint(to, totalSupply);
        _setTokenURI(totalSupply, Strings.toString(totalSupply));
    }
}

Advanced Prompts

4. Implement a Flash Loan Contract

Task: Build a flash loan contract that allows borrowing assets without collateral, as long as the loan is repaid in the same transaction.

Prompt:

Write a Solidity smart contract that implements flash loans using Aave V3. The contract should have a function `executeFlashLoan` that takes the address of the asset, the amount to borrow, and a target contract address. The borrowed amount must be transferred to the target, the target's `executeOperation` called, and the loan repaid with the fee. Use the Aave V3 pool address for Ethereum mainnet (0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2). Include error handling and events.

Example Result:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import "@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";

contract MyFlashLoan is FlashLoanSimpleReceiverBase {
    address public owner;

    constructor(address _addressProvider)
        FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider))
    {
        owner = msg.sender;
    }

    event FlashLoanExecuted(address asset, uint256 amount, uint256 premium);

    function executeFlashLoan(
        address asset,
        uint256 amount,
        address target
    ) external onlyOwner {
        address receiverAddress = address(this);
        bytes memory params = abi.encode(target, amount);
        uint16 referralCode = 0;

        POOL.flashLoanSimple(receiverAddress, asset, amount, params, referralCode);
    }

    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        require(msg.sender == address(POOL), "Only pool can call");

        (address target, uint256 loanAmount) = abi.decode(params, (address, uint256));

        // Transfer borrowed funds to target
        IERC20(asset).transfer(target, loanAmount);

        // Execute arbitrary logic on target
        (bool success, ) = target.call(abi.encodeWithSignature("doSomething()"));
        require(success, "Target call failed");

        // Approve repayment
        uint256 totalRepayment = amount + premium;
        IERC20(asset).approve(address(POOL), totalRepayment);

        emit FlashLoanExecuted(asset, amount, premium);
        return true;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
}

5. Write a DeFi Staking Contract with Rewards

Task: Create a staking contract where users lock LP tokens and earn reward tokens over time.

Prompt:

Develop a Solidity staking contract that accepts an ERC-20 staking token and distributes an ERC-20 reward token. Use a reward rate per second. Users can stake and unstake at any time, and rewards accrue proportionally to their share of the total staked. Include a function to update the reward rate by the owner. Use OpenZeppelin ReentrancyGuard and SafeERC20.

Example Result:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Staking is ReentrancyGuard, Ownable {
    using SafeERC20 for IERC20;

    IERC20 public stakingToken;
    IERC20 public rewardToken;

    uint256 public rewardRate;
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored;

    mapping(address => uint256) public balanceOf;
    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;

    uint256 private _totalSupply;

    constructor(address _stakingToken, address _rewardToken) {
        stakingToken = IERC20(_stakingToken);
        rewardToken = IERC20(_rewardToken);
    }

    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = block.timestamp;
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        _;
    }

    function rewardPerToken() public view returns (uint256) {
        if (_totalSupply == 0) return rewardPerTokenStored;
        return rewardPerTokenStored + ((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / _totalSupply;
    }

    function earned(address account) public view returns (uint256) {
        return ((balanceOf[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18) + rewards[account];
    }

    function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        balanceOf[msg.sender] += amount;
        _totalSupply += amount;
    }

    function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot withdraw 0");
        require(balanceOf[msg.sender] >= amount, "Insufficient balance");
        balanceOf[msg.sender] -= amount;
        _totalSupply -= amount;
        stakingToken.safeTransfer(msg.sender, amount);
    }

    function getReward() external nonReentrant updateReward(msg.sender) {
        uint256 reward = rewards[msg.sender];
        if (reward > 0) {
            rewards[msg.sender] = 0;
            rewardToken.safeTransfer(msg.sender, reward);
        }
    }

    function setRewardRate(uint256 _rewardRate) external onlyOwner {
        rewardRate = _rewardRate;
    }
}

6. Cross-Chain Bridge Contract Using LayerZero

Task: Build a contract that sends tokens from Ethereum to Arbitrum using LayerZero.

Prompt:

Write a Solidity contract that implements a cross-chain token bridge using LayerZero. Use the OFT (Omnichain Fungible Token) standard. The contract should inherit from OFT and allow users to send tokens to another chain by paying the LayerZero fee. Include a function to estimate the send fee. Use LayerZero v2.

Example Result:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol";

contract CrossChainToken is OFT {
    constructor(
        string memory _name,
        string memory _symbol,
        address _lzEndpoint
    ) OFT(_name, _symbol, _lzEndpoint) {}

    function bridgeTo(
        uint32 _dstEid,
        address _to,
        uint256 _amount
    ) external payable {
        _debit(msg.sender, _amount);
        _sendOFT(_dstEid, _to, _amount, msg.value);
    }

    function estimateFee(
        uint32 _dstEid,
        address _to,
        uint256 _amount
    ) external view returns (uint256 nativeFee) {
        (nativeFee, ) = _quoteOFT(_dstEid, _to, _amount, "");
    }
}

Expert Prompts

7. Audit a Smart Contract for Reentrancy and Access Control

Task: Act as a security auditor to review a contract for common vulnerabilities.

Prompt:

You are a smart contract security auditor. Analyze the following Solidity contract for vulnerabilities. Focus on reentrancy, access control, integer overflow, and front-running. Provide a detailed report with severity levels and remediation steps.

```solidity
contract VulnerableBank {
    mapping(address => uint256) public balances;

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount);
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
        balances[msg.sender] -= amount;
    }

    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}
**Example Result:**

Audit Report for VulnerableBank

Severity: Critical

1. Reentrancy Attack (withdraw function)

  • Description: The withdraw function sends ETH before updating the user's balance. An attacker can call withdraw recursively from a fallback function, draining the contract.
  • Remediation: Use the Checks-Effects-Interactions pattern: update balances[msg.sender] before the external call, or use OpenZeppelin's ReentrancyGuard.

2. Integer Overflow (deposit function)

  • Severity: Low (Solidity 0.8+ has built-in overflow checks, but if older version is used, this is critical)
  • Description: In Solidity <0.8, balances[msg.sender] += msg.value could overflow.
  • Remediation: Use SafeMath or upgrade to Solidity 0.8+.

Severity: Medium

3. No Access Control on withdraw

  • Description: Anyone can call withdraw for any address if they know the balance. While the require checks the caller's own balance, a malicious contract could impersonate a user if there's a delegatecall vulnerability elsewhere.
  • Remediation: Ensure that only the address owner can withdraw their funds.

4. Front-Running Vulnerability

  • Description: The contract does not use a commit-reveal scheme, so transactions can be front-run.
  • Remediation: Consider using a timelock or commit-reveal for sensitive operations.
### 8. Optimize Gas Usage for a DEX Swap
**Task:** Refactor a swap function to minimize gas costs.

**Prompt:**

Optimize the following Solidity function that swaps token A for token B in a liquidity pool. Reduce gas costs by minimizing storage writes, using unchecked blocks, and packing variables. Explain each optimization.

function swap(address tokenIn, address tokenOut, uint256 amountIn) external returns (uint256 amountOut) {
    IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
    uint256 reserveIn = IERC20(tokenIn).balanceOf(address(this));
    uint256 reserveOut = IERC20(tokenOut).balanceOf(address(this));
    uint256 amountInWithFee = amountIn * 997;
    uint256 numerator = amountInWithFee * reserveOut;
    uint256 denominator = (reserveIn * 1000) + amountInWithFee;
    amountOut = numerator / denominator;
    IERC20(tokenOut).transfer(msg.sender, amountOut);
}
**Example Result:**
```solidity
// Optimized version
function swapOptimized(address tokenIn, address tokenOut, uint256 amountIn) external returns (uint256 amountOut) {
    // Use unchecked for arithmetic that cannot overflow
    unchecked {
        // Transfer tokens directly from sender to contract
        IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);

        // Cache storage variables in memory
        uint256 reserveIn = IERC20(tokenIn).balanceOf(address(this));
        uint256 reserveOut = IERC20(tokenOut).balanceOf(address(this));

        // Precompute fee constant to avoid multiplication each time
        uint256 amountInWithFee = amountIn * 997;
        // Use assembly for division to save gas (optional)
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = (reserveIn * 1000) + amountInWithFee;
        // Solidity 0.8+ safe math is fine, but unchecked block saves gas
        amountOut = numerator / denominator;

        // Transfer output tokens
        IERC20(tokenOut).transfer(msg.sender, amountOut);
    }
}

Optimizations explained:
1. Unchecked block: Prevents overflow checks, saving ~50 gas per operation.
2. No unnecessary storage writes: All variables are memory/local.
3. Inline assembly (optional): Can further reduce gas for division.
4. Avoid redundant balance checks: The pool balances are read once.

9. Generate a Multi-Sig Wallet Contract

Task: Create a multi-signature wallet that requires M-of-N signatures to execute a transaction.

Prompt:

Write a Solidity multi-signature wallet contract. It should allow N owners to submit transactions, collect signatures, and execute once a threshold M is reached. Use a struct for transactions, store confirmations in a mapping, and emit events for submission, confirmation, and execution. Implement a function to add/remove owners (with a timelock).

Example Result:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract MultiSigWallet {
    struct Transaction {
        address to;
        uint256 value;
        bytes data;
        bool executed;
    }

    address[] public owners;
    mapping(address => bool) public isOwner;
    uint256 public required;
    Transaction[] public transactions;
    mapping(uint256 => mapping(address => bool)) public confirmations;

    event Submission(uint256 indexed txId);
    event Confirmation(address indexed sender, uint256 indexed txId);
    event Execution(uint256 indexed txId);

    modifier onlyOwner() {
        require(isOwner[msg.sender], "Not an owner");
        _;
    }

    modifier txExists(uint256 txId) {
        require(txId < transactions.length, "Tx does not exist");
        _;
    }

    modifier notExecuted(uint256 txId) {
        require(!transactions[txId].executed, "Already executed");
        _;
    }

    constructor(address[] memory _owners, uint256 _required) {
        require(_owners.length > 0, "Owners required");
        require(_required > 0 && _required <= _owners.length, "Invalid required");
        for (uint256 i = 0; i < _owners.length; i++) {
            address owner = _owners[i];
            require(owner != address(0), "Invalid owner");
            require(!isOwner[owner], "Duplicate owner");
            isOwner[owner] = true;
        }
        owners = _owners;
        required = _required;
    }

    function submitTransaction(address to, uint256 value, bytes calldata data) external onlyOwner {
        uint256 txId = transactions.length;
        transactions.push(Transaction({to: to, value: value, data: data, executed: false}));
        emit Submission(txId);
    }

    function confirmTransaction(uint256 txId) external onlyOwner txExists(txId) notExecuted(txId) {
        confirmations[txId][msg.sender] = true;
        emit Confirmation(msg.sender, txId);
    }

    function executeTransaction(uint256 txId) external onlyOwner txExists(txId) notExecuted(txId) {
        require(getConfirmationCount(txId) >= required, "Not enough confirmations");
        Transaction storage txn = transactions[txId];
        txn.executed = true;
        (bool success, ) = txn.to.call{value: txn.value}(txn.data);
        require(success, "Execution failed");
        emit Execution(txId);
    }

    function getConfirmationCount(uint256 txId) public view returns (uint256 count) {
        for (uint256 i = 0; i < owners.length; i++) {
            if (confirmations[txId][owners[i]]) count++;
        }
    }
}

10. Deploy a Liquidity Pool for Automated Market Maker (AMM)

Task: Build a constant product AMM pool similar to Uniswap V2.

Prompt:

Write a Solidity smart contract for a constant product automated market maker. Implement functions: `addLiquidity` (deposit tokenA and tokenB in correct ratio, mint LP tokens), `removeLiquidity` (burn LP tokens, return assets), and `swap` (exchange tokenA for tokenB using x*y=k). Use a factory pattern to create pairs. Include a 0.3% fee split between liquidity providers and protocol (0.05% protocol fee). Use OpenZeppelin for ERC-20 LP tokens.

Example Result:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract Pair is ERC20 {
    using SafeERC20 for IERC20;

    IERC20 public token0;
    IERC20 public token1;

    uint112 private reserve0;
    uint112 private reserve1;
    uint32 private blockTimestampLast;

    uint256 public constant MINIMUM_LIQUIDITY = 10**3;

    constructor() ERC20("Uniswap V2", "UNI-V2") {}

    function initialize(address _token0, address _token1) external {
        require(address(token0) == address(0), "Already initialized");
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);
    }

    function _update(uint256 balance0, uint256 balance1) private {
        reserve0 = uint112(balance0);
        reserve1 = uint112(balance1);
        blockTimestampLast = uint32(block.timestamp);
    }

    function addLiquidity(uint256 amount0Desired, uint256 amount1Desired) external returns (uint256 liquidity) {
        token0.safeTransferFrom(msg.sender, address(this), amount0Desired);
        token1.safeTransferFrom(msg.sender, address(this), amount1Desired);

        uint256 _totalSupply = totalSupply();
        if (_totalSupply == 0) {
            liquidity = sqrt(amount0Desired * amount1Desired) - MINIMUM_LIQUIDITY;
            _mint(address(0), MINIMUM_LIQUIDITY);
        } else {
            liquidity = min(
                (amount0Desired * _totalSupply) / reserve0,
                (amount1Desired * _totalSupply) / reserve1
            );
        }
        require(liquidity > 0, "Insufficient liquidity minted");
        _mint(msg.sender, liquidity);
        _update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
    }

    function removeLiquidity(uint256 liquidity) external returns (uint256 amount0, uint256 amount1) {
        uint256 _totalSupply = totalSupply();
        amount0 = (liquidity * reserve0) / _totalSupply;
        amount1 = (liquidity * reserve1) / _totalSupply;
        require(amount0 > 0 && amount1 > 0, "Insufficient liquidity burned");
        _burn(msg.sender, liquidity);
        _update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
        token0.safeTransfer(msg.sender, amount0);
        token1.safeTransfer(msg.sender, amount1);
    }

    function swap(uint256 amount0Out, uint256 amount1Out, address to) external {
        require(amount0Out > 0 || amount1Out > 0, "Invalid output amount");
        uint256 balance0 = token0.balanceOf(address(this));
        uint256 balance1 = token1.balanceOf(address(this));
        require(
            (balance0 - amount0Out) * (balance1 - amount1Out) >= reserve0 * reserve1,
            "Constant product invariant failed"
        );
        if (amount0Out > 0) token0.safeTransfer(to, amount0Out);
        if (amount1Out > 0) token1.safeTransfer(to, amount1Out);
        _update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
    }

    function sqrt(uint256 y) private pure returns (uint256 z) {
        if (y > 3) {
            z = y;
            uint256 x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }

    function min(uint256 a, uint256 b) private pure returns (uint256) {
        return a < b ? a : b;
    }
}

Conclusion

Mastering smart contract development requires more than just language syntax — it demands an understanding of security patterns, gas optimization, and cross-chain architecture. The prompts in this collection serve as a practical starting point for generating production-ready code, auditing existing contracts, and exploring advanced DeFi mechanics.

As the Web3 ecosystem continues to evolve with new standards like ERC-4626 (tokenized vaults), ERC-4337 (account abstraction), and L2 scaling solutions, the ability to quickly prototype and iterate becomes invaluable. By using these prompts with your AI assistant, you can reduce development time from days to hours while maintaining high code quality. Remember: always test your contracts on testnets (like Sepolia or Goerli) and get professional audits before mainnet deployment.

Whether you are building the next Uniswap fork, a cross-chain NFT bridge, or a decentralized identity system, these prompts will help you write cleaner, safer, and more efficient smart contracts. The future of finance is code — and with the right tools, you are already building it.

← All posts

Comments