11"use client" ;
22
3- import { Coins , Eye , Send } from "lucide-react" ;
4- import { Abi , AbiFunction } from "viem" ;
5- import { useAccount } from "wagmi" ;
3+ import { Coins , ExternalLinkIcon , Eye , LoaderIcon , Send } from "lucide-react" ;
4+ import Link from "next/link" ;
5+ import { useParams } from "next/navigation" ;
6+ import { toast } from "sonner" ;
7+ import { Abi , AbiFunction , Address , Hex , decodeEventLog } from "viem" ;
8+ import { useAccount , useConfig } from "wagmi" ;
9+ import { readContract , waitForTransactionReceipt , writeContract } from "wagmi/actions" ;
610import { z } from "zod" ;
711import { useState } from "react" ;
812import { useForm } from "react-hook-form" ;
@@ -12,7 +16,8 @@ import { Button } from "../../../../../../components/ui/Button";
1216import { Form , FormControl , FormField , FormItem , FormLabel , FormMessage } from "../../../../../../components/ui/Form" ;
1317import { Input } from "../../../../../../components/ui/Input" ;
1418import { Separator } from "../../../../../../components/ui/Separator" ;
15- import { useContractMutation } from "./useContractMutation" ;
19+ import { useChain } from "../../../../hooks/useChain" ;
20+ import { blockExplorerTransactionUrl } from "../../../../utils/blockExplorerTransactionUrl" ;
1621
1722export enum FunctionType {
1823 READ ,
@@ -24,6 +29,11 @@ type Props = {
2429 functionAbi : AbiFunction ;
2530} ;
2631
32+ type DecodedEvent = {
33+ eventName : string | undefined ;
34+ args : readonly unknown [ ] | undefined ;
35+ } ;
36+
2737const formSchema = z . object ( {
2838 inputs : z . array ( z . string ( ) ) ,
2939 value : z . string ( ) . optional ( ) ,
@@ -34,10 +44,16 @@ export function FunctionField({ worldAbi, functionAbi }: Props) {
3444 functionAbi . stateMutability === "view" || functionAbi . stateMutability === "pure"
3545 ? FunctionType . READ
3646 : FunctionType . WRITE ;
37- const [ result , setResult ] = useState < string | null > ( null ) ;
3847 const { openConnectModal } = useConnectModal ( ) ;
39- const mutation = useContractMutation ( { worldAbi , functionAbi , operationType } ) ;
48+ const wagmiConfig = useConfig ( ) ;
4049 const account = useAccount ( ) ;
50+ const { worldAddress } = useParams ( ) ;
51+ const { id : chainId } = useChain ( ) ;
52+ const [ isLoading , setIsLoading ] = useState ( false ) ;
53+ const [ result , setResult ] = useState < string > ( ) ;
54+ const [ events , setEvents ] = useState < DecodedEvent [ ] > ( ) ;
55+ const [ txHash , setTxHash ] = useState < Hex > ( ) ;
56+ const txUrl = blockExplorerTransactionUrl ( { hash : txHash , chainId } ) ;
4157
4258 const form = useForm < z . infer < typeof formSchema > > ( {
4359 resolver : zodResolver ( formSchema ) ,
@@ -51,74 +67,142 @@ export function FunctionField({ worldAbi, functionAbi }: Props) {
5167 return openConnectModal ?.( ) ;
5268 }
5369
54- const mutationResult = await mutation . mutateAsync ( {
55- inputs : values . inputs ,
56- value : values . value ,
57- } ) ;
70+ setIsLoading ( true ) ;
71+ let toastId ;
72+ try {
73+ if ( operationType === FunctionType . READ ) {
74+ const result = await readContract ( wagmiConfig , {
75+ abi : worldAbi ,
76+ address : worldAddress as Address ,
77+ functionName : functionAbi . name ,
78+ args : values . inputs ,
79+ chainId,
80+ } ) ;
81+
82+ setResult ( JSON . stringify ( result , null , 2 ) ) ;
83+ } else {
84+ toastId = toast . loading ( "Transaction submitted" ) ;
85+ const txHash = await writeContract ( wagmiConfig , {
86+ abi : worldAbi ,
87+ address : worldAddress as Address ,
88+ functionName : functionAbi . name ,
89+ args : values . inputs ,
90+ ...( values . value && { value : BigInt ( values . value ) } ) ,
91+ chainId,
92+ } ) ;
93+ setTxHash ( txHash ) ;
5894
59- if ( operationType === FunctionType . READ && "result" in mutationResult ) {
60- setResult ( JSON . stringify ( mutationResult . result , null , 2 ) ) ;
95+ const receipt = await waitForTransactionReceipt ( wagmiConfig , { hash : txHash } ) ;
96+ const events = receipt ?. logs . map ( ( log ) => decodeEventLog ( { ...log , abi : worldAbi } ) ) ;
97+ setEvents ( events ) ;
98+
99+ toast . success ( `Transaction successful with hash: ${ txHash } ` , {
100+ id : toastId ,
101+ } ) ;
102+ }
103+ } catch ( error ) {
104+ console . error ( error ) ;
105+ toast . error ( ( error as Error ) . message || "Something went wrong. Please try again." , {
106+ id : toastId ,
107+ } ) ;
108+ } finally {
109+ setIsLoading ( false ) ;
61110 }
62111 }
63112
64113 const inputsLabel = functionAbi ?. inputs . map ( ( input ) => input . type ) . join ( ", " ) ;
65114 return (
66- < Form { ...form } >
67- < form onSubmit = { form . handleSubmit ( onSubmit ) } id = { functionAbi . name } className = "space-y-4 pb-4" >
68- < h3 className = "pt-4 font-semibold" >
69- < span className = "text-orange-500" > { functionAbi ?. name } </ span >
70- < span className = "opacity-50" > { inputsLabel && ` (${ inputsLabel } )` } </ span >
71- < span className = "ml-2 opacity-50" >
72- { functionAbi . stateMutability === "payable" && < Coins className = "mr-2 inline-block h-4 w-4" /> }
73- { ( functionAbi . stateMutability === "view" || functionAbi . stateMutability === "pure" ) && (
74- < Eye className = "mr-2 inline-block h-4 w-4" />
75- ) }
76- { functionAbi . stateMutability === "nonpayable" && < Send className = "mr-2 inline-block h-4 w-4" /> }
77- </ span >
78- </ h3 >
79-
80- { functionAbi ?. inputs . map ( ( input , index ) => (
81- < FormField
82- key = { index }
83- control = { form . control }
84- name = { `inputs.${ index } ` }
85- render = { ( { field } ) => (
86- < FormItem >
87- < FormLabel > { input . name } </ FormLabel >
88- < FormControl >
89- < Input placeholder = { input . type } { ...field } />
90- </ FormControl >
91- < FormMessage />
92- </ FormItem >
93- ) }
94- />
95- ) ) }
96-
97- { functionAbi . stateMutability === "payable" && (
98- < FormField
99- control = { form . control }
100- name = "value"
101- render = { ( { field } ) => (
102- < FormItem >
103- < FormLabel > ETH value</ FormLabel >
104- < FormControl >
105- < Input placeholder = "uint256" { ...field } />
106- </ FormControl >
107- < FormMessage />
108- </ FormItem >
109- ) }
110- />
111- ) }
112-
113- < Button type = "submit" disabled = { mutation . isPending } >
114- { ( functionAbi . stateMutability === "view" || functionAbi . stateMutability === "pure" ) && "Read" }
115- { ( functionAbi . stateMutability === "payable" || functionAbi . stateMutability === "nonpayable" ) && "Write" }
116- </ Button >
117-
118- { result && < pre className = "text-md rounded border p-3 text-sm" > { result } </ pre > }
119- </ form >
120-
121- < Separator />
122- </ Form >
115+ < div className = "pb-6" >
116+ < Form { ...form } >
117+ < form onSubmit = { form . handleSubmit ( onSubmit ) } id = { functionAbi . name } className = "space-y-4" >
118+ < h3 className = "font-semibold" >
119+ < span className = "text-orange-500" > { functionAbi ?. name } </ span >
120+ < span className = "opacity-50" > { inputsLabel && ` (${ inputsLabel } )` } </ span >
121+ < span className = "ml-2 opacity-50" >
122+ { functionAbi . stateMutability === "payable" && < Coins className = "mr-2 inline-block h-4 w-4" /> }
123+ { ( functionAbi . stateMutability === "view" || functionAbi . stateMutability === "pure" ) && (
124+ < Eye className = "mr-2 inline-block h-4 w-4" />
125+ ) }
126+ { functionAbi . stateMutability === "nonpayable" && < Send className = "mr-2 inline-block h-4 w-4" /> }
127+ </ span >
128+ </ h3 >
129+
130+ { functionAbi ?. inputs . map ( ( input , index ) => (
131+ < FormField
132+ key = { index }
133+ control = { form . control }
134+ name = { `inputs.${ index } ` }
135+ render = { ( { field } ) => (
136+ < FormItem >
137+ < FormLabel > { input . name } </ FormLabel >
138+ < FormControl >
139+ < Input placeholder = { input . type } { ...field } />
140+ </ FormControl >
141+ < FormMessage />
142+ </ FormItem >
143+ ) }
144+ />
145+ ) ) }
146+
147+ { functionAbi . stateMutability === "payable" && (
148+ < FormField
149+ control = { form . control }
150+ name = "value"
151+ render = { ( { field } ) => (
152+ < FormItem >
153+ < FormLabel > ETH value</ FormLabel >
154+ < FormControl >
155+ < Input placeholder = "uint256" { ...field } />
156+ </ FormControl >
157+ < FormMessage />
158+ </ FormItem >
159+ ) }
160+ />
161+ ) }
162+
163+ < Button type = "submit" size = "sm" disabled = { isLoading || ! account . isConnected } >
164+ { isLoading && < LoaderIcon className = "-ml-1 mr-2 h-4 w-4 animate-spin" /> }
165+ { operationType === FunctionType . READ ? "Read" : "Write" }
166+ </ Button >
167+ </ form >
168+ </ Form >
169+
170+ { result && < pre className = "text-md mt-4 rounded border p-3 text-sm" > { result } </ pre > }
171+ { events && (
172+ < div className = "mt-4 flex-grow break-all border border-white/20 p-2 pb-3" >
173+ < ul >
174+ { events . map ( ( event , idx ) => (
175+ < li key = { idx } >
176+ { event . eventName && < span className = "text-xs" > { event . eventName } :</ span > }
177+ { event . args && (
178+ < ul className = "list-inside" >
179+ { Object . entries ( event . args ) . map ( ( [ key , value ] ) => (
180+ < li key = { key } className = "mt-1 flex" >
181+ < span className = "text-xs text-white/60" > { key } :</ span > { " " }
182+ < span className = "text-xs" > { String ( value ) } </ span >
183+ </ li >
184+ ) ) }
185+ </ ul >
186+ ) }
187+ { idx < events . length - 1 && < Separator className = "my-4" /> }
188+ </ li >
189+ ) ) }
190+ </ ul >
191+ </ div >
192+ ) }
193+ { txUrl && (
194+ < div className = "mt-3" >
195+ < Link
196+ href = { txUrl }
197+ target = "_blank"
198+ className = "flex items-center text-xs text-muted-foreground hover:underline"
199+ >
200+ < ExternalLinkIcon className = "mr-2 h-3 w-3" /> View on block explorer
201+ </ Link >
202+ </ div >
203+ ) }
204+
205+ < Separator className = "mt-6" />
206+ </ div >
123207 ) ;
124208}
0 commit comments