Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# akave-testnet-faucet
# akave-testnet-faucet
34 changes: 16 additions & 18 deletions app/api/faucet/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,53 @@ const akaveChain = {
nativeCurrency: {
name: 'AKVT',
symbol: 'AKVT',
decimals: 18
decimals: 18,
},
rpcUrls: {
default: {
http: ['https://n1-us.akave.ai/ext/bc/2JMWNmZbYvWcJRPPy1siaDBZaDGTDAaqXoY5UBKh4YrhNFzEce/rpc']
http: [
'https://n1-us.akave.ai/ext/bc/2JMWNmZbYvWcJRPPy1siaDBZaDGTDAaqXoY5UBKh4YrhNFzEce/rpc',
],
},
public: {
http: ['https://n1-us.akave.ai/ext/bc/2JMWNmZbYvWcJRPPy1siaDBZaDGTDAaqXoY5UBKh4YrhNFzEce/rpc']
}
}
http: [
'https://n1-us.akave.ai/ext/bc/2JMWNmZbYvWcJRPPy1siaDBZaDGTDAaqXoY5UBKh4YrhNFzEce/rpc',
],
},
},
}

const publicClient = createPublicClient({
chain: akaveChain,
transport: http()
transport: http(),
})

export async function POST(request: Request) {
try {
const { address, email } = await request.json()

if (!address) {
return NextResponse.json({ error: 'Address is required' }, { status: 400 })
}

// Check user's balance
const balance = await publicClient.getBalance({ address })

if (balance >= parseEther('10')) {
return NextResponse.json(
{ error: 'Address already has more than 10 AKVT' },
{ status: 400 }
)
return NextResponse.json({ error: 'Address already has more than 10 AKVT' }, { status: 400 })
}

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: akaveChain,
transport: http()
transport: http(),
})

// Send transaction
const hash = await walletClient.sendTransaction({
to: address,
value: parseEther('10')
value: parseEther('10'),
})

// Store email if provided
Expand All @@ -72,9 +73,6 @@ export async function POST(request: Request) {
return NextResponse.json({ hash })
} catch (error) {
console.error(error)
return NextResponse.json(
{ error: 'Failed to process request' },
{ status: 500 }
)
return NextResponse.json({ error: 'Failed to process request' }, { status: 500 })
}
}
28 changes: 12 additions & 16 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ["latin"] });
const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
title: "Akave Faucet",
description: "Claim AKVF tokens",
title: 'Akave Faucet',
description: 'Claim AKVF tokens',
icons: {
icon: [
{
url: "/favicon-32x32.png",
sizes: "32x32",
type: "image/png",
url: '/favicon-32x32.png',
sizes: '32x32',
type: 'image/png',
},
],
},
};
}

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
)
}
126 changes: 61 additions & 65 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
"use client";
import Image from "next/image";
import { useState } from "react";
import toast, { Toaster } from "react-hot-toast";
'use client'
import Image from 'next/image'
import { useState } from 'react'
import toast, { Toaster } from 'react-hot-toast'

export default function Home() {
const [address, setAddress] = useState("");
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const [address, setAddress] = useState('')
const [email, setEmail] = useState('')
const [loading, setLoading] = useState(false)

const addToMetamask = async () => {
try {
// @ts-ignore
if (!window?.ethereum) throw new Error("Please install MetaMask");
if (!window?.ethereum) throw new Error('Please install MetaMask')

try {
// First try to switch to the network if it exists
// @ts-ignore
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x13474' }],
});
toast.success("Switched to Akave network!");
})
toast.success('Switched to Akave network!')
} catch (switchError: any) {
// If the network doesn't exist, add it
if (switchError.code === 4902) {
Expand All @@ -30,71 +30,71 @@ export default function Home() {
method: 'wallet_addEthereumChain',
params: [
{
chainId: "0x13474", // 78964 in decimal
chainName: "Akave Fuji",
chainId: '0x13474', // 78964 in decimal
chainName: 'Akave Fuji',
nativeCurrency: {
name: "AKVT",
symbol: "AKVT",
name: 'AKVT',
symbol: 'AKVT',
decimals: 18,
},
rpcUrls: [
"https://n1-us.akave.ai/ext/bc/2JMWNmZbYvWcJRPPy1siaDBZaDGTDAaqXoY5UBKh4YrhNFzEce/rpc",
'https://n1-us.akave.ai/ext/bc/2JMWNmZbYvWcJRPPy1siaDBZaDGTDAaqXoY5UBKh4YrhNFzEce/rpc',
],
blockExplorerUrls: ["http://explorer.akave.ai"],
blockExplorerUrls: ['http://explorer.akave.ai'],
},
],
});
toast.success("Akave network added to MetaMask!");
})
toast.success('Akave network added to MetaMask!')
} catch (addError) {
throw new Error("Failed to add Akave network.");
throw new Error('Failed to add Akave network.')
}
} else {
throw new Error("Failed to switch to Akave network.");
throw new Error('Failed to switch to Akave network.')
}
}
} catch (error) {
console.error(error);
console.error(error)
if (error instanceof Error) {
toast.error(error.message);
toast.error(error.message)
} else {
toast.error("Failed to add/switch to Akave network.");
toast.error('Failed to add/switch to Akave network.')
}
}
};
}

const faucet = async (address: string) => {
setLoading(true);
setLoading(true)
try {
const response = await fetch("/api/faucet", {
method: "POST",
const response = await fetch('/api/faucet', {
method: 'POST',
body: JSON.stringify({
address: address,
email: email,
}),
});
})

const data = await response.json();
const data = await response.json()

if (!response.ok) {
throw new Error(data.error || "Failed to claim AKVT.");
throw new Error(data.error || 'Failed to claim AKVT.')
}

toast.success("Claim successful!");
toast.success('Claim successful!')
} catch (error) {
console.error(error);
console.error(error)
if (error instanceof Error) {
toast.error(error.message || "An error occurred.");
toast.error(error.message || 'An error occurred.')
} else {
toast.error("An error occurred.");
toast.error('An error occurred.')
}
} finally {
setLoading(false);
setLoading(false)
}
};
}

return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-[90vh] p-4 pb-20 gap-8 sm:p-20 sm:gap-16 font-[family-name:var(--font-geist-sans)] overflow-y-hidden">
<Toaster
<div className="grid min-h-[90vh] grid-rows-[20px_1fr_20px] items-center justify-items-center gap-8 overflow-y-hidden p-4 pb-20 font-[family-name:var(--font-geist-sans)] sm:gap-16 sm:p-20">
<Toaster
toastOptions={{
duration: 3000,
}}
Expand All @@ -103,35 +103,32 @@ export default function Home() {
}}
position="top-right"
/>
<main className="flex flex-col gap-4 sm:gap-8 row-start-2 items-center w-full max-w-[500px]">
<Image
src="/logo.svg"
alt="Akave"
width={500}
<main className="row-start-2 flex w-full max-w-[500px] flex-col items-center gap-4 sm:gap-8">
<Image
src="/logo.svg"
alt="Akave"
width={500}
height={100}
className="w-[280px] sm:w-[500px] h-auto"
className="h-auto w-[280px] sm:w-[500px]"
priority
/>
<div className="flex flex-col gap-4 bg-[#010127] p-4 sm:p-8 rounded-lg w-full">
<div className="flex w-full flex-col gap-4 rounded-lg bg-[#010127] p-4 sm:p-8">
<div className="flex flex-col gap-2">
<label htmlFor="address" className="text-white">
Wallet Address *
</label>
<p className="text-sm text-gray-400 mb-2">
You can only claim if you have less than 10 AKVT. For bulk
requests, please reach out to us at{" "}
<a
href="https://t.me/akavebuilders"
className="underline hover:text-gray-200"
>
<p className="mb-2 text-sm text-gray-400">
You can only claim if you have less than 10 AKVT. For bulk requests, please reach out
to us at{' '}
<a href="https://t.me/akavebuilders" className="underline hover:text-gray-200">
Akave Builders
</a>
{" "}telegram channel
</a>{' '}
telegram channel
</p>
<input
id="address"
type="text"
className="px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-black w-full"
className="w-full rounded-md border border-gray-300 px-4 py-2 text-black focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter address..."
onChange={(e) => setAddress(e.target.value)}
required
Expand All @@ -142,32 +139,31 @@ export default function Home() {
<label htmlFor="email" className="text-white">
Email (optional)
</label>
<p className="text-sm text-gray-400 mb-2">
Leave your email ID to receive updates about network developments
and announcements
<p className="mb-2 text-sm text-gray-400">
Leave your email ID to receive updates about network developments and announcements
</p>
<input
id="email"
type="email"
className="px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-black w-full"
className="w-full rounded-md border border-gray-300 px-4 py-2 text-black focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter email..."
onChange={(e) => setEmail(e.target.value)}
/>
</div>

<div className="flex flex-col sm:flex-row gap-4 w-full mt-4">
<div className="mt-4 flex w-full flex-col gap-4 sm:flex-row">
<button
className="flex-grow px-4 py-2 bg-[#B0E4FF] text-black font-medium rounded-md transition-colors disabled:opacity-50"
className="flex-grow rounded-md bg-[#B0E4FF] px-4 py-2 font-medium text-black transition-colors disabled:opacity-50"
onClick={() => faucet(address)}
disabled={loading}
>
{loading ? "Claiming..." : "Claim 10 AKVT"}
{loading ? 'Claiming...' : 'Claim 10 AKVT'}
</button>
<button
onClick={addToMetamask}
className="flex-grow px-4 py-2 bg-[#B0E4FF] text-black font-medium rounded-md transition-colors"
className="flex-grow rounded-md bg-[#B0E4FF] px-4 py-2 font-medium text-black transition-colors"
>
<span className="flex flex-row gap-2 justify-center items-center">
<span className="flex flex-row items-center justify-center gap-2">
<Image src="/fox.svg" alt="MetaMask" width={20} height={20} />
<span className="whitespace-nowrap">Add Akave to metamask</span>
</span>
Expand All @@ -176,5 +172,5 @@ export default function Home() {
</div>
</main>
</div>
);
)
}
6 changes: 3 additions & 3 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextConfig } from "next";
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
/* config options here */
};
}

export default nextConfig;
export default nextConfig
4 changes: 2 additions & 2 deletions postcss.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ const config = {
plugins: {
tailwindcss: {},
},
};
}

export default config;
export default config
Loading