1- import { useState , useEffect } from 'react' ;
1+ import { useEffect } from 'react' ;
22import {
33 Box ,
44 Button ,
55 Container ,
6- FormControl ,
7- FormLabel ,
8- Input ,
9- Text ,
106 VStack ,
11- useToast ,
12- IconButton ,
137 useColorMode ,
14- Link ,
8+ IconButton ,
159 Heading ,
1610 Card ,
1711 CardBody ,
18- HStack ,
1912} from '@chakra-ui/react' ;
20- import { SunIcon , MoonIcon , RepeatIcon } from '@chakra-ui/icons' ;
13+ import { SunIcon , MoonIcon } from '@chakra-ui/icons' ;
2114import { useForm } from 'react-hook-form' ;
2215import { zodResolver } from '@hookform/resolvers/zod' ;
23- import { useQuery , useMutation } from '@tanstack/react-query' ;
16+ import WalletDetails from './components/WalletDetails' ;
17+ import TransferForm from './components/TransferForm' ;
2418import { transferFormSchema , type TransferFormData } from './utils/validation' ;
25- import {
26- DENOM ,
27- DENOM_DISPLAY ,
28- DECIMAL ,
29- RPC_ENDPOINT ,
30- } from './utils/constants' ;
31- import { chain as cosmoshubChain , assetList as cosmoshubAssetList } from '@chain-registry/v2/mainnet/cosmoshub'
32- import { WalletManager } from '@interchain-kit/core'
33- import { keplrWallet } from '@interchain-kit/keplr-extension'
34- import { createSend } from "interchainjs/cosmos/bank/v1beta1/tx.rpc.func" ;
35- import { createGetBalance } from "interchainjs/cosmos/bank/v1beta1/query.rpc.func" ;
19+ import { useWalletManager } from './hooks/useWalletManager' ;
20+ import { useBalance } from './hooks/useBalance' ;
21+ import { useTransfer } from './hooks/useTransfer' ;
3622
3723function App ( ) {
38- const [ address , setAddress ] = useState ( '' ) ;
39- const [ walletManager , setWalletManager ] = useState < WalletManager | null > ( null )
4024 const { colorMode, toggleColorMode } = useColorMode ( ) ;
41- const toast = useToast ( ) ;
25+ const { walletManager, address, connectWallet } = useWalletManager ( ) ;
26+ const { balance, refetchBalance } = useBalance ( address , walletManager ) ;
27+ const transferMutation = useTransfer ( address , walletManager , refetchBalance ) ;
4228
43- const {
44- register,
45- handleSubmit,
46- formState : { errors } ,
47- reset,
48- } = useForm < TransferFormData > ( {
29+ const { register, handleSubmit, formState : { errors } , reset } = useForm < TransferFormData > ( {
4930 resolver : zodResolver ( transferFormSchema ) ,
50- defaultValues : {
51- amount : "0.000001" ,
52- } ,
31+ defaultValues : { amount : '0.000001' } ,
5332 } ) ;
5433
55- useEffect ( ( ) => {
56- ( async ( ) => {
57- const walletManager = await WalletManager . create (
58- [ cosmoshubChain ] ,
59- [ cosmoshubAssetList ] ,
60- [ keplrWallet ] ,
61- { } ,
62- {
63- endpoints : {
64- cosmoshub : {
65- rpc : [ RPC_ENDPOINT ] ,
66- } ,
67- }
68- }
69- )
70- setWalletManager ( walletManager )
71- } ) ( )
72- } , [ ] )
73-
74- const { data : balance , refetch : refetchBalance } = useQuery ( {
75- queryKey : [ 'balance' , address ] ,
76- queryFn : async ( ) => {
77- if ( ! address ) return null ;
78- try {
79- const balanceQuery = createGetBalance ( RPC_ENDPOINT ) ;
80- const { balance : atomBalance } = await balanceQuery ( {
81- address,
82- denom : DENOM ,
83- } ) ;
84- return Number ( atomBalance ?. amount || 0 ) / Math . pow ( 10 , DECIMAL ) ;
85- } catch ( error ) {
86- console . error ( 'Error fetching balance:' , error ) ;
87- toast ( {
88- title : 'Error fetching balance' ,
89- description : ( error as Error ) . message ,
90- status : 'error' ,
91- duration : 5000 ,
92- isClosable : true ,
93- } ) ;
94- return null ;
95- }
96- } ,
97- enabled : ! ! address && ! ! walletManager ,
98- staleTime : 10000 , // Consider data fresh for 10 seconds
99- refetchInterval : 30000 , // Refresh every 30 seconds
100- } ) ;
101-
102- const transferMutation = useMutation ( {
103- mutationFn : async ( data : TransferFormData ) => {
104- if ( ! window . keplr || ! address ) throw new Error ( 'Keplr not connected' ) ;
105- const amount = Math . floor ( Number ( data . amount ) * Math . pow ( 10 , DECIMAL ) ) ;
106- const fee = {
107- amount : [ { denom : DENOM , amount : "5000" } ] , // adjust fee amount as needed
108- gas : "200000" // adjust gas limit as needed
109- } ;
110-
111- const message = {
112- fromAddress : address ,
113- toAddress : data . recipient ,
114- amount : [
115- {
116- denom : DENOM ,
117- amount : amount . toString ( )
118- } ,
119- ] ,
120- }
121- const signingClient = await walletManager ?. getSigningClient ( keplrWallet . info ?. name as string , cosmoshubChain . chainName )
122- const txSend = createSend ( signingClient ) ;
123- const res = await txSend (
124- address ,
125- message ,
126- fee ,
127- ''
128- )
129- await new Promise ( resolve => setTimeout ( resolve , 6000 ) ) ;
130- return ( res as any ) . hash
131- } ,
132- onSuccess : ( txHash ) => {
133- toast ( {
134- title : 'Transfer successful' ,
135- description : (
136- < Link
137- href = { `https://www.mintscan.io/cosmos/txs/${ txHash } ` }
138- isExternal
139- color = "white"
140- >
141- < u > View transaction details</ u >
142- </ Link >
143- ) ,
144- status : 'success' ,
145- duration : null ,
146- isClosable : true ,
147- } ) ;
148- reset ( ) ;
149- refetchBalance ( ) ;
150- } ,
151- onError : ( error : Error ) => {
152- console . error ( 'Error transferring funds:' , error ) ;
153- toast ( {
154- title : 'Transfer failed' ,
155- description : error . message ,
156- status : 'error' ,
157- duration : 5000 ,
158- isClosable : true ,
159- } ) ;
160- } ,
161- } ) ;
162-
163- const connectWallet = async ( ) => {
164- try {
165- if ( ! window . keplr ) {
166- throw new Error ( 'Please install Keplr extension' ) ;
167- }
168- await walletManager ?. connect ( keplrWallet . info ?. name as string , cosmoshubChain . chainName )
169- const account = await walletManager ?. getAccount ( keplrWallet . info ?. name as string , cosmoshubChain . chainName )
170- setAddress ( account ?. address as string )
171- } catch ( error ) {
172- console . error ( 'Error connecting wallet:' , error ) ;
173- toast ( {
174- title : 'Connection failed' ,
175- description : ( error as Error ) . message ,
176- status : 'error' ,
177- duration : 5000 ,
178- isClosable : true ,
179- } ) ;
180- }
181- } ;
182-
18334 const onSubmit = ( data : TransferFormData ) => {
18435 if ( ! balance || Number ( data . amount ) > balance ) {
185- toast ( {
186- title : 'Insufficient balance' ,
187- status : 'error' ,
188- duration : 5000 ,
189- isClosable : true ,
190- } ) ;
19136 return ;
19237 }
19338 transferMutation . mutate ( data ) ;
39+ reset ( ) ;
19440 } ;
19541
19642 useEffect ( ( ) => {
197- if ( ! walletManager ) return
198- connectWallet ( ) ;
199- } , [
200- walletManager
201- ] ) ;
202-
203- const handleRefreshBalance = ( ) => {
204- refetchBalance ( ) ;
205- toast ( {
206- title : 'Refreshing balance...' ,
207- status : 'info' ,
208- duration : 2000 ,
209- isClosable : true ,
210- } ) ;
211- } ;
43+ if ( walletManager ) {
44+ connectWallet ( ) ;
45+ }
46+ } , [ walletManager , connectWallet ] ) ;
21247
21348 return (
21449 < Container maxW = "container.sm" py = { 8 } >
@@ -220,57 +55,26 @@ function App() {
22055 onClick = { toggleColorMode }
22156 />
22257 </ Box >
223-
22458 < Card >
22559 < CardBody >
22660 < VStack spacing = { 4 } align = "stretch" >
22761 < Heading size = "md" > Cosmos Wallet</ Heading >
228-
22962 { ! address ? (
23063 < Button onClick = { connectWallet } > Connect Keplr</ Button >
23164 ) : (
23265 < >
233- < Text > Address: { address } </ Text >
234- < HStack >
235- < Text >
236- Balance: { balance ?? '0' } { DENOM_DISPLAY }
237- </ Text >
238- < IconButton
239- aria-label = "Refresh balance"
240- icon = { < RepeatIcon /> }
241- size = "sm"
242- onClick = { handleRefreshBalance }
243- />
244- </ HStack >
245-
246- < form onSubmit = { handleSubmit ( onSubmit ) } >
247- < VStack spacing = { 4 } >
248- < FormControl isInvalid = { ! ! errors . recipient } >
249- < FormLabel > Recipient Address</ FormLabel >
250- < Input { ...register ( 'recipient' ) } />
251- { errors . recipient && (
252- < Text color = "red.500" > { errors . recipient . message } </ Text >
253- ) }
254- </ FormControl >
255-
256- < FormControl isInvalid = { ! ! errors . amount } >
257- < FormLabel > Amount ({ DENOM_DISPLAY } )</ FormLabel >
258- < Input { ...register ( 'amount' ) } type = "number" step = "0.000001" />
259- { errors . amount && (
260- < Text color = "red.500" > { errors . amount . message } </ Text >
261- ) }
262- </ FormControl >
263-
264- < Button
265- type = "submit"
266- colorScheme = "blue"
267- isLoading = { transferMutation . isPending }
268- width = "100%"
269- >
270- Transfer
271- </ Button >
272- </ VStack >
273- </ form >
66+ < WalletDetails
67+ address = { address }
68+ balance = { balance ?? '0' }
69+ onRefresh = { refetchBalance }
70+ />
71+ < TransferForm
72+ register = { register }
73+ errors = { errors }
74+ handleSubmit = { handleSubmit }
75+ onSubmit = { onSubmit }
76+ isLoading = { transferMutation . isMutating }
77+ />
27478 </ >
27579 ) }
27680 </ VStack >
0 commit comments