-
Notifications
You must be signed in to change notification settings - Fork 143
Bnb faucet #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mauridev777
wants to merge
6
commits into
bnb-chain:main
Choose a base branch
from
mauridev777:bnb-faucet
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Bnb faucet #95
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.* | ||
.yarn/* | ||
!.yarn/patches | ||
!.yarn/plugins | ||
!.yarn/releases | ||
!.yarn/versions | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
.pnpm-debug.log* | ||
|
||
# env files (can opt-in for committing if needed) | ||
.env* | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
# πͺ BNB Testnet Faucet | ||
|
||
A modern web faucet for the BNB Smart Chain testnet, built with Next.js (App Router, TypeScript), Supabase, ethers.js, and Tailwind CSS. | ||
|
||
π Live Demo | ||
You can try the faucet live here: | ||
π https://bnb-faucet.netlify.app/ | ||
|
||
Built with Next.js, Tailwind CSS, and Supabase. | ||
|
||
Deployed on Netlify for fast global delivery. | ||
|
||
Sends 0.001 BNB on BNB Testnet (1 claim per address per 24h). | ||
|
||
--- | ||
|
||
## π Features | ||
|
||
- **Request BNB testnet tokens** directly from the web UI | ||
- **Rate-limited**: 0.001 BNB per address every 24 hours | ||
- **Mainnet eligibility check**: Optionally blocks addresses with β₯ X BNB on mainnet | ||
- **Cooldown/anti-spam**: Tracks last claim per address in Supabase | ||
- **Beautiful, responsive UI** with Tailwind CSS | ||
- **TypeScript** codebase for safety and clarity | ||
- **Supabase** for database and claim tracking | ||
- **Environment-configurable**: faucet amount, cooldown, and more | ||
- **Easy to deploy** on Vercel or any Node.js host | ||
|
||
--- | ||
|
||
## π§βπ» Tech Stack | ||
|
||
- [Next.js 14+](https://nextjs.org/) (App Router, TypeScript) | ||
- [ethers.js](https://docs.ethers.org/) β blockchain interactions | ||
- [Supabase](https://supabase.com/) β Postgres DB for rate-limiting | ||
- [Tailwind CSS](https://tailwindcss.com/) β modern utility-first styling | ||
|
||
--- | ||
|
||
## βοΈ Environment Variables | ||
|
||
Copy `.env.local.example` to `.env.local` and fill in your values: | ||
|
||
```env | ||
FAUCET_PK=0x... # Private key for funded testnet wallet (never mainnet!) | ||
BNB_RPC=https://... # BNB testnet or opBNB testnet RPC URL | ||
BNB_AMOUNT=0.001 # Amount of BNB to send per request | ||
COOLDOWN_HOURS=24 # Cooldown in hours per address | ||
SUPABASE_URL=https://... # Supabase project URL | ||
SUPABASE_KEY=... # Supabase service role key | ||
CHECK_MAINNET_BALANCE=true # If true, blocks users with enough mainnet BNB | ||
MAINNET_BALANCE_AMOUNT=0.01 # Mainnet balance threshold (in BNB) | ||
MAINNET_RPC=https://bsc-dataseed.binance.org/ # BNB Chain mainnet RPC | ||
``` | ||
|
||
**Warning:** Always use a testnet wallet for the faucet! | ||
|
||
--- | ||
|
||
## π¦ Database Setup (Supabase) | ||
|
||
Create a table named `faucet_claims`: | ||
|
||
```sql | ||
create table faucet_claims ( | ||
id bigint generated always as identity primary key, | ||
address text unique not null, | ||
last_claimed timestamptz, | ||
created_at timestamptz not null default now() | ||
); | ||
``` | ||
|
||
--- | ||
|
||
## π οΈ Running Locally | ||
|
||
1. Install dependencies: | ||
|
||
```bash | ||
npm install | ||
``` | ||
|
||
2. Add your `.env.local` file as above. | ||
|
||
3. Start the app: | ||
|
||
```bash | ||
npm run dev | ||
``` | ||
|
||
4. Visit [http://localhost:3000](http://localhost:3000) to try it out! | ||
|
||
--- | ||
|
||
## π₯οΈ API Endpoints | ||
|
||
- **POST `/api/faucet`** βΒ Request BNB for an address | ||
Request body: `{ "address": "0x..." }` | ||
Responses: | ||
- Success: `{ success: true, txHash, amount }` | ||
- Cooldown: `{ error: "Cooldown active", timeLeftSeconds, nextClaimAt }` | ||
- Not eligible: `{ error: "Address has enough mainnet BNB", ... }` | ||
- Error: `{ error, details }` | ||
|
||
- **GET `/api/faucet-config`** βΒ Returns faucet config (amount, cooldown, etc) | ||
|
||
--- | ||
|
||
## π¨βπ¨ Frontend Features | ||
|
||
- Enter wallet address and request testnet BNB | ||
- See success (with BscScan link), error, or cooldown timer | ||
- Mobile-friendly, fast, and simple | ||
|
||
--- | ||
|
||
## π‘οΈ Security & Abuse Protection | ||
|
||
- Cooldown enforced per address | ||
- Optional mainnet balance check to block hoarders | ||
- Use a testnet-only private key | ||
- Consider adding CAPTCHA or wallet signature in production | ||
|
||
--- | ||
|
||
## π Deployment | ||
|
||
You can deploy to [Vercel](https://vercel.com/) or any Node.js/Vercel-compatible host. | ||
|
||
- **Remember:** Set all env variables in your deployment settings! | ||
|
||
--- | ||
|
||
## π€ License | ||
|
||
MIT βΒ Use, remix, and hack freely! | ||
|
||
--- | ||
|
||
## π Credits | ||
|
||
Built using: | ||
- [Next.js](https://nextjs.org/) | ||
- [ethers.js](https://docs.ethers.org/) | ||
- [Supabase](https://supabase.com/) | ||
- [Tailwind CSS](https://tailwindcss.com/) | ||
|
||
--- | ||
|
||
## β¨ Hackathon Ready | ||
|
||
Showcase it live, or fork for your own chain/testnet faucet! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { NextResponse } from "next/server"; | ||
|
||
export async function GET() { | ||
return NextResponse.json({ | ||
bnbAmount: process.env.BNB_AMOUNT || "0.0001", | ||
cooldownHours: process.env.COOLDOWN_HOURS || "24", | ||
checkMainnetBalance: process.env.CHECK_MAINNET_BALANCE === "false", | ||
mainnetBalanceAmount: process.env.MAINNET_BALANCE_AMOUNT || "0.01" | ||
}); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { NextRequest, NextResponse } from "next/server"; | ||
import { ethers } from "ethers"; | ||
import { createClient } from "@supabase/supabase-js"; | ||
|
||
// Load environment variables for faucet setup | ||
const FAUCET_PK = process.env.FAUCET_PK!; // Faucet wallet private key | ||
const BNB_RPC = process.env.BNB_RPC!; // Testnet RPC endpoint | ||
const BNB_AMOUNT = process.env.BNB_AMOUNT || "0.0001"; // Amount to send per claim | ||
const COOLDOWN_HOURS = parseInt(process.env.COOLDOWN_HOURS || "24"); // Cooldown duration per wallet | ||
const SUPABASE_URL = process.env.SUPABASE_URL!; // Supabase project URL | ||
const SUPABASE_KEY = process.env.SUPABASE_KEY!; // Supabase service key | ||
const CHECK_MAINNET_BALANCE = process.env.CHECK_MAINNET_BALANCE === "true"; // Toggle mainnet check | ||
const MAINNET_BALANCE_AMOUNT = process.env.MAINNET_BALANCE_AMOUNT || "0.01"; // Threshold | ||
const MAINNET_RPC = process.env.MAINNET_RPC || "https://bsc-dataseed.binance.org/"; // Mainnet RPC | ||
|
||
// Setup provider and wallet for testnet | ||
const provider = new ethers.JsonRpcProvider(BNB_RPC); | ||
const wallet = new ethers.Wallet(FAUCET_PK, provider); | ||
|
||
// Initialize Supabase client | ||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY); | ||
|
||
// Type-safe error message handler | ||
function getErrorMessage(err: unknown): string { | ||
if (typeof err === "string") return err; | ||
if (err instanceof Error) return err.message; | ||
return JSON.stringify(err); | ||
} | ||
|
||
// Faucet request endpoint | ||
export async function POST(req: NextRequest) { | ||
const { address } = await req.json(); | ||
|
||
// 1. Validate that the address is correct | ||
if (!ethers.isAddress(address)) { | ||
return NextResponse.json({ error: "Invalid address" }, { status: 400 }); | ||
} | ||
|
||
// 2. Optionally check the user's balance on BNB mainnet | ||
if (CHECK_MAINNET_BALANCE) { | ||
try { | ||
const mainnetProvider = new ethers.JsonRpcProvider(MAINNET_RPC); | ||
const bal = await mainnetProvider.getBalance(address); | ||
const required = ethers.parseEther(MAINNET_BALANCE_AMOUNT); | ||
|
||
if (bal >= required) { | ||
return NextResponse.json( | ||
{ | ||
error: "Address has enough mainnet BNB", | ||
mainnetBalance: ethers.formatEther(bal), | ||
required: MAINNET_BALANCE_AMOUNT, | ||
}, | ||
{ status: 403 } | ||
); | ||
} | ||
} catch (err: unknown) { | ||
return NextResponse.json( | ||
{ error: "Mainnet balance check failed", details: getErrorMessage(err) }, | ||
{ status: 500 } | ||
); | ||
} | ||
} | ||
|
||
// 3. Check cooldown for this address | ||
try { | ||
const { data } = await supabase | ||
.from("faucet_claims") | ||
.select("last_claimed") | ||
.eq("address", address.toLowerCase()) | ||
.single(); | ||
|
||
const now = new Date(); | ||
let canSend = true; | ||
let timeLeft = 0; | ||
|
||
if (data?.last_claimed) { | ||
const last = new Date(data.last_claimed); | ||
const nextAllowed = new Date(last.getTime() + COOLDOWN_HOURS * 3600 * 1000); | ||
|
||
if (nextAllowed > now) { | ||
canSend = false; | ||
timeLeft = Math.ceil((nextAllowed.getTime() - now.getTime()) / 1000); // seconds | ||
} | ||
} | ||
|
||
if (!canSend) { | ||
return NextResponse.json( | ||
{ | ||
error: "Cooldown active", | ||
timeLeftSeconds: timeLeft, | ||
nextClaimAt: new Date(now.getTime() + timeLeft * 1000).toISOString(), | ||
}, | ||
{ status: 429 } | ||
); | ||
} | ||
|
||
// 4. Check if the faucet wallet has enough funds | ||
try { | ||
const faucetBalance = await provider.getBalance(wallet.address); | ||
const amountToSend = ethers.parseEther(BNB_AMOUNT); | ||
|
||
if (faucetBalance < amountToSend) { | ||
return NextResponse.json( | ||
{ | ||
error: "Faucet depleted", | ||
message: | ||
"Faucet wallet has insufficient balance. Please contact the maintainer or try again later.", | ||
currentBalance: ethers.formatEther(faucetBalance), | ||
required: BNB_AMOUNT, | ||
}, | ||
{ status: 503 } | ||
); | ||
} | ||
|
||
// 5. Send BNB to user address | ||
const tx = await wallet.sendTransaction({ | ||
to: address, | ||
value: amountToSend, | ||
}); | ||
|
||
// 6. Update claim history in Supabase | ||
await supabase.from("faucet_claims").upsert( | ||
[ | ||
{ | ||
address: address.toLowerCase(), | ||
last_claimed: now.toISOString(), | ||
}, | ||
], | ||
{ onConflict: "address" } | ||
); | ||
|
||
// 7. Return success response | ||
return NextResponse.json({ success: true, txHash: tx.hash, amount: BNB_AMOUNT }); | ||
} catch (err: unknown) { | ||
return NextResponse.json( | ||
{ error: "Transaction failed", details: getErrorMessage(err) }, | ||
{ status: 500 } | ||
); | ||
} | ||
} catch (err: unknown) { | ||
return NextResponse.json( | ||
{ error: "Database error", details: getErrorMessage(err) }, | ||
{ status: 500 } | ||
); | ||
} | ||
} |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
@import "tailwindcss"; | ||
|
||
:root { | ||
--background: #ffffff; | ||
--foreground: #171717; | ||
} | ||
|
||
@theme inline { | ||
--color-background: var(--background); | ||
--color-foreground: var(--foreground); | ||
--font-sans: var(--font-geist-sans); | ||
--font-mono: var(--font-geist-mono); | ||
} | ||
|
||
@media (prefers-color-scheme: dark) { | ||
:root { | ||
--background: #0a0a0a; | ||
--foreground: #ededed; | ||
} | ||
} | ||
|
||
body { | ||
background: var(--background); | ||
color: var(--foreground); | ||
font-family: Arial, Helvetica, sans-serif; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.