Skip to content

Commit d2e05b9

Browse files
authored
Merge pull request #37 from hyperweb-io/ethereum
Ethereum demo
2 parents 9bea558 + 8ca8a46 commit d2e05b9

24 files changed

+10433
-18
lines changed

examples/ethereum/.gitignore

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# next.js
7+
/.next/
8+
/out/
9+
10+
# production
11+
/build
12+
13+
# debug
14+
npm-debug.log*
15+
yarn-debug.log*
16+
yarn-error.log*
17+
.pnpm-debug.log*
18+
19+
# env files
20+
.env*
21+
22+
# typescript
23+
*.tsbuildinfo
24+
next-env.d.ts

examples/ethereum/app/globals.css

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
body {
6+
font-family: Arial, Helvetica, sans-serif;
7+
}
8+
9+
@layer utilities {
10+
.text-balance {
11+
text-wrap: balance;
12+
}
13+
}
14+
15+
@layer base {
16+
:root {
17+
--background: 0 0% 100%;
18+
--foreground: 0 0% 3.9%;
19+
--card: 0 0% 100%;
20+
--card-foreground: 0 0% 3.9%;
21+
--popover: 0 0% 100%;
22+
--popover-foreground: 0 0% 3.9%;
23+
--primary: 0 0% 9%;
24+
--primary-foreground: 0 0% 98%;
25+
--secondary: 0 0% 96.1%;
26+
--secondary-foreground: 0 0% 9%;
27+
--muted: 0 0% 96.1%;
28+
--muted-foreground: 0 0% 45.1%;
29+
--accent: 0 0% 96.1%;
30+
--accent-foreground: 0 0% 9%;
31+
--destructive: 0 84.2% 60.2%;
32+
--destructive-foreground: 0 0% 98%;
33+
--border: 0 0% 89.8%;
34+
--input: 0 0% 89.8%;
35+
--ring: 0 0% 3.9%;
36+
--chart-1: 12 76% 61%;
37+
--chart-2: 173 58% 39%;
38+
--chart-3: 197 37% 24%;
39+
--chart-4: 43 74% 66%;
40+
--chart-5: 27 87% 67%;
41+
--radius: 0.5rem;
42+
--sidebar-background: 0 0% 98%;
43+
--sidebar-foreground: 240 5.3% 26.1%;
44+
--sidebar-primary: 240 5.9% 10%;
45+
--sidebar-primary-foreground: 0 0% 98%;
46+
--sidebar-accent: 240 4.8% 95.9%;
47+
--sidebar-accent-foreground: 240 5.9% 10%;
48+
--sidebar-border: 220 13% 91%;
49+
--sidebar-ring: 217.2 91.2% 59.8%;
50+
}
51+
.dark {
52+
--background: 0 0% 3.9%;
53+
--foreground: 0 0% 98%;
54+
--card: 0 0% 3.9%;
55+
--card-foreground: 0 0% 98%;
56+
--popover: 0 0% 3.9%;
57+
--popover-foreground: 0 0% 98%;
58+
--primary: 0 0% 98%;
59+
--primary-foreground: 0 0% 9%;
60+
--secondary: 0 0% 14.9%;
61+
--secondary-foreground: 0 0% 98%;
62+
--muted: 0 0% 14.9%;
63+
--muted-foreground: 0 0% 63.9%;
64+
--accent: 0 0% 14.9%;
65+
--accent-foreground: 0 0% 98%;
66+
--destructive: 0 62.8% 30.6%;
67+
--destructive-foreground: 0 0% 98%;
68+
--border: 0 0% 14.9%;
69+
--input: 0 0% 14.9%;
70+
--ring: 0 0% 83.1%;
71+
--chart-1: 220 70% 50%;
72+
--chart-2: 160 60% 45%;
73+
--chart-3: 30 80% 55%;
74+
--chart-4: 280 65% 60%;
75+
--chart-5: 340 75% 55%;
76+
--sidebar-background: 240 5.9% 10%;
77+
--sidebar-foreground: 240 4.8% 95.9%;
78+
--sidebar-primary: 224.3 76.3% 48%;
79+
--sidebar-primary-foreground: 0 0% 100%;
80+
--sidebar-accent: 240 3.7% 15.9%;
81+
--sidebar-accent-foreground: 240 4.8% 95.9%;
82+
--sidebar-border: 240 3.7% 15.9%;
83+
--sidebar-ring: 217.2 91.2% 59.8%;
84+
}
85+
}
86+
87+
@layer base {
88+
* {
89+
@apply border-border;
90+
}
91+
body {
92+
@apply bg-background text-foreground;
93+
}
94+
}

examples/ethereum/app/layout.tsx

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { Metadata } from 'next'
2+
import "@interchain-ui/react/styles";
3+
// import '../styles/globals.css'
4+
import Provider from './provider'
5+
6+
export const metadata: Metadata = {
7+
title: 'Ethereum Demo - InterchainJS',
8+
description: 'Created with InterchainJS, Interchain-Kit',
9+
}
10+
11+
export default function RootLayout({
12+
children,
13+
}: Readonly<{
14+
children: React.ReactNode
15+
}>) {
16+
return (
17+
<html lang="en">
18+
<body>
19+
<Provider>
20+
{children}
21+
</Provider>
22+
</body>
23+
</html>
24+
)
25+
}

examples/ethereum/app/page.tsx

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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

Comments
 (0)