|  | 
|  | 1 | +"use client" | 
|  | 2 | + | 
|  | 3 | +import { Box, Button, TextField, NumberField, FieldLabel, Callout } from "@interchain-ui/react" | 
|  | 4 | +import React, { useState, useEffect } from "react" | 
|  | 5 | +import { Wallet, ArrowRight, RefreshCw, AlertCircle } from "lucide-react" | 
|  | 6 | +import { SignerFromBrowser } from "@interchainjs/ethereum/signers/SignerFromBrowser" | 
|  | 7 | +import { MetaMaskInpageProvider } from "@metamask/providers"; | 
|  | 8 | +import BigNumber from "bignumber.js"; | 
|  | 9 | +import { useChain } from '@interchain-kit/react' | 
|  | 10 | +import { WalletState } from "@interchain-kit/core" | 
|  | 11 | + | 
|  | 12 | +type EthereumProvider = MetaMaskInpageProvider | 
|  | 13 | + | 
|  | 14 | +// Alias Card components | 
|  | 15 | +const Card = Box | 
|  | 16 | +const CardHeader = Box | 
|  | 17 | +const CardContent = Box | 
|  | 18 | +const CardFooter = Box | 
|  | 19 | +const CardTitle = Box | 
|  | 20 | +const CardDescription = Box | 
|  | 21 | + | 
|  | 22 | +export default function WalletPage() { | 
|  | 23 | +  const [balance, setBalance] = useState<string>("0") | 
|  | 24 | +  const [isLoading, setIsLoading] = useState(false) | 
|  | 25 | +  const [recipient, setRecipient] = useState("") | 
|  | 26 | +  const [amount, setAmount] = useState<number>(0) | 
|  | 27 | +  const [error, setError] = useState("") | 
|  | 28 | +  const [ethereum, setEthereum] = useState<EthereumProvider>() | 
|  | 29 | + | 
|  | 30 | +  const { wallet, status, connect, address: account, disconnect } = useChain('ethereum') | 
|  | 31 | + | 
|  | 32 | +  useEffect(() => { | 
|  | 33 | +    console.log('status from useChain:', status) | 
|  | 34 | +    if (status === WalletState.Connected) { | 
|  | 35 | +      const setEthProviderFromWallet = async () => { | 
|  | 36 | +        await new Promise(resolve => setTimeout(resolve, 500)) | 
|  | 37 | +        const ethProviderFromWallet = await wallet.getProvider('1') as EthereumProvider | 
|  | 38 | +        console.log("Ethereum provider:", ethProviderFromWallet) | 
|  | 39 | +        setEthereum(ethProviderFromWallet) | 
|  | 40 | +      } | 
|  | 41 | +      setEthProviderFromWallet() | 
|  | 42 | +    } | 
|  | 43 | +    setIsLoading(status === WalletState.Connecting) | 
|  | 44 | +  }, [status]) | 
|  | 45 | + | 
|  | 46 | +  // Connect wallet | 
|  | 47 | +  const connectWallet = async () => { | 
|  | 48 | +    connect() | 
|  | 49 | +  } | 
|  | 50 | + | 
|  | 51 | +  // Disconnect wallet | 
|  | 52 | +  const disconnectWallet = () => { | 
|  | 53 | +    disconnect() | 
|  | 54 | +    setBalance("0") | 
|  | 55 | +    setError("") | 
|  | 56 | +  } | 
|  | 57 | + | 
|  | 58 | +  // Get balance | 
|  | 59 | +  const getBalance = async () => { | 
|  | 60 | +    if (!ethereum) return | 
|  | 61 | +    try { | 
|  | 62 | +      console.log('ethereum in getBalance:', ethereum) | 
|  | 63 | +      const wallet = new SignerFromBrowser( | 
|  | 64 | +        ethereum! | 
|  | 65 | +        // window.ethereum as EthereumProvider | 
|  | 66 | +      ) | 
|  | 67 | +      console.log('wallet in getBalance:', wallet) | 
|  | 68 | +      const balance = await wallet.getBalance() | 
|  | 69 | +      console.log('balance in getBalance:', balance) | 
|  | 70 | +      setBalance(new BigNumber(balance.toString()).div(10 ** 18).toString()) | 
|  | 71 | +    } catch (err: any) { | 
|  | 72 | +      console.error("Failed to get balance:", err) | 
|  | 73 | +      setError(err.message || "Failed to get balance") | 
|  | 74 | +    } | 
|  | 75 | +  } | 
|  | 76 | + | 
|  | 77 | +  // Refresh balance | 
|  | 78 | +  const refreshBalance = async () => { | 
|  | 79 | +    console.log('account in refreshBalance:', account) | 
|  | 80 | +    if (account) { | 
|  | 81 | +      await getBalance() | 
|  | 82 | +    } | 
|  | 83 | +  } | 
|  | 84 | + | 
|  | 85 | +  // Send transaction | 
|  | 86 | +  const sendTransaction = async () => { | 
|  | 87 | +    setIsLoading(true) | 
|  | 88 | +    setError("") | 
|  | 89 | + | 
|  | 90 | +    try { | 
|  | 91 | +      if (!recipient || amount <= 0) { | 
|  | 92 | +        throw new Error("Please enter recipient address and amount") | 
|  | 93 | +      } | 
|  | 94 | + | 
|  | 95 | +      if (!/^0x[a-fA-F0-9]{40}$/.test(recipient)) { | 
|  | 96 | +        throw new Error("Invalid Ethereum address") | 
|  | 97 | +      } | 
|  | 98 | + | 
|  | 99 | +      const signer = new SignerFromBrowser(ethereum!) | 
|  | 100 | + | 
|  | 101 | +      // Create transaction | 
|  | 102 | +      const tx = { | 
|  | 103 | +        to: recipient, | 
|  | 104 | +        value: BigInt(new BigNumber(amount).shiftedBy(18).integerValue(BigNumber.ROUND_DOWN).toString()) | 
|  | 105 | +      } | 
|  | 106 | + | 
|  | 107 | +      // Send transaction | 
|  | 108 | +      const transaction = await signer.send(tx) | 
|  | 109 | + | 
|  | 110 | +      // Wait for confirmation | 
|  | 111 | +      await transaction.wait() | 
|  | 112 | + | 
|  | 113 | +      // Update balance | 
|  | 114 | +      await getBalance() | 
|  | 115 | + | 
|  | 116 | +      // Clear form | 
|  | 117 | +      setRecipient("") | 
|  | 118 | +      setAmount(0) | 
|  | 119 | +    } catch (err: any) { | 
|  | 120 | +      setError(err.message || "Transaction failed") | 
|  | 121 | +    } finally { | 
|  | 122 | +      setIsLoading(false) | 
|  | 123 | +    } | 
|  | 124 | +  } | 
|  | 125 | + | 
|  | 126 | +  // Listen for account changes | 
|  | 127 | +  useEffect(() => { | 
|  | 128 | +    if (account) { | 
|  | 129 | +      getBalance() | 
|  | 130 | +      return | 
|  | 131 | +    } | 
|  | 132 | +    setBalance("0") | 
|  | 133 | +  }, [account, ethereum]) | 
|  | 134 | + | 
|  | 135 | +  return ( | 
|  | 136 | +    <main className="container mx-auto py-10 px-4"> | 
|  | 137 | +      <h1 className="text-3xl font-bold text-center mb-8">Ethereum Wallet</h1> | 
|  | 138 | + | 
|  | 139 | +      <Box className={`grid gap-6 ${status === WalletState.Connected ? "md:grid-cols-2" : ""}`}> | 
|  | 140 | +        <Card className='border border-1 p-5 rounded-md'> | 
|  | 141 | +          <CardHeader className='mb-4'> | 
|  | 142 | +            <CardTitle className='font-bold text-2xl'>Wallet Connection</CardTitle> | 
|  | 143 | +            <CardDescription className='text-gray-500'>Connect your Ethereum wallet to view balance</CardDescription> | 
|  | 144 | +          </CardHeader> | 
|  | 145 | +          <CardContent> | 
|  | 146 | +            {status !== WalletState.Connected ? ( | 
|  | 147 | +              <Button onClick={connectWallet} disabled={isLoading} className="w-full"> | 
|  | 148 | +                {isLoading ? "Connecting..." : "Connect Wallet"} | 
|  | 149 | +                <Wallet className="ml-2 h-4 w-4" /> | 
|  | 150 | +              </Button> | 
|  | 151 | +            ) : ( | 
|  | 152 | +              <Box className="space-y-4"> | 
|  | 153 | +                <Box className="flex flex-col space-y-1"> | 
|  | 154 | +                  <FieldLabel htmlFor="account" label='Wallet Address'>Wallet Address</FieldLabel> | 
|  | 155 | +                  <Box id="account" className="p-2 border rounded-md bg-muted font-mono text-sm break-all">{account}</Box> | 
|  | 156 | +                </Box> | 
|  | 157 | + | 
|  | 158 | +                <Box className="flex flex-col space-y-1"> | 
|  | 159 | +                  <Box className="flex justify-between items-center"> | 
|  | 160 | +                    <FieldLabel htmlFor="balance" label="ETH Balance">ETH Balance</FieldLabel> | 
|  | 161 | +                    <Button onClick={refreshBalance} disabled={isLoading} size="sm"> | 
|  | 162 | +                      <RefreshCw className="h-4 w-4" /> | 
|  | 163 | +                    </Button> | 
|  | 164 | +                  </Box> | 
|  | 165 | +                  <Box id="balance" className="p-2 border rounded-md bg-muted font-mono text-xl">{balance} ETH</Box> | 
|  | 166 | +                </Box> | 
|  | 167 | + | 
|  | 168 | +                <Button onClick={disconnectWallet} className="w-full"> | 
|  | 169 | +                  Disconnect Wallet | 
|  | 170 | +                </Button> | 
|  | 171 | +              </Box> | 
|  | 172 | +            )} | 
|  | 173 | +          </CardContent> | 
|  | 174 | +        </Card> | 
|  | 175 | + | 
|  | 176 | +        {status === WalletState.Connected && ( | 
|  | 177 | +          <Card className='border border-1 p-5 rounded-md'> | 
|  | 178 | +            <CardHeader className='mb-4'> | 
|  | 179 | +              <CardTitle className='font-bold text-2xl'>Send Ethereum</CardTitle> | 
|  | 180 | +              <CardDescription className='text-gray-500'>Transfer ETH to another address</CardDescription> | 
|  | 181 | +            </CardHeader> | 
|  | 182 | +            <CardContent> | 
|  | 183 | +              <Box className="space-y-4"> | 
|  | 184 | +                <Box className="space-y-2"> | 
|  | 185 | +                  <FieldLabel htmlFor="recipient" label="Recipient Address">Recipient Address</FieldLabel> | 
|  | 186 | +                  <TextField | 
|  | 187 | +                    id="recipient" | 
|  | 188 | +                    placeholder="0x..." | 
|  | 189 | +                    value={recipient} | 
|  | 190 | +                    onChange={(e: React.ChangeEvent<HTMLInputElement>) => setRecipient(e.target.value)} | 
|  | 191 | +                    disabled={isLoading} | 
|  | 192 | +                  /> | 
|  | 193 | +                </Box> | 
|  | 194 | + | 
|  | 195 | +                <Box className="space-y-2"> | 
|  | 196 | +                  <FieldLabel htmlFor="amount" label='Amount (ETH)'>Amount (ETH)</FieldLabel> | 
|  | 197 | +                  <NumberField | 
|  | 198 | +                    id="amount" | 
|  | 199 | +                    placeholder="0.01" | 
|  | 200 | +                    value={amount} | 
|  | 201 | +                    onChange={(value: number) => setAmount(value)} | 
|  | 202 | +                    isDisabled={isLoading} | 
|  | 203 | +                  /> | 
|  | 204 | +                </Box> | 
|  | 205 | +              </Box> | 
|  | 206 | +            </CardContent> | 
|  | 207 | +            <CardFooter className="mt-4"> | 
|  | 208 | +              <Button className="w-full" onClick={sendTransaction} disabled={isLoading || !recipient || amount <= 0}> | 
|  | 209 | +                {isLoading ? "Processing..." : "Send Transaction"} | 
|  | 210 | +                <ArrowRight className="ml-2 h-4 w-4" /> | 
|  | 211 | +              </Button> | 
|  | 212 | +            </CardFooter> | 
|  | 213 | +          </Card> | 
|  | 214 | +        )} | 
|  | 215 | +      </Box> | 
|  | 216 | + | 
|  | 217 | +      {error && ( | 
|  | 218 | +        <Callout title="Error" className="mt-6" intent="error"> | 
|  | 219 | +          <Box as="span" className="h-4 w-4 inline-block mr-2"><AlertCircle /></Box> | 
|  | 220 | +          {error} | 
|  | 221 | +        </Callout> | 
|  | 222 | +      )} | 
|  | 223 | +    </main> | 
|  | 224 | +  ) | 
|  | 225 | +} | 
0 commit comments