orchestration

star 0

Three-phase build system for Scaffold-ETH 2 dApps. Localhost to testnet to production. When to use Scaffold hooks vs raw wagmi, auto-generated types, and common agent mistakes.

andginja By andginja schedule Updated 3/6/2026

name: orchestration description: Three-phase build system for Scaffold-ETH 2 dApps. Localhost to testnet to production. When to use Scaffold hooks vs raw wagmi, auto-generated types, and common agent mistakes. metadata: author: Andre Ginja version: 1.0.0

Orchestration: Three-Phase Build System

You are building a dApp with Scaffold-ETH 2. There are exactly three phases. You do not skip phases. You do not deploy to mainnet from localhost. You follow the pipeline.

What You Probably Got Wrong

  1. You used raw wagmi hooks. useWriteContract from wagmi resolves its promise when the transaction is SUBMITTED, not when it is CONFIRMED. Your UI says "Success!" while the tx is still pending. Scaffold-ETH 2 hooks (useScaffoldWriteContract, useScaffoldReadContract) wait for confirmation and handle errors. Use them.

  2. You deployed contracts before the UI worked locally. Now you are debugging contract logic AND network issues AND gas problems simultaneously. Phase 1 exists to isolate contract+UI bugs from deployment bugs.

  3. You hardcoded contract addresses. Scaffold-ETH 2 auto-generates deployment files with addresses and ABIs. If you are manually pasting addresses into your frontend, you are fighting the framework.

  4. You committed your deployer private key. It is now on GitHub forever. You need to rotate that key immediately and use environment variables going forward.

  5. You ran yarn chain instead of yarn fork. Without a fork, your local chain has no USDC, no Uniswap, no Aave, no real token balances. If your dApp interacts with ANY existing protocol, you need a fork.


Phase 1: Localhost (Contracts + UI on Anvil)

Goal: Get everything working locally. Zero gas costs. Instant feedback. Fix all logic bugs here.

Start the Local Chain

For a standalone dApp with no external protocol dependencies:

yarn chain

For any dApp that interacts with existing protocols (Uniswap, Aave, Chainlink, any ERC-20):

yarn fork --network mainnet
# or
yarn fork --network base
# or
yarn fork --network optimism

This starts Anvil with a fork of the target chain. All protocol state is available locally. You get 10 pre-funded accounts with 10,000 ETH each.

Deploy Contracts Locally

yarn deploy

This runs your deploy scripts in packages/hardhat/deploy/ (or packages/foundry/script/). It generates:

  • deployedContracts.ts — auto-generated TypeScript with addresses and ABIs
  • This file is imported by Scaffold hooks automatically

Never manually edit deployedContracts.ts. It is regenerated on every deploy.

Start the Frontend

yarn start

Frontend runs at http://localhost:3000. Hot reload is enabled. Changes to contracts require re-deploy (yarn deploy) but the frontend picks up new ABIs automatically.

Use Scaffold Hooks, Not Raw Wagmi

This is the single most important rule in this entire skill.

WRONG — raw wagmi:

import { useWriteContract } from "wagmi";

const { writeContract } = useWriteContract();

const handleClick = async () => {
  // THIS RESOLVES WHEN TX IS SUBMITTED, NOT CONFIRMED
  // Your UI will show success while the tx might still revert
  await writeContract({
    address: "0x...", // hardcoded — breaks on redeploy
    abi: [...],       // manually pasted — goes stale
    functionName: "mint",
    args: [amount],
  });
  setSuccess(true); // WRONG — tx might not be confirmed yet
};

RIGHT — Scaffold hooks:

import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";

const { writeContractAsync, isMining } = useScaffoldWriteContract("MyToken");

const handleClick = async () => {
  // THIS RESOLVES WHEN TX IS CONFIRMED
  // Address and ABI come from auto-generated deployedContracts
  await writeContractAsync({
    functionName: "mint",
    args: [amount],
  });
  // Safe to update UI — tx is confirmed
  setSuccess(true);
};

Reading contract data:

// WRONG
const { data } = useReadContract({
  address: "0x...",
  abi: [...],
  functionName: "balanceOf",
  args: [userAddress],
});

// RIGHT
const { data: balance } = useScaffoldReadContract({
  contractName: "MyToken",
  functionName: "balanceOf",
  args: [userAddress],
});

Writing with value (sending ETH):

const { writeContractAsync } = useScaffoldWriteContract("MyContract");

await writeContractAsync({
  functionName: "deposit",
  value: parseEther("0.1"),
});

Watching events:

import { useScaffoldEventHistory } from "~~/hooks/scaffold-eth";

const { data: events } = useScaffoldEventHistory({
  contractName: "MyToken",
  eventName: "Transfer",
  fromBlock: 0n,
  filters: { to: userAddress },
});

Auto-Generated Types

When you deploy, Scaffold-ETH 2 generates full TypeScript types for your contracts. This means:

  • functionName is autocompleted — typos are caught at compile time
  • args are type-checked — wrong argument types are caught at compile time
  • contractName must match a deployed contract — non-existent contracts are caught at compile time

If you bypass Scaffold hooks and use raw wagmi, you lose ALL of this type safety.

Phase 1 Checklist

  • Local chain running (forked if needed)
  • All contracts deployed locally
  • Every contract function callable from the UI
  • Every read function displays data correctly
  • Error states handled (reverts show user-friendly messages)
  • No hardcoded addresses in frontend code
  • No raw wagmi hooks — only Scaffold hooks
  • Hot reload works — change contract, redeploy, UI updates

Phase 2: Live Testnet / Mainnet Contracts, Local UI

Goal: Deploy contracts to a real chain. Test with real gas. Verify on block explorer. Frontend still runs locally for fast iteration.

Configure the Target Chain

In packages/hardhat/hardhat.config.ts:

const config: HardhatUserConfig = {
  networks: {
    base: {
      url: process.env.BASE_RPC_URL || "https://mainnet.base.org",
      accounts: [process.env.DEPLOYER_PRIVATE_KEY!],
    },
  },
};

CRITICAL: Use environment variables for private keys and RPC URLs.

# .env (NEVER commit this file)
DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_KEY

Verify .gitignore includes:

.env
.env.local
.env.production

Deploy to Target Chain

yarn deploy --network base

This updates deployedContracts.ts with the new addresses on the target chain. The frontend automatically switches to using these addresses when you connect to the correct network.

Verify Contracts on Block Explorer

yarn verify --network base

This submits your source code to Basescan (or Etherscan, etc.). Verified contracts show the "Contract" tab with readable source code. Users can interact directly through the explorer.

If automatic verification fails:

npx hardhat verify --network base CONTRACT_ADDRESS "constructor_arg_1" "constructor_arg_2"

Test with Real Gas

Connect your wallet to the target chain. Execute every flow:

  1. Connect wallet
  2. Switch to correct network (if needed)
  3. Approve tokens (if needed)
  4. Execute transaction
  5. Wait for confirmation
  6. Verify state changed

Pay attention to:

  • Gas estimation: Does the UI show reasonable gas costs?
  • Transaction speed: How long does confirmation take?
  • Error messages: Do reverts show useful information?
  • Token approvals: Does the approval flow work correctly?

Phase 2 Checklist

  • Contracts deployed to target chain
  • Contracts verified on block explorer
  • deployedContracts.ts updated with live addresses
  • Every flow tested with real gas
  • Approval flows work correctly
  • Error messages are user-friendly
  • No secrets in committed code
  • Gas costs are reasonable

Phase 3: Production Deployment

Goal: Ship the frontend. Real users can access your dApp.

Option A: IPFS Deployment (Censorship-Resistant)

See the frontend-playbook skill for detailed IPFS deployment instructions. The critical points:

# Clean build is MANDATORY
rm -rf packages/nextjs/.next packages/nextjs/out

# Build for static export
yarn build

# Deploy to IPFS via BuidlGuidl
yarn ipfs

CRITICAL: Set trailingSlash: true in next.config.js or every route except / returns 404 on IPFS.

Option B: Vercel Deployment

# From the monorepo root
vercel --prod

Configure in Vercel dashboard:

  • Root Directory: packages/nextjs
  • Framework Preset: Next.js
  • Environment Variables: Add all required env vars

Option C: Custom Server

yarn build
yarn start

Run behind nginx or similar reverse proxy with SSL.

Post-Deploy Verification

After deploying the frontend:

  1. Open in incognito — no cached state, no connected wallet
  2. Connect a fresh wallet — test the first-time user experience
  3. Execute every flow — don't assume it works because it worked locally
  4. Check mobile — responsive design, wallet connect on mobile
  5. Monitor transactions — watch for failed txs in the first hour

Phase 3 Checklist

  • Frontend deployed and accessible
  • All routes load correctly (not just /)
  • Wallet connect works
  • Network switching works
  • All transactions execute successfully
  • Mobile responsive
  • No console errors in production
  • Default Scaffold-ETH branding removed
  • Monitoring in place (at minimum, watch the contract on a block explorer)

Common Mistakes Agents Make

Mistake 1: Skipping Phase 1

Agent deploys directly to testnet. Every contract change costs gas and takes 15+ seconds to confirm. Debugging is 10x slower.

Fix: Always start with yarn chain or yarn fork. Get everything working locally first.

Mistake 2: Using useWriteContract Instead of useScaffoldWriteContract

The raw wagmi hook resolves on tx submission. The Scaffold hook resolves on tx confirmation. This difference causes:

  • "Success" toasts before the tx is actually mined
  • UI state updates that get rolled back on revert
  • Missing error handling for reverted transactions

Fix: Search your codebase for useWriteContract, useReadContract, useContractRead, useContractWrite. Replace ALL of them with Scaffold equivalents.

Mistake 3: Hardcoding ABIs

Agent copies the ABI from Etherscan and pastes it into the frontend. When the contract is redeployed, the ABI goes stale.

Fix: The ABI lives in deployedContracts.ts. Scaffold hooks read it automatically. You never need to manually handle ABIs.

Mistake 4: Committing Secrets

# Check if secrets are in git history
git log --all --oneline -S "PRIVATE_KEY" -- "*.ts" "*.tsx" "*.js" "*.env"

If this returns results, the key is compromised. Rotate it immediately. Even if you delete the file, the key lives in git history forever.

Fix: Use .env files. Add them to .gitignore BEFORE creating them. Use process.env.VARIABLE_NAME in config files.

Mistake 5: Not Verifying Contracts

Unverified contracts are black boxes on block explorers. Users cannot read the code. This destroys trust.

Fix: Run yarn verify --network <chain> immediately after deployment. If it fails, debug until it passes.

Mistake 6: Deploying Frontend Before Testing on Target Chain

Agent deploys contracts to mainnet, deploys frontend to Vercel, and THEN discovers the approval flow is broken with real tokens.

Fix: Phase 2 exists specifically to catch these issues. Test every flow with real gas before deploying the frontend.


Quick Reference: Scaffold-ETH 2 Hooks

Task Hook Key Prop
Read contract useScaffoldReadContract contractName, functionName, args
Write contract useScaffoldWriteContract Returns writeContractAsync, isMining
Send ETH with call useScaffoldWriteContract Add value: parseEther("0.1")
Watch events useScaffoldEventHistory eventName, fromBlock, filters
Contract address useDeployedContractInfo Returns data.address, data.abi
Network info useTargetNetwork Returns targetNetwork with chain config

Directory Structure Reference

packages/
├── hardhat/              # or foundry/
│   ├── contracts/        # Your Solidity contracts
│   ├── deploy/           # Deploy scripts (numbered: 00_deploy_X.ts)
│   └── hardhat.config.ts # Network configuration
├── nextjs/
│   ├── app/              # Next.js app router pages
│   ├── components/       # React components
│   ├── hooks/            # Custom hooks (Scaffold hooks are here)
│   ├── contracts/
│   │   └── deployedContracts.ts  # AUTO-GENERATED — do not edit
│   └── scaffold.config.ts        # Chain selection, polling intervals
└── package.json          # Monorepo root

The three phases are not optional. They are the difference between a dApp that works and one that fails in production. Follow the pipeline.

Install via CLI
npx skills add https://github.com/andginja/ethskills --skill orchestration
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator