Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ pnpm dev
bun dev
```

### Environment Variables

Create a `.env.local` file in the project root with the following variables:

```bash
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=your_site_key_here
RECAPTCHA_SECRET_KEY=your_secret_key_here
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
Expand Down
32 changes: 32 additions & 0 deletions app/api/recaptcha/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
const { token } = await request.json();
if (!token) {
return NextResponse.json({ success: false, error: 'No CAPTCHA token provided' }, { status: 400 });
}

const secret = process.env.RECAPTCHA_SECRET_KEY;
if (!secret) {
console.error('RECAPTCHA_SECRET_KEY is not defined');
return NextResponse.json({ success: false, error: 'Server misconfiguration' }, { status: 500 });
}

const params = new URLSearchParams();
params.append('secret', secret);
params.append('response', token);

const verificationResponse = await fetch('https://www.google.com/recaptcha/api/siteverify', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
});

const verificationJson = (await verificationResponse.json()) as { success: boolean; [key: string]: any };

if (!verificationJson.success) {
return NextResponse.json({ success: false, error: 'CAPTCHA verification failed' }, { status: 400 });
}

return NextResponse.json({ success: true });
}
21 changes: 21 additions & 0 deletions components/faucet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from ".
import { Input } from "./ui/input";
import Link from "next/link";
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle } from "./ui/alert-dialog";
import ReCAPTCHA from "react-google-recaptcha";

export type NetworkType = "Devnet" | "Testnet";

Expand Down Expand Up @@ -40,6 +41,7 @@ export function Faucet({ network, setNetwork, evmAddressFromHeader }: FaucetProp
const [showMissingRequirementsModal, setShowMissingRequirementsModal] = useState<boolean>(false);
const [showTxModal, setShowTxModal] = useState<boolean>(false);
const [showInvalidAddressModal, setShowInvalidAddressModal] = useState<boolean>(false);
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
const [chainId, setChainId] = useState<string | null>(null);

const ethereum = getEthereumProvider();
Expand Down Expand Up @@ -94,8 +96,21 @@ export function Faucet({ network, setNetwork, evmAddressFromHeader }: FaucetProp
return;
}

if (!captchaToken) {
alert("Please complete the CAPTCHA");
return;
}
setLoading(true);
try {
const recaptchaResponse = await fetch('/api/recaptcha', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: captchaToken }),
});
const recaptchaResult = await recaptchaResponse.json();
if (!recaptchaResponse.ok || !recaptchaResult.success) {
throw new Error(recaptchaResult.error || 'CAPTCHA verification failed');
}
const txHash = await getXrp(evmAddress);
const closeTimeIso = new Date().toISOString();
setTxData({ txHash, sourceCloseTimeIso: closeTimeIso });
Expand Down Expand Up @@ -292,6 +307,12 @@ export function Faucet({ network, setNetwork, evmAddressFromHeader }: FaucetProp
{socialsCompleted.discord && "✅"}
</li>
</ul>
<div className="mt-4">
<ReCAPTCHA
sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!}
onChange={(token) => setCaptchaToken(token)}
/>
</div>
<Button
variant="default"
size="lg"
Expand Down
41 changes: 36 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"next-themes": "^0.4.6",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-google-recaptcha": "^3.1.0",
"tailwind-merge": "^3.0.2",
"tailwindcss-animate": "^1.0.7",
"xrpl": "^4.2.0"
Expand Down