diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton.tsx index 48264ebd066..c5605f59d13 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/(chainPage)/components/client/FaucetButton.tsx @@ -237,6 +237,9 @@ export function FaucetButton({ {canClaimFaucetQuery.data.type === "unsupported-chain" && "Faucet is empty right now"} + + {canClaimFaucetQuery.data.type === "paid-plan-required" && + "Faucet is only available on Starter, Growth and Pro plans."} ); } diff --git a/apps/dashboard/src/app/api/testnet-faucet/can-claim/CanClaimResponseType.ts b/apps/dashboard/src/app/api/testnet-faucet/can-claim/CanClaimResponseType.ts index 28616e54f33..882cb0031e0 100644 --- a/apps/dashboard/src/app/api/testnet-faucet/can-claim/CanClaimResponseType.ts +++ b/apps/dashboard/src/app/api/testnet-faucet/can-claim/CanClaimResponseType.ts @@ -7,4 +7,8 @@ export type CanClaimResponseType = | { canClaim: false; type: "unsupported-chain"; + } + | { + canClaim: false; + type: "paid-plan-required"; }; diff --git a/apps/dashboard/src/app/api/testnet-faucet/can-claim/route.ts b/apps/dashboard/src/app/api/testnet-faucet/can-claim/route.ts index 503779977b0..2024c53decf 100644 --- a/apps/dashboard/src/app/api/testnet-faucet/can-claim/route.ts +++ b/apps/dashboard/src/app/api/testnet-faucet/can-claim/route.ts @@ -1,3 +1,4 @@ +import { getTeams } from "@/api/team"; import { DISABLE_FAUCET_CHAIN_IDS, THIRDWEB_ACCESS_TOKEN, @@ -46,6 +47,25 @@ export const GET = async (req: NextRequest) => { } catch {} } + // get the teams for the account + const teams = await getTeams(); + if (!teams) { + const res: CanClaimResponseType = { + canClaim: false, + type: "paid-plan-required", + }; + return NextResponse.json(res); + } + + const hasPaidPlan = teams.some((team) => team.billingPlan !== "free"); + if (!hasPaidPlan) { + const res: CanClaimResponseType = { + canClaim: false, + type: "paid-plan-required", + }; + return NextResponse.json(res); + } + if ( !THIRDWEB_ENGINE_URL || !THIRDWEB_ENGINE_FAUCET_WALLET || diff --git a/apps/dashboard/src/app/api/testnet-faucet/claim/route.ts b/apps/dashboard/src/app/api/testnet-faucet/claim/route.ts index f2d7d0d6e87..61ce9ace2c5 100644 --- a/apps/dashboard/src/app/api/testnet-faucet/claim/route.ts +++ b/apps/dashboard/src/app/api/testnet-faucet/claim/route.ts @@ -1,3 +1,4 @@ +import { getTeams } from "@/api/team"; import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie"; import { API_SERVER_URL, @@ -53,36 +54,6 @@ export const POST = async (req: NextRequest) => { ); } - // Make sure the connected wallet has a thirdweb account - const accountRes = await fetch(`${API_SERVER_URL}/v1/account/me`, { - method: "GET", - headers: { - Authorization: `Bearer ${authCookie.value}`, - }, - }); - - if (accountRes.status !== 200) { - // Account not found on this connected address - return NextResponse.json( - { - error: "thirdweb account not found", - }, - { status: 400 }, - ); - } - - const account: { data: Account } = await accountRes.json(); - - // Make sure the logged-in account has verified its email - if (!account.data.email) { - return NextResponse.json( - { - error: "Account owner hasn't verified email", - }, - { status: 400 }, - ); - } - const requestBody = (await req.json()) as RequestTestnetFundsPayload; const { chainId, toAddress, turnstileToken } = requestBody; if (Number.isNaN(chainId)) { @@ -150,6 +121,61 @@ export const POST = async (req: NextRequest) => { ); } + // Make sure the connected wallet has a thirdweb account + const accountRes = await fetch(`${API_SERVER_URL}/v1/account/me`, { + method: "GET", + headers: { + Authorization: `Bearer ${authCookie.value}`, + }, + }); + + if (accountRes.status !== 200) { + // Account not found on this connected address + return NextResponse.json( + { + error: "thirdweb account not found", + }, + { status: 400 }, + ); + } + + const account: { data: Account } = await accountRes.json(); + + // Make sure the logged-in account has verified its email + if (!account.data.email) { + return NextResponse.json( + { + error: "Account owner hasn't verified email", + }, + { status: 400 }, + ); + } + + // get the teams for the account + const teams = await getTeams(); + if (!teams) { + return NextResponse.json( + { + error: "No teams found for this account.", + }, + { + status: 500, + }, + ); + } + // check if ANY of the customer's teams has "growth" or "pro" plan + const hasPaidPlan = teams.some((team) => team.billingPlan !== "free"); + if (!hasPaidPlan) { + return NextResponse.json( + { + error: "Free plan cannot claim on this chain.", + }, + { + status: 402, + }, + ); + } + const ipCacheKey = `testnet-faucet:${chainId}:${ip}`; const addressCacheKey = `testnet-faucet:${chainId}:${toAddress}`; const accountCacheKey = `testnet-faucet:${chainId}:${account.data.id}`;