diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index f1760a2..48a3a41 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -31,7 +31,7 @@ By checking the box below, I agree and represent on behalf of the protocol/entit
- the plugin and the underlying protocol comply with all applicable laws, and my API enforces the same geofencing and eligibility restrictions as my own user-facing app;
- the plugin is in full compliance with our protocol's/entity's then-current terms and conditions, and if not, submission and use thereafter is considered a formal written exemption to such terms and conditions;
- the plugin accurately describes its behavior and contains no hidden, deceptive, or malicious instructions, and my API will not return malicious or unexpected calldata;
-- the protocol/entity is solely responsible for the plugin and is submitting a plugin is subject to the [Base Account and Base App Terms of Service](https://wallet.coinbase.com/terms-of-service) as using a "Service"; and
+- the protocol/entity is solely responsible for the plugin, and submitting a plugin is subject to the [applicable terms of service](https://wallet.coinbase.com/terms-of-service) as using a "Service"; and
- Base may modify, decline to list, or remove the plugin at any time, for any reason.
- [ ] I have read and agree to the Base MCP Plugin Submission Agreement above.
diff --git a/README.md b/README.md
index df43619..f25ff9e 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@

-[Agent Skills](https://agentskills.io) for building on [Base](https://base.org). These skills enable AI agents to connect to Base, deploy contracts, integrate wallets, run nodes, and more.
+[Agent Skills](https://agentskills.io) for building on [Base](https://base.org). These skills enable AI agents to connect to Base, deploy contracts, register agents, run nodes, and more.
@@ -29,7 +29,7 @@ Consolidated skills that cover the most common use cases. Each uses progressive
| Skill | Install | Description |
| ----- | ------- | ----------- |
-| [build-on-base](./skills/build-on-base/SKILL.md) | `npx skills add base/skills --skill build-on-base` | Complete Base development playbook: network, contracts, wallet auth, payments, attribution, and migrations. Consolidates all individual skills into one. |
+| [build-on-base](./skills/build-on-base/SKILL.md) | `npx skills add base/skills --skill build-on-base` | Complete Base development playbook: network, contracts, attribution, agent registration, and nodes. Consolidates all individual skills into one. |
| [base-mcp](./skills/base-mcp/SKILL.md) | `npx skills add base/skills --skill base-mcp` | Base MCP server — gives your AI assistant a wallet via mcp.base.org. Sending, swapping, signing, batched calls, balances, and partner plugins for lending, swaps, and more. |
| [vibenet](./skills/vibenet/SKILL.md) | `npx skills add base/skills --skill vibenet` | Build on [vibenet](https://chain.base.org/vibenet), the Base Vibes devnet for native account abstraction (EIP-8130) and Cobalt 200ms native blocks, with viem: smart accounts, batched calls, session keys and policies, ERC-8168 payer gas sponsorship, and millisecond block timestamps. |
@@ -55,14 +55,6 @@ Deploy my contract to Base Sepolia
How do I connect to Base mainnet?
```
-```text
-Add Sign in with Base to my app
-```
-
-```text
-Convert my existing Farcaster miniapp to a standard app on Base
-```
-
```text
Register my trading bot and add builder code attribution to its transactions
```
diff --git a/skills/build-on-base/SKILL.md b/skills/build-on-base/SKILL.md
index 526a191..bfd22de 100644
--- a/skills/build-on-base/SKILL.md
+++ b/skills/build-on-base/SKILL.md
@@ -6,20 +6,15 @@ description: >
forge create, BaseScan verification, CDP faucet, testnet ETH, deploy contract to Base;
(3) Builder Codes — ERC-8021 attribution suffix, referral fees, dataSuffix for Wagmi/Viem/Privy/
ethers.js/window.ethereum, transaction attribution, earn referral fees, append builder code;
- (4) Base Account SDK — Sign in with Base (SIWB), Base Pay, USDC payments, paymasters, gas
- sponsorship, sub-accounts, spend permissions, prolinks, batch transactions, smart wallet,
- payment link, recurring subscription; (5) Agent registration — trading bots, AI agents, automated
+ (4) Agent registration — trading bots, AI agents, automated
senders, ERC-8021 attribution wiring, base.dev API, register agent, builder code registration;
- (6) Node operation — run Base node, Reth setup, hardware requirements, self-hosted RPC, sync;
- (7) Migrations — migrate OnchainKit, OnchainKitProvider to WagmiProvider, wagmi migration,
- remove onchainkit dependency, MiniKit to Farcaster SDK, convert miniapp, Farcaster miniapp to
- regular app, convert Farcaster miniapp.
+ (5) Node operation — run Base node, Reth setup, hardware requirements, self-hosted RPC, sync.
---
# Base Development
-Complete playbook for building on Base L2 — network setup, smart contracts, wallet auth, payments,
-developer tool attribution, and framework migrations.
+Complete playbook for building on Base L2 — network setup, smart contracts, developer tool
+attribution, agent registration, and node operation.
## Default Stack
@@ -27,8 +22,6 @@ developer tool attribution, and framework migrations.
|-------|---------|
| Network | Base Mainnet (8453) / Base Sepolia testnet (84532) |
| Contracts | Foundry (`forge create` + BaseScan verification) |
-| Wallet auth | Base Account SDK (`@base-org/account`) |
-| Payments | Base Pay — USDC, gasless, settles in <2s |
| Transactions | wagmi + viem |
| Attribution | Builder Codes — ERC-8021 via `ox/erc8021` |
| RPC (prod) | Dedicated node provider or self-hosted Reth |
@@ -37,10 +30,8 @@ developer tool attribution, and framework migrations.
- **Never commit private keys** — use `cast wallet import` for Foundry keystores
- **Never expose RPC API keys or CDP credentials client-side** — proxy through backend
-- **Never skip server-side payment verification** — always call `getPaymentStatus()` server-side and verify `sender`, `amount`, `recipient`; track processed tx IDs to prevent replay attacks
- **Never send transactions without Builder Code attribution** — silent data loss, no errors, no warnings
- **Validate all user-provided shell inputs** before constructing forge/cast commands (no spaces, semicolons, pipes)
-- **COOP headers for Base Account popups** — use `same-origin-allow-popups`, not `same-origin`
## Task Routing
@@ -52,11 +43,7 @@ Read the reference for your task:
| **Deploy contracts** | Foundry deployment, BaseScan verification, faucet | [references/deploy-contracts.md](references/deploy-contracts.md) |
| **Run a Base node** | Self-hosted RPC, Reth, hardware requirements | [references/run-node.md](references/run-node.md) |
| **Builder Codes** | Add ERC-8021 attribution to transactions | [references/builder-codes/overview.md](references/builder-codes/overview.md) |
-| **Base Account SDK** | SIWB, Base Pay, subscriptions, sub-accounts | [references/base-account/overview.md](references/base-account/overview.md) |
| **Register AI agent/bot** | Register wallet, get builder code, wire attribution | [references/agents/register.md](references/agents/register.md) |
-| **Migrate from OnchainKit** | OnchainKitProvider → wagmi, wallet/tx components | [references/migrations/onchainkit/overview.md](references/migrations/onchainkit/overview.md) |
-| **MiniKit → Farcaster SDK** | `@coinbase/onchainkit/minikit` → `@farcaster/miniapp-sdk` | [references/migrations/minikit-to-farcaster/overview.md](references/migrations/minikit-to-farcaster/overview.md) |
-| **Farcaster miniapp → regular app** | Remove Mini App host coupling, convert to Base/web app | [references/migrations/farcaster-miniapp-to-app.md](references/migrations/farcaster-miniapp-to-app.md) |
## Operating Procedure
@@ -69,7 +56,6 @@ Read the reference for your task:
## For Edge Cases and Latest API Changes
- **AI-optimized docs**: [docs.base.org/llms.txt](https://docs.base.org/llms.txt)
-- **Base Account reference**: [docs.base.org/base-account](https://docs.base.org/base-account)
- **Base chain docs**: [docs.base.org](https://docs.base.org)
## Installation
diff --git a/skills/build-on-base/references/base-account/authentication.md b/skills/build-on-base/references/base-account/authentication.md
deleted file mode 100644
index b111d3a..0000000
--- a/skills/build-on-base/references/base-account/authentication.md
+++ /dev/null
@@ -1,234 +0,0 @@
-# Authentication (Sign in with Base)
-
-## Table of Contents
-
-- [Overview](#overview)
-- [How It Works](#how-it-works)
-- [SDK Setup](#sdk-setup)
-- [Sign-In Flow](#sign-in-flow)
-- [Backend Verification](#backend-verification)
-- [SignInWithBaseButton Component](#signinwithbasebutton-component)
-- [Framework Integration: Wagmi](#framework-integration-wagmi)
-- [Framework Integration: Privy](#framework-integration-privy)
-- [Smart Wallet Signatures (ERC-6492)](#smart-wallet-signatures-erc-6492)
-- [Security Checklist](#security-checklist)
-
-## Overview
-
-Sign in with Base (SIWB) provides passwordless authentication using wallet signatures. It builds on Sign-In with Ethereum (SIWE, EIP-4361) — the user signs a message with their wallet key, and the backend verifies it. No passwords, no seed phrases.
-
-Base Accounts are ERC-4337 smart wallets. Unlike traditional wallets (EOAs), the user's key is a passkey — the wallet contract verifies signatures via `isValidSignature` (EIP-1271). Viem handles this automatically.
-
-## How It Works
-
-1. Generate a nonce **before** the user clicks sign-in (avoids popup blockers)
-2. Call `wallet_connect` with the `signInWithEthereum` capability
-3. User approves in the Base Account popup (`keys.coinbase.com`)
-4. SDK returns `{ address, message, signature }`
-5. Send `message` + `signature` to your backend
-6. Backend verifies with viem and creates a session/JWT
-
-## SDK Setup
-
-```bash
-npm install @base-org/account @base-org/account-ui
-```
-
-```typescript
-import { createBaseAccountSDK } from '@base-org/account';
-
-const sdk = createBaseAccountSDK({
- appName: 'My App',
- appLogoUrl: 'https://example.com/logo.png',
- appChainIds: [8453],
-});
-
-const provider = sdk.getProvider();
-```
-
-`createBaseAccountSDK` parameters:
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `appName` | `string` | No | App name shown in wallet UI (default: `"App"`) |
-| `appLogoUrl` | `string` | No | Logo URL for wallet UI |
-| `appChainIds` | `number[]` | No | Supported chain IDs |
-| `paymasterUrls` | `Record` | No | Chain ID to paymaster URL mapping |
-
-## Sign-In Flow
-
-```typescript
-const nonce = crypto.randomUUID().replace(/-/g, '');
-
-const { accounts } = await provider.request({
- method: 'wallet_connect',
- params: [{
- version: '1',
- capabilities: {
- signInWithEthereum: {
- nonce,
- chainId: '0x2105', // Base Mainnet (8453)
- },
- },
- }],
-});
-
-const { address } = accounts[0];
-const { message, signature } = accounts[0].capabilities.signInWithEthereum;
-```
-
-`signInWithEthereum` capability parameters:
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `nonce` | `string` | Yes | Unique random string per auth attempt |
-| `chainId` | `string` | Yes | Hex chain ID (`"0x2105"` = Base Mainnet 8453) |
-
-Response shape:
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `accounts[0].address` | `string` | User's wallet address |
-| `accounts[0].capabilities.signInWithEthereum.message` | `string` | SIWE-formatted message |
-| `accounts[0].capabilities.signInWithEthereum.signature` | `string` | Cryptographic signature |
-
-### Fallback for Non-Base Wallets
-
-Not every wallet supports `wallet_connect`. Fall back to `eth_requestAccounts` + `personal_sign`:
-
-```typescript
-try {
- const { accounts } = await provider.request({
- method: 'wallet_connect',
- params: [{ version: '1', capabilities: { signInWithEthereum: { nonce, chainId: '0x2105' } } }],
- });
- // use accounts[0].capabilities.signInWithEthereum
-} catch (err) {
- if (err.code === 4100) {
- const [address] = await provider.request({ method: 'eth_requestAccounts' });
- const signature = await provider.request({
- method: 'personal_sign',
- params: [siweMessage, address],
- });
- }
-}
-```
-
-## Backend Verification
-
-Use viem to verify the signature. It handles both EOA and smart wallet (EIP-1271/ERC-6492) signatures automatically.
-
-```typescript
-import { createPublicClient, http } from 'viem';
-import { base } from 'viem/chains';
-
-const client = createPublicClient({ chain: base, transport: http() });
-
-const valid = await client.verifyMessage({
- address,
- message,
- signature,
-});
-```
-
-### Full Express Server Example
-
-```typescript
-import express from 'express';
-import { createPublicClient, http } from 'viem';
-import { base } from 'viem/chains';
-
-const app = express();
-const client = createPublicClient({ chain: base, transport: http() });
-const usedNonces = new Set();
-
-app.get('/auth/nonce', (req, res) => {
- const nonce = crypto.randomUUID().replace(/-/g, '');
- res.json({ nonce });
-});
-
-app.post('/auth/verify', async (req, res) => {
- const { address, message, signature } = req.body;
- const nonceMatch = message.match(/Nonce: (\w+)/);
- if (!nonceMatch || usedNonces.has(nonceMatch[1])) {
- return res.status(401).json({ error: 'Invalid or reused nonce' });
- }
-
- const valid = await client.verifyMessage({ address, message, signature });
- if (!valid) return res.status(401).json({ error: 'Invalid signature' });
-
- usedNonces.add(nonceMatch[1]);
- // Create session/JWT here
- res.json({ success: true, address });
-});
-```
-
-## SignInWithBaseButton Component
-
-Pre-built React button from `@base-org/account-ui`.
-
-```tsx
-import { SignInWithBaseButton } from '@base-org/account-ui/react';
-
-
-```
-
-| Prop | Type | Values | Default |
-|------|------|--------|---------|
-| `align` | `string` | `'left'`, `'center'`, `'right'` | `'center'` |
-| `variant` | `string` | `'solid'`, `'transparent'` | `'solid'` |
-| `colorScheme` | `string` | `'light'`, `'dark'`, `'system'` | `'light'` |
-| `size` | `string` | `'small'`, `'medium'`, `'large'` | `'medium'` |
-| `disabled` | `boolean` | — | `false` |
-| `onClick` | `() => void` | — | — |
-| `onSignInResult` | `(result) => void` | — | — |
-
-Follow the [Brand Guidelines](https://docs.base.org/base-account/reference/ui-elements/brand-guidelines): use Base blue (`#0000FF`) on light backgrounds, all-white lockup on dark backgrounds. Do not modify the Base Square color or corner radius.
-
-## Framework Integration: Wagmi
-
-```typescript
-import { createConfig, http } from 'wagmi';
-import { base } from 'wagmi/chains';
-import { createBaseAccountSDK } from '@base-org/account';
-import { custom } from 'viem';
-
-const sdk = createBaseAccountSDK({
- appName: 'My App',
- appLogoUrl: 'https://example.com/logo.png',
- appChainIds: [8453],
-});
-
-const config = createConfig({
- chains: [base],
- transports: {
- [base.id]: custom(sdk.getProvider()),
- },
-});
-```
-
-Then use wagmi hooks (`useConnect`, `useAccount`, `useSignMessage`) as usual.
-
-## Framework Integration: Privy
-
-Privy has day-1 Base Account support. Configure it as a wallet connector — see [Privy docs](https://docs.privy.io/) for the latest integration guide. Base Account appears as a wallet option in the Privy modal.
-
-## Smart Wallet Signatures (ERC-6492)
-
-Base Accounts may not be deployed onchain until the user's first transaction. Signatures from undeployed wallets include an ERC-6492 wrapper that lets verifiers deploy the contract in a simulation to validate the signature.
-
-**You don't need to do anything special** — viem's `verifyMessage` and `verifyTypedData` handle ERC-6492 automatically. Just make sure you're using viem for verification.
-
-## Security Checklist
-
-- Generate nonces **before** the user clicks sign-in (avoids popup blockers)
-- Track used nonces server-side — reject any reused nonce
-- Verify signatures on your backend, never trust the frontend alone
-- Use `Cross-Origin-Opener-Policy: same-origin-allow-popups` (NOT `same-origin`, which breaks the popup)
-- Set appropriate session/JWT expiry times
-- Include `chainId` in verification to prevent cross-chain replay
diff --git a/skills/build-on-base/references/base-account/capabilities.md b/skills/build-on-base/references/base-account/capabilities.md
deleted file mode 100644
index 0b26b21..0000000
--- a/skills/build-on-base/references/base-account/capabilities.md
+++ /dev/null
@@ -1,263 +0,0 @@
-# Capabilities & Batch Transactions
-
-## Table of Contents
-
-- [Overview](#overview)
-- [Discovering Capabilities](#discovering-capabilities)
-- [wallet_sendCalls](#wallet_sendcalls)
-- [wallet_getCallsStatus](#wallet_getcallsstatus)
-- [Capability: paymasterService](#capability-paymasterservice)
-- [Capability: auxiliaryFunds](#capability-auxiliaryfunds)
-- [Capability: atomic](#capability-atomic)
-- [Capability: flowControl](#capability-flowcontrol)
-- [Capability: dataCallback](#capability-datacallback)
-- [Capability: dataSuffix (Attribution)](#capability-datasuffix-attribution)
-
-## Overview
-
-Capabilities are chain-specific feature flags that describe what a wallet supports. They're discovered via `wallet_getCapabilities` and used in `wallet_connect` and `wallet_sendCalls` calls.
-
-Base Account (a smart wallet) supports capabilities that traditional wallets (EOAs) cannot: atomic batching, gas sponsorship, auxiliary funds, etc.
-
-## Discovering Capabilities
-
-```typescript
-const capabilities = await provider.request({
- method: 'wallet_getCapabilities',
- params: [userAddress],
-});
-
-const baseCapabilities = capabilities['0x2105']; // Base Mainnet
-```
-
-Response structure (keyed by hex chain ID):
-
-```typescript
-{
- "0x2105": {
- auxiliaryFunds: { supported: true },
- atomic: { supported: "supported" },
- paymasterService: { supported: true },
- flowControl: { supported: false },
- datacallback: { supported: false },
- }
-}
-```
-
-Use this to conditionally enable features:
-
-```typescript
-const hasPaymaster = !!baseCapabilities.paymasterService?.supported;
-const hasAuxFunds = baseCapabilities.auxiliaryFunds?.supported || false;
-const hasAtomicBatch = baseCapabilities.atomic?.supported === 'supported';
-```
-
-## wallet_sendCalls
-
-**Spec: EIP-5792.** Submits a batch of calls to the wallet for execution.
-
-```typescript
-const { batchId } = await provider.request({
- method: 'wallet_sendCalls',
- params: [{
- version: '2.0.0',
- from: userAddress,
- chainId: '0x2105',
- atomicRequired: true,
- calls: [
- { to: '0xTokenAddress', data: '0xapproveCalldata', value: '0x0' },
- { to: '0xDexAddress', data: '0xswapCalldata', value: '0x0' },
- ],
- capabilities: {
- paymasterService: { url: 'https://your-paymaster.xyz' },
- },
- }],
-});
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `version` | `string` | Yes | Must be `"2.0.0"` |
-| `from` | `string` | Yes | Sender address |
-| `chainId` | `string` | Yes | Hex chain ID |
-| `atomicRequired` | `boolean` | Yes | Require all-or-nothing execution |
-| `calls` | `Call[]` | Yes | Array of `{ to, value, data? }` |
-| `capabilities` | `object` | No | Capability config |
-
-Returns: `{ batchId, status }`
-
-Error codes:
-
-| Code | Meaning |
-|------|---------|
-| `4001` | User rejected |
-| `5700` | Missing required capability |
-| `5720` | Duplicate batch ID |
-| `5740` | Batch too large |
-
-## wallet_getCallsStatus
-
-Check the status of a batch submitted via `wallet_sendCalls`.
-
-```typescript
-const result = await provider.request({
- method: 'wallet_getCallsStatus',
- params: [batchId],
-});
-```
-
-Status codes:
-
-| Code | Meaning |
-|------|---------|
-| `100` | Pending — received, not yet onchain |
-| `200` | Success — included onchain, no reverts |
-| `400` | Offchain failure — wallet will not retry |
-| `500` | Chain failure — batch reverted |
-| `600` | Partial failure — some changes may be onchain |
-
-Returns: `{ version, chainId, id, status, atomic, receipts, capabilities }`
-
-Polling pattern:
-
-```typescript
-async function waitForBatch(batchId: string) {
- while (true) {
- const { status, receipts } = await provider.request({
- method: 'wallet_getCallsStatus',
- params: [batchId],
- });
- if (status !== 100) return { status, receipts };
- await new Promise(r => setTimeout(r, 1000));
- }
-}
-```
-
-## Capability: paymasterService
-
-**Spec: ERC-7677.** Sponsors gas fees so users transact for free.
-
-```typescript
-capabilities: {
- paymasterService: {
- url: 'https://your-paymaster-service.xyz',
- },
-}
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `url` | `string` | Yes | HTTPS URL of an ERC-7677-compliant paymaster |
-
-The paymaster service must implement:
-- `pm_getPaymasterStubData` — for gas estimation
-- `pm_getPaymasterData` — for actual UserOp paymaster data
-
-Get a paymaster URL from [Coinbase Developer Platform](https://portal.cdp.coinbase.com). See also the [Base Gasless Campaign](https://docs.base.org/base-account/more/base-gasless-campaign) for gas credits.
-
-Best practice: handle failures gracefully with a fallback to regular (user-pays-gas) transactions.
-
-## Capability: auxiliaryFunds
-
-**Spec: EIP-5792.** Indicates the wallet has access to funds beyond the visible onchain balance (MagicSpend — use Coinbase balances onchain).
-
-No configuration parameters — it's a support flag only.
-
-When `auxiliaryFunds.supported === true`:
-- **Do not** block transactions based on visible onchain balance
-- **Do not** show "insufficient funds" warnings based on balance checks
-- Let the wallet handle funding — it can pull from the user's Coinbase account
-
-```typescript
-if (baseCapabilities.auxiliaryFunds?.supported) {
- // Skip balance check, let wallet handle it
-} else {
- // Traditional balance check
- const balance = await client.getBalance({ address: userAddress });
- if (balance < requiredAmount) showInsufficientFundsWarning();
-}
-```
-
-## Capability: atomic
-
-**Spec: EIP-5792.** Ensures batched calls execute atomically — all succeed or all revert.
-
-Support values (string, not boolean):
-
-| Value | Meaning |
-|-------|---------|
-| `"supported"` | Wallet executes atomically |
-| `"ready"` | Wallet can upgrade to atomic via EIP-7702 |
-| `"unsupported"` | No atomicity guarantees |
-
-Set `atomicRequired: true` in `wallet_sendCalls` to enforce atomic execution. If the wallet doesn't support it, the call fails with error `5700`.
-
-Use cases: approve + swap, mint + pay, any multi-step flow requiring all-or-nothing.
-
-## Capability: flowControl
-
-**Spec: ERC-7867 (proposed, not finalized).** Controls behavior when individual calls in a batch fail.
-
-```typescript
-calls: [{
- to: '0x...',
- data: '0x...',
- flowControl: {
- onFailure: 'continue',
- fallbackCall: { to: '0xFallback', data: '0x...' },
- },
-}]
-```
-
-| Parameter | Type | Values | Description |
-|-----------|------|--------|-------------|
-| `onFailure` | `string` | `'continue'`, `'stop'`, `'retry'` | What to do when this call reverts |
-| `fallbackCall` | `object` | `{ to, value?, data? }` | Optional alternative call to execute on failure |
-
-**Note:** This spec is actively being developed. Check the latest docs before using.
-
-## Capability: dataCallback
-
-Collects user profile information (email, phone, address) during transaction flows. Same mechanism as `payerInfo` in `pay()` but for `wallet_sendCalls`.
-
-```typescript
-capabilities: {
- dataCallback: {
- requests: [
- { type: 'email' },
- { type: 'name', optional: true },
- ],
- callbackURL: 'https://your-api.com/validate',
- },
-}
-```
-
-Request types: `'email'`, `'phoneNumber'`, `'physicalAddress'`, `'name'`
-
-The `callbackURL` receives a POST with user data before the transaction. Respond with `{ request: requestData }` to accept or `{ errors: { email: 'Invalid' } }` to reject.
-
-## Capability: dataSuffix (Attribution)
-
-**Spec: ERC-8021.** Appends arbitrary bytes to transaction calldata for attribution tracking. Used primarily with **Builder Codes** for tracking which app generated a transaction.
-
-```typescript
-import { Attribution } from 'ox/erc8021';
-
-const builderCodeSuffix = Attribution.toDataSuffix({
- codes: ['bc_foobar'], // Register at base.dev
-});
-
-capabilities: {
- dataSuffix: {
- value: builderCodeSuffix,
- optional: true,
- },
-}
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `value` | `0x${string}` | Yes | Hex bytes to append to calldata |
-| `optional` | `boolean` | No | If `true`, wallet may ignore if unsupported |
-
-Best practice: use `optional: true` if your app functions without attribution. Register for a Builder Code at [base.dev](https://base.dev). Keep suffixes small — larger means more gas.
diff --git a/skills/build-on-base/references/base-account/overview.md b/skills/build-on-base/references/base-account/overview.md
deleted file mode 100644
index fceca8a..0000000
--- a/skills/build-on-base/references/base-account/overview.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# Building with Base Account
-
-Base Account is an ERC-4337 smart wallet providing universal sign-on, one-tap USDC payments, and multi-chain support (Base, Arbitrum, Optimism, Zora, Polygon, BNB, Avalanche, Lordchain, Ethereum Mainnet).
-
-## Quick Start
-
-```bash
-npm install @base-org/account @base-org/account-ui
-```
-
-```typescript
-import { createBaseAccountSDK } from '@base-org/account';
-
-const sdk = createBaseAccountSDK({
- appName: 'My App',
- appLogoUrl: 'https://example.com/logo.png',
- appChainIds: [8453], // Base Mainnet
-});
-
-const provider = sdk.getProvider();
-```
-
-## Feature References
-
-Read the reference for the feature you're implementing:
-
-| Feature | Reference | When to Read |
-|---------|-----------|-------------|
-| Sign in with Base | [authentication.md](authentication.md) | Wallet auth, SIWE, backend verification, SignInWithBaseButton, Wagmi/Privy setup |
-| Base Pay | [payments.md](payments.md) | One-tap USDC payments, payerInfo, server-side verification, BasePayButton |
-| Subscriptions | [subscriptions.md](subscriptions.md) | Recurring charges, spend permissions, CDP wallet setup, charge/revoke lifecycle |
-| Sub Accounts | [sub-accounts.md](sub-accounts.md) | App-specific embedded wallets, key generation, funding |
-| Capabilities | [capabilities.md](capabilities.md) | Batch transactions, gas sponsorship (paymasters), atomic execution, auxiliaryFunds, attribution |
-| Prolinks | [prolinks.md](prolinks.md) | Shareable payment links, QR codes, encoded transaction URLs |
-| Troubleshooting | [troubleshooting.md](troubleshooting.md) | Popup issues, gas usage, unsupported calls, migration, doc links |
-
-## Critical Requirements
-
-### Security
-
-- **Track transaction IDs** to prevent replay attacks
-- **Verify sender matches authenticated user** to prevent impersonation
-- **Use a proxy** to protect Paymaster URLs from frontend exposure
-- **Paymaster providers must be ERC-7677-compliant**
-- **Never expose CDP credentials client-side** (subscription backend only)
-
-### Popup Handling
-
-- Generate nonces **before** user clicks "Sign in" to avoid popup blockers
-- Use `Cross-Origin-Opener-Policy: same-origin-allow-popups`
-- `same-origin` breaks the Base Account popup
-
-### Base Pay
-
-- Base Pay works independently from SIWB — no auth required for `pay()`
-- `testnet` param in `getPaymentStatus()` must match `pay()` call
-- Never disable actions based on onchain balance alone — check `auxiliaryFunds` capability
-
-### Sub Accounts
-
-- Call `wallet_addSubAccount` each session before use
-- Ownership changes expected on new devices/browsers
-- Only Coinbase Smart Wallet contracts supported for import
-
-### Smart Wallets
-
-- ERC-6492 wrapper enables signature verification before wallet deployment
-- Viem's `verifyMessage`/`verifyTypedData` handle this automatically
-
-## For Edge Cases and Latest API Changes
-
-- **AI-optimized docs**: [docs.base.org/llms.txt](https://docs.base.org/llms.txt)
-- **Full reference**: [docs.base.org/base-account](https://docs.base.org/base-account)
diff --git a/skills/build-on-base/references/base-account/payments.md b/skills/build-on-base/references/base-account/payments.md
deleted file mode 100644
index afd9287..0000000
--- a/skills/build-on-base/references/base-account/payments.md
+++ /dev/null
@@ -1,225 +0,0 @@
-# Payments (Base Pay)
-
-## Table of Contents
-
-- [Overview](#overview)
-- [One-Time Payments](#one-time-payments)
-- [Checking Payment Status](#checking-payment-status)
-- [Collecting User Info (payerInfo)](#collecting-user-info-payerinfo)
-- [Server-Side Verification](#server-side-verification)
-- [Server-Side User Info Validation](#server-side-user-info-validation)
-- [BasePayButton Component](#basepaybutton-component)
-- [Framework Integration: Wagmi](#framework-integration-wagmi)
-- [Testing](#testing)
-- [Security Checklist](#security-checklist)
-
-## Overview
-
-Base Pay enables one-tap USDC payments on Base. Key facts:
-
-- Currency is USDC (a digital dollar stablecoin), not ETH
-- Gas is sponsored automatically — users don't pay gas fees
-- Settles in under 2 seconds on Base
-- No chargebacks, no FX fees, no merchant fees
-- **Base Pay works independently from Sign in with Base** — no authentication required to call `pay()`
-- Users can pay from their Base Account or Coinbase account
-
-## One-Time Payments
-
-### `pay()`
-
-```typescript
-import { pay } from '@base-org/account';
-
-const payment = await pay({
- amount: '10.50',
- to: '0xRecipientAddress',
- testnet: false,
-});
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `amount` | `string` | Yes | USDC amount (e.g., `"10.50"`) |
-| `to` | `string` | Yes | Recipient Ethereum address (`0x...`) |
-| `testnet` | `boolean` | No | Use Base Sepolia testnet (default: `false`) |
-| `payerInfo` | `object` | No | Collect user info during payment — see [payerInfo section](#collecting-user-info-payerinfo) |
-
-Returns `PayResult`:
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `id` | `string` | Transaction hash |
-| `amount` | `string` | Amount sent |
-| `to` | `string` | Recipient address |
-| `payerInfoResponses` | `object` | Collected user info (if `payerInfo` was provided) |
-
-## Checking Payment Status
-
-### `getPaymentStatus()`
-
-```typescript
-import { getPaymentStatus } from '@base-org/account';
-
-const status = await getPaymentStatus({
- id: payment.id,
- testnet: false,
-});
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `id` | `string` | Yes | Transaction hash from `pay()` |
-| `testnet` | `boolean` | No | **Must match** the `testnet` value used in `pay()` |
-
-Returns `PaymentStatus`:
-
-| Field | Type | Present When |
-|-------|------|-------------|
-| `status` | `"completed" \| "pending" \| "failed" \| "not_found"` | Always |
-| `id` | `string` | Always |
-| `message` | `string` | Always |
-| `sender` | `string` | `pending`, `completed`, `failed` |
-| `amount` | `string` | `completed` |
-| `recipient` | `string` | `completed` |
-| `error` | `object` | `failed` |
-
-## Collecting User Info (payerInfo)
-
-Request user information (email, name, phone, address) during the payment flow.
-
-```typescript
-const payment = await pay({
- amount: '25.00',
- to: '0xRecipient',
- payerInfo: {
- requests: [
- { type: 'email' },
- { type: 'phoneNumber', optional: true },
- { type: 'physicalAddress', optional: true },
- ],
- callbackURL: 'https://your-api.com/validate',
- },
-});
-```
-
-Supported `payerInfo` request types:
-
-| Type | Response Shape |
-|------|---------------|
-| `email` | `string` |
-| `name` | `{ firstName: string, familyName: string }` |
-| `phoneNumber` | `{ number: string, country: string }` |
-| `physicalAddress` | `{ address1, address2?, city, state, postalCode, country, name: { firstName, familyName } }` |
-| `onchainAddress` | `string` |
-
-Fields are **required by default**. Set `optional: true` to avoid aborting the payment if the user declines to share.
-
-## Server-Side Verification
-
-Never trust frontend payment confirmations alone. Always verify on your backend.
-
-```typescript
-import { getPaymentStatus } from '@base-org/account';
-
-async function verifyPayment(txId: string, expectedAmount: string, expectedRecipient: string, authenticatedUser: string) {
- // 1. Check if already processed (dedup by txId)
- if (await isProcessed(txId)) throw new Error('Already processed');
-
- // 2. Verify payment status
- const { status, sender, amount, recipient } = await getPaymentStatus({ id: txId });
- if (status !== 'completed') throw new Error(`Payment not completed: ${status}`);
-
- // 3. Verify sender matches authenticated user (prevents impersonation)
- if (sender.toLowerCase() !== authenticatedUser.toLowerCase()) {
- throw new Error('Sender mismatch');
- }
-
- // 4. Validate amount and recipient
- if (amount !== expectedAmount || recipient.toLowerCase() !== expectedRecipient.toLowerCase()) {
- throw new Error('Payment details mismatch');
- }
-
- // 5. Mark processed BEFORE fulfilling
- await markProcessed(txId);
- await fulfillOrder(txId);
-}
-```
-
-Key threats this prevents:
-- **Replay attacks**: Track processed transaction IDs with unique constraints
-- **Impersonation**: Verify `sender` matches the authenticated user
-- **Amount tampering**: Validate `amount` and `recipient` server-side
-
-## Server-Side User Info Validation
-
-When you provide a `callbackURL` in `payerInfo`, your endpoint receives the user's data **before** the transaction is submitted. You can validate and accept or reject.
-
-```typescript
-// POST handler at your callbackURL
-app.post('/validate', (req, res) => {
- const { requestData } = req.body;
- const info = requestData.capabilities.dataCallback.requestedInfo;
-
- // Reject with errors (shown to user)
- if (!isValidEmail(info.email)) {
- return res.json({ errors: { email: 'Invalid email address' } });
- }
-
- // Accept — return the original request data
- return res.json({ request: requestData });
-});
-```
-
-## BasePayButton Component
-
-Pre-built React button from `@base-org/account-ui`.
-
-```tsx
-import { BasePayButton } from '@base-org/account-ui/react';
-
-
-```
-
-| Prop | Type | Values | Default |
-|------|------|--------|---------|
-| `colorScheme` | `string` | `'light'`, `'dark'`, `'system'` | `'light'` |
-| `size` | `string` | `'small'`, `'medium'`, `'large'` | `'medium'` |
-| `variant` | `string` | `'solid'`, `'outline'` | `'solid'` |
-| `disabled` | `boolean` | — | `false` |
-| `onClick` | `() => void` | — | — |
-| `onPaymentResult` | `(result) => void` | — | — |
-
-Follow the [Brand Guidelines](https://docs.base.org/base-account/reference/ui-elements/brand-guidelines): always use the combination mark (never plain text "Base Pay"), pad the button with at least 1x height on all sides.
-
-## Framework Integration: Wagmi
-
-`pay()` and `getPaymentStatus()` are standalone functions — they don't require a provider or wagmi config. Call them directly:
-
-```typescript
-import { pay, getPaymentStatus } from '@base-org/account';
-
-const { id } = await pay({ amount: '5.00', to: '0x...', testnet: true });
-const status = await getPaymentStatus({ id, testnet: true });
-```
-
-If you're also using SIWB with wagmi, the `pay()` function still works independently alongside the wagmi provider setup.
-
-## Testing
-
-- Use `testnet: true` in both `pay()` and `getPaymentStatus()`
-- Test on Base Sepolia (chain ID 84532)
-- Get test USDC from the [Circle Faucet](https://faucet.circle.com/) on Base Sepolia
-
-## Security Checklist
-
-- Always verify payments server-side with `getPaymentStatus()`
-- Track processed transaction IDs in a database with unique constraints
-- Verify `sender` matches your authenticated user
-- Validate `amount` and `recipient` match the expected order
-- `testnet` param must match between `pay()` and `getPaymentStatus()`
-- Never disable payment buttons based on onchain balance alone — check `auxiliaryFunds` capability (users may have Coinbase balances available via MagicSpend)
diff --git a/skills/build-on-base/references/base-account/prolinks.md b/skills/build-on-base/references/base-account/prolinks.md
deleted file mode 100644
index 0b74411..0000000
--- a/skills/build-on-base/references/base-account/prolinks.md
+++ /dev/null
@@ -1,192 +0,0 @@
-# Prolinks (Shareable Payment Links)
-
-## Table of Contents
-
-- [Overview](#overview)
-- [encodeProlink](#encodeprolink)
-- [decodeProlink](#decodeprolink)
-- [createProlinkUrl](#createprolinkurl)
-- [Common Patterns](#common-patterns)
-
-## Overview
-
-Prolinks encode transaction requests (JSON-RPC) into compressed, URL-safe strings that can be shared as links. When a user opens a prolink URL, their Base Account app decodes and executes the request.
-
-Use cases: shareable payment requests, pre-filled transaction links, QR codes for onchain actions.
-
-The encoding is optimized per method type (`wallet_sendCalls`, `wallet_sign`, generic JSON-RPC) and uses gzip compression for payloads >= 1KB (50-80% size reduction).
-
-## encodeProlink
-
-Encodes a JSON-RPC request into a compressed, base64url-encoded prolink payload.
-
-```typescript
-import { encodeProlink } from '@base-org/account';
-
-const prolink = await encodeProlink({
- method: 'wallet_sendCalls',
- params: {
- version: '2.0.0',
- chainId: '0x2105',
- calls: [{
- to: '0xUSDCAddress',
- data: '0xtransferCalldata',
- value: '0x0',
- }],
- },
-});
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `method` | `string` | Yes | JSON-RPC method (`wallet_sendCalls`, `wallet_sign`, or any) |
-| `params` | `unknown` | Yes | Method parameters |
-| `chainId` | `number` | No | Required for generic methods; auto-extracted for `wallet_sendCalls`/`wallet_sign` |
-| `capabilities` | `Record` | No | Wallet capabilities (e.g., `dataCallback`) |
-
-Returns: `Promise` — base64url-encoded prolink payload.
-
-### Examples
-
-**ERC-20 Transfer (USDC):**
-
-```typescript
-const prolink = await encodeProlink({
- method: 'wallet_sendCalls',
- params: {
- version: '2.0.0',
- chainId: '0x2105',
- calls: [{
- to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
- data: '0xa9059cbb000000000000000000000000RECIPIENT0000000000000000000000000000000000000000000000000000000000989680', // transfer(address,uint256)
- value: '0x0',
- }],
- },
-});
-```
-
-**With Capabilities (dataCallback):**
-
-```typescript
-const prolink = await encodeProlink({
- method: 'wallet_sendCalls',
- params: { /* ... */ },
- capabilities: {
- dataCallback: {
- callbackURL: 'https://your-api.com/callback',
- events: ['initiated', 'postSign'],
- },
- },
-});
-```
-
-**Batch Calls (approve + swap):**
-
-```typescript
-const prolink = await encodeProlink({
- method: 'wallet_sendCalls',
- params: {
- version: '2.0.0',
- chainId: '0x2105',
- calls: [
- { to: '0xToken', data: '0xapproveData', value: '0x0' },
- { to: '0xDex', data: '0xswapData', value: '0x0' },
- ],
- },
-});
-```
-
-## decodeProlink
-
-Decodes a prolink payload back into a JSON-RPC request.
-
-```typescript
-import { decodeProlink } from '@base-org/account';
-
-const decoded = await decodeProlink(payload);
-// decoded.method → 'wallet_sendCalls'
-// decoded.params → { version, chainId, calls }
-// decoded.chainId → number | undefined
-// decoded.capabilities → Record | undefined
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `payload` | `string` | Yes | Base64url-encoded prolink payload |
-
-Returns `ProlinkDecoded`:
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `method` | `string` | JSON-RPC method name |
-| `params` | `unknown` | Method parameters |
-| `chainId` | `number \| undefined` | Target chain ID |
-| `capabilities` | `Record \| undefined` | Wallet capabilities |
-
-### Validation Before Execution
-
-Always validate decoded prolinks before executing:
-
-```typescript
-const decoded = await decodeProlink(payload);
-
-if (decoded.chainId !== 8453) throw new Error('Wrong chain');
-if (decoded.method !== 'wallet_sendCalls') throw new Error('Unexpected method');
-
-const { calls } = decoded.params;
-const allowedContracts = ['0xUSDC...', '0xDex...'];
-for (const call of calls) {
- if (!allowedContracts.includes(call.to)) {
- throw new Error(`Untrusted contract: ${call.to}`);
- }
-}
-
-await provider.request({ method: decoded.method, params: [decoded.params] });
-```
-
-## createProlinkUrl
-
-Creates a complete URL with the prolink as a query parameter.
-
-```typescript
-import { createProlinkUrl } from '@base-org/account';
-
-const url = createProlinkUrl(prolink, 'https://yourapp.com/pay');
-// https://yourapp.com/pay?prolink=
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `prolink` | `string` | Yes | Base64url-encoded prolink from `encodeProlink` |
-| `url` | `string` | Yes | Base URL (default: `https://base.app/base-pay`) |
-| `additionalQueryParams` | `Record` | No | Extra query parameters |
-
-Returns: Complete URL string.
-
-## Common Patterns
-
-### Payment Request Link
-
-```typescript
-const prolink = await encodeProlink({
- method: 'wallet_sendCalls',
- params: {
- version: '2.0.0',
- chainId: '0x2105',
- calls: [{ to: recipientAddress, data: transferCalldata, value: '0x0' }],
- },
-});
-const paymentUrl = createProlinkUrl(prolink);
-// Share this URL or render as QR code
-```
-
-### Extract and Display Transaction Preview
-
-```typescript
-const decoded = await decodeProlink(payload);
-const { calls } = decoded.params;
-
-const preview = calls.map((call, i) =>
- `Call ${i + 1}: to=${call.to}, value=${call.value}`
-).join('\n');
-```
diff --git a/skills/build-on-base/references/base-account/sub-accounts.md b/skills/build-on-base/references/base-account/sub-accounts.md
deleted file mode 100644
index 9e52765..0000000
--- a/skills/build-on-base/references/base-account/sub-accounts.md
+++ /dev/null
@@ -1,250 +0,0 @@
-# Sub Accounts
-
-## Table of Contents
-
-- [Overview](#overview)
-- [Key Concepts](#key-concepts)
-- [SDK Configuration](#sdk-configuration)
-- [Key Management](#key-management)
-- [Creating Sub Accounts](#creating-sub-accounts)
-- [Retrieving Sub Accounts](#retrieving-sub-accounts)
-- [Adding Owners](#adding-owners)
-- [wallet_addSubAccount RPC](#wallet_addsubaccount-rpc)
-- [wallet_getSubAccounts RPC](#wallet_getsubaccounts-rpc)
-- [Funding Sub Accounts](#funding-sub-accounts)
-- [Session Management](#session-management)
-
-## Overview
-
-Sub accounts are app-specific embedded wallets created under a user's Base Account. They let your app perform transactions on behalf of the user without requiring approval popups for every action — useful for gaming, DeFi automation, or any UX that needs low-friction transactions.
-
-Each sub account is a separate smart wallet owned by the parent Base Account.
-
-## Key Concepts
-
-- Sub accounts are **app-scoped** — each app gets its own sub account(s)
-- The parent Base Account is the **owner** of each sub account
-- Sub accounts can be funded via **spend permissions** or **manual transfers**
-- Ownership may change across devices/browsers — always call `wallet_addSubAccount` each session
-- Only **Coinbase Smart Wallet** contracts are supported for importing existing sub accounts
-
-## SDK Configuration
-
-Configure sub accounts when creating the SDK:
-
-```typescript
-import { createBaseAccountSDK, getCryptoKeyAccount } from '@base-org/account';
-
-const sdk = createBaseAccountSDK({
- appName: 'My App',
- appLogoUrl: 'https://example.com/logo.png',
- appChainIds: [8453],
- subAccounts: {
- creation: 'on-connect',
- defaultAccount: 'sub',
- funding: 'spend-permissions',
- toOwnerAccount: async () => {
- const { account } = await getCryptoKeyAccount();
- return { account };
- },
- },
-});
-```
-
-`SubAccountOptions`:
-
-| Property | Type | Values | Description |
-|----------|------|--------|-------------|
-| `creation` | `string` | `'on-connect'`, `'manual'` | When to create sub accounts |
-| `defaultAccount` | `string` | `'sub'`, `'universal'` | Which account is default (first in accounts array) |
-| `funding` | `string` | `'spend-permissions'`, `'manual'` | How sub accounts are funded |
-| `toOwnerAccount` | `function` | — | Returns `{ account: LocalAccount \| WebAuthnAccount \| null }` |
-
-## Key Management
-
-Sub accounts require a key pair for signing. The SDK provides utilities for P256 key management.
-
-### `generateKeyPair()`
-
-```typescript
-import { generateKeyPair } from '@base-org/account';
-
-const keyPair = await generateKeyPair();
-// keyPair.publicKey → hex string
-// keyPair.privateKey → hex string
-```
-
-### `getKeypair()`
-
-Retrieves an existing key pair from secure storage (returns `null` if none).
-
-```typescript
-import { getKeypair } from '@base-org/account';
-
-let keyPair = await getKeypair();
-if (!keyPair) {
- keyPair = await generateKeyPair();
-}
-```
-
-### `getCryptoKeyAccount()`
-
-Gets the current crypto key account info.
-
-```typescript
-import { getCryptoKeyAccount } from '@base-org/account';
-
-const { account } = await getCryptoKeyAccount();
-// account.publicKey → hex string
-// account.type → 'webauthn' | 'local'
-// account.address → (for LocalAccount only)
-```
-
-Returns `{ account }` where `account` is one of:
-- `WebAuthnAccount`: `{ publicKey, type: 'webauthn' }`
-- `LocalAccount`: `{ address, publicKey, type: 'local' }`
-- `null`: No account available
-
-## Creating Sub Accounts
-
-### Via SDK Helper
-
-```typescript
-const subAccount = await sdk.subAccount.create({
- type: 'webauthn-p256',
- publicKey: keyPair.publicKey,
-});
-// subAccount.address → the sub account address
-```
-
-### Via RPC (wallet_addSubAccount)
-
-```typescript
-const result = await provider.request({
- method: 'wallet_addSubAccount',
- params: [{
- account: {
- type: 'create',
- keys: [{
- type: 'webauthn-p256',
- publicKey: keyPair.publicKey,
- }],
- },
- }],
-});
-// result.address → the sub account address
-```
-
-Key types for the `keys` array:
-
-| Type | Description |
-|------|-------------|
-| `'address'` | Raw Ethereum address |
-| `'p256'` | P256 public key |
-| `'webcrypto-p256'` | WebCrypto P256 key |
-| `'webauthn-p256'` | WebAuthn P256 key (recommended) |
-
-## Retrieving Sub Accounts
-
-```typescript
-const subAccount = await sdk.subAccount.get();
-// Returns the current sub account or null
-```
-
-Or via RPC:
-
-```typescript
-const subAccounts = await provider.request({
- method: 'wallet_getSubAccounts',
-});
-// Array of { address, factory?, factoryData? }
-```
-
-## Adding Owners
-
-```typescript
-await sdk.subAccount.addOwner({
- address: newOwnerAddress,
- chainId: 8453,
-});
-```
-
-## wallet_addSubAccount RPC
-
-Two modes of operation:
-
-### Create a New Sub Account
-
-```typescript
-await provider.request({
- method: 'wallet_addSubAccount',
- params: [{
- account: {
- type: 'create',
- keys: [{ type: 'webauthn-p256', publicKey: '0x...' }],
- },
- }],
-});
-```
-
-### Import an Existing Deployed Account
-
-```typescript
-await provider.request({
- method: 'wallet_addSubAccount',
- params: [{
- account: {
- type: 'deployed',
- address: '0xExistingSubAccount',
- chainId: 8453,
- },
- }],
-});
-```
-
-Returns: `{ address, factory?, factoryData? }`
-
-## wallet_getSubAccounts RPC
-
-```typescript
-const accounts = await provider.request({
- method: 'wallet_getSubAccounts',
-});
-```
-
-Returns an array of sub account objects.
-
-## Funding Sub Accounts
-
-Two strategies:
-
-### Spend Permissions (Recommended)
-
-Set `funding: 'spend-permissions'` in SDK config. The parent Base Account grants a spend permission to the sub account, which can then spend tokens within the allowed limit.
-
-### Manual
-
-Set `funding: 'manual'`. You transfer tokens directly to the sub account address.
-
-## Session Management
-
-**Call `wallet_addSubAccount` at the start of each session** before using the sub account. This is necessary because:
-
-- Ownership may change when users switch devices or browsers
-- The sub account needs to be re-registered with the current session
-- Without this call, sub account operations may fail silently
-
-```typescript
-async function initSession() {
- const keyPair = await getKeypair() || await generateKeyPair();
- await provider.request({
- method: 'wallet_addSubAccount',
- params: [{
- account: {
- type: 'create',
- keys: [{ type: 'webauthn-p256', publicKey: keyPair.publicKey }],
- },
- }],
- });
-}
-```
diff --git a/skills/build-on-base/references/base-account/subscriptions.md b/skills/build-on-base/references/base-account/subscriptions.md
deleted file mode 100644
index 4d56c9f..0000000
--- a/skills/build-on-base/references/base-account/subscriptions.md
+++ /dev/null
@@ -1,238 +0,0 @@
-# Subscriptions (Recurring Payments)
-
-## Table of Contents
-
-- [Overview](#overview)
-- [Architecture](#architecture)
-- [Backend Setup: CDP Owner Wallet](#backend-setup-cdp-owner-wallet)
-- [Frontend: Create a Subscription](#frontend-create-a-subscription)
-- [Backend: Check Subscription Status](#backend-check-subscription-status)
-- [Backend: Charge a Subscription](#backend-charge-a-subscription)
-- [Backend: Cancel a Subscription](#backend-cancel-a-subscription)
-- [Advanced: Manual Execution](#advanced-manual-execution)
-- [Fund Routing Patterns](#fund-routing-patterns)
-- [Testing](#testing)
-
-## Overview
-
-Recurring payments use **Spend Permissions** — an onchain primitive that lets a user grant revocable spending rights to your app. The user approves once, and your backend charges periodically without further user interaction.
-
-Key properties:
-- Spending limit auto-resets each period (no rollover between periods)
-- User can cancel anytime from their wallet
-- USDC only (on Base Mainnet and Base Sepolia)
-- Requires both client-side (subscribe) and server-side (charge/revoke) code
-
-## Architecture
-
-```
-Client (browser) Server (Node.js)
-───────────────── ────────────────
-subscribe() ──────────────────────> Store subscription ID
- ↓
- getStatus() → check if chargeable
- ↓
- charge() → execute periodic charge
- ↓
- revoke() → cancel when needed
-```
-
-The server uses a **CDP (Coinbase Developer Platform) smart wallet** to act as the subscription owner (the entity authorized to spend).
-
-## Backend Setup: CDP Owner Wallet
-
-### Environment Variables
-
-```bash
-CDP_API_KEY_ID=your-api-key-id
-CDP_API_KEY_SECRET=your-api-key-secret
-CDP_WALLET_SECRET=your-wallet-secret
-PAYMASTER_URL=https://your-paymaster.xyz # optional, for gasless transactions
-```
-
-Get these from [Coinbase Developer Platform](https://portal.cdp.coinbase.com).
-
-### Create or Retrieve the Owner Wallet
-
-```typescript
-import { base } from '@base-org/account/node';
-
-const wallet = await base.subscription.getOrCreateSubscriptionOwnerWallet({
- walletName: 'my-app-subscriptions',
-});
-// wallet.address → share this with the frontend as subscriptionOwner
-// wallet.walletName → must match across charge() and revoke() calls
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `walletName` | `string` | No | Wallet identifier (default: `"subscription owner"`) |
-| `cdpApiKeyId` | `string` | No | Falls back to `CDP_API_KEY_ID` env var |
-| `cdpApiKeySecret` | `string` | No | Falls back to `CDP_API_KEY_SECRET` env var |
-| `cdpWalletSecret` | `string` | No | Falls back to `CDP_WALLET_SECRET` env var |
-
-Returns: `{ address, walletName, eoaAddress }`
-
-This is **idempotent** — the same `walletName` always returns the same wallet. The `address` is the CDP smart wallet address (safe to share publicly as `subscriptionOwner`).
-
-**Never expose CDP credentials client-side.** Only the wallet `address` is public.
-
-## Frontend: Create a Subscription
-
-```typescript
-import { base } from '@base-org/account';
-
-const subscription = await base.subscription.subscribe({
- recurringCharge: '9.99',
- subscriptionOwner: '0xYourCDPWalletAddress',
- periodInDays: 30,
- testnet: false,
-});
-// subscription.id → store this as the subscription identifier
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `recurringCharge` | `string` | Yes | USDC amount per period (max 6 decimals) |
-| `subscriptionOwner` | `string` | Yes | Your CDP wallet address |
-| `periodInDays` | `number` | No | Charge period in days (default: `30`) |
-| `testnet` | `boolean` | No | Use testnet (default: `false`) |
-| `requireBalance` | `boolean` | No | Check payer balance first (default: `true`) |
-
-Returns `SubscriptionResult`:
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `id` | `string` | Permission hash (subscription identifier) |
-| `subscriptionOwner` | `string` | Your app's wallet address |
-| `subscriptionPayer` | `string` | The user's wallet address |
-| `recurringCharge` | `string` | Amount in USD |
-| `periodInDays` | `number` | Period length |
-
-## Backend: Check Subscription Status
-
-```typescript
-import { base } from '@base-org/account';
-
-const status = await base.subscription.getStatus({
- id: subscriptionId,
- testnet: false,
-});
-```
-
-| Parameter | Type | Required |
-|-----------|------|----------|
-| `id` | `string` | Yes |
-| `testnet` | `boolean` | No |
-
-Returns `SubscriptionStatus`:
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `isSubscribed` | `boolean` | Whether subscription is active |
-| `recurringCharge` | `string` | Charge amount |
-| `remainingChargeInPeriod` | `string` | How much can still be charged this period |
-| `currentPeriodStart` | `Date` | — |
-| `nextPeriodStart` | `Date` | — |
-| `periodInDays` | `number` | — |
-
-Check before charging:
-
-```typescript
-const status = await base.subscription.getStatus({ id: subscriptionId });
-if (status.isSubscribed && parseFloat(status.remainingChargeInPeriod!) > 0) {
- // safe to charge
-}
-```
-
-## Backend: Charge a Subscription
-
-```typescript
-import { base } from '@base-org/account/node';
-
-const result = await base.subscription.charge({
- id: subscriptionId,
- amount: 'max-remaining-charge',
- paymasterUrl: process.env.PAYMASTER_URL,
- testnet: false,
-});
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `id` | `string` | Yes | Subscription ID |
-| `amount` | `string \| 'max-remaining-charge'` | Yes | USDC amount or `'max-remaining-charge'` |
-| `paymasterUrl` | `string` | No | For gasless transactions |
-| `recipient` | `string` | No | Send USDC to a different address (default: stays in CDP wallet) |
-| `testnet` | `boolean` | No | Default: `false` |
-| `walletName` | `string` | No | Must match the wallet used in setup |
-
-Returns: `{ success, id, subscriptionId, amount, subscriptionOwner, recipient }`
-
-`charge()` handles all transaction details: gas estimation, nonce management, and signing.
-
-## Backend: Cancel a Subscription
-
-```typescript
-import { base } from '@base-org/account/node';
-
-const result = await base.subscription.revoke({
- id: subscriptionId,
- paymasterUrl: process.env.PAYMASTER_URL,
- testnet: false,
-});
-```
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `id` | `string` | Yes | Subscription ID |
-| `paymasterUrl` | `string` | No | For gasless transactions |
-| `testnet` | `boolean` | No | Default: `false` |
-| `walletName` | `string` | No | Must match the wallet used in setup |
-
-Returns: `{ success, id, subscriptionId, subscriptionOwner }`
-
-Revoking is **permanent**. The user would need to create a new subscription.
-
-## Advanced: Manual Execution
-
-For custom wallet infrastructure (not using CDP wallets), use `prepareCharge` and `prepareRevoke` to get raw call data.
-
-### `prepareCharge()`
-
-```typescript
-import { base } from '@base-org/account';
-
-const calls = await base.subscription.prepareCharge({
- id: subscriptionId,
- amount: 'max-remaining-charge',
- testnet: false,
-});
-// calls → Array<{ to, data, value: '0x0' }>
-// Execute via wallet_sendCalls or eth_sendTransaction
-```
-
-### `prepareRevoke()`
-
-```typescript
-const call = await base.subscription.prepareRevoke({
- id: subscriptionId,
- testnet: false,
-});
-// call → { to, data, value: '0x0' }
-```
-
-## Fund Routing Patterns
-
-| Pattern | How | When |
-|---------|-----|------|
-| Default | Omit `recipient` | USDC stays in CDP wallet |
-| Treasury | `recipient: '0xTreasury'` | Auto-transfer to treasury |
-| Dynamic | Set `recipient` per charge | Route to different addresses based on plan type |
-
-## Testing
-
-- Use `testnet: true` in all calls (`subscribe`, `getStatus`, `charge`, `revoke`)
-- Use `periodInDays: 1` for faster testing cycles
-- Test on Base Sepolia (chain ID 84532)
-- Get test USDC from the [Circle Faucet](https://faucet.circle.com/)
diff --git a/skills/build-on-base/references/base-account/troubleshooting.md b/skills/build-on-base/references/base-account/troubleshooting.md
deleted file mode 100644
index db090e8..0000000
--- a/skills/build-on-base/references/base-account/troubleshooting.md
+++ /dev/null
@@ -1,146 +0,0 @@
-# Troubleshooting
-
-## Table of Contents
-
-- [Quick Fixes](#quick-fixes)
-- [Popup Issues](#popup-issues)
-- [Gas Usage](#gas-usage)
-- [Unsupported Operations](#unsupported-operations)
-- [Wallet Library Compatibility](#wallet-library-compatibility)
-- [Migration from Coinbase Wallet SDK](#migration-from-coinbase-wallet-sdk)
-- [Transaction Simulation Debugging](#transaction-simulation-debugging)
-- [When to Consult the Docs](#when-to-consult-the-docs)
-
-## Quick Fixes
-
-| Issue | Solution |
-|-------|----------|
-| Peer dependency error during install | Use `--legacy-peer-deps` flag |
-| Popup shows infinite spinner | Set COOP header to `same-origin-allow-popups` (not `same-origin`) |
-| Signature verification fails pre-deploy | Use viem — it handles ERC-6492 automatically |
-| `wallet_connect` throws `4100` | Wallet doesn't support it; fall back to `eth_requestAccounts` + `personal_sign` |
-| Payment status returns `not_found` | Ensure `testnet` param in `getPaymentStatus()` matches `pay()` |
-| Sub account operations fail | Call `wallet_addSubAccount` at the start of each session |
-| Balance appears insufficient | Check `auxiliaryFunds` capability — user may have Coinbase balances available |
-
-## Popup Issues
-
-Base Account uses a popup window at `keys.coinbase.com` for user approvals.
-
-### Cross-Origin-Opener-Policy (COOP)
-
-| COOP Value | Works? |
-|------------|--------|
-| `unsafe-none` (browser default) | Yes |
-| `same-origin-allow-popups` | Yes (recommended) |
-| `same-origin` | **No** — breaks the popup entirely |
-
-If using `same-origin`, the popup either errors or shows an infinite spinner. Switch to `same-origin-allow-popups`.
-
-### Popup Blockers
-
-Browsers block popups unless triggered by a direct user click. To avoid blocking:
-
-- Generate nonces and do any async work **before** the user clicks the sign-in button
-- Keep zero or minimal logic between the button click handler and the SDK call
-- Test across all target browsers — popup blocking behavior varies
-
-### Popup "Linger" Behavior
-
-After responding to a request, the popup stays open for **200ms** before closing. If a second SDK request arrives within that window, it's handled in the same popup (no new popup needed).
-
-If the second request arrives **after** 200ms (popup already closed), the browser will block the new programmatic popup. Design flows to either:
-- Chain requests quickly (< 200ms gap)
-- Require a new user click for the second request
-
-## Gas Usage
-
-Base Accounts use more gas than traditional Ethereum accounts (EOAs) because they're smart contracts processed through ERC-4337 bundling.
-
-| Operation | EOA | Base Account |
-|-----------|-----|-------------|
-| Native token transfer | ~21,000 gas | ~100,000 gas |
-| ERC-20 token transfer | ~65,000 gas | ~150,000 gas |
-| First-time deployment | N/A | ~300,000+ gas (one-time) |
-
-On L2 networks like Base, the cost difference is typically just a few cents. Use a paymaster to sponsor gas entirely (see [capabilities reference](capabilities.md#capability-paymasterservice)).
-
-## Unsupported Operations
-
-Base Account is an ERC-4337 smart wallet. Some operations behave differently:
-
-### Self-Calls
-
-Apps **cannot** make calls to the user's own Base Account address. This is a security measure to prevent changing owners, upgrading the account, or other admin operations.
-
-### CREATE Opcode
-
-Not supported due to ERC-4337 limitations. Workarounds:
-- Use a **factory contract** that deploys on behalf of the user
-- Use the `CREATE2` opcode instead
-
-### Solidity `transfer` Function
-
-Base Account wallets **cannot receive ETH** via Solidity's built-in `transfer` function because it only forwards 2,300 gas — insufficient for smart contract `receive`/`fallback` functions.
-
-Use `call` instead:
-
-```solidity
-// Won't work with Base Account
-payable(baseAccountAddress).transfer(amount);
-
-// Use this instead
-(bool success, ) = payable(baseAccountAddress).call{value: amount}("");
-require(success, "Transfer failed");
-```
-
-Known affected contract: **WETH9 on Base** (`0x4200000000000000000000000000000000000006`) — Base Accounts cannot directly unwrap ETH from it.
-
-## Wallet Library Compatibility
-
-These wallet aggregation libraries have day-1 Base Account support:
-
-| Library | Supported |
-|---------|-----------|
-| Dynamic | Yes |
-| Privy | Yes |
-| ThirdWeb | Yes |
-| ConnectKit | Yes |
-| Web3Modal / Reown | Yes |
-| Web3-Onboard | Yes |
-| RainbowKit | Yes |
-
-## Migration from Coinbase Wallet SDK
-
-The Coinbase Wallet app is transitioning to the Base app. To migrate:
-
-1. **Don't immediately replace** the existing "Coinbase Wallet" button
-2. **Add** a "Sign in with Base" button as a new option alongside it
-3. Over time, existing Coinbase Wallet users will be migrated to Base Accounts
-
-Code change:
-
-```typescript
-// New: add Base Account SDK
-import { createBaseAccountSDK } from '@base-org/account';
-const baseAccount = createBaseAccountSDK({ appName: 'My App' });
-```
-
-As of Coinbase Wallet SDK v4.0, users without the extension see a popup with options (mobile WalletLink or passkey-powered Smart Wallet). To avoid any popup window, use Coinbase Wallet SDK version < 4.0.
-
-## Transaction Simulation Debugging
-
-Hidden feature in the Base Account popup: click the transaction simulation area **five times** to copy the simulation request/response data to your clipboard. Paste into a text editor to inspect.
-
-## When to Consult the Docs
-
-This skill covers the most common patterns. For edge cases, advanced configurations, or the latest API changes, consult:
-
-- **AI-optimized docs**: [docs.base.org/llms.txt](https://docs.base.org/llms.txt) — feed this to your agent for comprehensive context
-- **Base Account reference**: [docs.base.org/base-account](https://docs.base.org/base-account) — full API reference, all RPC methods, all capabilities
-- **Base Account SDK source**: [github.com/base/account-sdk](https://github.com/base/account-sdk)
-- **Smart Wallet contracts**: [github.com/coinbase/smart-wallet](https://github.com/coinbase/smart-wallet)
-- **Spend Permissions contracts**: [github.com/coinbase/spend-permissions](https://github.com/coinbase/spend-permissions)
-- **Coinbase Developer Platform**: [portal.cdp.coinbase.com](https://portal.cdp.coinbase.com) — paymaster setup, API keys, wallet management
-
-For standard Ethereum RPC methods (`eth_getBalance`, `eth_sendTransaction`, `eth_getTransactionReceipt`, etc.), Base Account's provider supports all standard methods. See the [provider RPC methods reference](https://docs.base.org/base-account/reference/core/provider-rpc-methods/sdk-overview) for the full list.
diff --git a/skills/build-on-base/references/migrations/farcaster-miniapp-to-app.md b/skills/build-on-base/references/migrations/farcaster-miniapp-to-app.md
deleted file mode 100644
index 9aeb2ac..0000000
--- a/skills/build-on-base/references/migrations/farcaster-miniapp-to-app.md
+++ /dev/null
@@ -1,790 +0,0 @@
-# Convert Farcaster Miniapp to Base App
-
-Convert a Farcaster miniapp into a normal app on Base. The default outcome is a regular web app that works in the Base app browser and on the open web, with Farcaster Mini App host coupling removed.
-
-If some Farcaster functionality truly needs to survive, keep it separate from the main app surface. Prefer read-only data first. Only preserve Mini App-specific behavior when the user explicitly insists, and isolate it behind a dedicated route or page rather than carrying it through the whole app.
-
-## Core Principle
-
-Always separate these decisions:
-
-1. Remove Mini App host/runtime coupling.
-2. Decide whether any Farcaster-facing functionality should remain.
-3. If something remains, keep it isolated from the main app and avoid introducing new vendor dependencies by default.
-
-Do **not** automatically turn "keep some Farcaster functionality" into "migrate to Neynar." If a project already uses Neynar and the user wants to keep an isolated Farcaster-only area, you may preserve that existing integration. Do not introduce new Neynar adoption as the default recommendation.
-
-## Quick Start
-
-Follow these five phases sequentially:
-
-0. **Discovery** — Quick scan + quiz to choose a path
-1. **Analysis** — Detailed read-only analysis scoped to the chosen path
-2. **Conversion** — Remove Mini App SDK patterns and isolate any intentionally preserved Farcaster surface
-3. **Cleanup** — Remove dead code, env vars, and dependencies
-4. **Verification** — Type check, build, and summarize
-
-## Conversion Paths
-
-The quiz should route the user into one of two paths:
-
-| Path | Name | Who it's for | What happens |
-|------|------|-------------|-------------|
-| **A** | Regular App Default | Most projects | Strip Farcaster Mini App coupling and become a normal Base/web app |
-| **B** | Isolated Farcaster Surface | The app still needs a small Farcaster-specific area | Convert the main app into a normal app, then keep only a separate Farcaster route/page for the remaining functionality |
-
-`Path B` is still biased toward removing complexity:
-- Prefer **read-only** Farcaster data.
-- Avoid preserving Mini App host/runtime behavior unless the user explicitly asks for it.
-- Keep any preserved Farcaster logic out of the main app shell, shared providers, and primary auth flow.
-
----
-
-## Phase 0: Discovery
-
-### 0A. Quick Scan (automated, no user interaction)
-
-Run a lightweight scan before asking questions. Produce an internal tally:
-
-1. **Detect framework** from `package.json` (`next`, `vite`, `react-scripts`, `@remix-run/*`)
-2. **Count Farcaster packages** in `dependencies` and `devDependencies`
-3. **Grep source files** (`.ts`, `.tsx`, `.js`, `.jsx`) for:
- - `sdk.actions.*` calls (count total)
- - `sdk.quickAuth` usage (yes/no)
- - `sdk.context` usage (yes/no)
- - `.well-known/farcaster.json` (yes/no)
- - `farcasterMiniApp` / `miniAppConnector` connector (yes/no)
- - Total files with any `@farcaster/` import
- - `@neynar/` imports or `api.neynar.com` fetch calls (yes/no)
-4. **Identify the blast radius**:
- - Are Farcaster references spread across the main app shell?
- - Are they already mostly confined to a route like `app/farcaster/`, `pages/farcaster/`, or a small set of components?
- - Are there obvious host-only features such as haptics, notifications, `openMiniApp`, or `sdk.context.client`?
-
-Use this tally to inform quiz suggestions. Do **not** dump raw scan output to the user before asking the quiz.
-
-### 0B. Interactive Quiz
-
-Ask these questions one at a time. Use the quick scan results to suggest the most likely answer.
-
-**Q1** (always ask):
-
-> Based on my scan, your project has [X] files using the Farcaster SDK with [summary of what is used].
->
-> Which outcome do you want?
-> - **(a) Regular app everywhere** — remove Farcaster-specific behavior and just keep a normal Base/web app
-> - **(b) Regular app first, plus a separate Farcaster area** — keep the main app clean, but preserve a small isolated route/page if really needed
-
-**Q2** (always ask):
-
-> How deeply is the Mini App SDK used today?
-> - **(a) Minimal** — mostly `sdk.actions.ready()` and a few helpers
-> - **(b) Moderate** — some `context`, `openUrl`, profile links, or conditional `isInMiniApp` logic
-> - **(c) Heavy** — auth, wallet connector, notifications, compose flows, or host-specific behavior
-
-**Q3** (ask if Q1 = b):
-
-> What is the smallest Farcaster feature set you actually need to preserve?
-> - **(a) Read-only only** — profile or cast display, links out to Farcaster profiles, maybe a small social page
-> - **(b) Some Farcaster-specific interactions** — there is a separate page/path that still needs more than read-only behavior
-> - **(c) Not sure** — analyze what is isolated already and recommend the smallest keep-surface possible
-
-**Q4** (ask if Q1 = b and there is existing isolated Farcaster code or existing Neynar usage):
-
-> Does the project already have an isolated Farcaster-only route/page or integration that you want to keep as-is if possible?
-> - **(a) Yes** — preserve only that isolated surface
-> - **(b) No** — prefer removing it unless there is a very strong reason to keep it
-
-**Q5** (ask if quick auth or other Mini App auth is present):
-
-> After conversion, what should the main app use for authentication?
-> - **(a) SIWE** — wallet-based auth for the regular app
-> - **(b) Existing non-Farcaster auth** — keep whatever normal web auth already exists
-> - **(c) No auth** — remove auth entirely
-
-### 0C. Path Selection
-
-Map answers to a path:
-
-| Desired outcome | Typical result |
-|-----------------|----------------|
-| `Q1 = regular app everywhere` | **Path A** — Regular App Default |
-| `Q1 = regular app first, plus separate Farcaster area` | **Path B** — Isolated Farcaster Surface |
-
-Then tighten the recommendation:
-
-- If the user chose `Path B`, prefer **read-only preservation** unless they explicitly require something else.
-- If the scan shows heavy host/runtime coupling but the user wants `Path A`, warn them that some features will be deleted rather than recreated.
-- If the project already uses Neynar, only keep it if it remains inside the isolated Farcaster surface. Do not expand it into the main app.
-
-Announce the chosen path:
-
-> Based on your answers, I'll use **Path [X]: [Name]**. This will [one-sentence description]. I'll now do a detailed analysis of your project.
-
-Record the quiz answers internally. They guide whether the agent should:
-- fully remove Farcaster features
-- preserve only a read-only isolated surface
-- quarantine any unavoidable Farcaster-specific logic to a dedicated route/page
-
-**Proceed to Phase 1.**
-
----
-
-## Phase 1: Analysis (Read-Only)
-
-### 1A. Detect Framework
-
-Read `package.json`:
-- `next` → Next.js
-- `vite` → Vite
-- `react-scripts` → Create React App
-- `@remix-run/*` → Remix
-
-### 1B. Scan for Farcaster Dependencies
-
-List all packages matching:
-- `@farcaster/miniapp-sdk`, `@farcaster/miniapp-core`, `@farcaster/miniapp-wagmi-connector`
-- `@farcaster/frame-sdk`, `@farcaster/frame-wagmi-connector`
-- `@farcaster/quick-auth`, `@farcaster/auth-kit`
-- `@neynar/*` (compatibility only; do not assume it stays)
-
-### 1C. Grep for Farcaster Code
-
-Search source files (`.ts`, `.tsx`, `.js`, `.jsx`) for:
-
-**SDK imports:**
-```
-@farcaster/miniapp-sdk
-@farcaster/miniapp-core
-@farcaster/miniapp-wagmi-connector
-@farcaster/frame-sdk
-@farcaster/frame-wagmi-connector
-@farcaster/quick-auth
-@farcaster/auth-kit
-@neynar/
-```
-
-**SDK calls:**
-```
-sdk.actions.ready
-sdk.actions.openUrl
-sdk.actions.close
-sdk.actions.composeCast
-sdk.actions.addMiniApp
-sdk.actions.requestWalletAddress
-sdk.actions.viewProfile
-sdk.actions.viewToken
-sdk.actions.sendToken
-sdk.actions.swapToken
-sdk.actions.signIn
-sdk.actions.setPrimaryButton
-sdk.actions.openMiniApp
-sdk.quickAuth
-sdk.context
-sdk.isInMiniApp
-sdk.getCapabilities
-sdk.haptics
-sdk.back
-sdk.wallet
-```
-
-**Connectors & providers:**
-```
-farcasterMiniApp()
-miniAppConnector()
-farcasterFrame()
-MiniAppProvider
-MiniAppContext
-useMiniApp
-useMiniAppContext
-```
-
-**Manifest & meta:**
-```
-.well-known/farcaster.json
-fc:miniapp
-fc:frame
-```
-
-**Environment variables:**
-```
-NEYNAR_API_KEY
-NEXT_PUBLIC_NEYNAR_CLIENT_ID
-FARCASTER_
-FC_
-```
-
-### 1D. Check Existing Web3 Setup
-
-Look for:
-- `coinbaseWallet` connector in wagmi config
-- SIWE / `siwe` package usage
-- `connectkit`, `rainbowkit`, or `@coinbase/onchainkit`
-- Existing wallet connection UI
-
-### 1E. Check Separation Boundaries
-
-Map where Farcaster logic currently lives:
-
-- Root providers or app shell
-- Shared hooks or auth middleware
-- One-off components
-- Dedicated routes/pages like `app/farcaster/*`
-- Server routes used only by Farcaster functionality
-
-Mark each location with one action:
-- **remove**
-- **stub**
-- **move behind isolated route/page**
-- **keep only as read-only**
-
-### 1F. Report Findings
-
-Create a path-scoped summary.
-
-**All paths include:**
-```
-## Conversion Analysis — Path [X]: [Name]
-
-**Framework:** [detected]
-**Farcaster packages:** [list]
-**Files with Farcaster code:** [count]
-
-### Wagmi Connector
-- File: [path]
-- Current connector: [farcasterMiniApp / miniAppConnector / farcasterFrame / none]
-- Other connectors: [list]
-- Action: [replace with coinbaseWallet / leave existing wallet setup / remove only]
-
-### MiniApp Provider
-- File: [path]
-- Pattern: [simple / complex]
-- Consumers: [files importing from this]
-- Action: [stub / remove / isolate]
-
-### SDK Action Calls
-[list each: file, what it does, action]
-
-### Manifest & Meta
-- Manifest route: [path or N/A]
-- Meta tags: [file or N/A]
-```
-
-**Path A additionally includes:**
-```
-### Main App Outcome
-- Action: remove Farcaster-specific UI and flows from the main app entirely
-
-### Authentication
-- Quick Auth used: [yes/no, file]
-- Action: replace with SIWE / keep existing non-Farcaster auth / remove
-
-### Existing Neynar Usage
-- Package or files: [list or N/A]
-- Action: remove entirely unless the user later re-scopes to Path B
-
-### Environment Variables
-[list all FC/Neynar vars that will be removed]
-```
-
-**Path B additionally includes:**
-```
-### Main App Outcome
-- Action: convert the main app into a normal web app first
-
-### Isolated Farcaster Surface
-- Route/page to keep: [path or proposed path]
-- Scope: [read-only / mixed / host-specific]
-- Recommended target scope: [prefer read-only / quarantine existing behavior / remove]
-
-### Authentication
-- Quick Auth used: [yes/no, file]
-- Main app action: replace with SIWE / keep existing non-Farcaster auth / remove
-- Isolated Farcaster surface action: [remove auth coupling / preserve existing isolated flow only if explicitly requested]
-
-### Existing Neynar Usage
-- Package or files: [list or N/A]
-- Action: [remove / keep only inside isolated surface]
-
-### Environment Variables
-- Remove from main app: [FC_*, FARCASTER_*, etc.]
-- Keep only if isolated surface truly still needs them: [NEYNAR_API_KEY, etc. or N/A]
-```
-
-**All paths end with:**
-```
-### Potential Issues
-- [ ] FID used as database primary key
-- [ ] Farcaster colors in tailwind config
-- [ ] `isInMiniApp` branches with unique else logic
-- [ ] Components only meaningful inside Farcaster
-- [ ] Farcaster code mixed into shared providers or root layout
-```
-
-Ask:
-
-> Does this analysis look correct? Ready to proceed with conversion?
-
-**STOP and wait for user confirmation before Phase 2.**
-
----
-
-## Phase 2: Conversion
-
-Steps are organized by feature area. Each step notes which paths it applies to and what to do differently for `Path B`.
-
-### 2A. Wagmi Connector (All Paths)
-
-Find the wagmi config file (`lib/wagmi.ts`, `config/wagmi.ts`, `providers/wagmi-provider.tsx`, etc.):
-
-1. Remove import of `farcasterMiniApp` or `miniAppConnector` from `@farcaster/miniapp-wagmi-connector`
-2. Remove the `farcasterMiniApp()` / `miniAppConnector()` call from the `connectors` array
-3. If no wallet connector remains, add:
- ```typescript
- import { coinbaseWallet } from "wagmi/connectors";
-
- coinbaseWallet({ appName: "" })
- ```
-4. If `coinbaseWallet` already exists, leave it as-is
-5. Clean up empty lines and stale imports
-
-Skip this step if wagmi is not in the project.
-
-### 2B. MiniApp Provider / Context (All Paths)
-
-If the app has a shared Mini App provider, remove host/runtime assumptions from the main app.
-
-**Pattern A: simple provider**
-
-Replace with a safe stub:
-
-```tsx
-'use client'
-
-import React, { createContext, useContext, useMemo } from "react";
-
-interface MiniAppContextType {
- context: undefined;
- ready: boolean;
- isInMiniApp: boolean;
-}
-
-const MiniAppContext = createContext(undefined);
-
-export function useMiniAppContext() {
- const context = useContext(MiniAppContext);
- if (context === undefined) {
- throw new Error("useMiniAppContext must be used within a MiniAppProvider");
- }
- return context;
-}
-
-export default function MiniAppProvider({ children }: { children: React.ReactNode }) {
- const value = useMemo(
- () => ({
- context: undefined,
- ready: true,
- isInMiniApp: false,
- }),
- []
- );
-
- return {children} ;
-}
-```
-
-Preserve export style and hook names so consumers do not break.
-
-**Pattern B: complex provider**
-
-- If many consumers depend on it, stub it first.
-- If only a few files use it, remove it and update those imports directly.
-- In `Path B`, do not let the isolated Farcaster surface keep this provider wired through the root app shell. If needed, make it local to the isolated route only.
-
-### 2C. Authentication
-
-The main app should use normal web auth, not Mini App auth.
-
-**Default rule for both paths:**
-- If `sdk.quickAuth.getToken()` is used, replace it with SIWE or remove it.
-- If a normal non-Farcaster auth system already exists, prefer that over adding new auth.
-- Do not introduce new Farcaster or Neynar auth as the default conversion target.
-
-#### SIWE Replacement Pattern
-
-**Client-side** (e.g. `useSignIn.ts`):
-- Remove `import sdk from "@farcaster/miniapp-sdk"`
-- Remove `sdk.quickAuth.getToken()`
-- Replace with:
- 1. Get wallet address from `useAccount()` (wagmi)
- 2. Create a SIWE message with `siwe`
- 3. Sign with `useSignMessage()` (wagmi)
- 4. Send signature + message to the backend for verification
-
-**Server-side** (e.g. `app/api/auth/sign-in/route.ts`):
-- Remove `@farcaster/quick-auth` verification
-- Replace with SIWE verification:
- 1. Parse the SIWE message
- 2. Verify the signature with `siwe` or `viem`
- 3. Use recovered wallet address as the normal app identity
-
-**If FID is used as a database primary key:**
-- Do not auto-change schema
-- Add a TODO comment for later migration
-- Warn in Phase 4 summary
-
-**Path B note:**
-- If the isolated Farcaster surface already has its own auth or integration flow and the user explicitly wants to keep it, quarantine it there only.
-- Do not let that flow remain the default app-wide auth system.
-
-### 2D. SDK Action Calls
-
-#### 2D-1. Main replacements for both paths
-
-| Original | Replacement |
-|----------|-------------|
-| `sdk.actions.ready()` | Remove entirely |
-| `sdk.actions.openUrl(url)` | `window.open(url, "_blank")` |
-| `sdk.actions.close()` | `window.close()` or remove |
-| `sdk.actions.composeCast(...)` | Remove from the main app |
-| `sdk.actions.addMiniApp()` | Remove call and UI |
-| `sdk.actions.requestWalletAddress()` | Remove; wagmi handles wallet access |
-| `sdk.actions.viewProfile(fid)` | `window.open(\`https://warpcast.com/~/profiles/${fid}\`, "_blank")` |
-| `sdk.actions.viewToken(opts)` | `window.open(\`https://basescan.org/token/${opts.token}\`, "_blank")` |
-| `sdk.actions.sendToken(opts)` | Replace with wagmi flow if the feature matters, otherwise remove |
-| `sdk.actions.swapToken(opts)` | Remove call and UI unless there is a real app-specific swap feature outside Farcaster |
-| `sdk.actions.signIn(...)` | Remove; auth handled by normal web auth |
-| `sdk.actions.setPrimaryButton(...)` | Remove entirely |
-| `sdk.actions.openMiniApp(...)` | Remove from the main app |
-| `sdk.isInMiniApp()` | Remove conditional and keep the non-Farcaster branch |
-| `sdk.context` | Remove from the main app |
-| `sdk.getCapabilities()` | Remove or replace with `async () => []` |
-| `sdk.haptics.*` | Remove entirely |
-| `sdk.back.*` | Remove entirely |
-| `sdk.wallet.*` | Remove; wagmi handles wallet access |
-
-For conditional `isInMiniApp` branches:
-
-```tsx
-// BEFORE
-if (isInMiniApp) {
- sdk.actions.openUrl(url);
-} else {
- window.open(url, "_blank");
-}
-
-// AFTER
-window.open(url, "_blank");
-```
-
-Always keep the normal web branch.
-
-#### 2D-2. Path B overrides
-
-`Path B` does **not** mean "recreate everything." It means "keep the main app clean and preserve the smallest separate Farcaster surface possible."
-
-- `sdk.context`
- - Remove from the main app
- - For the isolated surface, prefer replacing it with read-only fetched data or explicit route params
- - Remove `context.location`, `context.client`, safe area, and other host-derived assumptions unless the user explicitly insists on preserving a host-specific page
-
-- `sdk.actions.composeCast(...)`
- - Remove from the main app
- - If the user only needs read-only, delete it entirely
- - If they insist on preserving it, keep it isolated behind a dedicated page/path and flag it as a manual follow-up rather than rebuilding it by default
-
-- `sdk.actions.openMiniApp(...)`
- - Remove from the main app
- - Only keep it in an isolated route if the user explicitly wants a Farcaster-only surface
-
-- notifications / haptics / host buttons
- - Remove from the main app
- - Preserve only if the isolated route truly depends on them and the user has explicitly opted into that complexity
-
-### 2E. Optional Read-Only Farcaster Data (Path B only)
-
-If the user wants an isolated Farcaster surface, prefer **read-only** data first.
-
-**Create `lib/farcaster-readonly.ts`** (or equivalent) only if the app needs it:
-
-```typescript
-const HUB_URL = "https://hub.farcaster.xyz";
-
-export async function getUserData(fid: number) {
- const res = await fetch(`${HUB_URL}/v1/userDataByFid?fid=${fid}`);
- if (!res.ok) throw new Error(`Hub user data fetch failed: ${res.status}`);
- return res.json();
-}
-
-export async function getCastsByFid(fid: number, pageSize = 25) {
- const res = await fetch(`${HUB_URL}/v1/castsByFid?fid=${fid}&pageSize=${pageSize}`);
- if (!res.ok) throw new Error(`Hub casts fetch failed: ${res.status}`);
- return res.json();
-}
-```
-
-Then:
-- Keep these calls inside the isolated route/page only
-- Do not thread Farcaster data requirements through the main app shell
-- If the project already has a small isolated Neynar-based read-only integration, you may keep it only if removing it would create more churn than it saves
-- Do not add new Neynar packages for this by default
-
-### 2F. Manifest Route (All Paths)
-
-Delete `.well-known/farcaster.json` route:
-- `app/.well-known/farcaster.json/route.ts`
-- `public/.well-known/farcaster.json`
-- `api/farcaster-manifest.ts` or similar helpers
-
-If the `.well-known` directory becomes empty, delete it.
-
-### 2G. Meta Tags (All Paths)
-
-In root layout or metadata files, remove:
-- ` ` tags with `property="fc:miniapp*"` or `property="fc:frame*"`
-- `Metadata.other` entries that only exist for Farcaster tags
-- `generateMetadata` logic that only produces Mini App metadata
-
-### 2H. Dependencies
-
-**All paths — remove:**
-- `@farcaster/miniapp-sdk`, `@farcaster/miniapp-wagmi-connector`, `@farcaster/miniapp-core`
-- `@farcaster/frame-sdk`, `@farcaster/frame-wagmi-connector`
-- `@farcaster/quick-auth`, `@farcaster/auth-kit`
-
-**Path A — also remove:**
-- `@neynar/nodejs-sdk`, `@neynar/react`
-- any other Neynar packages or helpers
-
-**Path B — remove by default:**
-- `@neynar/nodejs-sdk`, `@neynar/react`, and Neynar helpers unless they remain inside the intentionally isolated Farcaster surface
-
-**All paths — add only if truly needed:**
-- `siwe` if SIWE auth is introduced and not already present
-
-Do not add new Neynar packages as part of the default conversion.
-
-### 2I. Farcaster-Specific Routes & Components
-
-**Path A:**
-- Delete `app/farcaster/`, `pages/farcaster/`, and Farcaster-only components entirely
-- Delete Farcaster-only API routes such as `/api/farcaster/*` and `/api/neynar/*`
-- Remove any navigation links that point to deleted routes
-
-**Path B:**
-- Keep the main app route tree clean
-- Move preserved Farcaster UI behind one dedicated route/page if it is not already isolated
-- Prefer names like `app/farcaster/` or `app/social/` over spreading Farcaster logic throughout generic shared pages
-- Remove any component that has no purpose outside that isolated surface
-- Keep any remaining Neynar usage, if any, confined to that isolated route/page and its server helpers only
-
----
-
-## Phase 3: Cleanup
-
-### 3A. Update `package.json`
-
-**All paths — remove Mini App packages:**
-- `@farcaster/miniapp-sdk`, `@farcaster/miniapp-wagmi-connector`, `@farcaster/miniapp-core`
-- `@farcaster/frame-sdk`, `@farcaster/frame-wagmi-connector`
-- `@farcaster/quick-auth`, `@farcaster/auth-kit`
-
-**Path A — also remove:**
-- all `@neynar/*` packages
-
-**Path B — remove unless still isolated and intentionally preserved:**
-- `@neynar/*`
-
-**All paths — add if introduced:**
-- `siwe`
-
-### 3B. Environment Variables
-
-**Path A — remove from all `.env*` files:**
-- `NEYNAR_API_KEY`
-- `NEXT_PUBLIC_NEYNAR_CLIENT_ID`
-- `FARCASTER_*`
-- `FC_*`
-- `NEXT_PUBLIC_FC_*`
-- `NEXT_PUBLIC_FARCASTER_*`
-
-**Path B — remove from the main app by default:**
-- `FARCASTER_*`
-- `FC_*`
-- `NEXT_PUBLIC_FC_*`
-- `NEXT_PUBLIC_FARCASTER_*`
-
-Only keep `NEYNAR_*` vars if the isolated surface explicitly still depends on existing Neynar integration.
-
-Also update env validation schemas (`env.ts`, `env.mjs`, zod schemas, etc.).
-
-### 3C. Dead Code Cleanup
-
-- Remove unused imports from modified files
-- Remove unused Farcaster types and helper functions
-- Remove empty import statements
-- Remove dead hooks or API wrappers that only existed for the Mini App SDK
-
-### 3D. Tailwind Colors
-
-If `tailwind.config.ts` or `tailwind.config.js` includes Farcaster brand colors such as `farcaster: "#8B5CF6"`, remove them unless that branding is intentionally kept inside an isolated Farcaster-only surface.
-
-### 3E. Install Dependencies
-
-Tell the user:
-
-```bash
-npm install
-```
-
----
-
-## Phase 4: Verification
-
-### 4A. Search for Remaining References
-
-**All paths — search for:**
-```
-@farcaster
-farcasterMiniApp
-miniAppConnector
-sdk.actions
-sdk.quickAuth
-sdk.context
-fc:miniapp
-fc:frame
-```
-
-**Path A — also search for:**
-```
-@neynar
-NEYNAR_API_KEY
-NEXT_PUBLIC_NEYNAR_CLIENT_ID
-api.neynar.com
-```
-
-**Path B — if Neynar was intentionally preserved:**
-- verify that remaining `@neynar` imports and env vars exist only inside the isolated Farcaster surface and its server helpers
-
-Report any source matches, ignoring `node_modules`, lock files, and git history.
-
-### 4B. Type Check
-
-```bash
-npx tsc --noEmit
-```
-
-Report and fix type errors.
-
-### 4C. Build Check
-
-```bash
-npm run build
-```
-
-Report and fix build errors.
-
-### 4D. Conversion Summary
-
-```
-## Conversion Complete — Path [X]: [Name]
-
-**Files modified:** [count]
-**Files deleted:** [count]
-**Files created:** [count, if any]
-**Packages removed:** [list]
-**Packages added:** [list, if any]
-
-### What was done
-- [x] Removed Farcaster Mini App wagmi connector
-- [x] Stubbed or removed Mini App provider/context
-- [x] Replaced Mini App auth with normal web auth or removed it
-- [x] Removed or replaced SDK action calls
-- [x] Deleted manifest route
-- [x] Removed Farcaster meta tags
-- [x] Cleaned up dependencies and env vars
-```
-
-**Path B summary additionally includes:**
-```
-- [x] Kept the main app as a normal web app
-- [x] Confined remaining Farcaster functionality to a dedicated route/page
-- [x] Preferred read-only Farcaster data where possible
-- [x] Removed Farcaster host/runtime coupling from shared app infrastructure
-```
-
-**All paths end with:**
-```
-### Manual steps
-- [ ] Run `npm install`
-- [ ] Test wallet connection flow
-- [ ] If FID migration is needed, migrate from FID-based identity to wallet address
-- [ ] If Path B preserves a Farcaster-only area, verify it stays isolated from the main app shell
-
-### Verification
-- TypeScript: [pass/fail]
-- Build: [pass/fail]
-- Remaining Farcaster references: [none / list]
-```
-
----
-
-## Edge Cases
-
-### No wagmi
-
-Skip Phase 2A. Do not introduce wagmi unless the app actually needs wallet connectivity.
-
-### No auth system
-
-Skip Phase 2C. Do not add SIWE unnecessarily.
-
-### `@farcaster/frame-sdk` (older)
-
-Treat identically to `@farcaster/miniapp-sdk`.
-
-### Monorepo
-
-Ask which workspace(s) to convert. Only modify those.
-
-### FID as database primary key
-
-Do not change schema automatically. Flag it and warn in Phase 4.
-
-### Conditional `isInMiniApp` branches
-
-Always keep the normal web branch and remove the Mini App branch.
-
-### Components with no non-Farcaster purpose
-
-Delete them entirely in `Path A`. In `Path B`, keep them only if they live inside the isolated Farcaster route/page.
-
-### Existing Neynar usage
-
-If the project already uses Neynar:
-- remove it in `Path A`
-- keep it only if it remains inside the isolated `Path B` surface
-- do not add more Neynar usage than already exists unless the user explicitly requests it
-
-### Read-only is usually enough
-
-If the user says they want to "keep Farcaster stuff," bias toward:
-- profile links
-- read-only profile or cast display
-- a dedicated social page
-
-Do not assume they want write access, notifications, or host/runtime behavior.
-
-### Quiz ambiguity
-
-If the scan and quiz conflict, point it out and ask the user to confirm the smaller keep-surface first.
-
----
-
-## Security
-
-- **Validate wallet setup** — ensure `coinbaseWallet` or the chosen wallet connector is configured correctly
-- **FID-based identity** — requires manual database migration if used as a primary key
-- **SIWE verification** — verify signatures server-side before trusting them
-- **Preserved isolated surface** — do not let a Farcaster-only route/page leak host/runtime assumptions into the main app shell
-- **Existing Neynar usage** — keep API keys server-side only, and only if that isolated surface still depends on them
\ No newline at end of file
diff --git a/skills/build-on-base/references/migrations/minikit-to-farcaster/auth.md b/skills/build-on-base/references/migrations/minikit-to-farcaster/auth.md
deleted file mode 100644
index 8bb9bc3..0000000
--- a/skills/build-on-base/references/migrations/minikit-to-farcaster/auth.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# Quick Auth Migration
-
-`useAuthenticate` → `sdk.quickAuth`
-
-## Client
-
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-// Make authenticated request (recommended)
-const res = await sdk.quickAuth.fetch('/api/auth');
-
-// Or get token directly
-const { token } = await sdk.quickAuth.getToken();
-```
-
-## Server (Next.js)
-
-```bash
-npm install @farcaster/quick-auth
-```
-
-```typescript
-// app/api/auth/route.ts
-import { createClient } from '@farcaster/quick-auth';
-import { NextRequest, NextResponse } from 'next/server';
-
-const client = createClient();
-
-export async function GET(request: NextRequest) {
- const auth = request.headers.get('Authorization');
- if (!auth?.startsWith('Bearer ')) {
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
- }
-
- try {
- const payload = await client.verifyJwt({
- token: auth.split(' ')[1],
- domain: 'your-domain.com',
- });
- return NextResponse.json({ fid: payload.sub });
- } catch {
- return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
- }
-}
-```
-
-See [Farcaster Quick Auth docs](https://miniapps.farcaster.xyz/docs/sdk/quick-auth).
diff --git a/skills/build-on-base/references/migrations/minikit-to-farcaster/dependencies.md b/skills/build-on-base/references/migrations/minikit-to-farcaster/dependencies.md
deleted file mode 100644
index e897688..0000000
--- a/skills/build-on-base/references/migrations/minikit-to-farcaster/dependencies.md
+++ /dev/null
@@ -1,54 +0,0 @@
-# Dependencies
-
-## Requirements
-
-- Node.js 22.11.0+
-- Farcaster SDK 0.2.0+ (breaking changes from 0.1.x)
-
-## Quick Install
-
-```bash
-npm uninstall @coinbase/onchainkit && \
-npm install @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector wagmi viem @tanstack/react-query
-```
-
-## package.json
-
-```json
-{
- "engines": { "node": ">=22.11.0" },
- "dependencies": {
- "@farcaster/miniapp-sdk": "^0.3.0",
- "@farcaster/miniapp-wagmi-connector": "^0.0.15",
- "@tanstack/react-query": "^5.50.0",
- "viem": "^2.17.0",
- "wagmi": "^2.12.0"
- }
-}
-```
-
-## Check Version
-
-```bash
-npm list @farcaster/miniapp-sdk
-```
-
-## Common Errors
-
-**Peer dependency conflict:**
-```bash
-npm install @farcaster/miniapp-sdk@^0.3.0 @farcaster/miniapp-wagmi-connector@^0.0.15
-```
-
-**Node.js too old:**
-```bash
-nvm install 22 && nvm use 22
-```
-
-## Optional: Server Auth
-
-```bash
-npm install @farcaster/quick-auth
-```
-
-See [AUTH.md](AUTH.md) for server-side token verification.
diff --git a/skills/build-on-base/references/migrations/minikit-to-farcaster/examples.md b/skills/build-on-base/references/migrations/minikit-to-farcaster/examples.md
deleted file mode 100644
index 0cb6ffe..0000000
--- a/skills/build-on-base/references/migrations/minikit-to-farcaster/examples.md
+++ /dev/null
@@ -1,202 +0,0 @@
-# Conversion Examples
-
-## Contents
-- Social actions
-- User profile
-- App initialization
-- Primary button
-- Sign-in flow
-- Safe area insets
-
----
-
-## Social Actions
-
-**Before:**
-```typescript
-import { useClose, useOpenUrl, useViewProfile } from '@coinbase/onchainkit/minikit';
-
-function Actions({ fid }) {
- const close = useClose();
- const viewProfile = useViewProfile();
- return (
- <>
- viewProfile(fid)}>Profile
- Close
- >
- );
-}
-```
-
-**After:**
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-function Actions({ fid }) {
- return (
- <>
- sdk.actions.viewProfile({ fid })}>Profile
- sdk.actions.close()}>Close
- >
- );
-}
-```
-
----
-
-## User Profile
-
-**Before:**
-```typescript
-const { context } = useMiniKit();
-const { fid, username } = context?.user ?? {};
-```
-
-**After:**
-```typescript
-const [user, setUser] = useState(null);
-
-useEffect(() => {
- const load = async () => {
- const ctx = await sdk.context;
- setUser(ctx?.user);
- };
- load();
-}, []);
-
-const { fid, username } = user ?? {};
-```
-
-Or use FrameProvider (see [PROVIDER.md](PROVIDER.md)):
-```typescript
-import { useFrameContext } from '@/components/providers/FrameProvider';
-
-const frameContext = useFrameContext();
-const { fid, username } = frameContext?.context?.user ?? {};
-```
-
----
-
-## App Initialization
-
-**Before:**
-```typescript
-const { setFrameReady, context, isSDKLoaded } = useMiniKit();
-
-useEffect(() => {
- if (isSDKLoaded) setFrameReady();
-}, [isSDKLoaded]);
-```
-
-**After:**
-```typescript
-const [ready, setReady] = useState(false);
-const [context, setContext] = useState(null);
-
-useEffect(() => {
- const init = async () => {
- const inMiniApp = await sdk.isInMiniApp();
- if (inMiniApp) {
- const ctx = await sdk.context;
- setContext(ctx);
- await sdk.actions.ready();
- }
- setReady(true);
- };
- init();
-}, []);
-```
-
----
-
-## Primary Button (Breaking Change)
-
-**Before:**
-```typescript
-usePrimaryButton(
- { text: `Clicked ${count}`, disabled: false },
- () => setCount(c => c + 1)
-);
-```
-
-**After (no callback support):**
-```typescript
-useEffect(() => {
- const setup = async () => {
- await sdk.actions.setPrimaryButton({
- text: "Action",
- disabled: false,
- hidden: false,
- loading: false
- });
- };
- setup();
-}, []);
-
-// Use regular React buttons for click handling
-```
-
----
-
-## Sign-In Flow
-
-**Before:**
-```typescript
-const { signIn } = useAuthenticate();
-const result = await signIn({ nonce });
-if (result === false) { /* failed */ }
-```
-
-**After (Quick Auth):**
-```typescript
-const { token } = await sdk.quickAuth.getToken();
-await fetch('/api/auth', {
- headers: { Authorization: `Bearer ${token}` }
-});
-```
-
-Or use authenticated fetch:
-```typescript
-const res = await sdk.quickAuth.fetch('/api/auth');
-```
-
----
-
-## Safe Area Insets
-
-**Before:**
-```typescript
-const { context } = useMiniKit();
-const insets = context?.client?.safeAreaInsets;
-```
-
-**After:**
-```typescript
-const [insets, setInsets] = useState(null);
-
-useEffect(() => {
- const load = async () => {
- const ctx = await sdk.context;
- setInsets(ctx?.client?.safeAreaInsets);
- };
- load();
-}, []);
-```
-
----
-
-## Add Mini App
-
-**Before:**
-```typescript
-const addFrame = useAddFrame();
-const result = await addFrame();
-```
-
-**After:**
-```typescript
-const result = await sdk.actions.addMiniApp();
-if (result) {
- saveTokenToServer(result.url, result.token);
-}
-```
diff --git a/skills/build-on-base/references/migrations/minikit-to-farcaster/manifest.md b/skills/build-on-base/references/migrations/minikit-to-farcaster/manifest.md
deleted file mode 100644
index f2c5f17..0000000
--- a/skills/build-on-base/references/migrations/minikit-to-farcaster/manifest.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# Manifest Migration
-
-Change root key from `frame` to `miniapp` in `/.well-known/farcaster.json`.
-
-## Before
-
-```typescript
-return Response.json({
- accountAssociation: { ... },
- frame: {
- version: "1",
- name: "My App",
- ...
- }
-});
-```
-
-## After
-
-```typescript
-return Response.json({
- accountAssociation: { ... },
- miniapp: {
- version: "1",
- name: "My App",
- homeUrl: "https://yourapp.com",
- iconUrl: "https://yourapp.com/icon.png",
- splashImageUrl: "https://yourapp.com/splash.png",
- splashBackgroundColor: "#000000",
- // Optional
- subtitle: "Short tagline",
- description: "Longer description",
- primaryCategory: "utilities",
- webhookUrl: "https://yourapp.com/api/webhook",
- }
-});
-```
-
-## Required Fields
-
-- `version`: Always `"1"`
-- `name`: App name (max 32 chars)
-- `homeUrl`: Main app URL
-- `iconUrl`: 1:1 ratio, min 200x200
-- `splashImageUrl`: 1:1 ratio
-- `splashBackgroundColor`: Hex color
-
-## Categories
-
-`games` | `social` | `finance` | `utilities` | `productivity` | `entertainment` | `news` | `shopping` | `health` | `education`
diff --git a/skills/build-on-base/references/migrations/minikit-to-farcaster/mapping.md b/skills/build-on-base/references/migrations/minikit-to-farcaster/mapping.md
deleted file mode 100644
index a1e24a3..0000000
--- a/skills/build-on-base/references/migrations/minikit-to-farcaster/mapping.md
+++ /dev/null
@@ -1,452 +0,0 @@
-# MiniKit to Farcaster SDK Mapping
-
-Complete reference for converting each MiniKit hook to Farcaster SDK calls.
-
-## Table of Contents
-
-- [Import Changes](#import-changes)
-- [useMiniKit](#useminikit)
-- [useClose](#useclose)
-- [useOpenUrl](#useopenurl)
-- [useViewProfile](#useviewprofile)
-- [useViewCast](#useviewcast)
-- [useComposeCast](#usecomposecast)
-- [useAddFrame](#useaddframe)
-- [useAuthenticate](#useauthenticate)
-- [useNotification](#usenotification)
-
----
-
-## Import Changes
-
-### Before (MiniKit)
-```typescript
-import {
- useMiniKit,
- useClose,
- useOpenUrl,
- useViewProfile,
- useViewCast,
- useComposeCast,
- useAddFrame,
- useAuthenticate
-} from '@coinbase/onchainkit/minikit';
-```
-
-### After (Farcaster SDK)
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-```
-
-**Note**: All hooks become direct SDK method calls. No React hooks needed.
-
----
-
-## useMiniKit
-
-The main hook that provides context and ready signal.
-
-### Before (MiniKit)
-```typescript
-import { useMiniKit } from '@coinbase/onchainkit/minikit';
-
-function App() {
- const { setFrameReady, isFrameReady, context } = useMiniKit();
-
- useEffect(() => {
- if (!isFrameReady) {
- setFrameReady();
- }
- }, [setFrameReady, isFrameReady]);
-
- // Access user info
- const userFid = context?.user?.fid;
- const username = context?.user?.username;
-
- return Hello {username}
;
-}
-```
-
-### After (Farcaster SDK)
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-function App() {
- const [isReady, setIsReady] = useState(false);
- const [context, setContext] = useState(null);
-
- useEffect(() => {
- const init = async () => {
- // Get context first (must await - it's a Promise)
- const context = await sdk.context;
- setContext(context);
-
- // Signal ready to hide splash screen
- await sdk.actions.ready();
- setIsReady(true);
- };
- init();
- }, []);
-
- // Access user info
- const userFid = context?.user?.fid;
- const username = context?.user?.username;
-
- return Hello {username}
;
-}
-```
-
-### Context Structure (Same for Both)
-```typescript
-type MiniAppContext = {
- user: {
- fid: number;
- username?: string;
- displayName?: string;
- pfpUrl?: string;
- };
- client: {
- clientFid: number;
- added: boolean;
- notificationDetails?: {
- url: string;
- token: string;
- };
- safeAreaInsets?: {
- top: number;
- bottom: number;
- left: number;
- right: number;
- };
- };
- location?: LocationContext;
-};
-```
-
----
-
-## useClose
-
-Closes the mini app.
-
-### Before (MiniKit)
-```typescript
-import { useClose } from '@coinbase/onchainkit/minikit';
-
-function CloseButton() {
- const close = useClose();
-
- return Close App ;
-}
-```
-
-### After (Farcaster SDK)
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-function CloseButton() {
- const handleClose = async () => {
- await sdk.actions.close();
- };
-
- return Close App ;
-}
-```
-
----
-
-## useOpenUrl
-
-Opens an external URL in the browser.
-
-### Before (MiniKit)
-```typescript
-import { useOpenUrl } from '@coinbase/onchainkit/minikit';
-
-function LinkButton() {
- const openUrl = useOpenUrl();
-
- const handleClick = () => {
- openUrl('https://example.com');
- };
-
- return Visit Site ;
-}
-```
-
-### After (Farcaster SDK)
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-function LinkButton() {
- const handleClick = async () => {
- await sdk.actions.openUrl('https://example.com');
- };
-
- return Visit Site ;
-}
-```
-
----
-
-## useViewProfile
-
-Opens a Farcaster user's profile.
-
-### Before (MiniKit)
-```typescript
-import { useViewProfile } from '@coinbase/onchainkit/minikit';
-
-function ProfileLink({ fid }) {
- const viewProfile = useViewProfile();
-
- const handleClick = () => {
- viewProfile(fid);
- };
-
- return View Profile ;
-}
-```
-
-### After (Farcaster SDK)
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-function ProfileLink({ fid }) {
- const handleClick = async () => {
- await sdk.actions.viewProfile({ fid });
- };
-
- return View Profile ;
-}
-```
-
-**Note**: The SDK requires an object with `fid` property, not just the fid directly.
-
----
-
-## useViewCast
-
-Opens a specific cast.
-
-### Before (MiniKit)
-```typescript
-import { useViewCast } from '@coinbase/onchainkit/minikit';
-
-function CastLink({ hash }) {
- const viewCast = useViewCast();
-
- const handleClick = () => {
- viewCast(hash);
- };
-
- return View Cast ;
-}
-```
-
-### After (Farcaster SDK)
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-function CastLink({ hash }) {
- const handleClick = async () => {
- await sdk.actions.viewCast({ hash });
- };
-
- return View Cast ;
-}
-```
-
----
-
-## useComposeCast
-
-Opens the cast composer with prefilled content.
-
-### Before (MiniKit)
-```typescript
-import { useComposeCast } from '@coinbase/onchainkit/minikit';
-
-function ShareButton() {
- const { composeCast } = useComposeCast();
-
- const handleShare = () => {
- composeCast({
- text: 'Check out this app!',
- embeds: ['https://myapp.com']
- });
- };
-
- return Share ;
-}
-```
-
-### After (Farcaster SDK)
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-function ShareButton() {
- const handleShare = async () => {
- const result = await sdk.actions.composeCast({
- text: 'Check out this app!',
- embeds: ['https://myapp.com']
- });
-
- // result.cast contains the posted cast if successful
- if (result?.cast) {
- console.log('Cast posted:', result.cast.hash);
- }
- };
-
- return Share ;
-}
-```
-
-### Full Options
-```typescript
-await sdk.actions.composeCast({
- text: string; // Suggested text (user can modify)
- embeds?: string[]; // URLs to embed (max 2)
- parent?: { // Reply to a cast
- hash: string;
- };
- channelKey?: string; // Post to a channel
- close?: boolean; // Close app after posting
-});
-```
-
----
-
-## useAddFrame
-
-Prompts user to add/save the mini app.
-
-### Before (MiniKit)
-```typescript
-import { useAddFrame } from '@coinbase/onchainkit/minikit';
-
-function SaveButton() {
- const addFrame = useAddFrame();
-
- const handleAdd = async () => {
- const result = await addFrame();
- if (result) {
- console.log('Added! Token:', result.token);
- // Save result.url and result.token for notifications
- }
- };
-
- return Save App ;
-}
-```
-
-### After (Farcaster SDK)
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-function SaveButton() {
- const handleAdd = async () => {
- const result = await sdk.actions.addMiniApp();
- if (result) {
- console.log('Added! Token:', result.token);
- // Save result.url and result.token for notifications
- }
- };
-
- return Save App ;
-}
-```
-
----
-
-## useAuthenticate
-
-Authenticates the user with Sign In with Farcaster.
-
-### Before (MiniKit)
-```typescript
-import { useAuthenticate } from '@coinbase/onchainkit/minikit';
-
-function AuthButton() {
- const authenticate = useAuthenticate();
-
- const handleAuth = async () => {
- const result = await authenticate();
- if (result) {
- // Send result to your backend for verification
- await verifyOnBackend(result);
- }
- };
-
- return Sign In ;
-}
-```
-
-### After (Farcaster SDK)
-```typescript
-import { sdk } from '@farcaster/miniapp-sdk';
-
-function AuthButton() {
- const handleAuth = async () => {
- const result = await sdk.actions.signIn({
- // Optional: specify nonce for verification
- nonce: 'your-random-nonce'
- });
-
- if (result) {
- // result contains signature for verification
- await verifyOnBackend(result);
- }
- };
-
- return Sign In ;
-}
-```
-
-**Important**: Always verify the signature on your backend for security-critical operations.
-
----
-
-## useNotification
-
-Notifications require server-side implementation. See [NOTIFICATIONS.md](NOTIFICATIONS.md) for details.
-
-### Before (MiniKit)
-```typescript
-import { useNotification } from '@coinbase/onchainkit/minikit';
-
-function NotifyButton() {
- const sendNotification = useNotification();
-
- const handleNotify = async () => {
- await sendNotification({
- title: 'Hello!',
- body: 'You have a new message'
- });
- };
-
- return Send Test ;
-}
-```
-
-### After (Farcaster SDK)
-Notifications are sent via webhook from your server, not from the client.
-
-```typescript
-// Client: Just trigger your backend
-function NotifyButton() {
- const handleNotify = async () => {
- await fetch('/api/send-notification', {
- method: 'POST',
- body: JSON.stringify({
- title: 'Hello!',
- body: 'You have a new message'
- })
- });
- };
-
- return Send Test ;
-}
-```
-
-See [NOTIFICATIONS.md](NOTIFICATIONS.md) for server-side implementation.
diff --git a/skills/build-on-base/references/migrations/minikit-to-farcaster/overview.md b/skills/build-on-base/references/migrations/minikit-to-farcaster/overview.md
deleted file mode 100644
index 8acb2b2..0000000
--- a/skills/build-on-base/references/migrations/minikit-to-farcaster/overview.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# MiniKit to Farcaster SDK
-
-Converts Mini Apps from MiniKit (OnchainKit) to native Farcaster SDK.
-
-## Breaking Changes (SDK v0.2.0+)
-
-1. `sdk.context` is a **Promise** — must await
-2. `sdk.isInMiniApp()` accepts **no parameters**
-3. `sdk.actions.setPrimaryButton()` has no onClick callback
-
-Check version: `npm list @farcaster/miniapp-sdk`
-
-## Quick Reference
-
-| MiniKit | Farcaster SDK | Notes |
-|---------|---------------|-------|
-| `useMiniKit().setFrameReady()` | `await sdk.actions.ready()` | |
-| `useMiniKit().context` | `await sdk.context` | **Async** |
-| `useMiniKit().isSDKLoaded` | `await sdk.isInMiniApp()` | No params |
-| `useClose()` | `await sdk.actions.close()` | |
-| `useOpenUrl(url)` | `await sdk.actions.openUrl(url)` | |
-| `useViewProfile(fid)` | `await sdk.actions.viewProfile({ fid })` | |
-| `useViewCast(hash)` | `await sdk.actions.viewCast({ hash })` | |
-| `useComposeCast()` | `await sdk.actions.composeCast({ text, embeds })` | |
-| `useAddFrame()` | `await sdk.actions.addMiniApp()` | |
-| `usePrimaryButton(opts, cb)` | `await sdk.actions.setPrimaryButton(opts)` | No callback |
-| `useAuthenticate()` | `sdk.quickAuth.getToken()` | See [auth.md](auth.md) |
-
-## Context Access Pattern
-
-```typescript
-// WRONG
-const fid = sdk.context?.user?.fid;
-
-// CORRECT
-const context = await sdk.context;
-const fid = context?.user?.fid;
-```
-
-In React components, use state:
-
-```typescript
-const [context, setContext] = useState(null);
-
-useEffect(() => {
- const load = async () => {
- const ctx = await sdk.context;
- setContext(ctx);
- };
- load();
-}, []);
-```
-
-## Conversion Workflow
-
-1. Verify Node.js >= 22.11.0
-2. Update dependencies — see [dependencies.md](dependencies.md)
-3. Replace imports: `@coinbase/onchainkit/minikit` → `@farcaster/miniapp-sdk`
-4. Convert hooks using reference above
-5. Add FrameProvider — see [provider.md](provider.md)
-6. Update manifest: `frame` → `miniapp` — see [manifest.md](manifest.md)
-
-## Common Errors
-
-**"Property 'user' does not exist on type 'Promise'"**
-→ Await `sdk.context` before accessing properties
-
-**"Expected 0 arguments, but got 1"**
-→ Remove parameters from `sdk.isInMiniApp()`
-
-**Context is null in components**
-→ Ensure FrameProvider is in your provider chain
-
-## References
-
-- [mapping.md](mapping.md) — Complete hook-by-hook conversion reference
-- [examples.md](examples.md) — Before/after code examples
-- [provider.md](provider.md) — Provider setup with FrameProvider
-- [pitfalls.md](pitfalls.md) — Common errors and solutions
-- [dependencies.md](dependencies.md) — Package updates
-- [auth.md](auth.md) — Quick Auth migration
-- [manifest.md](manifest.md) — farcaster.json changes
-
-## Helper Scripts
-
-Two Python scripts are bundled at `scripts/` (skill root) to automate analysis and validation:
-
-- **`scripts/analyze_project.py `** — Scans all source files and reports every MiniKit import, hook usage, and provider location. Run before starting conversion to understand blast radius.
-- **`scripts/validate_conversion.py `** — Validates the converted project: no remaining MiniKit imports/hooks/providers, Farcaster SDK wired correctly, `package.json` updated, manifest uses `miniapp` key. Exit code 0 = pass, 1 = errors found.
diff --git a/skills/build-on-base/references/migrations/minikit-to-farcaster/pitfalls.md b/skills/build-on-base/references/migrations/minikit-to-farcaster/pitfalls.md
deleted file mode 100644
index eacd8d6..0000000
--- a/skills/build-on-base/references/migrations/minikit-to-farcaster/pitfalls.md
+++ /dev/null
@@ -1,225 +0,0 @@
-# Common Pitfalls & Errors
-
-## Contents
-- Type errors (sdk.context, isInMiniApp, setPrimaryButton)
-- Runtime issues (context null, detection fails)
-- React patterns (useEffect with async)
-- Sign-in migration
-
----
-
-## Type Errors
-
-### "Property 'user' does not exist on type 'Promise'"
-
-Accessing `sdk.context` without awaiting.
-
-```typescript
-// WRONG
-const fid = sdk.context?.user?.fid;
-
-// CORRECT
-const context = await sdk.context;
-const fid = context?.user?.fid;
-```
-
-### "Expected 0 arguments, but got 1"
-
-Passing parameters to `sdk.isInMiniApp()`.
-
-```typescript
-// WRONG
-await sdk.isInMiniApp({ timeoutMs: 500 });
-
-// CORRECT
-await sdk.isInMiniApp();
-```
-
-Custom timeout workaround:
-```typescript
-const checkWithTimeout = async (ms = 5000) => {
- try {
- return await Promise.race([
- sdk.isInMiniApp(),
- new Promise((_, r) => setTimeout(() => r(new Error('Timeout')), ms))
- ]);
- } catch {
- return false;
- }
-};
-```
-
-### "Type 'Promise' is not assignable..."
-
-Assigning `sdk.context` to state without awaiting.
-
-```typescript
-// WRONG
-const context = sdk.context;
-setFrameContext({ context, isInMiniApp: true });
-
-// CORRECT
-const context = await sdk.context;
-setFrameContext({ context, isInMiniApp: true });
-```
-
-### "'onClick' does not exist in type 'SetPrimaryButtonOptions'"
-
-`setPrimaryButton` no longer supports callbacks.
-
-```typescript
-// WRONG (MiniKit pattern)
-usePrimaryButton(
- { text: "Click" },
- () => handleClick()
-);
-
-// CORRECT - state only, no callback
-await sdk.actions.setPrimaryButton({
- text: "Click",
- disabled: false,
- hidden: false,
- loading: false
-});
-```
-
-For click handling, use regular React buttons.
-
----
-
-## Runtime Issues
-
-### isInMiniApp returns false unexpectedly
-
-Possible causes:
-- Not running in iframe or React Native WebView
-- Server-side rendering (detection is client-side only)
-- Missing `'use client'` directive
-
-### Context is null in components
-
-FrameProvider not in provider chain.
-
-```typescript
-// WRONG
-export function Providers({ children }) {
- return {children} ;
-}
-
-// CORRECT
-export function Providers({ children }) {
- return (
-
- {children}
-
- );
-}
-```
-
-### Context is null even when isInMiniApp is true
-
-Not awaiting `sdk.context`:
-
-```typescript
-// WRONG
-const context = sdk.context; // Promise, not data
-
-// CORRECT
-const context = await sdk.context;
-```
-
----
-
-## React Patterns
-
-### Async useEffect
-
-```typescript
-// WRONG - returns Promise
-useEffect(async () => {
- await sdk.actions.ready();
-}, []);
-
-// CORRECT - wrap in function
-useEffect(() => {
- const init = async () => {
- await sdk.actions.ready();
- };
- init();
-}, []);
-```
-
-### Loading context in components
-
-```typescript
-function MyComponent() {
- const [context, setContext] = useState(null);
- const [loading, setLoading] = useState(true);
-
- useEffect(() => {
- const load = async () => {
- try {
- const isInMiniApp = await sdk.isInMiniApp();
- if (isInMiniApp) {
- const ctx = await sdk.context;
- setContext(ctx);
- }
- } finally {
- setLoading(false);
- }
- };
- load();
- }, []);
-
- if (loading) return null;
- return {context?.user?.fid}
;
-}
-```
-
----
-
-## Sign-In Migration
-
-### "This comparison appears to be unintentional..."
-
-`signIn` returns `SignInResult`, not boolean.
-
-```typescript
-// WRONG (MiniKit pattern)
-const result = await signIn({ nonce });
-if (result === false) { ... }
-
-// CORRECT
-const result = await sdk.actions.signIn({ nonce });
-if (!result) {
- // Sign-in cancelled or failed
-}
-```
-
-For SDK v0.2.0+, prefer Quick Auth:
-
-```typescript
-const { token } = await sdk.quickAuth.getToken();
-// Or use authenticated fetch
-const res = await sdk.quickAuth.fetch('/api/auth');
-```
-
----
-
-## Validation Commands
-
-After conversion, verify:
-
-```bash
-# No MiniKit imports remaining
-grep -r "@coinbase/onchainkit/minikit" src/
-
-# Check sdk.context usage (should be awaited)
-grep -r "sdk\.context" src/
-
-# Check isInMiniApp calls (no parameters)
-grep -r "isInMiniApp(" src/
-
-# Build and type check
-npm run build && npx tsc --noEmit
-```
diff --git a/skills/build-on-base/references/migrations/minikit-to-farcaster/provider.md b/skills/build-on-base/references/migrations/minikit-to-farcaster/provider.md
deleted file mode 100644
index df9b597..0000000
--- a/skills/build-on-base/references/migrations/minikit-to-farcaster/provider.md
+++ /dev/null
@@ -1,170 +0,0 @@
-# Provider Migration
-
-Remove `MiniKitProvider`, add FrameProvider and Wagmi setup.
-
-## Contents
-- FrameProvider setup
-- Wagmi provider setup
-- Combined providers
-- Usage in components
-
----
-
-## Step 1: Create FrameProvider
-
-`src/components/providers/FrameProvider.tsx`:
-
-```typescript
-'use client'
-
-import { sdk } from '@farcaster/miniapp-sdk';
-import { createContext, useContext, useEffect, useState, ReactNode } from "react";
-
-type FrameContextType = {
- context: any;
- isInMiniApp: boolean;
-} | null;
-
-const FrameContext = createContext(null);
-
-export const useFrameContext = () => useContext(FrameContext);
-
-export default function FrameProvider({ children }: { children: ReactNode }) {
- const [frameContext, setFrameContext] = useState(null);
-
- useEffect(() => {
- const init = async () => {
- try {
- // No parameters in v0.2.0+
- const isInMiniApp = await sdk.isInMiniApp();
-
- if (isInMiniApp) {
- // Must await - context is a Promise
- const context = await sdk.context;
- setFrameContext({ context, isInMiniApp: true });
- } else {
- setFrameContext({ context: null, isInMiniApp: false });
- }
- } catch (error) {
- console.error('FrameProvider init error:', error);
- setFrameContext({ context: null, isInMiniApp: false });
- }
- };
- init();
- }, []);
-
- return (
-
- {children}
-
- );
-}
-```
-
----
-
-## Step 2: Create Wagmi Provider
-
-`src/components/providers/WagmiProvider.tsx`:
-
-```typescript
-'use client'
-
-import { createConfig, http, WagmiProvider as WagmiBase } from 'wagmi';
-import { base } from 'wagmi/chains';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { farcasterMiniApp } from '@farcaster/miniapp-wagmi-connector';
-import { ReactNode, useState } from 'react';
-
-const config = createConfig({
- chains: [base],
- transports: { [base.id]: http() },
- connectors: [farcasterMiniApp()],
-});
-
-export default function WagmiProvider({ children }: { children: ReactNode }) {
- const [queryClient] = useState(() => new QueryClient());
-
- return (
-
-
- {children}
-
-
- );
-}
-```
-
----
-
-## Step 3: Combine Providers
-
-`src/app/providers.tsx`:
-
-```typescript
-'use client'
-
-import { ReactNode } from 'react';
-import FrameProvider from '@/components/providers/FrameProvider';
-import WagmiProvider from '@/components/providers/WagmiProvider';
-
-export function Providers({ children }: { children: ReactNode }) {
- return (
-
-
- {children}
-
-
- );
-}
-```
-
----
-
-## Step 4: Use in Layout
-
-`src/app/layout.tsx`:
-
-```typescript
-import { Providers } from './providers';
-
-export default function RootLayout({ children }) {
- return (
-
-
- {children}
-
-
- );
-}
-```
-
----
-
-## Using the Context
-
-```typescript
-import { useFrameContext } from '@/components/providers/FrameProvider';
-
-function MyComponent() {
- const frameContext = useFrameContext();
-
- if (!frameContext) return Loading...
;
- if (!frameContext.isInMiniApp) return Open in Farcaster
;
-
- return Welcome {frameContext.context?.user?.displayName}
;
-}
-```
-
----
-
-## Remove Old Imports
-
-```typescript
-// Delete these
-import { MiniKitProvider } from '@coinbase/onchainkit/minikit';
-import '@coinbase/onchainkit/styles.css';
-
-// Delete from .env
-NEXT_PUBLIC_ONCHAINKIT_API_KEY=xxx
-```
diff --git a/skills/build-on-base/references/migrations/onchainkit/overview.md b/skills/build-on-base/references/migrations/onchainkit/overview.md
deleted file mode 100644
index ed59077..0000000
--- a/skills/build-on-base/references/migrations/onchainkit/overview.md
+++ /dev/null
@@ -1,131 +0,0 @@
-# OnchainKit Migration
-
-Migrate apps from `@coinbase/onchainkit` to standalone `wagmi`/`viem` components with zero OnchainKit dependency.
-
-## Before Starting
-
-Create a `mistakes.md` file in the project root:
-
-```markdown
-# Migration Mistakes & Learnings
-
-Track errors, fixes, and lessons learned during OnchainKit migration.
-
-## Errors
-
-## Lessons Learned
-```
-
-Append every error, fix, and lesson to this file throughout the migration. This file improves the skill over time.
-
-## Migration Workflow
-
-Migrations MUST happen in this order. Do not skip steps.
-
-### Step 1: Detection
-
-Scan the project to understand current OnchainKit usage:
-
-1. Search all files for `@coinbase/onchainkit` imports
-2. Identify which components are used:
- - **Provider**: `OnchainKitProvider` (always present if using OnchainKit)
- - **Wallet**: `Wallet`, `ConnectWallet`, `WalletDropdown`, `WalletModal`, `Connected`
- - **Transaction**: `Transaction`, `TransactionButton`, `TransactionStatus`
- - **Other**: `Identity`, `Avatar`, `Name`, `Address`, `Swap`, `Checkout`, etc.
-3. Check `package.json` for existing `wagmi`, `viem`, `@tanstack/react-query` dependencies
-4. Identify the project's styling approach (Tailwind, CSS Modules, styled-components, etc.)
-5. Report findings to the user before proceeding
-
-### Step 2: Provider Migration (always first)
-
-Read [provider.md](provider.md) for detailed instructions and code.
-
-Summary:
-1. Ensure `wagmi`, `viem`, and `@tanstack/react-query` are installed
-2. Create `wagmi-config.ts` with chain and connector configuration
-3. Replace `OnchainKitProvider` with `WagmiProvider` + `QueryClientProvider`
-4. Remove `@coinbase/onchainkit/styles.css` import
-5. Remove any `SafeArea` or MiniKit imports from layout files
-
-**Validation gate**: Run `npm run build` (or the project's build command). Must pass before continuing. If it fails, fix errors and document them in `mistakes.md`.
-
-### Step 3: Wallet Migration (after provider)
-
-Read [wallet.md](wallet.md) for detailed instructions and code.
-
-Summary:
-1. Create a `WalletConnect` component using wagmi hooks (`useAccount`, `useConnect`, `useDisconnect`)
-2. Component includes a modal with wallet options: Base Account, Coinbase Wallet, MetaMask
-3. Shows truncated address + disconnect button when connected
-4. Replace all OnchainKit wallet imports and component usage
-
-**Validation gate**: Run `npm run build`. Must pass before continuing. Document any errors in `mistakes.md`.
-
-### Step 4: Transaction Migration (after wallet)
-
-Read [transaction.md](transaction.md) for detailed instructions and code.
-
-Summary:
-1. Check the `chainId` prop on existing ` ` components -- add any missing chains to `wagmi-config.ts`
-2. Create a `TransactionForm` component using wagmi hooks (`useWriteContract`, `useWaitForTransactionReceipt`, `useSwitchChain`)
-3. Component handles the full lifecycle: idle, pending wallet confirmation, confirming on-chain, success, error
-4. Replace all OnchainKit transaction imports (`Transaction`, `TransactionButton`, `TransactionStatus`, `TransactionSponsor`, etc.)
-5. Update the `calls` array format -- use `address`, `abi`, `functionName`, `args` with proper `as const` typing
-6. Map `onStatus` callback to the new lifecycle status names (init, pending, confirmed, success, error)
-
-**Validation gate**: Run `npm run build`. Must pass before continuing. Document any errors in `mistakes.md`.
-
-### Step 5: Cleanup
-
-1. Search for any remaining `@coinbase/onchainkit` imports -- there should be none
-2. Optionally remove `@coinbase/onchainkit` from `package.json` dependencies
-3. Run final `npm run build` to verify everything works
-4. Update `mistakes.md` with final lessons learned
-
-## Troubleshooting
-
-See [troubleshooting.md](troubleshooting.md) for common build and runtime errors.
-
-### Build fails after provider migration
-- **Missing dependencies**: Ensure `wagmi`, `viem`, `@tanstack/react-query` are installed
-- **Type errors with wagmi config**: Check that `chains` array has at least one chain and `transports` has a matching entry
-- **"use client" missing**: Both the provider and wallet components must have `"use client"` directive
-
-### MetaMask SDK react-native warning
-- The warning `Can't resolve '@react-native-async-storage/async-storage'` is harmless in web builds. It comes from MetaMask SDK and does not affect functionality.
-
-### Wallet won't connect
-- Verify the wagmi config has the correct connectors configured
-- Check that `WagmiProvider` wraps the component tree before any wallet hooks are used
-- Ensure `QueryClientProvider` is inside `WagmiProvider`
-
-### Transaction receipt stuck in pending
-- **Symptom**: Transaction hash appears, tx confirms on-chain, but UI stays stuck on "Transaction in progress..." forever
-- **Cause**: `useWaitForTransactionReceipt` has no RPC to poll because the transaction's chain is missing from the wagmi config's `chains` + `transports`
-- **Fix**: (1) Add the target chain to `wagmi-config.ts` chains array AND transports object. (2) Always pass `chainId` to `useWaitForTransactionReceipt({ hash, chainId })` so it polls the correct chain
-
-### Transaction targets wrong chain
-- The `TransactionForm` auto-switches chains, but the target chain must exist in the wagmi config's `chains` array and `transports` object
-- Common: add `baseSepolia` for testnet transactions (chainId 84532)
-
-### Next.js page export restrictions
-- Next.js only allows specific named exports from page files. Exporting contract call arrays or ABI constants from a page file will cause a build error
-- Fix: make them non-exported `const` declarations or move them to a separate module
-
-### ABI type errors after transaction migration
-- When defining ABIs inline, use `as const` on the array for proper type inference
-- Mark individual fields like `type: 'function' as const` and `stateMutability: 'nonpayable' as const`
-
-### Existing wagmi setup detected
-- If the project already wraps with `WagmiProvider`, do NOT add another one
-- Instead, just update the existing wagmi config to include the needed connectors
-- OnchainKit's provider detects and defers to existing wagmi providers -- the standalone setup should too
-
-## Important Notes
-
-- Always use `wagmi` and `viem` directly. Never import from `@coinbase/onchainkit`.
-- The `baseAccount` connector comes from `wagmi/connectors`, not from a separate package.
-- `wagmi-config.ts` must include every chain the app transacts on. If the original OnchainKit ` ` used a specific chain, that chain must be in both `chains` and `transports`. Without it, `useWaitForTransactionReceipt` will hang forever.
-- If the project uses Tailwind, use Tailwind classes for the components. If not, adapt to inline styles or the project's existing styling approach (e.g., CSS Modules).
-- Do not export contract call arrays, ABI constants, or other non-page values from Next.js page files. Use non-exported constants or a separate module.
-- Inspect the OnchainKit source code in `node_modules/@coinbase/onchainkit/src/` if you need to understand how a specific component works internally.
diff --git a/skills/build-on-base/references/migrations/onchainkit/provider.md b/skills/build-on-base/references/migrations/onchainkit/provider.md
deleted file mode 100644
index 0fc7adf..0000000
--- a/skills/build-on-base/references/migrations/onchainkit/provider.md
+++ /dev/null
@@ -1,193 +0,0 @@
-# Provider Migration: OnchainKitProvider to WagmiProvider
-
-Replace `OnchainKitProvider` from `@coinbase/onchainkit` with direct `WagmiProvider` + `QueryClientProvider` setup.
-
-## What OnchainKitProvider Does Internally
-
-OnchainKitProvider is a wrapper that:
-1. Creates a wagmi config with `base` + `baseSepolia` chains
-2. Uses `cookieStorage` for persistence and `ssr: true`
-3. Default connector: `baseAccount()` from `wagmi/connectors`
-4. Sets up CDP RPC URLs if `apiKey` is provided
-5. Creates a default `QueryClient` from `@tanstack/react-query`
-6. Applies theme/appearance settings via CSS custom properties
-
-The provider detects if `WagmiProvider` or `QueryClientProvider` already exist in the React tree and skips creating them if so.
-
-## Prerequisites
-
-Ensure these packages are installed. They are likely already present since OnchainKit depends on them:
-
-```bash
-npm install wagmi viem @tanstack/react-query
-```
-
-If the project uses Tailwind CSS and it's not yet installed:
-
-```bash
-npm install tailwindcss @tailwindcss/postcss postcss
-```
-
-## Step-by-Step Migration
-
-### 1. Create wagmi-config.ts
-
-Create a new file for the wagmi configuration. Place it alongside the existing provider file (typically `app/wagmi-config.ts`).
-
-```typescript
-import { http, cookieStorage, createConfig, createStorage } from "wagmi";
-import { base } from "wagmi/chains";
-import { coinbaseWallet, metaMask } from "wagmi/connectors";
-
-export const wagmiConfig = createConfig({
- chains: [base],
- connectors: [
- coinbaseWallet({ appName: "My App", preference: "all" }),
- metaMask(),
- ],
- storage: createStorage({ storage: cookieStorage }),
- ssr: true,
- transports: {
- [base.id]: http(),
- },
-});
-```
-
-**Adapt based on the existing OnchainKitProvider config:**
-- If `chain` prop was set to something other than `base`, use that chain instead
-- If `apiKey` was set, you can use CDP RPC URLs: `http(\`https://api.developer.coinbase.com/rpc/v1/base/${apiKey}\`)`
-- If `config.wallet.preference` was `"smartWalletOnly"`, adjust the coinbaseWallet connector accordingly
-- Add additional chains to the `chains` array and `transports` object as needed
-
-### 2. Rewrite the Provider Component
-
-Replace the existing provider file (typically `rootProvider.tsx` or `providers.tsx`).
-
-**Before (OnchainKit):**
-```typescript
-"use client";
-import { ReactNode } from "react";
-import { base } from "wagmi/chains";
-import { OnchainKitProvider } from "@coinbase/onchainkit";
-import "@coinbase/onchainkit/styles.css";
-
-export function RootProvider({ children }: { children: ReactNode }) {
- return (
-
- {children}
-
- );
-}
-```
-
-**After (wagmi/viem):**
-```typescript
-"use client";
-import { type ReactNode, useState } from "react";
-import { WagmiProvider } from "wagmi";
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { wagmiConfig } from "./wagmi-config";
-
-export function RootProvider({ children }: { children: ReactNode }) {
- const [queryClient] = useState(() => new QueryClient());
-
- return (
-
-
- {children}
-
-
- );
-}
-```
-
-Key changes:
-- Remove `@coinbase/onchainkit` import
-- Remove `@coinbase/onchainkit/styles.css` import
-- `QueryClient` is created with `useState` to avoid re-creation on re-renders
-- `WagmiProvider` must wrap `QueryClientProvider`
-
-### 3. Update Layout File
-
-Remove any OnchainKit-specific imports from the layout:
-
-- Remove `SafeArea` from `@coinbase/onchainkit/minikit`
-- Remove `minikitConfig` imports
-- Remove MiniKit-related metadata generation
-- Move `` inside `` (wagmi provider must be a client component, so it should wrap the content, not the `` tag)
-
-**Before:**
-```typescript
-import { SafeArea } from "@coinbase/onchainkit/minikit";
-
-export default function RootLayout({ children }) {
- return (
-
-
-
- {children}
-
-
-
- );
-}
-```
-
-**After:**
-```typescript
-export default function RootLayout({ children }) {
- return (
-
-
- {children}
-
-
- );
-}
-```
-
-### 4. Verify
-
-Run the build command:
-```bash
-npm run build
-```
-
-Expected: Build succeeds. The MetaMask SDK warning about `@react-native-async-storage/async-storage` is harmless and can be ignored.
-
-## Edge Cases
-
-### Project already has a WagmiProvider
-If the project wraps with its own `WagmiProvider` outside of OnchainKit, simply remove the `OnchainKitProvider` wrapper. Update the existing wagmi config to include any connectors that were configured via OnchainKit.
-
-### Project uses CDP API key for RPC
-If the existing setup relied on `apiKey` for RPC access, add the CDP RPC URL to the transport:
-
-```typescript
-transports: {
- [base.id]: http(`https://api.developer.coinbase.com/rpc/v1/base/${process.env.NEXT_PUBLIC_ONCHAINKIT_API_KEY}`),
-},
-```
-
-### Project uses multiple chains
-Add all needed chains to both the `chains` array and `transports` object:
-
-```typescript
-import { base, baseSepolia } from "wagmi/chains";
-
-export const wagmiConfig = createConfig({
- chains: [base, baseSepolia],
- transports: {
- [base.id]: http(),
- [baseSepolia.id]: http(),
- },
- // ...rest
-});
-```
diff --git a/skills/build-on-base/references/migrations/onchainkit/transaction.md b/skills/build-on-base/references/migrations/onchainkit/transaction.md
deleted file mode 100644
index 86c2089..0000000
--- a/skills/build-on-base/references/migrations/onchainkit/transaction.md
+++ /dev/null
@@ -1,528 +0,0 @@
-# Transaction Migration: OnchainKit Transaction to wagmi
-
-Replace OnchainKit's `Transaction`, `TransactionButton`, `TransactionStatus`, `TransactionSponsor`, and related components with a standalone `TransactionForm` component built on wagmi hooks.
-
-## What OnchainKit's Transaction Components Do
-
-OnchainKit provides a composable transaction system:
-- ` ` -- container that manages the full transaction lifecycle, accepts `calls`, `chainId`, `onStatus`
-- ` ` -- submits the transaction, shows status-dependent text (Transact/Confirm/Try again/View transaction)
-- ` ` -- displays current transaction state with label and action
-- ` ` -- text label ("Confirm in wallet", "Transaction in progress", "Successful", error message)
-- ` ` -- link to block explorer or call status viewer
-- ` ` -- shows "Zero transaction fee" when paymaster is configured
-- `LifecycleStatus` type -- status object with `statusName` and `statusData`
-
-Internally, OnchainKit uses two submission paths:
-- **Smart Wallet (batched):** `useSendCalls` (EIP-5792) for wallets with `atomicBatch` capability
-- **EOA (single):** `useSendTransaction` with `encodeFunctionData` for standard wallets
-
-The replacement component uses `useWriteContract` which handles both EOA and smart wallet scenarios for single contract calls.
-
-## Prerequisites
-
-- Provider migration must be completed first (WagmiProvider + QueryClientProvider in the tree)
-- Tailwind CSS installed (if not, install it or adapt styles)
-- If the transaction targets a chain other than what's in the wagmi config, add that chain to `wagmi-config.ts`
-
-## Important: Chain Configuration
-
-OnchainKit's Transaction accepts a `chainId` prop and handles chain switching. The replacement does too, BUT the target chain must exist in the wagmi config's `chains` array and `transports` object.
-
-For example, if transactions target Base Sepolia (84532):
-
-```typescript
-import { base, baseSepolia } from "wagmi/chains";
-
-export const wagmiConfig = createConfig({
- chains: [base, baseSepolia],
- transports: {
- [base.id]: http(),
- [baseSepolia.id]: http(),
- },
- // ...rest
-});
-```
-
-## The TransactionForm Component
-
-Create `app/components/TransactionForm.tsx` (or wherever components live in the project):
-
-```typescript
-"use client";
-import { useCallback, useEffect, useState } from "react";
-import {
- useAccount,
- useWriteContract,
- useWaitForTransactionReceipt,
- useSwitchChain,
-} from "wagmi";
-import type { Abi, Address } from "viem";
-
-type ContractCall = {
- address: Address;
- abi: Abi;
- functionName: string;
- args?: readonly unknown[];
- value?: bigint;
-};
-
-type LifecycleStatus =
- | { statusName: "init"; statusData: null }
- | { statusName: "pending"; statusData: null }
- | { statusName: "confirmed"; statusData: { transactionHash: string } }
- | {
- statusName: "success";
- statusData: { transactionHash: string; blockNumber: bigint };
- }
- | { statusName: "error"; statusData: { message: string } };
-
-type TransactionFormProps = {
- calls: ContractCall[];
- chainId?: number;
- buttonText?: string;
- onStatus?: (status: LifecycleStatus) => void;
- disabled?: boolean;
- className?: string;
-};
-
-export function TransactionForm({
- calls,
- chainId,
- buttonText = "Transact",
- onStatus,
- disabled = false,
- className,
-}: TransactionFormProps) {
- const { isConnected, chainId: currentChainId } = useAccount();
- const { switchChainAsync } = useSwitchChain();
-
- const [status, setStatus] = useState({
- statusName: "init",
- statusData: null,
- });
-
- const updateStatus = useCallback(
- (newStatus: LifecycleStatus) => {
- setStatus(newStatus);
- onStatus?.(newStatus);
- },
- [onStatus]
- );
-
- const {
- writeContract,
- data: txHash,
- isPending: isWritePending,
- reset: resetWrite,
- } = useWriteContract();
-
- // CRITICAL: Always pass chainId so wagmi polls the correct chain's RPC.
- // Without this, if the user's wallet is on a different chain than the
- // transaction target, wagmi has no transport to poll and the receipt
- // is never found -- the UI hangs in "pending" forever.
- const { data: receipt, isLoading: isWaiting } =
- useWaitForTransactionReceipt({
- hash: txHash,
- chainId,
- });
-
- useEffect(() => {
- if (isWritePending) {
- updateStatus({ statusName: "pending", statusData: null });
- }
- }, [isWritePending, updateStatus]);
-
- useEffect(() => {
- if (txHash && !receipt) {
- updateStatus({
- statusName: "confirmed",
- statusData: { transactionHash: txHash },
- });
- }
- }, [txHash, receipt, updateStatus]);
-
- useEffect(() => {
- if (receipt) {
- updateStatus({
- statusName: "success",
- statusData: {
- transactionHash: receipt.transactionHash,
- blockNumber: receipt.blockNumber,
- },
- });
- }
- }, [receipt, updateStatus]);
-
- const handleSubmit = useCallback(async () => {
- if (!isConnected || calls.length === 0) return;
-
- try {
- if (chainId && currentChainId !== chainId) {
- await switchChainAsync({ chainId });
- }
-
- const call = calls[0];
- writeContract(
- {
- address: call.address,
- abi: call.abi,
- functionName: call.functionName,
- args: call.args ?? [],
- value: call.value,
- chainId,
- },
- {
- onError: (error) => {
- const isUserRejection =
- error.message?.includes("User rejected") ||
- error.message?.includes("User denied") ||
- error.message?.includes("Request denied");
- const message = isUserRejection
- ? "Request denied."
- : error.message || "Transaction failed";
- updateStatus({ statusName: "error", statusData: { message } });
- },
- }
- );
- } catch (error) {
- const message =
- error instanceof Error ? error.message : "Transaction failed";
- updateStatus({ statusName: "error", statusData: { message } });
- }
- }, [
- isConnected,
- calls,
- chainId,
- currentChainId,
- switchChainAsync,
- writeContract,
- updateStatus,
- ]);
-
- const handleReset = useCallback(() => {
- resetWrite();
- updateStatus({ statusName: "init", statusData: null });
- }, [resetWrite, updateStatus]);
-
- const isLoading = isWritePending || isWaiting;
-
- return (
-
- {status.statusName === "success" ? (
-
- {
- const hash = status.statusData.transactionHash;
- const explorerBase = chainId === 84532
- ? "https://sepolia.basescan.org"
- : "https://basescan.org";
- window.open(`${explorerBase}/tx/${hash}`, "_blank");
- }}
- className="rounded-lg bg-green-600 px-4 py-3 text-sm font-medium text-white transition-colors hover:bg-green-700"
- >
- View transaction
-
-
- Send another
-
-
- ) : (
-
- {isWritePending && (
-
-
-
-
-
- Confirm in wallet...
-
- )}
- {isWaiting && (
-
-
-
-
-
- Transaction in progress...
-
- )}
- {status.statusName === "error" && "Try again"}
- {status.statusName === "init" && buttonText}
- {status.statusName === "confirmed" && !isWaiting && buttonText}
-
- )}
-
-
-
- );
-}
-
-function TransactionStatusDisplay({
- status,
- chainId,
-}: {
- status: LifecycleStatus;
- chainId?: number;
-}) {
- if (status.statusName === "init") return null;
-
- const explorerBase =
- chainId === 84532
- ? "https://sepolia.basescan.org"
- : "https://basescan.org";
-
- return (
-
- {status.statusName === "pending" && (
-
- Confirm in wallet.
-
- )}
- {status.statusName === "confirmed" && (
-
- Transaction in progress...
-
- )}
- {status.statusName === "success" && (
-
- )}
- {status.statusName === "error" && (
-
- {status.statusData.message}
-
- )}
-
- );
-}
-```
-
-## Step-by-Step Replacement
-
-### 1. Check Chain Configuration
-
-Look at the `chainId` prop on the existing ` ` component. If it references a chain not in the wagmi config, add it:
-
-```typescript
-// Common: Base Sepolia for testnet
-import { base, baseSepolia } from "wagmi/chains";
-// Add to wagmi config chains array and transports
-```
-
-### 2. Create the Component File
-
-Copy the `TransactionForm` component code above into the project's components directory.
-
-### 3. Replace OnchainKit Transaction Imports and Usage
-
-**Before (OnchainKit):**
-```typescript
-import {
- Transaction,
- TransactionButton,
- TransactionSponsor,
- TransactionStatus,
- TransactionStatusAction,
- TransactionStatusLabel,
-} from '@coinbase/onchainkit/transaction';
-import type { LifecycleStatus } from '@coinbase/onchainkit/transaction';
-
-const calls = [
- {
- address: '0x67c97D1FB8184F038592b2109F854dfb09C77C75',
- abi: clickContractAbi,
- functionName: 'click',
- args: [],
- }
-];
-
-
-
-
-
-
-
-
-
-```
-
-**After (wagmi):**
-```typescript
-import { TransactionForm } from "./components/TransactionForm";
-import type { Address } from "viem";
-
-const clickContractAddress: Address = '0x67c97D1FB8184F038592b2109F854dfb09C77C75';
-const clickContractAbi = [
- {
- type: 'function' as const,
- name: 'click',
- inputs: [],
- outputs: [],
- stateMutability: 'nonpayable' as const,
- },
-] as const;
-
-const calls = [
- {
- address: clickContractAddress,
- abi: clickContractAbi,
- functionName: 'click',
- args: [],
- },
-];
-
-
-```
-
-### 4. Handle the onStatus Callback
-
-The OnchainKit `LifecycleStatus` type has these states: `init`, `transactionIdle`, `buildingTransaction`, `transactionPending`, `transactionLegacyExecuted`, `success`, `error`, `reset`.
-
-The replacement uses a simplified set: `init`, `pending`, `confirmed`, `success`, `error`.
-
-**Mapping:**
-
-| OnchainKit Status | Replacement Status |
-|---|---|
-| `init` / `transactionIdle` | `init` |
-| `buildingTransaction` / `transactionPending` | `pending` |
-| `transactionLegacyExecuted` | `confirmed` |
-| `success` | `success` |
-| `error` | `error` |
-
-If the existing `onStatus` callback checks specific OnchainKit status names, update the checks to use the new names.
-
-### 5. Verify
-
-Run `npm run build` and confirm no errors.
-
-## What's Not Covered
-
-### Gas Sponsorship (TransactionSponsor)
-OnchainKit's `TransactionSponsor` uses a paymaster URL to sponsor gas fees. This requires a paymaster service (e.g., Coinbase Developer Platform Paymaster). The replacement component does not include paymaster support. To add it, you would need to use wagmi's `useSendCalls` with the paymaster capability.
-
-### Batched Calls (EIP-5792)
-OnchainKit's Transaction supports batching multiple calls into a single transaction for smart wallets. The replacement uses `useWriteContract` which handles one call at a time. For batched calls, use wagmi's `useSendCalls` hook directly.
-
-### Transaction Toast
-OnchainKit's `TransactionToast` provides toast-style notifications. The replacement shows inline status instead. Add a toast library if toast notifications are needed.
-
-## Common Issues
-
-### Transaction receipt stuck in pending (UI hangs after wallet confirms)
-**This is the most common bug.** The transaction hash appears, the tx confirms on-chain, but the UI stays stuck on "Transaction in progress..." forever.
-
-**Cause:** `useWaitForTransactionReceipt` needs an RPC to poll for the receipt. If the transaction's chain is not in the wagmi config's `chains` + `transports`, wagmi has no RPC endpoint to poll, so `isSuccess` never becomes `true`.
-
-**Fix (two parts):**
-1. Add the transaction's target chain to `wagmi-config.ts`:
-```typescript
-import { base, baseSepolia } from "wagmi/chains";
-
-export const wagmiConfig = createConfig({
- chains: [base, baseSepolia], // Must include every chain the app transacts on
- transports: {
- [base.id]: http(),
- [baseSepolia.id]: http(), // Must have a transport for each chain
- },
- // ...rest
-});
-```
-2. Always pass `chainId` to `useWaitForTransactionReceipt`:
-```typescript
-const { data: receipt } = useWaitForTransactionReceipt({
- hash: txHash,
- chainId, // Ensures polling uses the correct chain's transport
-});
-```
-
-### Next.js page export restrictions
-Next.js only allows specific named exports from page files (`default`, `metadata`, `generateMetadata`, `generateStaticParams`, etc.). If you export contract call arrays, ABI constants, or other non-page values from a page file, the build will fail with an error like: `"calls" is not a valid Page export field`.
-
-**Fix:** Move contract call arrays, ABIs, and addresses to a separate module (e.g., `contracts.ts`) or make them non-exported `const` declarations within the page file.
-
-### Type error: comparison with "UserRejectedRequestError"
-The wagmi error types don't include `UserRejectedRequestError` as a direct name match. Instead, check `error.message` for "User rejected" or "User denied" strings.
-
-### Transaction targets wrong chain
-The component auto-switches chains via `useSwitchChain`. But the target chain must exist in the wagmi config. If you get a chain error, add the chain to `wagmi-config.ts`.
-
-### "useWriteContract must be used within WagmiProvider"
-Same as wallet: ensure the component is inside the WagmiProvider tree.
-
-### ABI type errors
-When defining the ABI inline, use `as const` on the array to get proper type inference:
-```typescript
-const abi = [
- {
- type: 'function' as const,
- name: 'click',
- inputs: [],
- outputs: [],
- stateMutability: 'nonpayable' as const,
- },
-] as const;
-```
diff --git a/skills/build-on-base/references/migrations/onchainkit/troubleshooting.md b/skills/build-on-base/references/migrations/onchainkit/troubleshooting.md
deleted file mode 100644
index 382bbbf..0000000
--- a/skills/build-on-base/references/migrations/onchainkit/troubleshooting.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Troubleshooting OnchainKit Migration
-
-## Build Errors
-
-### `Module not found: Can't resolve '@react-native-async-storage/async-storage'`
-**Cause**: MetaMask SDK includes a react-native dependency that doesn't resolve in web environments.
-**Impact**: Warning only. Does not affect functionality.
-**Solution**: Ignore. This is a known issue with MetaMask SDK's web bundle.
-
-### `Type error: Cannot find module 'wagmi/connectors'`
-**Cause**: Outdated wagmi version.
-**Solution**: Update wagmi to >= 2.16:
-```bash
-npm install wagmi@latest
-```
-
-### `Error: useAccount must be used within WagmiConfig`
-**Cause**: A component using wagmi hooks is rendering outside the WagmiProvider tree.
-**Solution**: Ensure `WagmiProvider` wraps the entire app. In Next.js, this goes in the root provider component. Both the provider and any component using wagmi hooks must have `"use client"` directive.
-
-### `Error: No QueryClient set, use QueryClientProvider`
-**Cause**: `QueryClientProvider` is missing from the provider tree.
-**Solution**: Add `QueryClientProvider` inside `WagmiProvider`:
-```typescript
-
-
- {children}
-
-
-```
-
-### `Error: Invalid chain configuration`
-**Cause**: The `transports` object doesn't have an entry for every chain in the `chains` array.
-**Solution**: Every chain in `chains` needs a matching transport:
-```typescript
-createConfig({
- chains: [base, baseSepolia],
- transports: {
- [base.id]: http(),
- [baseSepolia.id]: http(), // Must match
- },
-});
-```
-
-## Runtime Errors
-
-### Wallet modal opens but nothing happens on click
-**Cause**: The connector might not be available or the wallet extension isn't installed.
-**Solution**: For extension-based wallets (MetaMask), the user needs the extension installed. For Coinbase Wallet and Base Account, they work via popup/redirect without an extension.
-
-### Connection succeeds but address doesn't display
-**Cause**: Component not re-rendering after connection state change.
-**Solution**: Ensure the component using `useAccount()` is a client component with `"use client"`. wagmi hooks trigger re-renders automatically when state changes.
-
-### Dark mode styles not working
-**Cause**: Tailwind dark mode not configured.
-**Solution**: Tailwind v4 uses `prefers-color-scheme` by default. If the project uses class-based dark mode, ensure the `` element has the `dark` class. For Tailwind v3, check `tailwind.config.js` has `darkMode: 'class'`.
-
-## Migration-Specific Issues
-
-### OnchainKit styles break after removing the import
-**Cause**: Some layouts depended on OnchainKit's global CSS.
-**Solution**: The OnchainKit CSS mainly provides:
-- Custom `ock-*` CSS variables for theming
-- Rounded corner and color utilities
-- Font styling
-
-These are replaced by Tailwind utilities. If specific layouts break, inspect the element and add equivalent Tailwind classes.
-
-### Multiple wallet connection prompts
-**Cause**: The wagmi config has connectors that auto-connect on page load.
-**Solution**: Use `cookieStorage` for persistence (prevents reconnection prompts):
-```typescript
-storage: createStorage({ storage: cookieStorage }),
-```
-
-### SSR hydration mismatch
-**Cause**: Wallet state differs between server and client render.
-**Solution**: Ensure the wagmi config has `ssr: true` and the provider component has `"use client"` directive. Use `cookieStorage` for state persistence across SSR.
diff --git a/skills/build-on-base/references/migrations/onchainkit/wallet.md b/skills/build-on-base/references/migrations/onchainkit/wallet.md
deleted file mode 100644
index 88e9c56..0000000
--- a/skills/build-on-base/references/migrations/onchainkit/wallet.md
+++ /dev/null
@@ -1,346 +0,0 @@
-# Wallet Migration: OnchainKit Wallet to WalletConnect
-
-Replace OnchainKit's `Wallet`, `ConnectWallet`, `WalletDropdown`, `WalletModal`, and `Connected` components with a standalone `WalletConnect` component built on wagmi hooks.
-
-## What OnchainKit's Wallet Components Do
-
-OnchainKit provides several wallet components:
-- ` ` -- container that manages open/closed state
-- ` ` -- button that triggers connection (renders as "Connect Wallet" when disconnected)
-- ` ` -- dropdown with identity info and actions
-- ` ` -- modal with multiple wallet options (Base Account, Coinbase, MetaMask, Phantom, etc.)
-- ` ` -- conditional renderer based on wallet connection state
-
-The replacement `WalletConnect` component combines all of this into one component.
-
-## Prerequisites
-
-- Provider migration must be completed first (WagmiProvider + QueryClientProvider in the tree)
-- Tailwind CSS installed (if not, install it or adapt styles)
-
-## The WalletConnect Component
-
-Create `app/components/WalletConnect.tsx` (or wherever components live in the project):
-
-```typescript
-"use client";
-import { useCallback, useEffect, useRef, useState } from "react";
-import { useAccount, useConnect, useDisconnect } from "wagmi";
-import {
- baseAccount,
- coinbaseWallet,
- metaMask,
-} from "wagmi/connectors";
-
-function truncateAddress(address: string): string {
- return `${address.slice(0, 6)}...${address.slice(-4)}`;
-}
-
-type WalletOption = {
- id: string;
- name: string;
- connect: () => void;
-};
-
-function WalletModal({
- onClose,
- appName = "My App",
-}: {
- onClose: () => void;
- appName?: string;
-}) {
- const { connect } = useConnect();
- const backdropRef = useRef(null);
-
- const handleBackdropClick = useCallback(
- (e: React.MouseEvent) => {
- if (e.target === backdropRef.current) onClose();
- },
- [onClose]
- );
-
- useEffect(() => {
- const handleEsc = (e: KeyboardEvent) => {
- if (e.key === "Escape") onClose();
- };
- document.addEventListener("keydown", handleEsc);
- return () => document.removeEventListener("keydown", handleEsc);
- }, [onClose]);
-
- const walletOptions: WalletOption[] = [
- {
- id: "base-account",
- name: "Sign in with Base",
- connect: () => {
- connect({ connector: baseAccount({ appName }) });
- onClose();
- },
- },
- {
- id: "coinbase-wallet",
- name: "Coinbase Wallet",
- connect: () => {
- connect({
- connector: coinbaseWallet({ appName, preference: "all" }),
- });
- onClose();
- },
- },
- {
- id: "metamask",
- name: "MetaMask",
- connect: () => {
- connect({
- connector: metaMask({
- dappMetadata: {
- name: appName,
- url: typeof window !== "undefined" ? window.location.origin : "",
- },
- }),
- });
- onClose();
- },
- },
- ];
-
- const [primaryOption, ...otherOptions] = walletOptions;
-
- return (
-
-
-
-
-
-
-
-
-
- Connect Wallet
-
-
-
-
- {primaryOption.name}
-
-
-
-
-
-
- or use another wallet
-
-
-
-
- {otherOptions.map((wallet) => (
-
- {wallet.name}
-
- ))}
-
-
-
- );
-}
-
-export function WalletConnect({ appName = "My App" }: { appName?: string }) {
- const { address, isConnected } = useAccount();
- const { disconnect } = useDisconnect();
- const [isModalOpen, setIsModalOpen] = useState(false);
-
- if (isConnected && address) {
- return (
-
-
- {truncateAddress(address)}
-
- disconnect()}
- className="rounded-lg border border-zinc-200 px-3 py-2 text-sm font-medium text-zinc-600 transition-colors hover:bg-zinc-50 hover:text-zinc-900 dark:border-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100"
- >
- Disconnect
-
-
- );
- }
-
- return (
- <>
- setIsModalOpen(true)}
- className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
- >
- Connect Wallet
-
- {isModalOpen && (
- setIsModalOpen(false)}
- appName={appName}
- />
- )}
- >
- );
-}
-```
-
-## Step-by-Step Replacement
-
-### 1. Create the Component File
-
-Copy the component code above into the project's components directory.
-
-### 2. Replace OnchainKit Wallet Imports
-
-Find all files importing from `@coinbase/onchainkit/wallet` or using `Connected` from `@coinbase/onchainkit`:
-
-**Before:**
-```typescript
-import { Wallet } from "@coinbase/onchainkit/wallet";
-// or
-import { ConnectWallet, Wallet, WalletDropdown, WalletDropdownDisconnect } from "@coinbase/onchainkit/wallet";
-// or
-import { Connected } from "@coinbase/onchainkit";
-```
-
-**After:**
-```typescript
-import { WalletConnect } from "./components/WalletConnect";
-```
-
-### 3. Replace Component Usage
-
-**Simple ` `:**
-```typescript
-// Before
-
-
-// After
-
-```
-
-**Composed wallet with children:**
-```typescript
-// Before
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// After
-
-```
-
-**` ` conditional rendering:**
-```typescript
-// Before
-import { Connected } from "@coinbase/onchainkit";
-
-Please connect
}>
- You are connected
-
-
-// After -- use wagmi's useAccount directly
-import { useAccount } from "wagmi";
-
-const { isConnected } = useAccount();
-{isConnected ? You are connected
: Please connect
}
-```
-
-### 4. Remove MiniKit Usage
-
-If the page uses `useMiniKit` or other MiniKit hooks, remove them:
-
-```typescript
-// Remove these
-import { useMiniKit } from "@coinbase/onchainkit/minikit";
-const { setMiniAppReady, isMiniAppReady } = useMiniKit();
-useEffect(() => {
- if (!isMiniAppReady) setMiniAppReady();
-}, [setMiniAppReady, isMiniAppReady]);
-```
-
-### 5. Verify
-
-Run `npm run build` and confirm no errors.
-
-## Customization
-
-### Changing the app name
-Pass the `appName` prop to `WalletConnect`:
-```typescript
-
-```
-
-### Adding more wallet options
-Add entries to the `walletOptions` array in the `WalletModal` component. Use `injected({ target: 'walletName' })` from `wagmi/connectors` for browser extension wallets.
-
-### Changing styles
-The component uses Tailwind utility classes. Modify the `className` strings to match the project's design system. All styling is inline via Tailwind -- no external CSS files needed.
-
-### Using without Tailwind
-If the project doesn't use Tailwind, convert the Tailwind classes to inline styles or CSS modules. The key visual elements are:
-- Fixed overlay with semi-transparent black background
-- Centered card with white background, rounded corners, shadow
-- Primary button (blue) for Base Account
-- Secondary buttons (white/bordered) for other wallets
-- Dark mode support via `dark:` variants
-
-## Common Issues
-
-### "useAccount must be used within WagmiProvider"
-The component is being rendered outside the provider tree. Ensure `WagmiProvider` wraps the entire app in the layout or root provider.
-
-### Modal doesn't close after connecting
-This can happen if the connection is async and the component unmounts. The current implementation calls `onClose()` synchronously after `connect()`. If you need to wait for the connection, use the `onSuccess` callback from `useConnect`.
-
-### baseAccount connector not found
-Ensure wagmi version is >= 2.16. The `baseAccount` connector was added in recent wagmi versions. Check with:
-```bash
-npm ls wagmi
-```
diff --git a/skills/build-on-base/scripts/analyze_project.py b/skills/build-on-base/scripts/analyze_project.py
deleted file mode 100644
index e649395..0000000
--- a/skills/build-on-base/scripts/analyze_project.py
+++ /dev/null
@@ -1,290 +0,0 @@
-#!/usr/bin/env python3
-"""
-Analyze a project directory for MiniKit usage.
-Finds all imports and hook usages that need conversion.
-
-Usage:
- python scripts/analyze_project.py
- python scripts/analyze_project.py ./my-minikit-app
-
-Output:
- JSON report of all MiniKit usage found
-"""
-
-import os
-import re
-import json
-import sys
-from pathlib import Path
-from typing import TypedDict, List
-
-class HookUsage(TypedDict):
- hook: str
- file: str
- line: int
- code: str
-
-class ImportInfo(TypedDict):
- file: str
- line: int
- imports: List[str]
-
-class AnalysisReport(TypedDict):
- project_dir: str
- files_scanned: int
- files_with_minikit: int
- imports: List[ImportInfo]
- hook_usages: List[HookUsage]
- provider_locations: List[dict]
- summary: dict
-
-# MiniKit hooks to search for
-MINIKIT_HOOKS = [
- 'useMiniKit',
- 'useClose',
- 'useOpenUrl',
- 'useViewProfile',
- 'useViewCast',
- 'useComposeCast',
- 'useAddFrame',
- 'useAuthenticate',
- 'useNotification',
- 'usePrimaryButton',
-]
-
-# Import patterns - both @coinbase/onchainkit/minikit AND @coinbase/onchainkit
-MINIKIT_IMPORT_PATTERN = re.compile(
- r"from\s+['\"]@coinbase/onchainkit(?:/minikit)?['\"]"
-)
-
-# OnchainKitProvider with miniKit prop
-ONCHAINKIT_PROVIDER_PATTERN = re.compile(
- r" List[Path]:
- """Find all relevant source files in directory."""
- files = []
- for root, _, filenames in os.walk(directory):
- # Skip node_modules and .next
- if 'node_modules' in root or '.next' in root:
- continue
- for filename in filenames:
- if filename.endswith(extensions):
- files.append(Path(root) / filename)
- return files
-
-def analyze_file(filepath: Path) -> dict:
- """Analyze a single file for MiniKit usage."""
- result = {
- 'imports': [],
- 'hooks': [],
- 'provider': False,
- 'provider_lines': [],
- 'provider_type': None
- }
-
- try:
- content = filepath.read_text(encoding='utf-8')
- except Exception as e:
- print(f"Warning: Could not read {filepath}: {e}", file=sys.stderr)
- return result
-
- lines = content.split('\n')
-
- for i, line in enumerate(lines, 1):
- # Check for imports from @coinbase/onchainkit or @coinbase/onchainkit/minikit
- if MINIKIT_IMPORT_PATTERN.search(line):
- # Extract what's being imported
- import_match = re.search(r"import\s+\{([^}]+)\}", line)
- if import_match:
- imports = [s.strip() for s in import_match.group(1).split(',')]
- result['imports'].append({
- 'line': i,
- 'imports': imports
- })
-
- # Check for hook usages
- for hook in MINIKIT_HOOKS:
- hook_pattern = re.compile(rf'\b{hook}\s*\(')
- if hook_pattern.search(line):
- result['hooks'].append({
- 'hook': hook,
- 'line': i,
- 'code': line.strip()
- })
-
- # Check for OnchainKitProvider (may have miniKit prop)
- if ONCHAINKIT_PROVIDER_PATTERN.search(line):
- result['provider'] = True
- result['provider_type'] = 'OnchainKitProvider'
- result['provider_lines'].append({
- 'line': i,
- 'code': line.strip()
- })
-
- # Check for MiniKitProvider
- if MINIKIT_PROVIDER_PATTERN.search(line):
- result['provider'] = True
- result['provider_type'] = 'MiniKitProvider'
- result['provider_lines'].append({
- 'line': i,
- 'code': line.strip()
- })
-
- # Check for miniKit prop
- if MINIKIT_PROP_PATTERN.search(line):
- result['provider_lines'].append({
- 'line': i,
- 'code': line.strip(),
- 'is_minikit_prop': True
- })
-
- return result
-
-def analyze_project(project_dir: str) -> AnalysisReport:
- """Analyze entire project for MiniKit usage."""
- project_path = Path(project_dir).resolve()
-
- if not project_path.exists():
- print(f"Error: Directory {project_dir} does not exist", file=sys.stderr)
- sys.exit(1)
-
- files = find_files(str(project_path))
-
- report: AnalysisReport = {
- 'project_dir': str(project_path),
- 'files_scanned': len(files),
- 'files_with_minikit': 0,
- 'imports': [],
- 'hook_usages': [],
- 'provider_locations': [],
- 'summary': {}
- }
-
- hook_counts = {hook: 0 for hook in MINIKIT_HOOKS}
-
- for filepath in files:
- result = analyze_file(filepath)
- rel_path = str(filepath.relative_to(project_path))
-
- has_minikit = bool(result['imports'] or result['hooks'] or result['provider'])
-
- if has_minikit:
- report['files_with_minikit'] += 1
-
- # Collect imports
- for imp in result['imports']:
- report['imports'].append({
- 'file': rel_path,
- 'line': imp['line'],
- 'imports': imp['imports']
- })
-
- # Collect hook usages
- for hook_usage in result['hooks']:
- report['hook_usages'].append({
- 'hook': hook_usage['hook'],
- 'file': rel_path,
- 'line': hook_usage['line'],
- 'code': hook_usage['code']
- })
- hook_counts[hook_usage['hook']] += 1
-
- # Collect provider locations
- if result['provider']:
- for provider_info in result['provider_lines']:
- report['provider_locations'].append({
- 'file': rel_path,
- 'line': provider_info['line'],
- 'code': provider_info['code']
- })
-
- # Build summary
- report['summary'] = {
- 'total_hook_usages': sum(hook_counts.values()),
- 'hooks_by_type': {k: v for k, v in hook_counts.items() if v > 0},
- 'has_provider': len(report['provider_locations']) > 0,
- 'unique_hooks_used': [k for k, v in hook_counts.items() if v > 0]
- }
-
- return report
-
-def print_report(report: AnalysisReport):
- """Print human-readable report."""
- print("\n" + "=" * 60)
- print("MINIKIT ANALYSIS REPORT")
- print("=" * 60)
- print(f"\nProject: {report['project_dir']}")
- print(f"Files scanned: {report['files_scanned']}")
- print(f"Files with MiniKit: {report['files_with_minikit']}")
-
- print("\n--- IMPORTS ---")
- if report['imports']:
- for imp in report['imports']:
- print(f" {imp['file']}:{imp['line']}")
- print(f" Imports: {', '.join(imp['imports'])}")
- else:
- print(" No MiniKit imports found")
-
- print("\n--- HOOK USAGES ---")
- if report['hook_usages']:
- for usage in report['hook_usages']:
- print(f" {usage['file']}:{usage['line']}")
- print(f" Hook: {usage['hook']}")
- print(f" Code: {usage['code'][:80]}...")
- else:
- print(" No hook usages found")
-
- print("\n--- PROVIDER LOCATIONS ---")
- if report['provider_locations']:
- for loc in report['provider_locations']:
- print(f" {loc['file']}:{loc['line']}")
- print(f" {loc['code'][:80]}")
- else:
- print(" No MiniKitProvider found")
-
- print("\n--- SUMMARY ---")
- summary = report['summary']
- print(f" Total hook usages: {summary['total_hook_usages']}")
- print(f" Unique hooks: {', '.join(summary['unique_hooks_used']) or 'None'}")
- print(f" Has provider: {'Yes' if summary['has_provider'] else 'No'}")
-
- if summary['hooks_by_type']:
- print("\n Hooks by frequency:")
- for hook, count in sorted(summary['hooks_by_type'].items(), key=lambda x: -x[1]):
- print(f" {hook}: {count}")
-
- print("\n" + "=" * 60)
-
-def main():
- if len(sys.argv) < 2:
- print("Usage: python analyze_project.py [--json]")
- print("Example: python analyze_project.py ./my-minikit-app")
- sys.exit(1)
-
- project_dir = sys.argv[1]
- output_json = '--json' in sys.argv
-
- report = analyze_project(project_dir)
-
- if output_json:
- print(json.dumps(report, indent=2))
- else:
- print_report(report)
- # Also save JSON report
- report_path = Path(project_dir) / 'minikit-analysis.json'
- with open(report_path, 'w') as f:
- json.dump(report, f, indent=2)
- print(f"\nJSON report saved to: {report_path}")
-
-if __name__ == '__main__':
- main()
diff --git a/skills/build-on-base/scripts/validate_conversion.py b/skills/build-on-base/scripts/validate_conversion.py
deleted file mode 100644
index fd18dcd..0000000
--- a/skills/build-on-base/scripts/validate_conversion.py
+++ /dev/null
@@ -1,336 +0,0 @@
-#!/usr/bin/env python3
-"""
-Validate a converted project to ensure MiniKit has been fully removed
-and Farcaster SDK is properly set up.
-
-Usage:
- python scripts/validate_conversion.py
-
-Checks:
- 1. No remaining MiniKit imports
- 2. No remaining MiniKit hooks
- 3. No MiniKitProvider in code
- 4. Farcaster SDK is imported where needed
- 5. sdk.actions.ready() is called somewhere
- 6. package.json has correct dependencies
-"""
-
-import os
-import re
-import json
-import sys
-from pathlib import Path
-from typing import List, Tuple
-
-class ValidationResult:
- def __init__(self):
- self.errors: List[str] = []
- self.warnings: List[str] = []
- self.passed: List[str] = []
-
- def add_error(self, msg: str):
- self.errors.append(msg)
-
- def add_warning(self, msg: str):
- self.warnings.append(msg)
-
- def add_pass(self, msg: str):
- self.passed.append(msg)
-
- @property
- def is_valid(self) -> bool:
- return len(self.errors) == 0
-
-# Patterns to check
-MINIKIT_IMPORT = re.compile(r"from\s+['\"]@coinbase/onchainkit/minikit['\"]")
-ONCHAINKIT_IMPORT = re.compile(r"from\s+['\"]@coinbase/onchainkit['\"]")
-MINIKIT_HOOKS = re.compile(r'\b(useMiniKit|useClose|useOpenUrl|useViewProfile|useViewCast|useComposeCast|useAddFrame|useAuthenticate|useNotification|usePrimaryButton)\s*\(')
-MINIKIT_PROVIDER = re.compile(r' List[Path]:
- """Find all source files."""
- files = []
- for root, _, filenames in os.walk(directory):
- if 'node_modules' in root or '.next' in root:
- continue
- for filename in filenames:
- if filename.endswith(('.tsx', '.ts', '.jsx', '.js')):
- files.append(Path(root) / filename)
- return files
-
-def check_no_minikit_imports(files: List[Path], project_path: Path, result: ValidationResult):
- """Check that no MiniKit imports remain."""
- minikit_found = []
- onchainkit_found = []
-
- for filepath in files:
- try:
- content = filepath.read_text(encoding='utf-8')
- rel_path = str(filepath.relative_to(project_path))
-
- if MINIKIT_IMPORT.search(content):
- minikit_found.append(rel_path)
- if ONCHAINKIT_IMPORT.search(content):
- onchainkit_found.append(rel_path)
- except Exception:
- pass
-
- if minikit_found:
- result.add_error(f"MiniKit imports still present in: {', '.join(minikit_found)}")
-
- if onchainkit_found:
- result.add_error(f"OnchainKit imports still present in: {', '.join(onchainkit_found)} - replace with @farcaster/miniapp-sdk")
-
- if not minikit_found and not onchainkit_found:
- result.add_pass("No MiniKit/OnchainKit imports found")
-
-def check_no_minikit_hooks(files: List[Path], project_path: Path, result: ValidationResult):
- """Check that no MiniKit hooks are being used."""
- found = []
- for filepath in files:
- try:
- content = filepath.read_text(encoding='utf-8')
- matches = MINIKIT_HOOKS.findall(content)
- if matches:
- rel_path = filepath.relative_to(project_path)
- found.append(f"{rel_path}: {', '.join(set(matches))}")
- except Exception:
- pass
-
- if found:
- result.add_error(f"MiniKit hooks still in use:\n " + "\n ".join(found))
- else:
- result.add_pass("No MiniKit hooks found")
-
-def check_no_provider(files: List[Path], project_path: Path, result: ValidationResult):
- """Check that MiniKitProvider and OnchainKitProvider with miniKit are removed."""
- minikit_provider_found = []
- onchainkit_with_minikit_found = []
-
- for filepath in files:
- try:
- content = filepath.read_text(encoding='utf-8')
- rel_path = str(filepath.relative_to(project_path))
-
- if MINIKIT_PROVIDER.search(content):
- minikit_provider_found.append(rel_path)
-
- # Check for OnchainKitProvider with miniKit prop
- if ONCHAINKIT_PROVIDER.search(content) and MINIKIT_PROP.search(content):
- onchainkit_with_minikit_found.append(rel_path)
- except Exception:
- pass
-
- if minikit_provider_found:
- result.add_error(f"MiniKitProvider still present in: {', '.join(minikit_provider_found)}")
-
- if onchainkit_with_minikit_found:
- result.add_error(f"OnchainKitProvider with miniKit prop found in: {', '.join(onchainkit_with_minikit_found)} - replace with WagmiProvider + farcasterMiniApp connector")
-
- if not minikit_provider_found and not onchainkit_with_minikit_found:
- result.add_pass("MiniKit providers removed")
-
-def check_farcaster_sdk_usage(files: List[Path], project_path: Path, result: ValidationResult):
- """Check that Farcaster SDK is imported and ready() is called."""
- has_sdk_import = False
- has_wagmi_connector = False
- has_ready = False
- sdk_import_files = []
- wagmi_connector_files = []
- ready_files = []
-
- for filepath in files:
- try:
- content = filepath.read_text(encoding='utf-8')
- rel_path = str(filepath.relative_to(project_path))
-
- if FARCASTER_IMPORT.search(content):
- has_sdk_import = True
- sdk_import_files.append(rel_path)
- if FARCASTER_WAGMI_IMPORT.search(content):
- has_wagmi_connector = True
- wagmi_connector_files.append(rel_path)
- if SDK_READY.search(content):
- has_ready = True
- ready_files.append(rel_path)
- except Exception:
- pass
-
- if has_sdk_import:
- result.add_pass(f"Farcaster SDK imported in: {', '.join(sdk_import_files)}")
- else:
- result.add_error("Farcaster SDK not imported - add: import { sdk } from '@farcaster/miniapp-sdk'")
-
- if has_wagmi_connector:
- result.add_pass(f"Farcaster wagmi connector imported in: {', '.join(wagmi_connector_files)}")
- else:
- result.add_error("Farcaster wagmi connector not imported - add: import { farcasterMiniApp } from '@farcaster/miniapp-wagmi-connector'")
-
- if has_ready:
- result.add_pass(f"sdk.actions.ready() called in: {', '.join(ready_files)}")
- else:
- result.add_error("sdk.actions.ready() not found - add to your main page useEffect: await sdk.actions.ready()")
-
-def check_package_json(project_path: Path, result: ValidationResult):
- """Check package.json for correct dependencies."""
- package_json_path = project_path / 'package.json'
-
- if not package_json_path.exists():
- result.add_warning("package.json not found")
- return
-
- try:
- with open(package_json_path) as f:
- pkg = json.load(f)
- except Exception as e:
- result.add_error(f"Could not parse package.json: {e}")
- return
-
- deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
-
- # Check for old OnchainKit - this should be removed
- if '@coinbase/onchainkit' in deps:
- result.add_error("@coinbase/onchainkit still installed - run: npm uninstall @coinbase/onchainkit")
- else:
- result.add_pass("@coinbase/onchainkit removed")
-
- # Check for required Farcaster packages
- required_packages = [
- ('@farcaster/miniapp-sdk', 'npm install @farcaster/miniapp-sdk'),
- ('@farcaster/miniapp-wagmi-connector', 'npm install @farcaster/miniapp-wagmi-connector'),
- ('wagmi', 'npm install wagmi'),
- ('@tanstack/react-query', 'npm install @tanstack/react-query'),
- ]
-
- for package, install_cmd in required_packages:
- if package in deps:
- result.add_pass(f"{package} installed (version: {deps[package]})")
- else:
- result.add_error(f"{package} not installed - run: {install_cmd}")
-
-def check_env_variables(project_path: Path, result: ValidationResult):
- """Check for old environment variables that should be removed."""
- env_files = ['.env', '.env.local', '.env.example']
- old_vars = ['NEXT_PUBLIC_ONCHAINKIT_API_KEY', 'NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME']
-
- for env_file in env_files:
- env_path = project_path / env_file
- if env_path.exists():
- try:
- content = env_path.read_text()
- found = [var for var in old_vars if var in content]
- if found:
- result.add_warning(f"{env_file} contains old MiniKit vars: {', '.join(found)}")
- except Exception:
- pass
-
-def check_manifest(project_path: Path, result: ValidationResult):
- """Check farcaster.json manifest uses 'miniapp' instead of 'frame'."""
- # Check for manifest route
- manifest_paths = [
- project_path / 'app' / '.well-known' / 'farcaster.json' / 'route.ts',
- project_path / 'app' / '.well-known' / 'farcaster.json' / 'route.js',
- project_path / 'pages' / 'api' / '.well-known' / 'farcaster.json.ts',
- project_path / 'pages' / 'api' / '.well-known' / 'farcaster.json.js',
- project_path / 'public' / '.well-known' / 'farcaster.json',
- ]
-
- manifest_found = False
- for manifest_path in manifest_paths:
- if manifest_path.exists():
- manifest_found = True
- try:
- content = manifest_path.read_text()
-
- # Check for old 'frame' key
- if MANIFEST_FRAME_KEY.search(content):
- result.add_error(f"Manifest uses 'frame' key - change to 'miniapp': {manifest_path.relative_to(project_path)}")
-
- # Check for new 'miniapp' key
- if MANIFEST_MINIAPP_KEY.search(content):
- result.add_pass(f"Manifest correctly uses 'miniapp' key: {manifest_path.relative_to(project_path)}")
- elif not MANIFEST_FRAME_KEY.search(content):
- result.add_warning(f"Manifest found but no 'miniapp' or 'frame' key detected: {manifest_path.relative_to(project_path)}")
-
- except Exception as e:
- result.add_warning(f"Could not read manifest: {e}")
- break
-
- if not manifest_found:
- result.add_warning("No farcaster.json manifest found - create app/.well-known/farcaster.json/route.ts")
-
-def validate_project(project_dir: str) -> ValidationResult:
- """Run all validation checks."""
- project_path = Path(project_dir).resolve()
- result = ValidationResult()
-
- if not project_path.exists():
- result.add_error(f"Directory does not exist: {project_dir}")
- return result
-
- files = find_source_files(str(project_path))
-
- if not files:
- result.add_warning("No source files found")
- return result
-
- # Run checks
- check_no_minikit_imports(files, project_path, result)
- check_no_minikit_hooks(files, project_path, result)
- check_no_provider(files, project_path, result)
- check_farcaster_sdk_usage(files, project_path, result)
- check_package_json(project_path, result)
- check_env_variables(project_path, result)
- check_manifest(project_path, result)
-
- return result
-
-def print_result(result: ValidationResult):
- """Print validation results."""
- print("\n" + "=" * 60)
- print("CONVERSION VALIDATION REPORT")
- print("=" * 60)
-
- if result.passed:
- print("\n✅ PASSED:")
- for msg in result.passed:
- print(f" {msg}")
-
- if result.warnings:
- print("\n⚠️ WARNINGS:")
- for msg in result.warnings:
- print(f" {msg}")
-
- if result.errors:
- print("\n❌ ERRORS:")
- for msg in result.errors:
- print(f" {msg}")
-
- print("\n" + "-" * 60)
- if result.is_valid:
- print("✅ VALIDATION PASSED - Conversion looks complete!")
- else:
- print("❌ VALIDATION FAILED - Please fix the errors above")
- print("=" * 60 + "\n")
-
-def main():
- if len(sys.argv) < 2:
- print("Usage: python validate_conversion.py ")
- print("Example: python validate_conversion.py ./my-converted-app")
- sys.exit(1)
-
- project_dir = sys.argv[1]
- result = validate_project(project_dir)
- print_result(result)
-
- sys.exit(0 if result.is_valid else 1)
-
-if __name__ == '__main__':
- main()