Production QA checklist for Ethereum dApps. PASS/FAIL format for a separate reviewer agent. Covers approval flows, wallet connection, branding, USD values, decimals, error states, mobile, secrets, and gas.
You are a reviewer agent. You did NOT build this dApp. Your job is to inspect the deployed frontend and codebase, run through this checklist, and report findings. You do NOT fix anything. You report PASS or FAIL with evidence.
You started fixing bugs. Stop. You are a reviewer, not a builder. Your job is to find problems and report them. If you start fixing things, you lose objectivity and introduce new bugs.
You only tested the happy path. You connected a wallet, clicked one button, saw a success message, and reported "all good." You did not test what happens when the user rejects a transaction, runs out of gas, or visits on mobile.
You did not check the codebase. You only looked at the running app. The codebase contains secrets, hardcoded values, and raw wagmi hooks that you cannot see from the UI alone.
You skipped mobile. Over 40% of dApp users are on mobile. If you did not test mobile, you reviewed half the app.
For each item:
Report format:
## QA Report: [dApp Name]
Date: [date]
Reviewer: [agent name]
Deployment URL: [url]
Repository: [repo url]
### Results Summary
- PASS: X
- FAIL: Y
- N/A: Z
### Detailed Findings
[Each item below with PASS/FAIL/N/A and evidence]
Requirement: Every button that triggers an onchain transaction has its OWN independent loading/disabled state.
How to test:
What to look for in code:
# Search for shared isLoading state — this is a FAIL
grep -r "isLoading" packages/nextjs/
# Each onchain button should use its own isMining from useScaffoldWriteContract
PASS criteria: Each button has independent state. Clicking "Deposit" does not disable "Withdraw."
FAIL criteria: Multiple buttons share a single isLoading variable, OR a button can be clicked multiple times while a transaction is pending.
Requirement: After clicking a transaction button, it must be immediately disabled until the transaction completes or fails.
How to test:
PASS criteria: Only one wallet prompt appears regardless of click speed.
FAIL criteria: Multiple wallet prompts appear, or multiple transactions are submitted.
Requirement: Token interactions follow the three-step flow: Switch Network (if needed) -> Approve Token -> Execute Transaction. Only ONE button is visible at each step.
How to test:
What to look for in code:
# Search for approval logic
grep -rn "approve\|allowance" packages/nextjs/
# Verify conditional rendering (if/else, not three buttons side by side)
PASS criteria: One button visible at each step, clear labels indicating the action.
FAIL criteria: Multiple action buttons visible simultaneously, OR no approval step for ERC-20 transfers, OR generic "Submit" labels without token name/amount.
Requirement: A user with no prior connection to this dApp can connect their wallet and interact.
How to test:
PASS criteria: Clean connection flow, address displayed after connecting.
FAIL criteria: Connection fails, UI does not update, or error appears.
Requirement: If the user is on the wrong network, the dApp prompts them to switch.
How to test:
PASS criteria: Clear prompt to switch networks, switching works when clicked.
FAIL criteria: Transaction is attempted on wrong network, cryptic error, or no network prompt.
Requirement: Disconnecting and reconnecting works without page refresh.
How to test:
PASS criteria: Full functionality after reconnect without refresh.
FAIL criteria: Stale state, errors, or need to refresh the page.
Requirement: The dApp should NOT look like a default Scaffold-ETH 2 app. Custom branding should be applied.
What to check:
What to look for in code:
# Check page title
grep -r "Scaffold-ETH\|scaffold-eth\|SE-2" packages/nextjs/app/layout.tsx
# Check meta tags
grep -r "og:title\|og:description\|og:image" packages/nextjs/
# Check for debug page still accessible in production
ls packages/nextjs/app/debug/
PASS criteria: App has its own identity — title, favicon, branding, and content.
FAIL criteria: Any default Scaffold-ETH branding visible in production.
Requirement: The Debug Contracts page (/debug) should not be accessible in production.
How to test: Navigate to /debug in the production deployment.
PASS criteria: Page returns 404 or redirects, or is behind authentication.
FAIL criteria: Debug page is publicly accessible with contract interaction tools.
Requirement: Every token amount in the UI has a USD equivalent displayed alongside it.
What to check:
PASS criteria: USD values present next to all token amounts.
FAIL criteria: Any token amount displayed without USD equivalent.
Requirement: Token amounts are displayed with correct decimal precision.
What to check:
How to test:
What to look for in code:
# Search for hardcoded 18 decimals (common mistake for USDC/USDT)
grep -rn "parseEther\|formatEther" packages/nextjs/
# USDC/USDT should use parseUnits(amount, 6) / formatUnits(amount, 6)
PASS criteria: All token amounts display correctly with appropriate precision.
FAIL criteria: Token amounts off by orders of magnitude (USDC showing as 0.000001 instead of 1.0), or too many/few decimal places.
Requirement: Large numbers are human-readable with comma separators or abbreviations.
What to check:
1000000 should display as 1,000,000 or 1M0.000000001 should NOT be displayed — show < 0.0001 or similarPASS criteria: All numbers are formatted for readability.
FAIL criteria: Raw unformatted numbers displayed.
Requirement: All Ethereum addresses use proper display components with ENS resolution.
What to check:
What to look for in code:
# Search for raw address rendering
grep -rn "address.*0x\|{address}" packages/nextjs/components/
# Should be using <Address /> component
grep -rn "from.*scaffold-eth.*Address\|<Address" packages/nextjs/
PASS criteria: All addresses use <Address /> or equivalent component.
FAIL criteria: Any raw hex address displayed without truncation, copy, or explorer link.
Requirement: When a user rejects a transaction in their wallet, the UI handles it gracefully.
How to test:
PASS criteria: Friendly message, no error state, button returns to clickable.
FAIL criteria: Error toast with technical message, button stays in loading state, or unhandled promise rejection in console.
Requirement: When a user does not have enough ETH for gas, the error message is clear.
How to test: Attempt a transaction with a wallet that has near-zero ETH balance.
PASS criteria: Message like "Insufficient ETH for gas" or similar human-readable text.
FAIL criteria: Raw error message like "insufficient funds for intrinsic transaction cost."
Requirement: When a contract call reverts, the revert reason is shown in human-readable form.
How to test: Trigger a known revert condition (e.g., try to withdraw more than your balance).
PASS criteria: Clear message explaining why the transaction failed.
FAIL criteria: Generic "Transaction failed" with no context, or raw hex revert data.
Requirement: If the RPC endpoint is unreachable, the UI handles it gracefully.
How to test: Disconnect internet briefly, or check what happens with a slow connection.
PASS criteria: Error message about connectivity, retry option.
FAIL criteria: Blank page, infinite spinner, or unhandled exception.
Requirement: The dApp is fully functional on mobile viewport sizes.
How to test:
PASS criteria: Full functionality, no layout breaks, readable text.
FAIL criteria: Horizontal scroll, overlapping elements, text too small, buttons too small to tap.
Requirement: Wallet connection works on mobile devices.
How to test: Open the dApp in a mobile wallet's in-app browser (MetaMask mobile, Coinbase Wallet mobile).
PASS criteria: Wallet connects, transactions can be signed.
FAIL criteria: Connection fails, wallet not detected, or signing does not work.
Requirement: Long strings (addresses, large numbers) do not break the layout.
How to test: Check all address displays and number displays at mobile width.
PASS criteria: Addresses truncated, numbers formatted, no overflow.
FAIL criteria: Any text overflows its container or causes horizontal scroll.
Requirement: No private keys, API secrets, or sensitive credentials in the committed codebase.
How to test:
# Search for potential secrets
grep -rn "PRIVATE_KEY\|private_key\|secret\|password" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.json" .
grep -rn "0x[a-fA-F0-9]{64}" --include="*.ts" --include="*.tsx" . | grep -v "node_modules\|deployedContracts"
# Check .env files are not committed
git ls-files | grep -i "\.env"
# Check .gitignore
cat .gitignore | grep -i "env"
PASS criteria: No secrets found, .env in .gitignore, no private keys in git history.
FAIL criteria: Any secret, private key, or API key with embedded credentials found in code or git history.
Requirement: No RPC URLs with embedded API keys in client-side code.
How to test:
grep -rn "alchemy.com\|infura.io\|quicknode.com" --include="*.ts" --include="*.tsx" packages/nextjs/
# Should only find references via environment variables, never hardcoded keys
PASS criteria: RPC URLs use environment variables or public endpoints.
FAIL criteria: API keys visible in source code.
Requirement: Admin or owner-only functions are not callable from the production UI by regular users.
How to test: Check if there are admin panels or owner functions accessible without authentication.
PASS criteria: Admin functions are hidden or access-controlled.
FAIL criteria: Admin functions (pause, withdraw, setPrice, etc.) accessible to any connected wallet.
Requirement: Gas estimates shown to users are reasonable for the operations performed.
How to test: Compare estimated gas with actual gas used for completed transactions.
PASS criteria: Estimates within 20% of actual gas used.
FAIL criteria: Estimates wildly off (2x+ or showing unreasonably high gas).
Requirement: Users see clear feedback for transaction lifecycle: pending -> confirmed or failed.
How to test:
PASS criteria: Clear lifecycle feedback at each stage.
FAIL criteria: No pending state, or success shown before confirmation, or no failure feedback.
Requirement: Every writeContractAsync, fetch, or external API call is wrapped in try/catch.
How to test:
# Search for unhandled async calls
grep -rn "writeContractAsync\|writeContract(" packages/nextjs/ | grep -v "try\|catch\|\.then"
grep -rn "await fetch(" packages/nextjs/ | grep -v "try\|catch"
PASS criteria: All external calls wrapped in error handling.
FAIL criteria: Any await call to an external service without try/catch.
Requirement: No errors or warnings in the browser console during normal operation.
How to test: Open browser devtools console, navigate through all pages, perform all actions.
PASS criteria: No errors. Warnings acceptable only if from third-party libraries.
FAIL criteria: Any error in console during normal operation.
Requirement: No broken links in the application.
How to test: Click every link in the navigation, footer, and content.
PASS criteria: All links resolve correctly.
FAIL criteria: Any 404 or broken link.
Requirement: Pages show loading indicators while fetching initial data, not blank/empty content.
How to test: Hard refresh (Ctrl+Shift+R) and watch the page load.
PASS criteria: Skeleton screens, spinners, or loading text while data loads.
FAIL criteria: Blank sections that suddenly populate, causing layout shift.
Copy this template for your QA report:
## QA Report: [dApp Name]
**Date**: [date]
**Reviewer**: [agent name]
**Deployment URL**: [url]
**Repository**: [repo url]
**Chain**: [chain name and ID]
### Summary
| Category | PASS | FAIL | N/A |
|----------|------|------|-----|
| Button States | | | |
| Wallet Connection | | | |
| Branding | | | |
| Data Display | | | |
| Error Handling | | | |
| Mobile | | | |
| Security | | | |
| Gas & Transactions | | | |
| Final Checks | | | |
| **TOTAL** | | | |
### Critical Failures (must fix before ship)
1. [Finding]
2. [Finding]
### Medium Findings (should fix)
1. [Finding]
2. [Finding]
### Minor Findings (nice to fix)
1. [Finding]
2. [Finding]
### Detailed Results
[Each checklist item with PASS/FAIL/N/A and evidence]
Your job is to fill this out honestly. Do not soft-pedal failures. A FAIL is a FAIL — note exactly what is wrong and where. The build agent will use your report to fix issues.