Claude
Skills
Sign in
โ† Back

ethereum-wingman

Included with Lifetime
$97 forever

Ethereum development tutor and builder for Scaffold-ETH 2 projects. Triggers on "build", "create", "dApp", "smart contract", "Solidity", "DeFi", "Ethereum", "web3", or any blockchain development task. ALWAYS uses fork mode to test against real protocol state.

Productivityscripts

What this skill does


# Ethereum Wingman

Comprehensive Ethereum development guide for AI agents. Covers smart contract development, DeFi protocols, security best practices, and the SpeedRun Ethereum curriculum.

---

## AI AGENT INSTRUCTIONS - READ THIS FIRST

### ๐Ÿšซ CRITICAL: External Contracts & Scaffold Hooks

**These rules are MANDATORY. Violations cause real bugs in production.**

1. **ALL CONTRACTS IN externalContracts.ts** โ€” Any contract you want to interact with (tokens, protocols, etc.) MUST be added to `packages/nextjs/contracts/externalContracts.ts` with its address and ABI. Read the file first โ€” the pattern is self-evident.

2. **SCAFFOLD HOOKS ONLY โ€” NEVER RAW WAGMI** โ€” Always use `useScaffoldReadContract` and `useScaffoldWriteContract`, NEVER raw wagmi hooks like `useWriteContract` or `useReadContract`. 

**Why this matters:** Scaffold hooks use `useTransactor` which **waits for transaction confirmation** (not just wallet signing). Raw wagmi's `writeContractAsync` resolves the moment the user signs in MetaMask โ€” BEFORE the tx is mined. This causes buttons to re-enable while transactions are still pending.

```typescript
// โŒ WRONG: Raw wagmi - resolves after signing, not confirmation
const { writeContractAsync } = useWriteContract();
await writeContractAsync({...}); // Returns immediately after MetaMask signs!

// โœ… CORRECT: Scaffold hooks - waits for tx to be mined
const { writeContractAsync } = useScaffoldWriteContract("MyContract");
await writeContractAsync({...}); // Waits for actual on-chain confirmation
```

### ๐Ÿšจ BEFORE ANY TOKEN/APPROVAL/SECURITY CODE CHANGE
**STOP. Re-read the "Critical Gotchas" section below before writing or modifying ANY code that touches:**
- Token approvals (`approve`, `allowance`, `transferFrom`)
- Token transfers (`transfer`, `safeTransfer`, `safeTransferFrom`)
- Access control or permissions
- Price calculations or oracle usage
- Vault deposits/withdrawals

**This is not optional.** The gotchas section exists because these are the exact mistakes that lose real money. Every time you think "I'll just quickly fix this" is exactly when you need to re-read it.

---

## ๐Ÿšจ FRONTEND UX RULES (MANDATORY)

**These are HARD RULES, not suggestions. A build is NOT done until all of these are satisfied.**
**These rules have been learned the hard way. Do not skip them.**

### Rule 1: Every Onchain Button โ€” Loader + Disable

ANY button that triggers a blockchain transaction MUST:
1. **Disable immediately** on click
2. **Show a loader/spinner** ("Approving...", "Staking...", etc.)
3. **Stay disabled** until the state updates confirm the action completed
4. **Show success/error feedback** when done

```typescript
// โœ… CORRECT: Separate loading state PER ACTION
const [isApproving, setIsApproving] = useState(false);
const [isStaking, setIsStaking] = useState(false);

<button
  disabled={isApproving}
  onClick={async () => {
    setIsApproving(true);
    try {
      await writeContractAsync({ functionName: "approve", args: [...] });
    } catch (e) {
      console.error(e);
      notification.error("Approval failed");
    } finally {
      setIsApproving(false);
    }
  }}
>
  {isApproving ? "Approving..." : "Approve"}
</button>
```

**โŒ NEVER use a single shared `isLoading` for multiple buttons.** Each button gets its own loading state. A shared state causes the WRONG loading text to appear when UI conditionally switches between buttons.

### Rule 2: Three-Button Flow โ€” Network โ†’ Approve โ†’ Action

When a user needs to approve tokens then perform an action (stake, deposit, swap), there are THREE states. Show exactly ONE button at a time:

```
1. Wrong network?       โ†’ "Switch to Base" button
2. Not enough approved? โ†’ "Approve" button
3. Enough approved?     โ†’ "Stake" / "Deposit" / action button
```

```typescript
// ALWAYS read allowance with a hook (auto-updates when tx confirms)
const { data: allowance } = useScaffoldReadContract({
  contractName: "Token",
  functionName: "allowance",
  args: [address, contractAddress],
});

const needsApproval = !allowance || allowance < amount;
const wrongNetwork = chain?.id !== targetChainId;

{wrongNetwork ? (
  <button onClick={switchNetwork} disabled={isSwitching}>
    {isSwitching ? "Switching..." : "Switch to Base"}
  </button>
) : needsApproval ? (
  <button onClick={handleApprove} disabled={isApproving}>
    {isApproving ? "Approving..." : "Approve $TOKEN"}
  </button>
) : (
  <button onClick={handleStake} disabled={isStaking}>
    {isStaking ? "Staking..." : "Stake"}
  </button>
)}
```

**Critical:** Always read allowance via a hook so UI updates automatically. Never rely on local state alone. If the user clicks Approve while on the wrong network, EVERYTHING BREAKS โ€” that's why wrong network check comes FIRST.

### Rule 3: Address Display โ€” Always `<Address/>`

**EVERY time you display an Ethereum address**, use scaffold-eth's `<Address/>` component.

```typescript
// โœ… CORRECT
import { Address } from "~~/components/scaffold-eth";
<Address address={userAddress} />

// โŒ WRONG โ€” never render raw hex
<span>{userAddress}</span>
<p>0x1234...5678</p>
```

`<Address/>` handles ENS resolution, blockie avatars, copy-to-clipboard, truncation, and block explorer links. Raw hex is unacceptable.

### Rule 3b: Address Input โ€” Always `<AddressInput/>`

**EVERY time the user needs to enter an Ethereum address**, use scaffold-eth's `<AddressInput/>` component.

```typescript
// โœ… CORRECT
import { AddressInput } from "~~/components/scaffold-eth";
<AddressInput value={recipient} onChange={setRecipient} placeholder="Recipient address" />

// โŒ WRONG โ€” never use a raw text input for addresses
<input type="text" value={recipient} onChange={e => setRecipient(e.target.value)} />
```

`<AddressInput/>` provides ENS resolution (type "vitalik.eth" โ†’ resolves to address), blockie avatar preview, validation, and paste handling. A raw input gives none of this.

**The pair: `<Address/>` for DISPLAY, `<AddressInput/>` for INPUT. Always.**

### Rule 3c: USD Values โ€” Show Dollar Amounts Everywhere

**EVERY token or ETH amount displayed should include its USD value.**
**EVERY token or ETH input should show a live USD preview.**

```typescript
// โœ… CORRECT โ€” Display with USD
<span>1,000 TOKEN (~$4.20)</span>
<span>0.5 ETH (~$1,250.00)</span>

// โœ… CORRECT โ€” Input with live USD preview
<input value={amount} onChange={...} />
<span className="text-sm text-gray-500">
  โ‰ˆ ${(parseFloat(amount || "0") * tokenPrice).toFixed(2)} USD
</span>

// โŒ WRONG โ€” Amount with no USD context
<span>1,000 TOKEN</span>  // User has no idea what this is worth
```

**Where to get prices:**
- **ETH price:** SE2 has a built-in hook โ€” `useNativeCurrencyPrice()` or check the price display component in the bottom-left footer. It reads from mainnet Uniswap V2 WETH/DAI pool.
- **Custom tokens:** Use DexScreener API (`https://api.dexscreener.com/latest/dex/tokens/TOKEN_ADDRESS`), on-chain Uniswap quoter, or Chainlink oracle if available.

**This applies to both display AND input:**
- Displaying a balance? Show USD next to it.
- User entering an amount to send/stake/swap? Show live USD preview below the input.
- Transaction confirmation? Show USD value of what they're about to do.

### Rule 3d: No Duplicate Titles โ€” Header IS the Title

**DO NOT put the app name as an `<h1>` at the top of the page body.** The header already displays the app name. Repeating it wastes space and looks amateur.

```typescript
// โŒ WRONG โ€” AI agents ALWAYS do this
<Header />  {/* Already shows "๐Ÿฆž $TOKEN Hub" */}
<main>
  <h1>๐Ÿฆž $TOKEN Hub</h1>  {/* DUPLICATE! Delete this. */}
  <p>Buy, send, and track TOKEN on Base</p>
  ...
</main>

// โœ… CORRECT โ€” Jump straight into content
<Header />  {/* Shows the app name */}
<main>
  <div className="grid grid-cols-2 gap-4">
    {/* Stats, balances, actions โ€” no redundant title */}
  </div>
</main>
```

**The SE2 header component already handles the app title.** Your page content should start with the actual UI โ€” stats, forms, data โ€” not repe

Related in Productivity