1
+ import { NextRequest , NextResponse } from 'next/server' ;
2
+ import { createWalletClient , http , parseEther , createPublicClient } from 'viem' ;
3
+ import { privateKeyToAccount } from 'viem/accounts' ;
4
+ import { avalancheFuji , avalanche } from 'viem/chains' ;
5
+ import { getAuthSession } from '@/lib/auth/authSession' ;
6
+ import { rateLimit } from '@/lib/rateLimit' ;
7
+
8
+ const SERVER_PRIVATE_KEY = process . env . FAUCET_C_CHAIN_PRIVATE_KEY ;
9
+ const FAUCET_C_CHAIN_ADDRESS = process . env . FAUCET_C_CHAIN_ADDRESS ;
10
+ const FIXED_AMOUNT = '2' ;
11
+
12
+ if ( ! SERVER_PRIVATE_KEY || ! FAUCET_C_CHAIN_ADDRESS ) {
13
+ console . error ( 'necessary environment variables for C-Chain faucet are not set' ) ;
14
+ }
15
+
16
+ interface TransferResponse {
17
+ success : boolean ;
18
+ txHash ?: string ;
19
+ sourceAddress ?: string ;
20
+ destinationAddress ?: string ;
21
+ amount ?: string ;
22
+ message ?: string ;
23
+ }
24
+
25
+ async function transferCChainAVAX (
26
+ sourcePrivateKey : string ,
27
+ sourceAddress : string ,
28
+ destinationAddress : string ,
29
+ isTestnet : boolean
30
+ ) : Promise < { txHash : string } > {
31
+ const chain = isTestnet ? avalancheFuji : avalanche ;
32
+ const account = privateKeyToAccount ( sourcePrivateKey as `0x${string } `) ;
33
+ const walletClient = createWalletClient ( {
34
+ account,
35
+ chain,
36
+ transport : http ( )
37
+ } ) ;
38
+
39
+ const publicClient = createPublicClient ( {
40
+ chain,
41
+ transport : http ( )
42
+ } ) ;
43
+
44
+ const balance = await publicClient . getBalance ( {
45
+ address : sourceAddress as `0x${string } `
46
+ } ) ;
47
+
48
+ const amountToSend = parseEther ( FIXED_AMOUNT ) ;
49
+
50
+ if ( balance < amountToSend ) {
51
+ throw new Error ( 'Insufficient faucet balance' ) ;
52
+ }
53
+
54
+ const txHash = await walletClient . sendTransaction ( {
55
+ to : destinationAddress as `0x${string } `,
56
+ value : amountToSend ,
57
+ } ) ;
58
+
59
+ return { txHash } ;
60
+ }
61
+
62
+ async function validateFaucetRequest ( request : NextRequest ) : Promise < NextResponse | null > {
63
+ try {
64
+ const session = await getAuthSession ( ) ;
65
+ if ( ! session ?. user ) {
66
+ return NextResponse . json (
67
+ { success : false , message : 'Authentication required' } ,
68
+ { status : 401 }
69
+ ) ;
70
+ }
71
+
72
+ if ( ! SERVER_PRIVATE_KEY || ! FAUCET_C_CHAIN_ADDRESS ) {
73
+ return NextResponse . json (
74
+ { success : false , message : 'Server not properly configured' } ,
75
+ { status : 500 }
76
+ ) ;
77
+ }
78
+
79
+ const searchParams = request . nextUrl . searchParams ;
80
+ const destinationAddress = searchParams . get ( 'address' ) ;
81
+
82
+ if ( ! destinationAddress ) {
83
+ return NextResponse . json (
84
+ { success : false , message : 'Destination address is required' } ,
85
+ { status : 400 }
86
+ ) ;
87
+ }
88
+
89
+ if ( ! / ^ 0 x [ a - f A - F 0 - 9 ] { 40 } $ / . test ( destinationAddress ) ) {
90
+ return NextResponse . json (
91
+ { success : false , message : 'Invalid Ethereum address format' } ,
92
+ { status : 400 }
93
+ ) ;
94
+ }
95
+
96
+ if ( destinationAddress . toLowerCase ( ) === FAUCET_C_CHAIN_ADDRESS ?. toLowerCase ( ) ) {
97
+ return NextResponse . json (
98
+ { success : false , message : 'Cannot send tokens to the faucet address' } ,
99
+ { status : 400 }
100
+ ) ;
101
+ }
102
+ return null ;
103
+ } catch ( error ) {
104
+ console . error ( 'Validation failed:' , error ) ;
105
+ return NextResponse . json (
106
+ {
107
+ success : false ,
108
+ message : error instanceof Error ? error . message : 'Failed to validate request'
109
+ } ,
110
+ { status : 500 }
111
+ ) ;
112
+ }
113
+ }
114
+
115
+ async function handleFaucetRequest ( request : NextRequest ) : Promise < NextResponse > {
116
+ try {
117
+ const searchParams = request . nextUrl . searchParams ;
118
+ const destinationAddress = searchParams . get ( 'address' ) ! ;
119
+ const isTestnet = true ;
120
+
121
+ const tx = await transferCChainAVAX (
122
+ SERVER_PRIVATE_KEY ! ,
123
+ FAUCET_C_CHAIN_ADDRESS ! ,
124
+ destinationAddress ,
125
+ isTestnet
126
+ ) ;
127
+
128
+ const response : TransferResponse = {
129
+ success : true ,
130
+ txHash : tx . txHash ,
131
+ sourceAddress : FAUCET_C_CHAIN_ADDRESS ,
132
+ destinationAddress,
133
+ amount : FIXED_AMOUNT
134
+ } ;
135
+
136
+ return NextResponse . json ( response ) ;
137
+
138
+ } catch ( error ) {
139
+ console . error ( 'C-Chain transfer failed:' , error ) ;
140
+
141
+ const response : TransferResponse = {
142
+ success : false ,
143
+ message : error instanceof Error ? error . message : 'Failed to complete transfer'
144
+ } ;
145
+
146
+ return NextResponse . json ( response , { status : 500 } ) ;
147
+ }
148
+ }
149
+
150
+ export async function GET ( request : NextRequest ) : Promise < NextResponse > {
151
+ const validationResponse = await validateFaucetRequest ( request ) ;
152
+
153
+ if ( validationResponse ) {
154
+ return validationResponse ;
155
+ }
156
+
157
+ const rateLimitHandler = rateLimit ( handleFaucetRequest , {
158
+ windowMs : 24 * 60 * 60 * 1000 ,
159
+ maxRequests : 1
160
+ } ) ;
161
+
162
+ return rateLimitHandler ( request ) ;
163
+ }
0 commit comments