├── .eslintrc.json ├── .env.example ├── public ├── pex.png ├── vercel.svg └── next.svg ├── screenshot.png ├── src ├── app │ ├── favicon.ico │ ├── counter │ │ └── page.tsx │ ├── layout.tsx │ ├── globals.css │ └── page.tsx ├── constants │ ├── index.ts │ └── abi.ts ├── lib │ └── utils.ts ├── components │ ├── ThemeProvider.tsx │ ├── ui │ │ ├── input.tsx │ │ ├── sonner.tsx │ │ ├── button.tsx │ │ └── dropdown-menu.tsx │ ├── Nav.tsx │ ├── Modetoggle.tsx │ └── WriteCounter.tsx ├── configs │ └── index.tsx └── contexts │ └── Web3Modal.tsx ├── next.config.mjs ├── postcss.config.mjs ├── components.json ├── .gitignore ├── tsconfig.json ├── package.json ├── tailwind.config.ts └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PROJECT_ID= 2 | 3 | -------------------------------------------------------------------------------- /public/pex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Protocol-Explorer/web3_starter_kit/HEAD/public/pex.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Protocol-Explorer/web3_starter_kit/HEAD/screenshot.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Protocol-Explorer/web3_starter_kit/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { counterAbi } from "./abi"; 2 | const counterAddress = "0xeaE184E93f2a38d642Ae016f2a53110a2c5d1FCB"; 3 | 4 | export { counterAbi, counterAddress }; 5 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/components/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ThemeProvider as NextThemesProvider } from "next-themes" 5 | import { type ThemeProviderProps } from "next-themes/dist/types" 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children} 9 | } 10 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Various IDEs and Editors 4 | .vscode/ 5 | .idea/ 6 | **/*~ 7 | 8 | # dependencies 9 | /node_modules 10 | /.pnp 11 | .pnp.js 12 | .yarn/install-state.gz 13 | 14 | # testing 15 | /coverage 16 | 17 | # next.js 18 | /.next/ 19 | /out/ 20 | 21 | # production 22 | /build 23 | 24 | # misc 25 | .DS_Store 26 | *.pem 27 | 28 | # debug 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | 33 | # local env files 34 | .env*.local 35 | .env 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /src/constants/abi.ts: -------------------------------------------------------------------------------- 1 | export const counterAbi = [ 2 | { 3 | inputs: [], 4 | name: "number", 5 | outputs: [ 6 | { 7 | internalType: "uint256", 8 | name: "", 9 | type: "uint256", 10 | }, 11 | ], 12 | stateMutability: "view", 13 | type: "function", 14 | }, 15 | { 16 | inputs: [ 17 | { 18 | internalType: "uint256", 19 | name: "new_number", 20 | type: "uint256", 21 | }, 22 | ], 23 | name: "setNumber", 24 | outputs: [], 25 | stateMutability: "external", 26 | type: "function", 27 | }, 28 | { 29 | inputs: [], 30 | name: "increment", 31 | outputs: [], 32 | stateMutability: "external", 33 | type: "function", 34 | }, 35 | ]; 36 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /src/configs/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { defaultWagmiConfig } from "@web3modal/wagmi/react/config"; 3 | 4 | import { cookieStorage, createStorage } from "wagmi"; 5 | import { morphSepolia } from "viem/chains"; 6 | 7 | // Get projectId at https://cloud.walletconnect.com 8 | export const projectId = 9 | process.env.NEXT_PUBLIC_PROJECT_ID; 10 | 11 | if (!projectId) throw new Error("Project ID is not defined"); 12 | 13 | const metadata = { 14 | name: "Web3Modal", 15 | description: "Web3Modal Example", 16 | url: "https://web3modal.com", // origin must match your domain & subdomain 17 | icons: ["https://avatars.githubusercontent.com/u/37784886"], 18 | }; 19 | 20 | // Create wagmiConfig 21 | const chains = [morphSepolia] as const; 22 | 23 | export const config = defaultWagmiConfig({ 24 | chains, 25 | projectId, 26 | metadata, 27 | ssr: true, 28 | storage: createStorage({ 29 | storage: cookieStorage, 30 | }), 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/counter/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useReadContract } from "wagmi"; 3 | import { counterAddress, counterAbi } from "@/constants"; 4 | import { WriteContract } from "@/components/WriteCounter"; 5 | 6 | function Counter() { 7 | const { 8 | data: counter, 9 | status, 10 | isLoading, 11 | error, 12 | } = useReadContract({ 13 | abi: counterAbi, 14 | address: counterAddress, 15 | functionName: "number", 16 | }); 17 | return ( 18 |
19 |
20 |

Example Counter DApp

21 |

22 | {isLoading ? <>Loading... : <>Counter: {counter?.toString()}} 23 |

24 |
25 |
26 | 27 |
28 |
29 | ); 30 | } 31 | 32 | export default Counter; 33 | -------------------------------------------------------------------------------- /src/components/Nav.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import Image from "next/image" 3 | import { ModeToggle } from "./Modetoggle" 4 | 5 | 6 | export default function Nav() { 7 | return( 8 |
9 | 31 |
32 | ) 33 | } -------------------------------------------------------------------------------- /src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | import { Toaster as Sonner } from "sonner"; 5 | 6 | type ToasterProps = React.ComponentProps; 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = "system" } = useTheme(); 10 | 11 | return ( 12 | 28 | ); 29 | }; 30 | 31 | export { Toaster }; 32 | -------------------------------------------------------------------------------- /src/contexts/Web3Modal.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { ReactNode } from "react"; 4 | import { config, projectId } from "@/configs"; 5 | 6 | import { createWeb3Modal } from "@web3modal/wagmi/react"; 7 | 8 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 9 | 10 | import { State, WagmiProvider } from "wagmi"; 11 | 12 | // Setup queryClient 13 | const queryClient = new QueryClient(); 14 | 15 | if (!projectId) throw new Error("Project ID is not defined"); 16 | 17 | // Create modal 18 | createWeb3Modal({ 19 | wagmiConfig: config, 20 | projectId, 21 | enableAnalytics: true, // Optional - defaults to your Cloud configuration 22 | enableOnramp: true, // Optional - false as default 23 | }); 24 | 25 | export default function Web3ModalProvider({ 26 | children, 27 | initialState, 28 | }: { 29 | children: ReactNode; 30 | initialState?: State; 31 | }) { 32 | return ( 33 | 34 | {children} 35 | 36 | ); 37 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3_starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-dropdown-menu": "^2.0.6", 13 | "@radix-ui/react-slot": "^1.0.2", 14 | "@tanstack/react-query": "^5.32.1", 15 | "@web3modal/wagmi": "^4.1.11", 16 | "class-variance-authority": "^0.7.0", 17 | "clsx": "^2.1.1", 18 | "lucide-react": "^0.376.0", 19 | "next": "14.2.3", 20 | "next-themes": "^0.3.0", 21 | "react": "^18", 22 | "react-dom": "^18", 23 | "sonner": "^1.4.41", 24 | "tailwind-merge": "^2.3.0", 25 | "tailwindcss-animate": "^1.0.7", 26 | "viem": "^2.9.31", 27 | "wagmi": "^2.8.0" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^20", 31 | "@types/react": "^18", 32 | "@types/react-dom": "^18", 33 | "eslint": "^8", 34 | "eslint-config-next": "14.2.3", 35 | "postcss": "^8", 36 | "tailwindcss": "^3.4.1", 37 | "typescript": "^5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Modetoggle.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { Moon, Sun } from "lucide-react" 5 | import { useTheme } from "next-themes" 6 | 7 | import { Button } from "@/components/ui/button" 8 | import { 9 | DropdownMenu, 10 | DropdownMenuContent, 11 | DropdownMenuItem, 12 | DropdownMenuTrigger, 13 | } from "@/components/ui/dropdown-menu" 14 | 15 | export function ModeToggle() { 16 | const { setTheme } = useTheme() 17 | 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | setTheme("light")}> 29 | Light 30 | 31 | setTheme("dark")}> 32 | Dark 33 | 34 | setTheme("system")}> 35 | System 36 | 37 | 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import { Inter as FontSans } from "next/font/google"; 3 | import { ThemeProvider } from "@/components/ThemeProvider"; 4 | import { headers } from "next/headers"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | import Nav from "@/components/Nav"; 8 | import { Toaster } from "@/components/ui/sonner"; 9 | 10 | import { cookieToInitialState } from "wagmi"; 11 | 12 | import { config } from "@/configs"; 13 | import Web3ModalProvider from "@/contexts/Web3Modal"; 14 | 15 | const fontSans = FontSans({ 16 | subsets: ["latin"], 17 | variable: "--font-sans", 18 | }); 19 | 20 | export default function RootLayout({ 21 | children, 22 | }: { 23 | children: React.ReactNode; 24 | }) { 25 | // const initialState = cookieToInitialState(config, headers().get("cookie")) || null; 26 | return ( 27 | 28 | 29 | 30 | Modern Web3 Starter 31 | 35 | 36 | 42 |
43 | 49 | 50 |
55 | 56 | 57 | 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } -------------------------------------------------------------------------------- /src/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 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /src/components/WriteCounter.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import * as React from "react"; 3 | import { useEffect } from "react"; 4 | import { Input } from "@/components/ui/input"; 5 | import { Button } from "@/components/ui/button"; 6 | import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; 7 | import { toast } from "sonner"; 8 | 9 | import { counterAbi, counterAddress } from "@/constants"; 10 | 11 | export function WriteContract() { 12 | const { data: hash, isPending, writeContract } = useWriteContract(); 13 | 14 | async function submit(e: React.FormEvent) { 15 | e.preventDefault(); 16 | try { 17 | const formData = new FormData(e.target as HTMLFormElement); 18 | const tokenId = formData.get("value") as string; 19 | console.log(tokenId); 20 | writeContract({ 21 | address: counterAddress, 22 | abi: counterAbi, 23 | functionName: "setNumber", 24 | args: [BigInt(tokenId)], 25 | }); 26 | } catch (error) { 27 | console.log(error); 28 | toast.error("Transaction Failed=> " + error); 29 | } 30 | } 31 | 32 | const { 33 | isLoading: isConfirming, 34 | error, 35 | isSuccess: isConfirmed, 36 | } = useWaitForTransactionReceipt({ 37 | hash, 38 | }); 39 | 40 | useEffect(() => { 41 | if (isConfirming) { 42 | toast.loading("Transaction Pending"); 43 | } 44 | if (isConfirmed) { 45 | toast.success("Transaction Successful", { 46 | action: { 47 | label: "View on Etherscan", 48 | onClick: () => { 49 | window.open(`https://explorer-testnet.morphl2.io/tx/${hash}`); 50 | }, 51 | }, 52 | }); 53 | } 54 | if (error) { 55 | toast.error("Transaction Failed"); 56 | } 57 | }, [isConfirming, isConfirmed, error, hash]); 58 | 59 | return ( 60 |
61 |
62 | 63 | 66 |
67 |
68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | const { fontFamily } = require("tailwindcss/defaultTheme") 3 | 4 | const config = { 5 | darkMode: ["class"], 6 | content: [ 7 | "./pages/**/*.{ts,tsx}", 8 | "./components/**/*.{ts,tsx}", 9 | "./app/**/*.{ts,tsx}", 10 | "./src/**/*.{ts,tsx}", 11 | ], 12 | prefix: "", 13 | theme: { 14 | container: { 15 | center: true, 16 | padding: "2rem", 17 | screens: { 18 | "2xl": "1400px", 19 | }, 20 | }, 21 | extend: { 22 | fontFamily: { 23 | sans: ["var(--font-sans)", ...fontFamily.sans], 24 | }, 25 | colors: { 26 | border: "hsl(var(--border))", 27 | input: "hsl(var(--input))", 28 | ring: "hsl(var(--ring))", 29 | background: "hsl(var(--background))", 30 | foreground: "hsl(var(--foreground))", 31 | primary: { 32 | DEFAULT: "hsl(var(--primary))", 33 | foreground: "hsl(var(--primary-foreground))", 34 | }, 35 | secondary: { 36 | DEFAULT: "hsl(var(--secondary))", 37 | foreground: "hsl(var(--secondary-foreground))", 38 | }, 39 | destructive: { 40 | DEFAULT: "hsl(var(--destructive))", 41 | foreground: "hsl(var(--destructive-foreground))", 42 | }, 43 | muted: { 44 | DEFAULT: "hsl(var(--muted))", 45 | foreground: "hsl(var(--muted-foreground))", 46 | }, 47 | accent: { 48 | DEFAULT: "hsl(var(--accent))", 49 | foreground: "hsl(var(--accent-foreground))", 50 | }, 51 | popover: { 52 | DEFAULT: "hsl(var(--popover))", 53 | foreground: "hsl(var(--popover-foreground))", 54 | }, 55 | card: { 56 | DEFAULT: "hsl(var(--card))", 57 | foreground: "hsl(var(--card-foreground))", 58 | }, 59 | }, 60 | borderRadius: { 61 | lg: "var(--radius)", 62 | md: "calc(var(--radius) - 2px)", 63 | sm: "calc(var(--radius) - 4px)", 64 | }, 65 | keyframes: { 66 | "accordion-down": { 67 | from: { height: "0" }, 68 | to: { height: "var(--radix-accordion-content-height)" }, 69 | }, 70 | "accordion-up": { 71 | from: { height: "var(--radix-accordion-content-height)" }, 72 | to: { height: "0" }, 73 | }, 74 | }, 75 | animation: { 76 | "accordion-down": "accordion-down 0.2s ease-out", 77 | "accordion-up": "accordion-up 0.2s ease-out", 78 | }, 79 | }, 80 | }, 81 | plugins: [require("tailwindcss-animate")], 82 | } satisfies Config; 83 | 84 | export default config; 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js TypeScript Starter Kit onchain 2 | 3 | ![A screenshot of the starter kit](./screenshot.png) 4 | 5 | This starter kit is designed to provide a comprehensive template for building frontends for your dApps using Next.js, TypeScript, Shadcn, and Tailwind CSS. It includes setup for WAGMI React hooks and Viem for seamless onchain transactions. 6 | 7 | By default, this template connects to the Morph Sepolia testnet. 8 | 9 | ## 🧑‍🚀 Initial Setup 10 | 11 | ### Environment Configuration 12 | 13 | Before you start, you need to set up your environment variables. Create a `.env.local` file in the root directory by running: 14 | 15 | ```bash 16 | cp .env.example .env.local 17 | ``` 18 | 19 | In the file, update the `NEXT_PUBLIC_PROJECT_ID` variable with your WalletConnect project ID. You can obtain one by registering your project at [WalletConnect Cloud](https://cloud.walletconnect.com/). 20 | 21 | ### Install Dependencies 22 | 23 | ```bash 24 | npm install 25 | # or 26 | yarn 27 | # or 28 | pnpm install 29 | # or 30 | bun install 31 | ``` 32 | 33 | ### Running the Development Server 34 | 35 | To run the development server, execute one of the following commands in your terminal: 36 | 37 | ```bash 38 | npm run dev 39 | # or 40 | yarn dev 41 | # or 42 | pnpm dev 43 | # or 44 | bun dev 45 | ``` 46 | 47 | Visit [http://localhost:3000](http://localhost:3000) to see your application in action. Begin by editing `app/page.tsx` to make changes and see them reflected in real time. 48 | 49 | ## 🧞 Features 50 | 51 | - **TypeScript**: Utilize the strong typing of TypeScript to write more robust and error-free code. 52 | - **Tailwind CSS**: Style your application efficiently using utility-first CSS. 53 | - **WAGMI Hooks**: Manage blockchain wallet and network interactions with ease. 54 | - **Viem**: Handle on-chain interactions directly within your frontend application. 55 | - **Morph Sepolia Testnet**: Connect to the Morph testnet to develop and test your dApps. 56 | 57 | ## ✨ Learning Resources 58 | 59 | - **Morph L2**: Learn more about Morph and its capabilities by visiting [Morph Layer 2 Official Site](https://www.morphl2.io/). 60 | - **Morph Documentation**: For detailed information on how Morph works and how to integrate it into your applications, check out the [Morph Docs](https://docs.morphl2.io/docs/how-morph-works/intro/). 61 | 62 | ## 🚀 Deployment 63 | 64 | Deploy your application with ease using platforms like [Vercel](https://vercel.com/), which provides out-of-the-box support for Next.js applications, or [Juno](https://juno.build), which gives you full control over your dApp by enabling its deployment on Web3. Refer to platform-specific guides for details on deploying Next.js applications. 65 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Link from "next/link"; 3 | import { useEffect } from "react"; 4 | import { Button } from "@/components/ui/button"; 5 | import { 6 | useAccount, 7 | useSendTransaction, 8 | useSignMessage, 9 | useWaitForTransactionReceipt, 10 | } from "wagmi"; 11 | import { parseEther } from "viem"; 12 | import { toast } from "sonner"; 13 | import { useWeb3Modal } from "@web3modal/wagmi/react"; 14 | 15 | export default function Home() { 16 | const { isConnected } = useAccount(); 17 | const { signMessage } = useSignMessage(); 18 | const { sendTransaction, data: hash } = useSendTransaction(); 19 | const { open } = useWeb3Modal(); 20 | const handleConnect = () => { 21 | open(); 22 | }; 23 | const { 24 | isLoading: isConfirming, 25 | error, 26 | isSuccess: isConfirmed, 27 | } = useWaitForTransactionReceipt({ 28 | hash, 29 | }); 30 | 31 | useEffect(() => { 32 | if (isConfirming) { 33 | toast.loading("Transaction Pending"); 34 | } 35 | toast.dismiss(); 36 | 37 | if (isConfirmed) { 38 | toast.success("Transaction Successful", { 39 | action: { 40 | label: "View on Etherscan", 41 | onClick: () => { 42 | window.open(`https://explorer-testnet.morphl2.io/tx/${hash}`); 43 | }, 44 | }, 45 | }); 46 | } 47 | if (error) { 48 | toast.error("Transaction Failed"); 49 | } 50 | }, [isConfirming, isConfirmed, error, hash]); 51 | 52 | return ( 53 |
54 |
55 |

Web3 Starter Kit

56 |

57 | Build your dapp frontends with the latest tools. 58 |

59 |
60 |
61 | {!isConnected ? ( 62 | 63 | ) : ( 64 | <> 65 | 66 | 70 | 82 | 83 | )} 84 |
85 |
86 | 87 | 88 | 89 |
90 |
91 | ); 92 | } 93 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { Check, ChevronRight, Circle } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const DropdownMenu = DropdownMenuPrimitive.Root 10 | 11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 12 | 13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 14 | 15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 16 | 17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 18 | 19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 20 | 21 | const DropdownMenuSubTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef & { 24 | inset?: boolean 25 | } 26 | >(({ className, inset, children, ...props }, ref) => ( 27 | 36 | {children} 37 | 38 | 39 | )) 40 | DropdownMenuSubTrigger.displayName = 41 | DropdownMenuPrimitive.SubTrigger.displayName 42 | 43 | const DropdownMenuSubContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, ...props }, ref) => ( 47 | 55 | )) 56 | DropdownMenuSubContent.displayName = 57 | DropdownMenuPrimitive.SubContent.displayName 58 | 59 | const DropdownMenuContent = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, sideOffset = 4, ...props }, ref) => ( 63 | 64 | 73 | 74 | )) 75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 76 | 77 | const DropdownMenuItem = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef & { 80 | inset?: boolean 81 | } 82 | >(({ className, inset, ...props }, ref) => ( 83 | 92 | )) 93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 94 | 95 | const DropdownMenuCheckboxItem = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, children, checked, ...props }, ref) => ( 99 | 108 | 109 | 110 | 111 | 112 | 113 | {children} 114 | 115 | )) 116 | DropdownMenuCheckboxItem.displayName = 117 | DropdownMenuPrimitive.CheckboxItem.displayName 118 | 119 | const DropdownMenuRadioItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )) 139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 140 | 141 | const DropdownMenuLabel = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef & { 144 | inset?: boolean 145 | } 146 | >(({ className, inset, ...props }, ref) => ( 147 | 156 | )) 157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 158 | 159 | const DropdownMenuSeparator = React.forwardRef< 160 | React.ElementRef, 161 | React.ComponentPropsWithoutRef 162 | >(({ className, ...props }, ref) => ( 163 | 168 | )) 169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 170 | 171 | const DropdownMenuShortcut = ({ 172 | className, 173 | ...props 174 | }: React.HTMLAttributes) => { 175 | return ( 176 | 180 | ) 181 | } 182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 183 | 184 | export { 185 | DropdownMenu, 186 | DropdownMenuTrigger, 187 | DropdownMenuContent, 188 | DropdownMenuItem, 189 | DropdownMenuCheckboxItem, 190 | DropdownMenuRadioItem, 191 | DropdownMenuLabel, 192 | DropdownMenuSeparator, 193 | DropdownMenuShortcut, 194 | DropdownMenuGroup, 195 | DropdownMenuPortal, 196 | DropdownMenuSub, 197 | DropdownMenuSubContent, 198 | DropdownMenuSubTrigger, 199 | DropdownMenuRadioGroup, 200 | } 201 | --------------------------------------------------------------------------------