added faucet

This commit is contained in:
Jincheng Lu 2025-12-06 12:23:20 +08:00
parent f410f09d67
commit b540e1372e
6 changed files with 202 additions and 4 deletions

View File

@ -5,12 +5,12 @@ import clrTokenJson from './CLRToken/CLRToken.json';
const clearNetABI = (clearNetJson as any).abi ?? clearNetJson; const clearNetABI = (clearNetJson as any).abi ?? clearNetJson;
const clrTokenABI = (clrTokenJson as any).abi ?? clrTokenJson; const clrTokenABI = (clrTokenJson as any).abi ?? clrTokenJson;
const clearNetAddress = "0x265da498da1de3f22bb57c717d14806e4884cdda"; const clearNetAddress = "0x0305e95225f65db13e98c775dbb95b98178ae73b";
const clrTokenAddress = "0xf1664c17887767c8f58695846babb349ca61d2e9"; const clrTokenAddress = "0xf1664c17887767c8f58695846babb349ca61d2e9";
// const clearNetAddress = "0x9a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae"; // const clearNetAddress = "0x9a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae";
// const clrTokenAddress = "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707"; // const clrTokenAddress = "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707";
const DEFAULT_MIN_STAKE = BigInt(10000) * BigInt(1e18); // 10000 CLR const DEFAULT_MIN_STAKE = BigInt(100) * BigInt(1e18); // 100 CLR
export const getActiveNodes = async (provider: any): Promise<Node[]> => { export const getActiveNodes = async (provider: any): Promise<Node[]> => {

87
app/Components/Faucet.tsx Normal file
View File

@ -0,0 +1,87 @@
import React from "react";
import { Button, Box, Typography, Alert } from "@mui/material";
import useAuth from '~/hooks/useAuth';
import { Web3 } from 'web3';
const FAUCET_ADDRESS = "0xA86b97D7CF0c00cd0e82bBDCe9F06d689cFb12b5";
const FAUCET_ABI = [
{
inputs: [],
name: "claim",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
];
export default function FaucetClaim() {
const [status, setStatus] = React.useState<string | null>(null);
const [loading, setLoading] = React.useState(false);
const { auth } = useAuth();
const account = auth?.accounts?.[0];
const isAuthed = Boolean(account);
const claimFaucet = async () => {
if (!isAuthed) {
setStatus("Please connect your wallet first.");
return;
}
setLoading(true);
setStatus("Preparing transaction...");
try {
const provider = auth.providerWithInfo.provider;
const web3 = new Web3(provider);
const contract = new web3.eth.Contract(FAUCET_ABI, FAUCET_ADDRESS);
setStatus("Sending claim transaction...");
const tx = await contract.methods.claim().send({ from: account });
setStatus("Transaction Hash: " + tx.transactionHash + '\n' + "Claim confirmed. Check your balance.");
} catch (err: any) {
setStatus("Claim failed: " + (err?.message ?? String(err)));
} finally {
setLoading(false);
}
};
return (
<Box sx={{ fontFamily: "sans-serif", maxWidth: 640, margin: "24px auto", padding: "0 16px" }}>
<Typography variant="h5" gutterBottom>
CLR Faucet Claim
</Typography>
<Typography sx={{ mt: 1 }}>
Network: <strong>Sepolia</strong>
</Typography>
<Typography sx={{ mt: 1 }}>
Faucet: <code>{FAUCET_ADDRESS}</code>
</Typography>
<Typography sx={{ mt: 1 }}>
Token: <code>0xf1664c17887767c8f58695846babb349ca61d2e9</code>
</Typography>
<Box sx={{ display: "flex", gap: 1, mt: 3 }}>
<Button variant="outlined" onClick={claimFaucet} disabled={loading || !isAuthed}>
{loading ? "Processing..." : "Claim CLR"}
</Button>
</Box>
{account && (
<Typography sx={{ mt: 2, fontSize: 13, color: "text.secondary" }}>
Connected: {account}
</Typography>
)}
{status && (
<Alert severity="info" sx={{ mt: 2 }}>
{status}
</Alert>
)}
</Box>
);
}

View File

@ -30,7 +30,7 @@ export default function ButtonAppBar() {
}; };
const register = async () => { const register = async () => {
const clr_deposit_amount = BigInt(10000) * BigInt(1e18); // 10000 CLR const clr_deposit_amount = BigInt(100) * BigInt(1e18); // 100 CLR
await approveCLRTokenSpending(auth.providerWithInfo.provider, account!); await approveCLRTokenSpending(auth.providerWithInfo.provider, account!);
await openPaymentChannel(auth.providerWithInfo.provider, account!, clr_deposit_amount); await openPaymentChannel(auth.providerWithInfo.provider, account!, clr_deposit_amount);
setRegistered(true); setRegistered(true);
@ -67,6 +67,14 @@ export default function ButtonAppBar() {
Home Home
</Typography> </Typography>
<Typography
component={NavLink as any}
to="/faucet"
sx={{ color: 'inherit', textDecoration: 'none' }}
>
Faucet
</Typography>
<Typography <Typography
component={NavLink as any} component={NavLink as any}
to="/about" to="/about"

View File

@ -0,0 +1,95 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>CLR Faucet Claim</title>
<style>
body { font-family: sans-serif; max-width: 640px; margin: 24px auto; padding: 0 16px; }
button { padding: 10px 16px; font-size: 16px; cursor: pointer; }
.row { margin: 12px 0; }
#status { margin-top: 12px; white-space: pre-line; }
code { background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }
</style>
</head>
<body>
<h2>CLR Faucet Claim</h2>
<div class="row">Network: <strong>Sepolia</strong></div>
<div class="row">Faucet: <code id="faucetAddr">0xA86b97D7CF0c00cd0e82bBDCe9F06d689cFb12b5</code></div>
<div class="row">Token: <code>0xf1664c17887767c8f58695846babb349ca61d2e9</code></div>
<div class="row">
<button id="connect">Connect Wallet</button>
<button id="claim" disabled>Claim 100 CLR</button>
</div>
<div id="status"></div>
<script src="https://cdn.jsdelivr.net/npm/ethers@6.13.1/dist/ethers.umd.min.js"></script>
<script>
// Check if ethers loaded
if (typeof ethers === 'undefined') {
document.getElementById("status").textContent = "Error: ethers.js failed to load. Check internet connection.";
throw new Error("ethers.js not loaded");
}
// ---------------------- CONFIG ----------------------
const FAUCET_ADDRESS = "0xA86b97D7CF0c00cd0e82bBDCe9F06d689cFb12b5"; // set your deployed faucet address
const FAUCET_ABI = ["function claim() external"];
// ----------------------------------------------------
const statusEl = document.getElementById("status");
const connectBtn = document.getElementById("connect");
const claimBtn = document.getElementById("claim");
document.getElementById("faucetAddr").textContent = FAUCET_ADDRESS;
function log(msg) {
statusEl.textContent = msg;
}
async function connect() {
if (!window.ethereum) {
log("Please install MetaMask.");
return;
}
try {
log("Connecting wallet...");
await window.ethereum.request({ method: "eth_requestAccounts" });
const provider = new ethers.BrowserProvider(window.ethereum);
const network = await provider.getNetwork();
if (network.chainId !== 11155111n) {
log("Please switch MetaMask to Sepolia.");
return;
}
log("Wallet connected on Sepolia.");
claimBtn.disabled = false;
return provider;
} catch (err) {
console.error(err);
log("Connect failed: " + (err?.message || err));
}
}
async function claim() {
try {
claimBtn.disabled = true;
log("Sending claim transaction...");
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const faucet = new ethers.Contract(FAUCET_ADDRESS, FAUCET_ABI, signer);
const tx = await faucet.claim();
log("Pending tx: " + tx.hash + "\nWaiting for confirmation...");
await tx.wait();
log("✅ Claim confirmed! Tx: " + tx.hash);
} catch (err) {
console.error(err);
log("Claim failed: " + (err?.shortMessage || err?.message || err));
} finally {
claimBtn.disabled = false;
}
}
connectBtn.onclick = connect;
claimBtn.onclick = claim;
</script>
</body>
</html>

View File

@ -3,5 +3,6 @@ import { type RouteConfig, index, route } from "@react-router/dev/routes";
export default [ export default [
index("routes/home.tsx"), index("routes/home.tsx"),
route("wallets", "routes/wallets.tsx"), route("wallets", "routes/wallets.tsx"),
route("about", "routes/about.tsx") route("about", "routes/about.tsx"),
route("faucet", "routes/faucet.tsx")
] satisfies RouteConfig; ] satisfies RouteConfig;

7
app/routes/faucet.tsx Normal file
View File

@ -0,0 +1,7 @@
import FaucetClaim from "~/Components/Faucet";
export default function FaucetPage() {
return(
<FaucetClaim />
);
}