├── .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 |
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 |
--------------------------------------------------------------------------------