From Token Idea to Testnet in One Weekend: A Solidity and Web3 Prompt Chain That Survives an Audit

From Token Idea to Testnet in One Weekend: A Solidity and Web3 Prompt Chain That Survives an Audit

Most people who try to write their first smart contract quit in the same place. Not at the syntax. Not at the tooling. They quit when the contract compiles, deploys, and then a stranger on the internet points out that anyone can drain it with a single transaction. The gap between "it works" and "it is safe" is where real money lives, and it is exactly the gap that a well-built prompt chain can close.

This is a case study, not a listicle. The protagonist is a developer we will call M., a backend engineer with solid Python and zero blockchain experience. Over one weekend he took an idea — a capped ERC-20 token with a vesting cliff — and pushed it to the Sepolia testnet. He did not read the full Solidity documentation. He did not pay for an audit. He did read the OpenZeppelin docs, the Solidity security considerations page, and he used AI as a reviewer, a rubber duck, and a gas profiler. The prompts below are the ones that actually mattered.

A note before we start on why this matters in 2026. Smart contract exploits are not rare edge cases. The reentrancy bug that drained The DAO in 2016 is still the canonical teaching example, and it still appears in new code. According to the OpenZeppelin documentation on security, reentrancy remains one of the most common vulnerability classes. The good news: AI models trained on public audit reports and security advisories are surprisingly good at spotting these patterns — if you ask them the right way.

The setup: what you need before the first prompt

M. used Hardhat, the standard Ethereum development environment, with Node.js 20 and the @openzeppelin/contracts package. Nothing exotic. The toolchain matters because AI-generated code is only useful if you can compile, test, and deploy it locally without friction.

mkdir vesting-token && cd vesting-token
npm init -y
npm install --save-dev hardhat
npx hardhat init   # choose a TypeScript project
npm install @openzeppelin/contracts

One rule M. set for himself: never paste AI code into a project without reading it line by line. The prompts are designed to make that reading fast, not to skip it.

Prompt 1 — Turning a vague idea into a specification

Task: Convert a business idea into a precise, testable contract specification before writing any code.

Prompt:

Act as a smart contract architect. I want to build an ERC-20 token with the following
business rules:
- Fixed supply of 100,000,000 tokens, minted once at deployment.
- 20% of supply goes to the founding team, locked for 12 months, then released linearly
  over 24 months.
- The rest is immediately transferable.
- No owner can mint new tokens after deployment.

Write a specification document in plain English. For each rule, state:
1. What the contract must enforce on-chain.
2. What can be enforced off-chain instead.
3. What assumptions could break the rule.
4. Which OpenZeppelin contracts (with exact version) I should inherit from.

Do not write Solidity yet. Be explicit about anything you are uncertain about.

Result: The model returned a spec that separated on-chain enforcement (the cap, the vesting math) from off-chain concerns (who holds the keys). It recommended ERC20, ERC20Capped, and a custom VestingWallet-style contract, and it flagged a real issue M. had not considered: if the team allocation is in a single wallet, that wallet becomes a centralization risk that auditors will comment on. That single paragraph changed the design before a line of code existed.

Prompt 2 — Generating the first contract draft

Task: Produce a compilable first draft that uses audited building blocks instead of hand-rolled logic.

Prompt:

Write a Solidity 0.8.24 contract named TeamVestingToken that implements the
specification from the previous message.

Constraints:
- Inherit from OpenZeppelin ERC20 and ERC20Capped.
- Use the constructor to mint the full supply to the deployer.
- Implement a claim() function that releases vested tokens to the beneficiary.
- Follow the Checks-Effects-Interactions pattern.
- Add NatSpec comments for every external function.
- Do not use any deprecated OpenZeppelin functions.

After the code, list every line where you made a design choice that a security
auditor would question, and explain your reasoning.

Result: The draft compiled on the first try. More valuable than the code was the trailing list: the model admitted it had assumed the beneficiary address is immutable, warned that a mutable beneficiary would need an access control check, and noted that claim() had no reentrancy guard because it makes no external calls — a claim that M. later verified with Prompt 5.

Here is the core of what came back:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";

contract TeamVestingToken is ERC20, ERC20Capped {
    address public immutable beneficiary;
    uint64 public immutable cliffEnd;
    uint64 public immutable vestingEnd;
    uint256 public constant TEAM_ALLOCATION = 20_000_000e18;
    uint256 public claimed;

    constructor(address _beneficiary)
        ERC20("TeamVestingToken", "TVT")
        ERC20Capped(100_000_000e18)
    {
        beneficiary = _beneficiary;
        cliffEnd = uint64(block.timestamp + 365 days);
        vestingEnd = uint64(cliffEnd + 730 days);
        _mint(msg.sender, 100_000_000e18);
    }

    function vestedAmount() public view returns (uint256) {
        if (block.timestamp < cliffEnd) return 0;
        if (block.timestamp >= vestingEnd) return TEAM_ALLOCATION;
        return (TEAM_ALLOCATION * (block.timestamp - cliffEnd)) / (vestingEnd - cliffEnd);
    }

    function claim() external {
        require(msg.sender == beneficiary, "not beneficiary");
        uint256 amount = vestedAmount() - claimed;
        require(amount > 0, "nothing to claim");
        claimed += amount;
        _transfer(msg.sender, beneficiary, amount);
    }
}

Prompt 3 — A security review that actually finds things

Task: Run an adversarial review that assumes the contract is broken.

Prompt:

You are a smart contract security auditor. Review the TeamVestingToken contract
below as if you were paid to find bugs. Assume it is broken and your job is to
prove it.

Check specifically for:
- Reentrancy (all variants, including read-only reentrancy)
- Integer overflow and underflow
- Access control gaps
- Timestamp manipulation
- Front-running and MEV exposure
- Denial of service vectors
- Incorrect assumptions about block.timestamp

For each finding, give: severity (Critical/High/Medium/Low/Informational),
the exact line, a concrete exploit scenario, and a fix.

[contract pasted here]

Result: The model found a genuine Medium-severity issue M. had missed. Because claim() calls _transfer from the contract's own balance, and the contract holds the entire team allocation, a rounding artifact in vestedAmount() could theoretically allow the last claim to revert permanently if the arithmetic did not sum exactly to TEAM_ALLOCATION. The fix was to track claimed as the source of truth and cap the final claim. That is exactly the kind of finding a cheap automated tool misses and a human auditor charges for.

Prompt 4 — Writing tests that would have caught the bug

The finding in Prompt 3 was only useful because M. could prove it. AI is excellent at turning a described bug into a failing test.

Prompt:

Write Hardhat tests in TypeScript for TeamVestingToken using chai and
@nomicfoundation/hardhat-toolbox.

Cover:
1. Total supply equals the cap after deployment.
2. Claiming before the cliff reverts.
3. A time-travel test: use hardhat's evm_increaseTime and evm_mine helpers to
   simulate 18 months, then assert the claimable amount is exactly 50% of the
   team allocation.
4. The rounding bug described above: assert the sum of all claims never exceeds
   TEAM_ALLOCATION.
5. A non-beneficiary cannot claim.

Use ethers v6 syntax. Do not use deprecated helpers.

Result: The generated suite ran green after one fix — the model had used ethers.utils.parseEther, which is ethers v5 syntax, and the test output made the error obvious. This is worth stating plainly: AI code is wrong often enough that you should expect to fix it. The value is that it is wrong in ways that are fast to detect.

Prompt 5 — Gas optimization without breaking correctness

Task: Reduce deployment and runtime gas costs, with a measurement methodology.

Prompt:

Analyze TeamVestingToken for gas optimization opportunities.

For each suggestion:
- State the estimated saving (deployment cost, runtime cost, or both).
- Explain whether it changes behavior or security guarantees.
- Rank suggestions by saving-to-risk ratio.

Also tell me how to measure the real numbers with Hardhat, including the exact
command and the config changes needed to enable the gas reporter.

Result: The highest-value suggestion was packing cliffEnd, vestingEnd, and claimed more carefully — though the model correctly noted that claimed is a uint256 and cannot share a slot with the uint64 timestamps without a custom struct, so the saving was smaller than it first appeared. The measurement instructions were accurate: npm install --save-dev hardhat-gas-reporter, add the plugin to hardhat.config.ts, and run REPORT_GAS=true npx hardhat test. M. measured a modest but real reduction in deployment cost by replacing a redundant storage write. The important lesson: ask for the numbers, not just the advice.

Prompt 6 — Auditing dependencies, not just your own code

Most real-world exploits do not live in your contract. They live in what it inherits. This prompt forces the model to look outward.

Prompt:

I am using @openzeppelin/contracts version 5.x. List any known security
advisories, breaking changes, or deprecated patterns in the contracts I inherit
from: ERC20, ERC20Capped.

For each item, cite the source (GitHub advisory, changelog entry, or security
mailing list). If you are not certain an advisory exists, say so explicitly
instead of guessing.

Result: The model correctly pointed to the OpenZeppelin 5.0 migration guide, which removed several previously common functions and changed the _beforeTokenTransfer hook to _update. This is verifiable in the official OpenZeppelin documentation and changelog. M. had generated his first draft against a tutorial written for v4, and this prompt caught the mismatch. Treat any specific CVE number the model produces as unverified until you check the OpenZeppelin GitHub Security Advisories page yourself — that is not optional.

Prompt 7 — A threat model for the human layer

Smart contracts fail for non-technical reasons too. The private key behind the deployer address is often the weakest link.

Prompt:

Act as a security consultant. Write a threat model for deploying and operating
TeamVestingToken on mainnet.

Cover the operational side, not just the code:
- Private key management for the deployer and the beneficiary.
- What happens if the deployer key is compromised after deployment.
- Multisig versus single EOA for the team allocation.
- How to verify the contract on Etherscan and why it matters for user trust.
- What a professional audit would cost and when it is justified.

Result: The output was a checklist, not code, and that was the point. It recommended a hardware wallet for the deployer, a multisig for the team allocation, and verification on Etherscan so that users can read the contract. It also gave a realistic framing of audit cost: a professional audit of a small token contract is a real expense that scales with complexity and timeline, and skipping it is a business decision with a price tag attached.

Prompt 8 — Deployment scripts that do not leak secrets

Task: Generate a deployment script that reads secrets from the environment, never from source.

Prompt:

Write a Hardhat deployment script in TypeScript for TeamVestingToken.

Requirements:
- Read the RPC URL and private key from environment variables using dotenv.
- Never hardcode a private key or mnemonic.
- Accept the beneficiary address as a script argument.
- Print the deployed address and the block number.
- Include a .env.example file and a .gitignore entry.

Result: The script worked against Sepolia on the first run. The generated .gitignore correctly excluded .env. This is a small prompt with an outsized payoff: leaked private keys in public repositories are a recurring cause of stolen funds, and a one-line mistake in a .gitignore is all it takes.

import { ethers } from "hardhat";

async function main() {
  const beneficiary = process.argv[2];
  if (!ethers.isAddress(beneficiary)) throw new Error("invalid beneficiary");

  const Factory = await ethers.getContractFactory("TeamVestingToken");
  const token = await Factory.deploy(beneficiary);
  await token.waitForDeployment();

  console.log("Deployed to:", await token.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Prompt 9 — Explaining the contract to non-technical stakeholders

A token has holders, and holders have questions. Being able to explain your own contract is part of shipping it.

Prompt:

Explain TeamVestingToken to a non-technical reader in under 300 words.

They need to understand:
- What the token is and what it is not.
- When team tokens unlock and why.
- What they should verify on a block explorer.
- What the contract cannot do (for example, mint more tokens).

Avoid jargon. If you must use a term like 'smart contract', define it in one sentence.

Result: The explanation was accurate and, more importantly, falsifiable — every claim mapped to a function or an immutable variable. M. used it as the basis for a README, which is the first thing an auditor reads. Writing the plain-English version is not marketing. It is a consistency check on your own design.

Prompt 10 — The final pre-deployment checklist

The last prompt is the one that turns a weekend project into something you would let other people use.

Prompt:

Produce a final pre-deployment checklist for TeamVestingToken on mainnet.

Format it as a table with columns: Check, Command or Action, Pass/Fail.

Include:
- Full test suite passing.
- Gas report reviewed.
- Contract verified on a block explorer.
- Dependencies pinned to exact versions.
- No secrets in the repository.
- A rollback or pause plan, or an explicit statement that none exists.
- A second pair of human eyes on the final diff.

Result: The table came back with twelve rows, and the last one — a human reviewing the diff — was the only one the model could not perform. That is the honest boundary of AI in this workflow. It can generate, review, test, and optimize. It cannot be accountable. That job stays with you.

What M. actually shipped

By Sunday evening the contract was deployed to Sepolia, verified, and covered by a test suite that includes a time-travel test and a regression test for the rounding bug. He spent nothing on infrastructure beyond a free testnet faucet. He did not skip the audit because AI replaced it — he skipped it because the contract was small, the value at stake was zero on a testnet, and he now understood every line well enough to explain it to a stranger.

Stage Tool Outcome
Specification Prompt 1 Centralization risk caught before coding
Draft Prompt 2 Compilable contract using audited libraries
Security review Prompt 3 One Medium-severity rounding issue found
Testing Prompt 4 Regression test for that issue
Gas Prompt 5 Measured, not guessed, savings
Dependencies Prompt 6 v4/v5 API mismatch caught
Operations Prompt 7 Threat model for keys and multisig
Deployment Prompt 8 No secrets in the repository
Communication Prompt 9 README a human can read
Final gate Prompt 10 Checklist with one row AI cannot fill

The pattern that made this work is not any single prompt. It is the sequence: spec before code, adversarial review before tests, tests before optimization, and a human before mainnet. AI compresses each step from hours to minutes. It does not remove the steps, and the moment you treat it as a replacement for the last one, you are the person who loses money.

If you want to try this yourself, start with Prompt 1 tonight. Pick something small — a capped token, a simple escrow, a voting contract. Read every line the model gives you. Run the tests. Break the contract on purpose and watch the tests fail. The first time you catch a bug the AI introduced, you will stop trusting it blindly, and that is exactly when it becomes genuinely useful.

For the primary sources worth bookmarking before you begin: the Solidity documentation at docs.soliditylang.org, the OpenZeppelin Contracts documentation and their GitHub Security Advisories page, the Hardhat documentation at hardhat.org, and the Ethereum Foundation's security resources at ethereum.org/en/developers/docs/smart-contracts/security. Everything else is commentary.

← All posts

Comments