forked from FlowwStar/FlowStar
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtestnet-faucet-banner.tsx
More file actions
172 lines (149 loc) · 5.39 KB
/
Copy pathtestnet-faucet-banner.tsx
File metadata and controls
172 lines (149 loc) · 5.39 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
164
165
166
167
168
169
170
171
172
'use client'
import { useState, useEffect } from 'react'
import { AlertCircle, Loader2, Check, X } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { useWallet } from '@/hooks/use-wallet'
import { useNetwork } from '@/components/providers/network-provider'
import { getXlmBalance } from '@/lib/stellar'
import { toast } from 'sonner'
interface TestnetFaucetBannerProps {
onClose?: () => void
}
// Time to wait for Friendbot's funding transaction to be confirmed on-chain
const CONFIRMATION_DELAY_MS = 3000
// How long to show the success state before auto-dismissing the banner
const AUTO_DISMISS_DELAY_MS = 5000
/**
* Testnet faucet banner that appears when a user is on testnet
* with 0 XLM balance. Provides a one-click button to fund their
* account via Friendbot.
*/
export function TestnetFaucetBanner({ onClose }: TestnetFaucetBannerProps) {
const { address, isConnected } = useWallet()
const { network } = useNetwork()
const [xlmBalance, setXlmBalance] = useState<bigint | null>(null)
const [loading, setLoading] = useState(true)
const [funding, setFunding] = useState(false)
const [fundingStatus, setFundingStatus] = useState<'idle' | 'success' | 'error'>('idle')
const [errorMessage, setErrorMessage] = useState('')
const [dismissed, setDismissed] = useState(false)
// Only show on testnet
const isTestnet = network === 'testnet'
// Fetch balance when address changes
useEffect(() => {
if (!address || !isConnected || !isTestnet) {
setLoading(false)
return
}
setLoading(true)
getXlmBalance(address, 'testnet')
.then((balance) => {
setXlmBalance(balance)
setLoading(false)
})
.catch(() => {
setLoading(false)
})
}, [address, isConnected, isTestnet])
// Hide if not applicable
if (dismissed || !isTestnet || !isConnected || !address || loading) {
return null
}
// Hide if balance is not zero
const isZeroBalance = xlmBalance === null || xlmBalance === 0n
if (!isZeroBalance) {
return null
}
async function fundWithFriendbot() {
if (!address) return
setFunding(true)
setFundingStatus('idle')
setErrorMessage('')
try {
// Call Friendbot
const friendbotUrl = `https://friendbot.stellar.org?addr=${encodeURIComponent(address)}`
const response = await fetch(friendbotUrl)
if (!response.ok) {
const errorData = await response.text()
throw new Error(errorData || 'Friendbot funding failed')
}
// Wait a moment for the transaction to be confirmed
await new Promise((resolve) => setTimeout(resolve, CONFIRMATION_DELAY_MS))
// Refresh balance
const newBalance = await getXlmBalance(address, 'testnet')
setXlmBalance(newBalance)
setFundingStatus('success')
toast.success('Account funded!', {
description: 'Your testnet account has been funded with test XLM from Friendbot.',
})
// Auto-dismiss after success
setTimeout(() => {
setDismissed(true)
onClose?.()
}, AUTO_DISMISS_DELAY_MS)
} catch (error) {
console.error('Friendbot funding error:', error)
setFundingStatus('error')
const message =
error instanceof Error ? error.message : 'Failed to fund account. Please try again.'
setErrorMessage(message)
toast.error('Funding failed', {
description: message,
})
} finally {
setFunding(false)
}
}
return (
<div className="rounded-2xl border border-amber-500/40 bg-amber-500/10 p-4 space-y-3">
<div className="flex items-start gap-3">
<AlertCircle className="mt-0.5 size-5 shrink-0 text-amber-600 dark:text-amber-400" />
<div className="flex-1">
<p className="font-medium text-amber-700 dark:text-amber-300">
Your testnet account needs funding
</p>
<p className="text-sm text-amber-600 dark:text-amber-400 mt-1">
Fund your account with test XLM using Friendbot to start creating streams.
</p>
{fundingStatus === 'success' && (
<p className="text-sm text-green-600 dark:text-green-400 mt-2 flex items-center gap-1">
<Check className="size-4" />
Account funded successfully! Reloading...
</p>
)}
{fundingStatus === 'error' && (
<p className="text-sm text-destructive mt-2 flex items-center gap-1">
<X className="size-4" />
{errorMessage}
</p>
)}
</div>
</div>
<div className="flex flex-wrap gap-2">
<Button
size="sm"
onClick={fundWithFriendbot}
disabled={funding}
className="gap-1.5 bg-amber-600 hover:bg-amber-700 text-white"
>
{funding && <Loader2 className="size-4 animate-spin" />}
{funding ? 'Funding...' : 'Fund with Friendbot'}
</Button>
<Button
size="sm"
variant="outline"
onClick={() => setDismissed(true)}
disabled={funding}
className="border-amber-500/40 text-amber-700 hover:bg-amber-500/10 dark:text-amber-400"
>
Dismiss
</Button>
</div>
<p className="text-xs text-amber-600 dark:text-amber-400/70">
{' '}
Friendbot may be rate-limited if you fund multiple accounts. If funding fails, wait a few
minutes and try again.
</p>
</div>
)
}