├── .env.example ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── app ├── globals.css ├── layout.tsx └── page.tsx ├── components.json ├── components ├── Providers.tsx └── ui │ ├── button.tsx │ ├── card.tsx │ ├── input.tsx │ ├── label.tsx │ └── separator.tsx ├── eslint.config.mjs ├── lib └── utils.ts ├── next.config.ts ├── package.json ├── postcss.config.mjs ├── public └── azflin.jpg ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PROJECT_ID= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | 43 | .idea -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is opinionated starter code building an EVM dapp. Uses next-js, rainbow-kit, wagmi, tailwind, shad-cn. Bar none the cleanest and best stack to make EVM dapps. Very AI friendly, all the LLMs are very well versed in these frameworks. 2 | 3 | ```bash 4 | yarn 5 | npm run dev 6 | # Edit NEXT_PUBLIC_PROJECT_ID= in your .env, this is your WalletConnect project id required for rainbowkit. 7 | ``` 8 | 9 | Made by https://x.com/AzFlin -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @import "tw-animate-css"; 3 | 4 | @custom-variant dark (&:is(.dark *)); 5 | 6 | @theme inline { 7 | --color-background: var(--background); 8 | --color-foreground: var(--foreground); 9 | --color-sidebar-ring: var(--sidebar-ring); 10 | --color-sidebar-border: var(--sidebar-border); 11 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 12 | --color-sidebar-accent: var(--sidebar-accent); 13 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 14 | --color-sidebar-primary: var(--sidebar-primary); 15 | --color-sidebar-foreground: var(--sidebar-foreground); 16 | --color-sidebar: var(--sidebar); 17 | --color-chart-5: var(--chart-5); 18 | --color-chart-4: var(--chart-4); 19 | --color-chart-3: var(--chart-3); 20 | --color-chart-2: var(--chart-2); 21 | --color-chart-1: var(--chart-1); 22 | --color-ring: var(--ring); 23 | --color-input: var(--input); 24 | --color-border: var(--border); 25 | --color-destructive: var(--destructive); 26 | --color-accent-foreground: var(--accent-foreground); 27 | --color-accent: var(--accent); 28 | --color-muted-foreground: var(--muted-foreground); 29 | --color-muted: var(--muted); 30 | --color-secondary-foreground: var(--secondary-foreground); 31 | --color-secondary: var(--secondary); 32 | --color-primary-foreground: var(--primary-foreground); 33 | --color-primary: var(--primary); 34 | --color-popover-foreground: var(--popover-foreground); 35 | --color-popover: var(--popover); 36 | --color-card-foreground: var(--card-foreground); 37 | --color-card: var(--card); 38 | --radius-sm: calc(var(--radius) - 4px); 39 | --radius-md: calc(var(--radius) - 2px); 40 | --radius-lg: var(--radius); 41 | --radius-xl: calc(var(--radius) + 4px); 42 | } 43 | 44 | body { 45 | /* font-family is now handled by next/font in layout.tsx */ 46 | } 47 | 48 | :root { 49 | --radius: 0.625rem; 50 | --background: oklch(1 0 0); 51 | --foreground: oklch(0.145 0 0); 52 | --card: oklch(1 0 0); 53 | --card-foreground: oklch(0.145 0 0); 54 | --popover: oklch(1 0 0); 55 | --popover-foreground: oklch(0.145 0 0); 56 | --primary: oklch(0.205 0 0); 57 | --primary-foreground: oklch(0.985 0 0); 58 | --secondary: oklch(0.97 0 0); 59 | --secondary-foreground: oklch(0.205 0 0); 60 | --muted: oklch(0.97 0 0); 61 | --muted-foreground: oklch(0.556 0 0); 62 | --accent: oklch(0.97 0 0); 63 | --accent-foreground: oklch(0.205 0 0); 64 | --destructive: oklch(0.577 0.245 27.325); 65 | --border: oklch(0.922 0 0); 66 | --input: oklch(0.922 0 0); 67 | --ring: oklch(0.708 0 0); 68 | --chart-1: oklch(0.646 0.222 41.116); 69 | --chart-2: oklch(0.6 0.118 184.704); 70 | --chart-3: oklch(0.398 0.07 227.392); 71 | --chart-4: oklch(0.828 0.189 84.429); 72 | --chart-5: oklch(0.769 0.188 70.08); 73 | --sidebar: oklch(0.985 0 0); 74 | --sidebar-foreground: oklch(0.145 0 0); 75 | --sidebar-primary: oklch(0.205 0 0); 76 | --sidebar-primary-foreground: oklch(0.985 0 0); 77 | --sidebar-accent: oklch(0.97 0 0); 78 | --sidebar-accent-foreground: oklch(0.205 0 0); 79 | --sidebar-border: oklch(0.922 0 0); 80 | --sidebar-ring: oklch(0.708 0 0); 81 | } 82 | 83 | .dark { 84 | --background: oklch(0.145 0 0); 85 | --foreground: oklch(0.985 0 0); 86 | --card: oklch(0.205 0 0); 87 | --card-foreground: oklch(0.985 0 0); 88 | --popover: oklch(0.205 0 0); 89 | --popover-foreground: oklch(0.985 0 0); 90 | --primary: oklch(0.922 0 0); 91 | --primary-foreground: oklch(0.205 0 0); 92 | --secondary: oklch(0.269 0 0); 93 | --secondary-foreground: oklch(0.985 0 0); 94 | --muted: oklch(0.269 0 0); 95 | --muted-foreground: oklch(0.708 0 0); 96 | --accent: oklch(0.269 0 0); 97 | --accent-foreground: oklch(0.985 0 0); 98 | --destructive: oklch(0.704 0.191 22.216); 99 | --border: oklch(1 0 0 / 10%); 100 | --input: oklch(1 0 0 / 15%); 101 | --ring: oklch(0.556 0 0); 102 | --chart-1: oklch(0.488 0.243 264.376); 103 | --chart-2: oklch(0.696 0.17 162.48); 104 | --chart-3: oklch(0.769 0.188 70.08); 105 | --chart-4: oklch(0.627 0.265 303.9); 106 | --chart-5: oklch(0.645 0.246 16.439); 107 | --sidebar: oklch(0.205 0 0); 108 | --sidebar-foreground: oklch(0.985 0 0); 109 | --sidebar-primary: oklch(0.488 0.243 264.376); 110 | --sidebar-primary-foreground: oklch(0.985 0 0); 111 | --sidebar-accent: oklch(0.269 0 0); 112 | --sidebar-accent-foreground: oklch(0.985 0 0); 113 | --sidebar-border: oklch(1 0 0 / 10%); 114 | --sidebar-ring: oklch(0.556 0 0); 115 | } 116 | 117 | @layer base { 118 | * { 119 | @apply border-border outline-ring/50; 120 | } 121 | body { 122 | @apply bg-background text-foreground; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Open_Sans } from "next/font/google"; 3 | import "./globals.css"; 4 | import "@rainbow-me/rainbowkit/styles.css"; 5 | import { Providers } from "../components/Providers"; 6 | 7 | const openSans = Open_Sans({ 8 | subsets: ["latin"], 9 | }); 10 | 11 | export const metadata: Metadata = { 12 | title: "EVM Frontend Starter", 13 | description: "A Next.js application using Open Sans font.", 14 | }; 15 | 16 | export default function RootLayout({ 17 | children, 18 | }: Readonly<{ 19 | children: React.ReactNode; 20 | }>) { 21 | return ( 22 | 23 | 24 | {children} 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { ConnectButton } from "@rainbow-me/rainbowkit"; 3 | 4 | export default function Home() { 5 | return ( 6 |
7 |
8 |
9 | AzFlin's EVM Starter Code 10 | 11 |
12 |
13 | {"azflin"} 20 |
21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /components/Providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import "@rainbow-me/rainbowkit/styles.css"; 4 | import { getDefaultConfig, RainbowKitProvider } from "@rainbow-me/rainbowkit"; 5 | import { WagmiProvider } from "wagmi"; 6 | import { mainnet, polygon, optimism, arbitrum, base } from "wagmi/chains"; 7 | import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; 8 | 9 | const config = getDefaultConfig({ 10 | appName: "My RainbowKit App", 11 | projectId: process.env.NEXT_PUBLIC_PROJECT_ID!, 12 | chains: [mainnet, polygon, optimism, arbitrum, base], 13 | ssr: true, // If your dApp uses server side rendering (SSR) 14 | }); 15 | const queryClient = new QueryClient(); 16 | 17 | export function Providers({ children }: { children: React.ReactNode }) { 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 16 | outline: 17 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", 20 | ghost: 21 | "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 22 | link: "text-primary underline-offset-4 hover:underline", 23 | }, 24 | size: { 25 | default: "h-9 px-4 py-2 has-[>svg]:px-3", 26 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 27 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 28 | icon: "size-9", 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: "default", 33 | size: "default", 34 | }, 35 | }, 36 | ); 37 | 38 | function Button({ 39 | className, 40 | variant, 41 | size, 42 | asChild = false, 43 | ...props 44 | }: React.ComponentProps<"button"> & 45 | VariantProps & { 46 | asChild?: boolean; 47 | }) { 48 | const Comp = asChild ? Slot : "button"; 49 | 50 | return ( 51 | 56 | ); 57 | } 58 | 59 | export { Button, buttonVariants }; 60 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ); 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
28 | ); 29 | } 30 | 31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 32 | return ( 33 |
38 | ); 39 | } 40 | 41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 42 | return ( 43 |
48 | ); 49 | } 50 | 51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) { 52 | return ( 53 |
61 | ); 62 | } 63 | 64 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 65 | return ( 66 |
71 | ); 72 | } 73 | 74 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 75 | return ( 76 |
81 | ); 82 | } 83 | 84 | export { 85 | Card, 86 | CardHeader, 87 | CardFooter, 88 | CardTitle, 89 | CardAction, 90 | CardDescription, 91 | CardContent, 92 | }; 93 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ); 19 | } 20 | 21 | export { Input }; 22 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ); 22 | } 23 | 24 | export { Label }; 25 | -------------------------------------------------------------------------------- /components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Separator({ 9 | className, 10 | orientation = "horizontal", 11 | decorative = true, 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 25 | ); 26 | } 27 | 28 | export { Separator }; 29 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "evm-frontend-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-label": "^2.1.7", 13 | "@radix-ui/react-separator": "^1.1.7", 14 | "@radix-ui/react-slot": "^1.2.3", 15 | "@rainbow-me/rainbowkit": "^2.2.8", 16 | "@tanstack/react-query": "^5.81.2", 17 | "class-variance-authority": "^0.7.1", 18 | "clsx": "^2.1.1", 19 | "lucide-react": "^0.523.0", 20 | "next": "15.3.4", 21 | "react": "^19.0.0", 22 | "react-dom": "^19.0.0", 23 | "tailwind-merge": "^3.3.1", 24 | "viem": "2.x", 25 | "wagmi": "^2.15.6" 26 | }, 27 | "devDependencies": { 28 | "@eslint/eslintrc": "^3", 29 | "@tailwindcss/postcss": "^4", 30 | "@types/node": "^20", 31 | "@types/react": "^19", 32 | "@types/react-dom": "^19", 33 | "eslint": "^9", 34 | "eslint-config-next": "15.3.4", 35 | "prettier": "3.6.2", 36 | "tailwindcss": "^4", 37 | "tw-animate-css": "^1.3.4", 38 | "typescript": "^5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /public/azflin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azflin/evm-frontend-starter/89831626373363d238983c471326a6c584a56f7c/public/azflin.jpg -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------