Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
kien-ngo committed Oct 17, 2024
1 parent e67a300 commit fe0396e
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 39 deletions.
6 changes: 5 additions & 1 deletion apps/dashboard/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,8 @@ UNTHREAD_PRO_TIER_ID=""
NEXT_PUBLIC_DEMO_ENGINE_URL=""

# API server secret (required for thirdweb.com SIWE login). Copy from Vercel.
API_SERVER_SECRET=""
API_SERVER_SECRET=""

# Used for the Faucet page (/<chain_id>)
NEXT_PUBLIC_TURNSTILE_SITE_KEY=""
TURNSTILE_SECRET_KEY=""
2 changes: 1 addition & 1 deletion apps/dashboard/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const ContentSecurityPolicy = `
style-src 'self' 'unsafe-inline' vercel.live;
font-src 'self' vercel.live assets.vercel.com framerusercontent.com;
frame-src * data:;
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'wasm-unsafe-eval' 'inline-speculation-rules' *.thirdweb.com *.thirdweb-dev.com vercel.live js.stripe.com framerusercontent.com events.framer.com;
script-src 'self' 'unsafe-eval' 'unsafe-inline' 'wasm-unsafe-eval' 'inline-speculation-rules' *.thirdweb.com *.thirdweb-dev.com vercel.live js.stripe.com framerusercontent.com events.framer.com challenges.cloudflare.com;
connect-src * data: blob:;
worker-src 'self' blob:;
block-all-mixed-content;
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@emotion/react": "11.13.3",
"@emotion/styled": "11.13.0",
"@hookform/resolvers": "^3.9.0",
"@marsidev/react-turnstile": "^1.0.2",
"@n8tb1t/use-scroll-position": "^2.0.3",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-avatar": "^1.1.1",
Expand Down
3 changes: 3 additions & 0 deletions apps/dashboard/src/@/constants/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ export const DASHBOARD_STORAGE_URL =

export const API_SERVER_URL =
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com";

export const TURNSTILE_SITE_KEY =
process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "";
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@

import { Spinner } from "@/components/ui/Spinner/Spinner";
import { Button } from "@/components/ui/button";
import { THIRDWEB_ENGINE_FAUCET_WALLET } from "@/constants/env";
import { Form } from "@/components/ui/form";
import {
THIRDWEB_ENGINE_FAUCET_WALLET,
TURNSTILE_SITE_KEY,
} from "@/constants/env";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
import { Turnstile } from "@marsidev/react-turnstile";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { CanClaimResponseType } from "app/api/testnet-faucet/can-claim/CanClaimResponseType";
import { mapV4ChainToV5Chain } from "contexts/map-chains";
import { useTrack } from "hooks/analytics/useTrack";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { toUnits } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
Expand All @@ -29,6 +35,8 @@ function formatTime(seconds: number) {
return rtf.format(+seconds, "second");
}

type TurnstileForm = { "cf-turnstile-response": string };

export function FaucetButton({
chain,
amount,
Expand All @@ -52,7 +60,7 @@ export function FaucetButton({
const queryClient = useQueryClient();

const claimMutation = useMutation({
mutationFn: async () => {
mutationFn: async (turnstileToken: string) => {
trackEvent({
category: "faucet",
action: "claim",
Expand All @@ -67,6 +75,7 @@ export function FaucetButton({
body: JSON.stringify({
chainId: chainId,
toAddress: address,
turnstileToken,
}),
});

Expand Down Expand Up @@ -117,6 +126,8 @@ export function FaucetButton({
faucetWalletBalanceQuery.data !== undefined &&
faucetWalletBalanceQuery.data.value < toUnits("1", 17);

const form = useForm<TurnstileForm>();

// loading state
if (faucetWalletBalanceQuery.isPending || canClaimFaucetQuery.isPending) {
return (
Expand Down Expand Up @@ -161,28 +172,38 @@ export function FaucetButton({
);
}

const claimFunds = (values: TurnstileForm) => {
const turnstileToken = values["cf-turnstile-response"];
if (!turnstileToken) {
return toast.error("Failed to retrieve captcha token");
}
console.log({ turnstileToken });
// Instead of having a dedicated endpoint (/api/verify-token),
// we can just attach the token in the payload and send it to the claim-faucet endpoint, to avoid a round-trip request
const claimPromise = claimMutation.mutateAsync(turnstileToken.toString());
toast.promise(claimPromise, {
success: `${amount} ${chain.nativeCurrency.symbol} sent successfully`,
error: `Failed to claim ${amount} ${chain.nativeCurrency.symbol}`,
});
};

// eligible to claim and faucet has balance
return (
<div className="flex w-full flex-col text-center">
<Button
variant="primary"
className="w-full gap-2"
onClick={() => {
const claimPromise = claimMutation.mutateAsync();
toast.promise(claimPromise, {
success: `${amount} ${chain.nativeCurrency.symbol} sent successfully`,
error: `Failed to claim ${amount} ${chain.nativeCurrency.symbol}`,
});
}}
>
{claimMutation.isPending ? (
<>
Claiming <Spinner className="size-3" />
</>
) : (
`Get ${amount} ${chain.nativeCurrency.symbol}`
)}
</Button>
<Form {...form}>
<form onSubmit={form.handleSubmit(claimFunds)}>
<Button variant="primary" className="w-full gap-2" type="submit">
{claimMutation.isPending ? (
<>
Claiming <Spinner className="size-3" />
</>
) : (
`Get ${amount} ${chain.nativeCurrency.symbol}`
)}
</Button>
<Turnstile siteKey={TURNSTILE_SITE_KEY} />
</form>
</Form>

{faucetWalletBalanceQuery.data && (
<p className="mt-3 text-muted-foreground text-xs">
Expand Down
41 changes: 40 additions & 1 deletion apps/dashboard/src/app/api/testnet-faucet/claim/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ const THIRDWEB_ACCESS_TOKEN = process.env.THIRDWEB_ACCESS_TOKEN;
interface RequestTestnetFundsPayload {
chainId: number;
toAddress: string;

// Cloudflare Turnstile token received from the client-side
turnstileToken: string;
}

// Note: This handler cannot use "edge" runtime because of Redis usage.
export const POST = async (req: NextRequest) => {
const requestBody = (await req.json()) as RequestTestnetFundsPayload;
const { chainId, toAddress } = requestBody;
const { chainId, toAddress, turnstileToken } = requestBody;
if (Number.isNaN(chainId)) {
throw new Error("Invalid chain ID.");
}
Expand Down Expand Up @@ -46,6 +49,42 @@ export const POST = async (req: NextRequest) => {
);
}

if (!turnstileToken) {
return NextResponse.json(
{
error: "Missing Turnstile token.",
},
{ status: 400 },
);
}

// https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
// Validate the token by calling the "/siteverify" API endpoint.
const result = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
body: JSON.stringify({
secret: process.env.TURNSTILE_SECRET_KEY,
response: turnstileToken,
remoteip: ipAddress,
}),
method: "POST",
headers: {
"Content-Type": "application/json",
},
},
);

const outcome = await result.json();
if (!outcome.success) {
return NextResponse.json(
{
error: "Could not validate captcha.",
},
{ status: 400 },
);
}

const ipCacheKey = `testnet-faucet:${chainId}:${ipAddress}`;
const addressCacheKey = `testnet-faucet:${chainId}:${toAddress}`;

Expand Down
34 changes: 19 additions & 15 deletions pnpm-lock.yaml

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

0 comments on commit fe0396e

Please sign in to comment.