UNPKG

create-rainbow-app

Version:
608 lines (490 loc) 16.7 kB
/** * File content templates for create-rainbow-app */ // Wagmi configuration template const wagmiTemplate = `import { getDefaultConfig } from "@rainbow-me/rainbowkit"; import { mainnet, polygon, optimism, arbitrum, base } from "viem/chains"; import { createStorage } from "wagmi"; export const config = getDefaultConfig({ appName: "My RainbowKit App", projectId: "YOUR_PROJECT_ID", storage: createStorage({ key: "my-app" }), chains: [mainnet, polygon, optimism, arbitrum, base], ssr: true, // If your dApp uses server side rendering (SSR) }); `; // Fonts template (shared by both routers) const fontsTemplate = `import { Geist, Geist_Mono } from "next/font/google"; export const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], }); export const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"], }); `; // Demo ABI template const demoAbiTemplate = [ { inputs: [ { internalType: "string", name: "_greeting", type: "string", }, ], name: "setGreeting", outputs: [], stateMutability: "nonpayable", type: "function", }, { inputs: [], name: "greeting", outputs: [ { internalType: "string", name: "", type: "string", }, ], stateMutability: "view", type: "function", }, ]; // App Router templates const appRouter = { provider: `"use client"; import { config } from "@/wagmi"; import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { WagmiProvider } from "wagmi"; const queryClient = new QueryClient(); export default function Provider({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <WagmiProvider config={config}> <QueryClientProvider client={queryClient}> <RainbowKitProvider>{children}</RainbowKitProvider> </QueryClientProvider> </WagmiProvider> ); } `, layout: `import type { Metadata } from "next"; import "./globals.css"; import { geistMono, geistSans } from "@/lib/fonts"; import "@rainbow-me/rainbowkit/styles.css"; import Provider from "@/provider/web3provider"; export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body className={\`\${geistSans.variable} \${geistMono.variable} antialiased\`} > <Provider>{children}</Provider> </body> </html> ); } `, page: `import { ConnectButton } from "@rainbow-me/rainbowkit"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { geistMono, geistSans } from "@/lib/fonts"; export default function Home() { return ( <div className={\`\${geistSans.className} \${geistMono.className} grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]\`} > <main className="flex flex-col gap-10 row-start-2 items-center sm:items-start"> <div className="flex flex-col items-center gap-4"> <h1 className="text-4xl font-bold text-center"> Web3 Dapp Template </h1> <p className="text-lg text-center text-gray-600 dark:text-gray-400"> Web3 dApp template with Next.js, TypeScript, Tailwind CSS & shadcn/ui </p> </div> <div className="flex flex-col items-center justify-center gap-6 w-full"> <ConnectButton /> <div className="w-full p-6 border border-gray-200 dark:border-gray-800 rounded-lg"> <h2 className="text-xl font-semibold mb-4 text-center"> Get Started </h2> <ol className="list-inside list-decimal text-sm space-y-2 font-[family-name:var(--font-geist-mono)]"> <li> Edit{" "} <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> src/app/page.tsx </code> </li> <li> Update your project ID in{" "} <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> src/wagmi.ts </code> </li> <li>Add shadcn/ui components as needed</li> <li>Build your Web3 application!</li> </ol> </div> </div> <div className="flex gap-4 items-center flex-col sm:flex-row"> <Link href="https://rainbowkit.com/docs" target="_blank" rel="noopener noreferrer" > <Button variant="secondary">RainbowKit Docs</Button> </Link> <Link href="https://ui.shadcn.com" target="_blank" rel="noopener noreferrer" > <Button variant="outline">shadcn/ui</Button> </Link> </div> </main> <footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center text-sm text-gray-500"> <span>Built with Web3 Dapp Template</span> </footer> </div> ); } `, }; // Pages Router templates const pagesRouter = { provider: `"use client"; import { config } from "@/wagmi"; import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { WagmiProvider } from "wagmi"; const queryClient = new QueryClient(); export default function Provider({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <WagmiProvider config={config}> <QueryClientProvider client={queryClient}> <RainbowKitProvider>{children}</RainbowKitProvider> </QueryClientProvider> </WagmiProvider> ); } `, app: `import "@/styles/globals.css"; import type { AppProps } from "next/app"; import "@rainbow-me/rainbowkit/styles.css"; import Provider from "@/provider/web3provider"; export default function App({ Component, pageProps }: AppProps) { return ( <Provider> <Component {...pageProps} /> </Provider> ); } `, index: `import { ConnectButton } from "@rainbow-me/rainbowkit"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { geistMono, geistSans } from "@/lib/fonts"; export default function Home() { return ( <div className={\`\${geistSans.className} \${geistMono.className} grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]\`} > <main className="flex flex-col gap-10 row-start-2 items-center sm:items-start"> <div className="flex flex-col items-center gap-4"> <h1 className="text-4xl font-bold text-center"> Web3 Dapp Template </h1> <p className="text-lg text-center text-gray-600 dark:text-gray-400"> Web3 dApp template with Next.js, TypeScript, Tailwind CSS & shadcn/ui </p> </div> <div className="flex flex-col items-center justify-center gap-6 w-full"> <ConnectButton /> <div className="w-full p-6 border border-gray-200 dark:border-gray-800 rounded-lg"> <h2 className="text-xl font-semibold mb-4 text-center"> Get Started </h2> <ol className="list-inside list-decimal text-sm space-y-2 font-[family-name:var(--font-geist-mono)]"> <li> Edit{" "} <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> src/pages/index.tsx </code> </li> <li> Update your project ID in{" "} <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold"> src/wagmi.ts </code> </li> <li>Add shadcn/ui components as needed</li> <li>Build your Web3 application!</li> </ol> </div> </div> <div className="flex gap-4 items-center flex-col sm:flex-row"> <Link href="https://rainbowkit.com/docs" target="_blank" rel="noopener noreferrer" > <Button variant="secondary">RainbowKit Docs</Button> </Link> <Link href="https://ui.shadcn.com" target="_blank" rel="noopener noreferrer" > <Button variant="outline">shadcn/ui</Button> </Link> </div> </main> <footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center text-sm text-gray-500"> <span>Built with Web3 Dapp Template</span> </footer> </div> ); } `, }; // AGENTS.md template written to the root of every scaffolded project const agentsMdTemplate = `# Ethereum DApp — Agent Guidelines This project was scaffolded with create-rainbow-app. Read this file in full before writing any code. --- ## Stack & Versions | Package | Version | Notes | | ---------------------- | ------- | ------------------------------------ | | next | 15.x | App Router or Pages Router | | typescript | 5.x | | | wagmi | 2.x | Breaking changes from v1 — see below | | viem | 2.x | Replaces ethers.js | | @rainbow-me/rainbowkit | 2.x | | | @tanstack/react-query | 5.x | Breaking changes from v4 — see below | | tailwindcss | 3.x | | | shadcn/ui | latest | | --- ## Project Structure \`\`\` src/ abi/ # Contract ABI JSON files app/ # App Router: layout.tsx, page.tsx (if App Router) pages/ # Pages Router: _app.tsx, index.tsx (if Pages Router) components/ ui/ # shadcn/ui primitives — do not edit these files lib/ fonts.ts # Geist font exports provider/ web3provider.tsx # Single file that composes all Web3 providers wagmi.ts # Wagmi config — chains, connectors, projectId live here \`\`\` --- ## wagmi v2 — Critical Changes from v1 **Hook renames — always use the v2 names:** | v1 (wrong) | v2 (correct) | | ------------------------- | -------------------------- | | \`useContractRead\` | \`useReadContract\` | | \`useContractWrite\` | \`useWriteContract\` | | \`usePrepareContractWrite\` | removed — do not use | | \`useContractReads\` | \`useReadContracts\` | | \`useContractEvent\` | \`useWatchContractEvent\` | | \`useNetwork\` | \`useChainId\` / \`useChains\` | | \`useSwitchNetwork\` | \`useSwitchChain\` | | \`useAccount\` | \`useConnection\` | | \`useBalance\` | same name, new signature | **Reading from a contract:** \`\`\`ts import { useReadContract } from "wagmi"; import abi from "@/abi/demo.json"; const { data, isLoading } = useReadContract({ address: "0x...", abi, functionName: "greeting", }); \`\`\` **Writing to a contract:** \`\`\`ts import { useWriteContract, useWaitForTransactionReceipt } from "wagmi"; const writeContractCall = useWriteContract(); const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash, }); writeContractCall.mutate({ address: "0x...", abi, functionName: "setGreeting", args: ["hello"], }); // or writeContractCall.mutateAsync({ address: "0x...", abi, functionName: "setGreeting", args: ["hello"], }); \`\`\` There is no \`prepare\` step in v2. Call \`writeContract\` directly. **Account:** \`\`\`ts import { useConnection } from "wagmi"; const { address, isConnected, chain } = useConnection(); // address is \`0x\${string} | undefined\` \`\`\` **Balance:** \`\`\`ts const { data: balance } = useBalance({ address }); // balance.value is bigint, balance.formatted is string \`\`\` --- ## viem v2 — Key Patterns viem replaces ethers.js entirely. Never import from ethers in this project. **Common utilities:** \`\`\`ts import { parseEther, formatEther, parseUnits, formatUnits } from "viem"; parseEther("1.0"); // => 1000000000000000000n formatEther(1000000000000000000n); // => "1.0" \`\`\` **Address type:** \`\`\`ts import type { Address } from "viem"; // All addresses are typed as \`0x\${string}\` // Never use plain string for an address param \`\`\` **Encoding calldata:** \`\`\`ts import { encodeFunctionData } from "viem"; const data = encodeFunctionData({ abi, functionName: "setGreeting", args: ["hello"], }); \`\`\` **All amounts are bigint — never use Number for on-chain values.** --- ## RainbowKit **Config is in \`src/wagmi.ts\` — use \`getDefaultConfig\`:** \`\`\`ts import { getDefaultConfig } from "@rainbow-me/rainbowkit"; export const config = getDefaultConfig({ appName: "My App", projectId: "YOUR_PROJECT_ID", // Replace with WalletConnect Cloud project ID chains: [mainnet, polygon, optimism, arbitrum, base], ssr: true, }); \`\`\` \`getDefaultConfig\` replaces the old \`configureChains\` + \`createConfig\` pattern. Do not use the old pattern. **Connect button — use as-is, do not rebuild it:** \`\`\`tsx import { ConnectButton } from "@rainbow-me/rainbowkit"; <ConnectButton />; \`\`\` **Provider order in \`src/provider/web3provider.tsx\` must be:** \`\`\` WagmiProvider QueryClientProvider RainbowKitProvider {children} \`\`\` Do not change this nesting order. **Before shipping**, replace \`YOUR_PROJECT_ID\` in \`src/wagmi.ts\` with a real WalletConnect Cloud project ID from https://cloud.walletconnect.com. --- ## @tanstack/react-query v5 — Critical Changes from v4 **useQuery now takes a single options object — no overloads:** \`\`\`ts // v4 (wrong) useQuery(["key"], fetchFn); // v5 (correct) useQuery({ queryKey: ["key"], queryFn: fetchFn }); \`\`\` **\`onSuccess\` / \`onError\` callbacks are removed from \`useQuery\`.** Use \`useEffect\` to react to data changes: \`\`\`ts const { data } = useQuery({ queryKey: ["key"], queryFn: fetchFn }); useEffect(() => { if (data) { /* handle */ } }, [data]); \`\`\` **\`cacheTime\` is renamed to \`gcTime\`.** **\`isLoading\` vs \`isPending\`:** - \`isLoading\` — query has no cached data and is fetching - \`isPending\` — no data yet (includes disabled queries) Use \`isPending\` when the query may be disabled. --- ## shadcn/ui Components live in \`src/components/ui/\`. Do not edit files in that directory. To add a new component: \`\`\` bunx shadcn@latest add <component-name> \`\`\` Import from the \`@/components/ui/\` alias: \`\`\`ts import { Button } from "@/components/ui/button"; \`\`\` Available variants for Button: \`default\`, \`destructive\`, \`outline\`, \`secondary\`, \`ghost\`, \`link\`. --- ## TypeScript Conventions - All contract addresses must be typed as \`0x\${string}\` or \`Address\` from viem — never \`string\`. - All on-chain numeric values must be \`bigint\` — never \`number\`. - ABIs must be typed with \`as const\` for full type inference: \`\`\`ts import abi from "@/abi/demo.json" assert { type: "json" }; // or define inline with \`as const\` \`\`\` - Never use \`any\`. Use \`unknown\` and narrow with type guards. --- ## Provider & Client Component Rules (App Router only) - \`src/provider/web3provider.tsx\` has \`"use client"\` at the top — it must stay there. - Any component that uses a wagmi hook, RainbowKit hook, or react-query hook must be a client component (\`"use client"\`). - \`layout.tsx\` is a server component — do not add hooks or browser APIs there. Use the Provider for all client-side setup. - Never add \`"use client"\` to \`layout.tsx\` or \`page.tsx\` unless strictly required. --- ## Adding a New Chain Import from \`viem/chains\` and add to the \`chains\` array in \`src/wagmi.ts\`: \`\`\`ts import { mainnet, polygon, base, arbitrum, optimism, zora } from "viem/chains"; export const config = getDefaultConfig({ ... chains: [mainnet, polygon, base, arbitrum, optimism, zora], }); \`\`\` --- ## Common Mistakes to Avoid - Do not use \`ethers\`, \`web3.js\`, or \`@ethersproject/*\` — this project uses viem exclusively. - Do not call \`usePrepareContractWrite\` — it does not exist in wagmi v2. - Do not use \`Number()\` to convert bigint on-chain values — use \`formatEther\` / \`formatUnits\`. - Do not read \`theme\` from \`useTheme\` during SSR without a \`mounted\` guard — it will be \`undefined\` on the server. - Do not use plain \`string\` for Ethereum addresses — always use \`0x\${string}\` or \`Address\`. - Do not mutate the \`QueryClient\` instance — it is created once and shared via \`QueryClientProvider\`. - Do not add new dependencies without checking if wagmi or viem already exports the utility you need. `; module.exports = { wagmiTemplate, fontsTemplate, demoAbiTemplate, appRouter, pagesRouter, agentsMdTemplate, };