2
2
3
3
import { Spinner } from "@/components/ui/Spinner/Spinner" ;
4
4
import { Button } from "@/components/ui/button" ;
5
- import { THIRDWEB_ENGINE_FAUCET_WALLET } from "@/constants/env" ;
5
+ import { Form } from "@/components/ui/form" ;
6
+ import {
7
+ THIRDWEB_ENGINE_FAUCET_WALLET ,
8
+ TURNSTILE_SITE_KEY ,
9
+ } from "@/constants/env" ;
6
10
import { useThirdwebClient } from "@/constants/thirdweb.client" ;
7
11
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet" ;
12
+ import { Turnstile } from "@marsidev/react-turnstile" ;
8
13
import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
9
14
import type { CanClaimResponseType } from "app/api/testnet-faucet/can-claim/CanClaimResponseType" ;
10
15
import { mapV4ChainToV5Chain } from "contexts/map-chains" ;
11
16
import { useTrack } from "hooks/analytics/useTrack" ;
17
+ import { useForm } from "react-hook-form" ;
12
18
import { toast } from "sonner" ;
13
19
import { toUnits } from "thirdweb" ;
14
20
import type { ChainMetadata } from "thirdweb/chains" ;
@@ -29,6 +35,8 @@ function formatTime(seconds: number) {
29
35
return rtf . format ( + seconds , "second" ) ;
30
36
}
31
37
38
+ type TurnstileForm = { "cf-turnstile-response" : string } ;
39
+
32
40
export function FaucetButton ( {
33
41
chain,
34
42
amount,
@@ -52,7 +60,7 @@ export function FaucetButton({
52
60
const queryClient = useQueryClient ( ) ;
53
61
54
62
const claimMutation = useMutation ( {
55
- mutationFn : async ( ) => {
63
+ mutationFn : async ( turnstileToken : string ) => {
56
64
trackEvent ( {
57
65
category : "faucet" ,
58
66
action : "claim" ,
@@ -67,6 +75,7 @@ export function FaucetButton({
67
75
body : JSON . stringify ( {
68
76
chainId : chainId ,
69
77
toAddress : address ,
78
+ turnstileToken,
70
79
} ) ,
71
80
} ) ;
72
81
@@ -117,6 +126,8 @@ export function FaucetButton({
117
126
faucetWalletBalanceQuery . data !== undefined &&
118
127
faucetWalletBalanceQuery . data . value < toUnits ( "1" , 17 ) ;
119
128
129
+ const form = useForm < TurnstileForm > ( ) ;
130
+
120
131
// loading state
121
132
if ( faucetWalletBalanceQuery . isPending || canClaimFaucetQuery . isPending ) {
122
133
return (
@@ -161,28 +172,38 @@ export function FaucetButton({
161
172
) ;
162
173
}
163
174
175
+ const claimFunds = ( values : TurnstileForm ) => {
176
+ const turnstileToken = values [ "cf-turnstile-response" ] ;
177
+ if ( ! turnstileToken ) {
178
+ return toast . error ( "Failed to retrieve captcha token" ) ;
179
+ }
180
+ console . log ( { turnstileToken } ) ;
181
+ // Instead of having a dedicated endpoint (/api/verify-token),
182
+ // we can just attach the token in the payload and send it to the claim-faucet endpoint, to avoid a round-trip request
183
+ const claimPromise = claimMutation . mutateAsync ( turnstileToken . toString ( ) ) ;
184
+ toast . promise ( claimPromise , {
185
+ success : `${ amount } ${ chain . nativeCurrency . symbol } sent successfully` ,
186
+ error : `Failed to claim ${ amount } ${ chain . nativeCurrency . symbol } ` ,
187
+ } ) ;
188
+ } ;
189
+
164
190
// eligible to claim and faucet has balance
165
191
return (
166
192
< div className = "flex w-full flex-col text-center" >
167
- < Button
168
- variant = "primary"
169
- className = "w-full gap-2"
170
- onClick = { ( ) => {
171
- const claimPromise = claimMutation . mutateAsync ( ) ;
172
- toast . promise ( claimPromise , {
173
- success : `${ amount } ${ chain . nativeCurrency . symbol } sent successfully` ,
174
- error : `Failed to claim ${ amount } ${ chain . nativeCurrency . symbol } ` ,
175
- } ) ;
176
- } }
177
- >
178
- { claimMutation . isPending ? (
179
- < >
180
- Claiming < Spinner className = "size-3" />
181
- </ >
182
- ) : (
183
- `Get ${ amount } ${ chain . nativeCurrency . symbol } `
184
- ) }
185
- </ Button >
193
+ < Form { ...form } >
194
+ < form onSubmit = { form . handleSubmit ( claimFunds ) } >
195
+ < Button variant = "primary" className = "w-full gap-2" type = "submit" >
196
+ { claimMutation . isPending ? (
197
+ < >
198
+ Claiming < Spinner className = "size-3" />
199
+ </ >
200
+ ) : (
201
+ `Get ${ amount } ${ chain . nativeCurrency . symbol } `
202
+ ) }
203
+ </ Button >
204
+ < Turnstile siteKey = { TURNSTILE_SITE_KEY } />
205
+ </ form >
206
+ </ Form >
186
207
187
208
{ faucetWalletBalanceQuery . data && (
188
209
< p className = "mt-3 text-muted-foreground text-xs" >
0 commit comments