-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathpage.tsx
More file actions
264 lines (235 loc) · 9.29 KB
/
Copy pathpage.tsx
File metadata and controls
264 lines (235 loc) · 9.29 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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
"use client"
import { Box, Button, TextField, NumberField, FieldLabel, Callout } from "@interchain-ui/react"
import React, { useState, useEffect } from "react"
import { Wallet, ArrowRight, RefreshCw, AlertCircle } from "lucide-react"
import { SignerFromBrowser } from "@interchainjs/ethereum/signers/SignerFromBrowser"
import { MetaMaskInpageProvider } from "@metamask/providers";
import { useChain } from '@interchain-kit/react'
import { WalletState } from "@interchain-kit/core"
import { BSC_TESTNET, HOLESKY_TESTNET, SEPOLIA_TESTNET } from "./provider"
const CHAIN_INFO = SEPOLIA_TESTNET
type EthereumProvider = MetaMaskInpageProvider
// Alias Card components
const Card = Box
const CardHeader = Box
const CardContent = Box
const CardFooter = Box
const CardTitle = Box
const CardDescription = Box
export default function WalletPage() {
const [balance, setBalance] = useState<string>("0")
const [isLoading, setIsLoading] = useState(false)
const [recipient, setRecipient] = useState("")
const [amount, setAmount] = useState<number>(0)
const [error, setError] = useState("")
const [txLink, setTxLink] = useState("") // ← add success link state
const [ethereum, setEthereum] = useState<EthereumProvider>()
const { wallet, status, connect, address: account, disconnect } = useChain(CHAIN_INFO.chainName) // chain name must be same as getProvider chain id
useEffect(() => {
console.log('status from useChain:', status)
if (status === WalletState.Connected) {
const setEthProviderFromWallet = async () => {
await new Promise(resolve => setTimeout(resolve, 500))
const ethProviderFromWallet = await wallet.getProvider(CHAIN_INFO.chainId) as EthereumProvider
console.log("Ethereum provider:", ethProviderFromWallet)
setEthereum(ethProviderFromWallet)
}
setEthProviderFromWallet()
}
setIsLoading(status === WalletState.Connecting)
}, [status])
// Connect wallet
const connectWallet = async () => {
connect()
}
// Disconnect wallet
const disconnectWallet = () => {
disconnect()
setBalance("0")
setError("")
}
// Get balance
const getBalance = async () => {
if (!ethereum) return
try {
console.log('ethereum in getBalance:', ethereum)
// Use EIP-1193 provider directly to fetch balance
const addr = account
if (!addr) throw new Error('No connected account')
const hexBalance = await (ethereum as any).request({
method: 'eth_getBalance',
params: [addr, 'latest']
}) as string
const wei = BigInt(hexBalance)
setBalance(formatEther(wei))
} catch (err: any) {
console.error("Failed to get balance:", err)
setError(err.message || "Failed to get balance")
}
}
// Refresh balance
const refreshBalance = async () => {
console.log('account in refreshBalance:', account)
if (account) {
await getBalance()
}
}
// Send transaction
const sendTransaction = async () => {
setIsLoading(true)
setError("")
setTxLink("") // ← clear old link
try {
if (!recipient || amount <= 0) {
throw new Error("Please enter recipient address and amount")
}
if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
throw new Error("Invalid Ethereum address")
}
const signer = new SignerFromBrowser(ethereum!)
const tx = { to: recipient, value: parseEther(amount) }
const transaction = await signer.send(tx)
// Wait for confirmation
await transaction.wait()
setTxLink(`${CHAIN_INFO.blockExplorerUrls[0]}/tx/${transaction.transactionHash}`) // ← set explorer link
// Update balance
await getBalance()
// Clear form
setRecipient("")
setAmount(0)
} catch (err: any) {
setError(err.message || "Transaction failed")
} finally {
setIsLoading(false)
}
}
// Listen for account changes
useEffect(() => {
if (account) {
getBalance()
return
}
setBalance("0")
}, [account, ethereum])
return (
<main className="container mx-auto py-10 px-4">
<h1 className="text-3xl font-bold text-center mb-8">Ethereum Demo</h1>
<Box className={`grid gap-6 ${status === WalletState.Connected ? "md:grid-cols-2" : ""}`}>
<Card className='border border-1 p-5 rounded-md'>
<CardHeader className='mb-4'>
<CardTitle className='font-bold text-2xl'>Wallet Connection</CardTitle>
<CardDescription className='text-gray-500'>Connect your Ethereum wallet to view balance</CardDescription>
</CardHeader>
<CardContent>
{status !== WalletState.Connected ? (
<Button onClick={connectWallet} disabled={isLoading} className="w-full">
{isLoading ? "Connecting..." : "Connect Wallet"}
<Wallet className="ml-2 h-4 w-4" />
</Button>
) : (
<Box className="space-y-4">
<Box className="flex flex-col space-y-1">
<FieldLabel htmlFor="account" label='Wallet Address'>Wallet Address</FieldLabel>
<Box id="account" className="p-2 border rounded-md bg-muted font-mono text-sm break-all">{account}</Box>
</Box>
<Box className="flex flex-col space-y-1">
<Box className="flex items-center">
<Box className="flex-1">
<FieldLabel htmlFor="balance" label="ETH Balance">ETH Balance</FieldLabel>
</Box>
<Button size="sm" className="mr-2">
<a href="https://cloud.google.com/application/web3/faucet/ethereum/sepolia"
target="_blank"
>Faucet</a>
</Button>
<Button onClick={refreshBalance} disabled={isLoading} size="sm">
<RefreshCw className="h-4 w-4" />
</Button>
</Box>
<Box id="balance" className="p-2 border rounded-md bg-muted font-mono text-xl">{balance} ETH</Box>
</Box>
<Button onClick={disconnectWallet} className="w-full">
Disconnect Wallet
</Button>
</Box>
)}
</CardContent>
</Card>
{status === WalletState.Connected && (
<Card className='border border-1 p-5 rounded-md'>
<CardHeader className='mb-4'>
<CardTitle className='font-bold text-2xl'>Send Ethereum</CardTitle>
<CardDescription className='text-gray-500'>Transfer ETH to another address</CardDescription>
</CardHeader>
<CardContent>
<Box className="space-y-4">
<Box className="space-y-2">
<FieldLabel htmlFor="recipient" label="Recipient Address">Recipient Address</FieldLabel>
<TextField
id="recipient"
placeholder="0x..."
value={recipient}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setRecipient(e.target.value)}
disabled={isLoading}
/>
</Box>
<Box className="space-y-2">
<FieldLabel htmlFor="amount" label='Amount (ETH)'>Amount (ETH)</FieldLabel>
<NumberField
id="amount"
placeholder="0.01"
value={amount}
onChange={(value: number) => setAmount(value)}
isDisabled={isLoading}
/>
</Box>
</Box>
</CardContent>
<CardFooter className="mt-4">
<Button className="w-full" onClick={sendTransaction} disabled={isLoading || !recipient || amount <= 0}>
{isLoading ? "Processing..." : "Send Transaction"}
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</CardFooter>
</Card>
)}
</Box>
{error && (
<Callout title="Error" className="mt-6" intent="error">
<Box as="span" className="h-4 w-4 inline-block mr-2"><AlertCircle /></Box>
{error}
</Callout>
)}
{txLink && ( // ← success message
<Callout title="Success" className="mt-6" intent="success">
Transaction sent.{" "}
<a
href={txLink}
target="_blank"
rel="noopener noreferrer"
className="underline"
>
View on Explorer
</a>
</Callout>
)}
</main>
)
}
// Minimal helpers for ETH denominations (18 decimals)
const WEI_PER_ETHER = 10n ** 18n
function parseEther(value: number | string): bigint {
const str = typeof value === 'number' ? value.toString() : value
if (!str.includes('.')) return BigInt(str) * WEI_PER_ETHER
const [whole, fracRaw] = str.split('.')
const frac = (fracRaw || '').slice(0, 18).padEnd(18, '0')
return BigInt(whole || '0') * WEI_PER_ETHER + BigInt(frac || '0')
}
function formatEther(wei: bigint): string {
const negative = wei < 0n
const n = negative ? -wei : wei
const whole = n / WEI_PER_ETHER
const frac = n % WEI_PER_ETHER
const fracStr = frac.toString().padStart(18, '0').replace(/0+$/, '')
return `${negative ? '-' : ''}${whole.toString()}${fracStr ? '.' + fracStr : ''}`
}