-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathFaucet.ts
More file actions
163 lines (136 loc) · 5.2 KB
/
Faucet.ts
File metadata and controls
163 lines (136 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { ethers } from "ethers";
import type { VercelRequest, VercelResponse } from "@vercel/node";
// Define the ABI for the specific function we need to call
// This is more gas-efficient than loading the entire ABI
const FAUCET_ABI = ["function requestFaucet(address _recipient) external"];
/**
* Sets CORS headers to allow requests from your frontend.
* For production, replace '*' with your specific frontend domain for security.
* e.g., 'https://my-game-app.vercel.app'
*/
const setCorsHeaders = (res: VercelResponse) => {
res.setHeader(
"Access-Control-Allow-Origin",
process.env.FRONTEND_URL || "*" // Allow specified frontend or all
);
res.setHeader(
"Access-Control-Allow-Methods",
"POST, OPTIONS" // Allow POST and the preflight OPTIONS requests
);
res.setHeader(
"Access-Control-Allow-Headers",
"Content-Type" // Allow 'Content-Type' header
);
};
/**
* Checks if all required environment variables are set.
* Throws an error if any are missing.
*/
const checkEnvVars = () => {
const requiredVars = [
"DEGEN_RPC_URL",
"FAUCET_CONTRACT_ADDRESS",
"DEV_PRIVATE_KEY",
];
const missingVars = requiredVars.filter((v) => !process.env[v]);
if (missingVars.length > 0) {
console.error(`Missing environment variables: ${missingVars.join(", ")}`);
// This error will be caught by the main try...catch block
throw new Error("Server configuration error.");
}
// Return the variables in a structured object for easy access
return {
rpcUrl: process.env.DEGEN_RPC_URL!,
contractAddress: process.env.FAUCET_CONTRACT_ADDRESS!,
privateKey: process.env.DEV_PRIVATE_KEY!,
};
};
/**
* The main Vercel Serverless Function handler.
* This function is triggered for all requests to /api/faucet.
*/
export default async function handler(
req: VercelRequest,
res: VercelResponse
) {
// Apply CORS headers to every response
setCorsHeaders(res);
// Handle CORS preflight (OPTIONS) request
// Browsers send this automatically before a POST request to check permissions
if (req.method === "OPTIONS") {
return res.status(204).end(); // Respond with "No Content"
}
// Ensure the request is a POST method
if (req.method !== "POST") {
res.setHeader("Allow", ["POST", "OPTIONS"]);
return res.status(405).json({ error: "Method Not Allowed" });
}
try {
// --- 1. Configuration & Validation ---
// Verify and get environment variables. Fails if any are missing.
const { rpcUrl, contractAddress, privateKey } = checkEnvVars();
// Get the user's address from the JSON request body
// Vercel automatically parses `req.body` from JSON
const { userAddress } = req.body;
// Validate the Ethereum address format
if (!userAddress || !ethers.isAddress(userAddress)) {
return res.status(400).json({ error: "address not valid." });
}
// --- 2. Ethers.js Logic ---
// Connect to the blockchain using the RPC URL
const provider = new ethers.JsonRpcProvider(rpcUrl);
// Create a wallet instance for our developer/operator
// This wallet pays the gas for the faucet transaction
const devWallet = new ethers.Wallet(privateKey, provider);
// Check the user's current native token balance
const balance = await provider.getBalance(userAddress);
// Eligibility check: Only allow claims if the user's balance is 0
if (balance > 0) {
return res
.status(403) // 403 Forbidden
.json({ message: "your not eligible (balance > 0)." });
}
// --- 3. Smart Contract Interaction ---
// Create an instance of the faucet smart contract
// We connect it to `devWallet` so it can sign transactions
const faucetContract = new ethers.Contract(
contractAddress,
FAUCET_ABI,
devWallet
);
// Log in English
console.log(`Processing faucet request for: ${userAddress}`);
// Call the 'requestFaucet' function on the smart contract
// The `devWallet` will pay the gas for this transaction
const tx = await faucetContract.requestFaucet(userAddress);
// Log in English
console.log(`Faucet sent successfully! Tx hash: ${tx.hash}`);
// --- 4. Success Response ---
// Send a 200 OK response with the transaction hash
return res.status(200).json({
success: true,
message: `Faucet successfully sent to ${userAddress}`,
transactionHash: tx.hash,
});
} catch (error: any) {
// --- 5. Error Handling ---
console.error("Faucet Error:", error);
// Handle the specific configuration error we defined
if (error.message?.includes("Server configuration error")) {
return res
.status(500) // Internal Server Error
.json({ error: "Server configuration error." });
}
// Handle specific smart contract errors (e.g., revert messages)
// This assumes your contract reverts with this specific string
if (error.message?.includes("Address has already claimed faucet")) {
return res
.status(409) // 409 Conflict (good for "already exists")
.json({ error: "address has already been claimed by faucet." });
}
// Generic catch-all error for any other failures
return res
.status(500)
.json({ error: "An internal error occurred on the server." });
}
}