├── .eslintrc.json ├── app ├── favicon.ico ├── fonts │ ├── GeistVF.woff │ └── GeistMonoVF.woff ├── images │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── solana-airdrop.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── site.webmanifest │ └── solanaLogo.svg ├── page.tsx ├── layout.tsx └── globals.css ├── next.config.mjs ├── postcss.config.mjs ├── lib └── utils.ts ├── .gitignore ├── components.json ├── tsconfig.json ├── components ├── ui │ ├── label.tsx │ ├── progress.tsx │ ├── input.tsx │ ├── sonner.tsx │ ├── button.tsx │ └── card.tsx ├── ConnectWallet.tsx ├── common │ └── UiLoader.tsx └── AirdropHomePage.tsx ├── package.json ├── tailwind.config.ts └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NishantCoder108/faucet/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NishantCoder108/faucet/HEAD/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NishantCoder108/faucet/HEAD/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /app/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NishantCoder108/faucet/HEAD/app/images/favicon-16x16.png -------------------------------------------------------------------------------- /app/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NishantCoder108/faucet/HEAD/app/images/favicon-32x32.png -------------------------------------------------------------------------------- /app/images/solana-airdrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NishantCoder108/faucet/HEAD/app/images/solana-airdrop.png -------------------------------------------------------------------------------- /app/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NishantCoder108/faucet/HEAD/app/images/apple-touch-icon.png -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /app/images/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NishantCoder108/faucet/HEAD/app/images/android-chrome-192x192.png -------------------------------------------------------------------------------- /app/images/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NishantCoder108/faucet/HEAD/app/images/android-chrome-512x512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/images/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /.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.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | .env. 32 | .env.* 33 | # vercel 34 | .vercel 35 | .env 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts -------------------------------------------------------------------------------- /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": "tailwind.config.ts", 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 | "images": "@/app/images" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ProgressPrimitive from "@radix-ui/react-progress" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, value, ...props }, ref) => ( 12 | 20 | 24 | 25 | )) 26 | Progress.displayName = ProgressPrimitive.Root.displayName 27 | 28 | export { Progress } 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import AirdropHomePage from "@/components/AirdropHomePage"; 3 | import { 4 | ConnectionProvider, 5 | WalletProvider, 6 | } from "@solana/wallet-adapter-react"; 7 | import "@solana/wallet-adapter-react-ui/styles.css"; 8 | import { Toaster } from "@/components/ui/sonner"; 9 | import { useEffect, useState } from "react"; 10 | import UiLoader from "@/components/common/UiLoader"; 11 | 12 | export default function Home() { 13 | const [isMount, setIsMount] = useState(true); 14 | 15 | useEffect(() => { 16 | setIsMount(false); 17 | }, []); 18 | 19 | return isMount ? ( 20 | 21 | ) : ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "faucet", 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-icons": "^1.3.0", 13 | "@radix-ui/react-label": "^2.1.0", 14 | "@radix-ui/react-progress": "^1.1.0", 15 | "@radix-ui/react-slot": "^1.1.0", 16 | "@solana/wallet-adapter-base": "^0.9.23", 17 | "@solana/wallet-adapter-react": "^0.15.35", 18 | "@solana/wallet-adapter-react-ui": "^0.9.35", 19 | "@solana/web3.js": "^1.95.3", 20 | "class-variance-authority": "^0.7.0", 21 | "clsx": "^2.1.1", 22 | "framer-motion": "^11.5.6", 23 | "lucide-react": "^0.445.0", 24 | "next": "14.2.13", 25 | "next-themes": "^0.3.0", 26 | "react": "^18", 27 | "react-dom": "^18", 28 | "sonner": "^1.5.0", 29 | "tailwind-merge": "^2.5.2", 30 | "tailwindcss-animate": "^1.0.7" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^20", 34 | "@types/react": "^18", 35 | "@types/react-dom": "^18", 36 | "eslint": "^8", 37 | "eslint-config-next": "14.2.13", 38 | "eslint-config-prettier": "^9.1.0", 39 | "postcss": "^8", 40 | "tailwindcss": "^3.4.1", 41 | "typescript": "^5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | darkMode: ["class"], 5 | content: [ 6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | colors: { 13 | background: 'hsl(var(--background))', 14 | foreground: 'hsl(var(--foreground))', 15 | card: { 16 | DEFAULT: 'hsl(var(--card))', 17 | foreground: 'hsl(var(--card-foreground))' 18 | }, 19 | popover: { 20 | DEFAULT: 'hsl(var(--popover))', 21 | foreground: 'hsl(var(--popover-foreground))' 22 | }, 23 | primary: { 24 | DEFAULT: 'hsl(var(--primary))', 25 | foreground: 'hsl(var(--primary-foreground))' 26 | }, 27 | secondary: { 28 | DEFAULT: 'hsl(var(--secondary))', 29 | foreground: 'hsl(var(--secondary-foreground))' 30 | }, 31 | muted: { 32 | DEFAULT: 'hsl(var(--muted))', 33 | foreground: 'hsl(var(--muted-foreground))' 34 | }, 35 | accent: { 36 | DEFAULT: 'hsl(var(--accent))', 37 | foreground: 'hsl(var(--accent-foreground))' 38 | }, 39 | destructive: { 40 | DEFAULT: 'hsl(var(--destructive))', 41 | foreground: 'hsl(var(--destructive-foreground))' 42 | }, 43 | border: 'hsl(var(--border))', 44 | input: 'hsl(var(--input))', 45 | ring: 'hsl(var(--ring))', 46 | chart: { 47 | '1': 'hsl(var(--chart-1))', 48 | '2': 'hsl(var(--chart-2))', 49 | '3': 'hsl(var(--chart-3))', 50 | '4': 'hsl(var(--chart-4))', 51 | '5': 'hsl(var(--chart-5))' 52 | } 53 | }, 54 | borderRadius: { 55 | lg: 'var(--radius)', 56 | md: 'calc(var(--radius) - 2px)', 57 | sm: 'calc(var(--radius) - 4px)' 58 | } 59 | } 60 | }, 61 | plugins: [require("tailwindcss-animate")], 62 | }; 63 | export default config; 64 | -------------------------------------------------------------------------------- /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 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /components/ConnectWallet.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BaseWalletMultiButton, 3 | WalletModalProvider, 4 | } from "@solana/wallet-adapter-react-ui"; 5 | import React from "react"; 6 | import { useWallet } from "@solana/wallet-adapter-react"; 7 | import { WalletIcon } from "lucide-react"; 8 | 9 | const LABELS = { 10 | "change-wallet": "Change wallet", 11 | connecting: "Connecting ...", 12 | "copy-address": "Copy address", 13 | copied: "Copied", 14 | disconnect: "Disconnect", 15 | "has-wallet": "Connect", 16 | "no-wallet": "Connect Wallet", 17 | } as const; 18 | 19 | const ConnectWallet = () => { 20 | const wallet = useWallet(); 21 | const isWalletConnected = wallet.connected; 22 | 23 | return ( 24 |
25 |
26 | 27 |
28 | 29 |
30 | 38 |
39 |
40 |
41 | ); 42 | }; 43 | 44 | const connectedStyle = { 45 | background: "transparent", 46 | height: "2.5rem", 47 | width: "10rem", 48 | fontSize: ".875rem", 49 | borderRadius: ".30rem", 50 | // border: "0.5px solid rgba(128, 128, 128, 0.2)", 51 | color: "white", 52 | cursor: "pointer", 53 | }; 54 | const disconnectedStyle = { 55 | background: "#f9f8fa", 56 | height: "2.5rem", 57 | width: "10rem", 58 | fontSize: ".875rem", 59 | borderRadius: ".30rem", 60 | border: ".5px solid gray", 61 | color: "black", 62 | }; 63 | 64 | export default ConnectWallet; 65 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import "./globals.css"; 4 | 5 | const geistSans = localFont({ 6 | src: "./fonts/GeistVF.woff", 7 | variable: "--font-geist-sans", 8 | weight: "100 900", 9 | }); 10 | const geistMono = localFont({ 11 | src: "./fonts/GeistMonoVF.woff", 12 | variable: "--font-geist-mono", 13 | weight: "100 900", 14 | }); 15 | 16 | export const metadata: Metadata = { 17 | title: "Solana Airdrop Faucet", 18 | description: "Get airdropped SOL tokens on the Solana Devnet easily.", 19 | keywords: [ 20 | "Solana", 21 | "faucet", 22 | "airdrop", 23 | "Devnet", 24 | "crypto", 25 | "blockchain", 26 | "developer tools", 27 | ], 28 | viewport: "width=device-width, initial-scale=1", 29 | themeColor: "#ffffff", 30 | openGraph: { 31 | title: "Solana Airdrop Faucet", 32 | description: "Receive free SOL tokens for testing on Solana Devnet.", 33 | url: "https://faucet.nishantcoder.com", 34 | siteName: "Solana Airdrop Faucet", 35 | images: [ 36 | { 37 | url: "./images/solana-airdrop.png", 38 | width: 800, 39 | height: 600, 40 | alt: "Faucet Application Screenshot", 41 | }, 42 | ], 43 | }, 44 | twitter: { 45 | card: "summary_large_image", 46 | title: "Solana Airdrop Faucet", 47 | description: "Get airdropped SOL tokens on the Solana Devnet easily.", 48 | images: "./images/solana-airdrop.png", 49 | site: "@NishantTechie", 50 | creator: "@NishantTechie", 51 | }, 52 | }; 53 | 54 | export default function RootLayout({ 55 | children, 56 | }: Readonly<{ 57 | children: React.ReactNode; 58 | }>) { 59 | return ( 60 | 61 | 64 | {children} 65 | 66 | 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLParagraphElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |

53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |

61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solana Airdrop Faucet 2 | 3 | ## Overview 4 | 5 | The Solana Airdrop Faucet is a web application designed to provide users with free SOL tokens on the Solana Devnet. It simplifies the process of obtaining SOL for developers and testers, enabling them to experiment with the Solana blockchain without any initial investment. 6 | 7 | ![Solana Airdrop](https://github.com/NishantCoder108/faucet/blob/master/app/images/solana-airdrop.png) 8 | 9 | 10 | ## Features 11 | 12 | - **Instant Airdrops**: Users can request SOL tokens instantly with just their wallet address. 13 | - **User-Friendly Interface**: An intuitive design that makes it easy for anyone to use the faucet. 14 | - **No Registration Required**: Users can start receiving tokens without needing to create an account. 15 | - **Transaction History**: Users can view their airdrop requests and transaction statuses in real-time. 16 | - **Balance Check**: Easily check your current balance on the Solana Devnet. 17 | - **Responsive Design**: Optimized for use on both desktop and mobile devices. 18 | 19 | ## How to Use 20 | 21 | 1. **Access the Application**: Navigate to the [Faucet URL](https://faucet.nishantcoder.com). 22 | 2. **Connect Your Wallet Address**: Connect your Solana wallet address with click on Connect wallet. 23 | 3. **Request Airdrop**: Click the "Get Airdrop" button to submit your request. 24 | 4. **Check Transaction Status**: View the status of your transaction on the screen, including any errors or confirmations. 25 | 5. **Review History**: Check your transaction history to see previous airdrop requests. 26 | 27 | ## Technical Details 28 | 29 | - **Technology Stack**: 30 | - **Frontend**: Built with Next.js and React. 31 | - **Styling**: Styled using Tailwind CSS for a modern look. 32 | - **Blockchain Interaction**: Uses Solana Web3.js to interact with the Solana blockchain. 33 | 34 | ## Installation 35 | 36 | To run the application locally, follow these steps: 37 | 38 | 1. Clone the repository: 39 | ```bash 40 | git clone https://github.com/NishantCoder108/faucet.git 41 | ``` 42 | 2. Navigate to the project directory: 43 | ```bash 44 | cd faucet 45 | ``` 46 | 3. Install dependencies: 47 | ```bash 48 | npm install 49 | ``` 50 | 4. Start the development server: 51 | ```bash 52 | npm run dev 53 | ``` 54 | 5. Open your browser and navigate to `http://localhost:3000`. 55 | 56 | ## Contributing 57 | 58 | We welcome contributions! If you'd like to contribute, please fork the repository and submit a pull request. Ensure to follow the coding standards and write clear commit messages. 59 | 60 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | 23 | @layer utilities { 24 | .text-balance { 25 | text-wrap: balance; 26 | } 27 | } 28 | 29 | @layer base { 30 | :root { 31 | --background: 0 0% 100%; 32 | --foreground: 0 0% 3.9%; 33 | --card: 0 0% 100%; 34 | --card-foreground: 0 0% 3.9%; 35 | --popover: 0 0% 100%; 36 | --popover-foreground: 0 0% 3.9%; 37 | --primary: 0 0% 9%; 38 | --primary-foreground: 0 0% 98%; 39 | --secondary: 0 0% 96.1%; 40 | --secondary-foreground: 0 0% 9%; 41 | --muted: 0 0% 96.1%; 42 | --muted-foreground: 0 0% 45.1%; 43 | --accent: 0 0% 96.1%; 44 | --accent-foreground: 0 0% 9%; 45 | --destructive: 0 84.2% 60.2%; 46 | --destructive-foreground: 0 0% 98%; 47 | --border: 0 0% 89.8%; 48 | --input: 0 0% 89.8%; 49 | --ring: 0 0% 3.9%; 50 | --chart-1: 12 76% 61%; 51 | --chart-2: 173 58% 39%; 52 | --chart-3: 197 37% 24%; 53 | --chart-4: 43 74% 66%; 54 | --chart-5: 27 87% 67%; 55 | --radius: 0.5rem; 56 | 57 | --background: 240 10% 3.9%; 58 | --foreground: 0 0% 98%; 59 | --muted: 240 3.7% 15.9%; 60 | --muted-foreground: 240 5% 64.9%; 61 | --popover: 240 10% 3.9%; 62 | --popover-foreground: 0 0% 98%; 63 | --card: 240 10% 3.9%; 64 | --card-foreground: 0 0% 98%; 65 | --border: 240 3.7% 15.9%; 66 | --input: 240 3.7% 15.9%; 67 | --primary: 0 0% 98%; 68 | --primary-foreground: 240 5.9% 10%; 69 | --secondary: 240 3.7% 15.9%; 70 | --secondary-foreground: 0 0% 98%; 71 | --accent: 240 3.7% 15.9%; 72 | --accent-foreground: 0 0% 98%; 73 | --destructive: 0 62.8% 30.6%; 74 | --destructive-foreground: 0 85.7% 97.3%; 75 | --ring: 240 3.7% 15.9%; 76 | --radius: 0.5rem; 77 | } 78 | 79 | .dark { 80 | --background: 0 0% 3.9%; 81 | --foreground: 0 0% 98%; 82 | --card: 0 0% 3.9%; 83 | --card-foreground: 0 0% 98%; 84 | --popover: 0 0% 3.9%; 85 | --popover-foreground: 0 0% 98%; 86 | --primary: 0 0% 98%; 87 | --primary-foreground: 0 0% 9%; 88 | --secondary: 0 0% 14.9%; 89 | --secondary-foreground: 0 0% 98%; 90 | --muted: 0 0% 14.9%; 91 | --muted-foreground: 0 0% 63.9%; 92 | --accent: 0 0% 14.9%; 93 | --accent-foreground: 0 0% 98%; 94 | --destructive: 0 62.8% 30.6%; 95 | --destructive-foreground: 0 0% 98%; 96 | --border: 0 0% 14.9%; 97 | --input: 0 0% 14.9%; 98 | --ring: 0 0% 83.1%; 99 | --chart-1: 220 70% 50%; 100 | --chart-2: 160 60% 45%; 101 | --chart-3: 30 80% 55%; 102 | --chart-4: 280 65% 60%; 103 | --chart-5: 340 75% 55%; 104 | } 105 | } 106 | 107 | @layer base { 108 | * { 109 | @apply border-border; 110 | } 111 | 112 | body { 113 | @apply bg-background text-foreground; 114 | } 115 | } -------------------------------------------------------------------------------- /components/common/UiLoader.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | 3 | export default function UiLoader() { 4 | return ( 5 |
6 | {/* Background glow effect */} 7 |
8 | 9 | 15 | 24 | {[...Array(12)].map((_, index) => ( 25 | 42 | ))} 43 | 44 | 45 | 53 | 59 | 65 | 69 | 70 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 95 |
99 | 100 | 101 |
102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /app/images/solanaLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /components/AirdropHomePage.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Image from "next/image"; 3 | import React, { useEffect, useState } from "react"; 4 | import solanaLogo from "@/app/images/solanaLogo.svg"; 5 | import { Input } from "./ui/input"; 6 | import ConnectWallet from "./ConnectWallet"; 7 | import { Button } from "./ui/button"; 8 | import { RefreshCw, SendIcon } from "lucide-react"; 9 | import { Label } from "./ui/label"; 10 | import { motion } from "framer-motion"; 11 | import { useConnection, useWallet } from "@solana/wallet-adapter-react"; 12 | import { LAMPORTS_PER_SOL } from "@solana/web3.js"; 13 | import { toast } from "sonner"; 14 | 15 | const AirdropHomePage = () => { 16 | const [amount, setAmount] = useState(0); 17 | const [solBal, setSolBal] = useState(0); 18 | const [isLoading, setIsLoading] = useState(false); 19 | const [isAirdroping, setIsAirdroping] = useState(false); 20 | 21 | const { connection } = useConnection(); 22 | const wallet = useWallet(); 23 | 24 | async function getBalance() { 25 | try { 26 | if (wallet.publicKey) { 27 | const balance = await connection.getBalance(wallet.publicKey); 28 | const solBalance = balance / LAMPORTS_PER_SOL; 29 | 30 | setSolBal(solBalance); 31 | return; 32 | } 33 | 34 | throw new Error("Wallet public key is missing."); 35 | } catch (error) { 36 | console.log({ error }); 37 | 38 | setSolBal(0); 39 | } 40 | } 41 | 42 | const handleRefreshBtn = async () => { 43 | setIsLoading(true); 44 | 45 | try { 46 | await getBalance(); 47 | setIsLoading(false); 48 | } catch (error) { 49 | console.log({ error }); 50 | setIsLoading(false); 51 | } 52 | }; 53 | 54 | const handleConfirmAirdrop = async () => { 55 | setIsAirdroping(true); 56 | console.log({ amount }); 57 | 58 | try { 59 | if (!wallet.publicKey) 60 | throw new Error("Wallet public key is missing!"); 61 | const airdropAmount = await connection.requestAirdrop( 62 | wallet.publicKey, 63 | amount * LAMPORTS_PER_SOL 64 | ); 65 | 66 | toast.success("Airdrop successful!"); 67 | console.log({ airdropAmount }); 68 | setIsAirdroping(false); 69 | handleRefreshBtn(); 70 | } catch (error) { 71 | console.log({ error }); 72 | const err = error as Error; 73 | setIsAirdroping(false); 74 | toast("Airdrop failed!", { 75 | description: err.message, 76 | }); 77 | } 78 | }; 79 | useEffect(() => { 80 | getBalance(); 81 | }, [wallet.connected]); 82 | 83 | return ( 84 |
85 |
86 |
87 | Solana Logo 97 |
98 | 99 |
100 |
101 |
102 |
103 |

104 |
105 | Request Airdrop 106 | 107 | 108 |
109 |

110 |

111 | Maximum of 5 requests per hour 112 |

113 |
114 | 115 |
116 |
117 |

Balance

118 | 119 | 131 |
132 | {/* Show Balance */} 133 |

134 | {isLoading ? ( 135 | ... 136 | ) : ( 137 |
138 | 139 | {" "} 140 | {solBal}{" "} 141 | {" "} 142 | SOL 143 |
144 | )} 145 |

146 | 147 |
148 | 154 | 160 | setAmount(Number(e.target.value)) 161 | } 162 | /> 163 |
164 | 165 | 194 |
195 |
196 | 197 |
198 |
199 |
200 |
201 |
202 | ); 203 | }; 204 | 205 | export default AirdropHomePage; 206 | --------------------------------------------------------------------------------