1- import { useState , useEffect , useMemo } from 'react' ;
1+ import { useState , useEffect , useMemo , useRef } from 'react' ;
22import { Button } from '@/components/ui/button' ;
33import { Card , CardContent , CardDescription , CardHeader , CardTitle } from '@/components/ui/card' ;
44import { Label } from '@/components/ui/label' ;
55import { Input } from '@/components/ui/input' ;
66import { Alert , AlertDescription } from '@/components/ui/alert' ;
7- import { Send , Copy , CheckCircle , Loader2 , Zap , Code , Code2 } from 'lucide-react' ;
7+ import { Send , Copy , CheckCircle , Loader2 , Zap , Code , Code2 , XCircle } from 'lucide-react' ;
88import { toast } from 'sonner' ;
99import { useWallet } from '@provablehq/aleo-wallet-adaptor-react' ;
10- import { Network } from '@provablehq/aleo-types' ;
10+ import { Network , TransactionStatus } from '@provablehq/aleo-types' ;
1111import { HookCodeModal } from '../HookCodeModal' ;
1212import { ProgramAutocomplete } from '../ProgramAutocomplete' ;
1313import { FunctionSelector } from '../FunctionSelector' ;
@@ -18,14 +18,23 @@ import { functionNameAtom, programAtom, useDynamicInputsAtom } from '@/lib/store
1818import { useAtom } from 'jotai' ;
1919
2020export function ExecuteTransaction ( ) {
21- const { connected, executeTransaction, network } = useWallet ( ) ;
21+ const {
22+ connected,
23+ executeTransaction,
24+ transactionStatus : getTransactionStatus ,
25+ network,
26+ } = useWallet ( ) ;
2227 const [ program , setProgram ] = useAtom ( programAtom ) ;
2328 const [ functionName , setFunctionName ] = useAtom ( functionNameAtom ) ;
2429 const [ inputs , setInputs ] = useState ( '' ) ;
2530 const [ dynamicInputValues , setDynamicInputValues ] = useState < string [ ] > ( [ ] ) ;
2631 const [ fee , setFee ] = useState ( '100000' ) ;
27- const [ transactionHash , setTransactionHash ] = useState < string | null > ( null ) ;
32+ const [ onchainTransactionId , setOnchainTransactionId ] = useState < string | null > ( null ) ;
2833 const [ isExecutingTransaction , setIsExecutingTransaction ] = useState ( false ) ;
34+ const [ isPollingStatus , setIsPollingStatus ] = useState ( false ) ;
35+ const [ transactionStatus , setTransactionStatus ] = useState < string | null > ( null ) ;
36+ const [ transactionError , setTransactionError ] = useState < string | null > ( null ) ;
37+ const pollingIntervalRef = useRef < NodeJS . Timeout | null > ( null ) ;
2938 const [ isCodeModalOpen , setIsCodeModalOpen ] = useState ( false ) ;
3039 const [ isProgramCodeModalOpen , setIsProgramCodeModalOpen ] = useState ( false ) ;
3140 const [ programCode , setProgramCode ] = useState < string > ( '' ) ;
@@ -54,6 +63,15 @@ export function ExecuteTransaction() {
5463 return functions . find ( f => f . name === functionName ) ;
5564 } , [ functions , functionName ] ) ;
5665
66+ useEffect ( ( ) => {
67+ if ( ! connected ) {
68+ setTransactionStatus ( null ) ;
69+ setOnchainTransactionId ( null ) ;
70+ setTransactionError ( null ) ;
71+ setIsPollingStatus ( false ) ;
72+ }
73+ } , [ connected ] ) ;
74+
5775 useEffect ( ( ) => {
5876 if ( functionNames . length > 0 && isLoading ) {
5977 setIsLoading ( false ) ;
@@ -115,12 +133,66 @@ export function ExecuteTransaction() {
115133 }
116134 } , [ programIsError ] ) ;
117135
136+ // Cleanup polling interval on unmount
137+ useEffect ( ( ) => {
138+ return ( ) => {
139+ if ( pollingIntervalRef . current ) {
140+ clearInterval ( pollingIntervalRef . current ) ;
141+ }
142+ } ;
143+ } , [ ] ) ;
144+
145+ // Function to poll transaction status
146+ const pollTransactionStatus = async ( tempTransactionId : string ) => {
147+ function clear ( ) {
148+ if ( pollingIntervalRef . current ) {
149+ clearInterval ( pollingIntervalRef . current ) ;
150+ pollingIntervalRef . current = null ;
151+ }
152+ }
153+ try {
154+ const statusResponse = await getTransactionStatus ( tempTransactionId ) ;
155+ setTransactionStatus ( statusResponse . status ) ;
156+ if ( statusResponse . transactionId ) {
157+ // Transaction is now onchain, we have the final transaction ID
158+ setOnchainTransactionId ( statusResponse . transactionId ) ;
159+ }
160+
161+ if ( statusResponse . status . toLowerCase ( ) === TransactionStatus . ACCEPTED . toLowerCase ( ) ) {
162+ setIsPollingStatus ( false ) ;
163+ clear ( ) ;
164+ toast . success ( 'Transaction ' + statusResponse . status ) ;
165+ } else if (
166+ statusResponse . status . toLowerCase ( ) === TransactionStatus . FAILED . toLowerCase ( ) ||
167+ statusResponse . status . toLowerCase ( ) === TransactionStatus . REJECTED . toLowerCase ( )
168+ ) {
169+ // Transaction failed
170+ setIsPollingStatus ( false ) ;
171+ if ( statusResponse . error ) {
172+ setTransactionError ( statusResponse . error ) ;
173+ }
174+ clear ( ) ;
175+ toast . error ( 'Transaction ' + statusResponse . status ) ;
176+ }
177+ } catch ( error ) {
178+ console . error ( 'Error polling transaction status, will stop polling. Error:' , error ) ;
179+ toast . error ( 'Error polling transaction status. Check console for details.' ) ;
180+ setTransactionError ( 'Error polling transaction status' ) ;
181+ setIsPollingStatus ( false ) ;
182+ setTransactionStatus ( TransactionStatus . FAILED ) ;
183+ clear ( ) ;
184+ }
185+ } ;
186+
118187 const handleExecuteTransaction = async ( ) => {
119188 if ( ! program . trim ( ) || ! functionName . trim ( ) || ! fee . trim ( ) ) {
120189 toast . error ( 'Please enter program, function, and fee' ) ;
121190 return ;
122191 }
123192 setIsExecutingTransaction ( true ) ;
193+ setOnchainTransactionId ( null ) ;
194+ setTransactionStatus ( null ) ;
195+ setTransactionError ( null ) ;
124196 try {
125197 let inputArray : string [ ] ;
126198
@@ -138,8 +210,21 @@ export function ExecuteTransaction() {
138210 inputs : inputArray ,
139211 fee : Number ( fee ) ,
140212 } ) ;
141- setTransactionHash ( tx ?. id ?? null ) ;
142- toast . success ( 'Transaction submitted successfully' ) ;
213+
214+ if ( tx ?. transactionId ) {
215+ toast . success ( 'Transaction submitted successfully' ) ;
216+ setIsPollingStatus ( true ) ;
217+
218+ // Start polling for transaction status every 1 second
219+ pollingIntervalRef . current = setInterval ( ( ) => {
220+ pollTransactionStatus ( tx . transactionId ) ;
221+ } , 1000 ) ;
222+
223+ // Initial status check
224+ pollTransactionStatus ( tx . transactionId ) ;
225+ } else {
226+ toast . error ( 'Failed to get transaction ID' ) ;
227+ }
143228 } catch ( error ) {
144229 console . error ( error ) ;
145230 toast . error ( 'Failed to execute transaction. Check console for details.' ) ;
@@ -343,6 +428,7 @@ export function ExecuteTransaction() {
343428 disabled = {
344429 ! connected ||
345430 isExecutingTransaction ||
431+ isPollingStatus ||
346432 ! program . trim ( ) ||
347433 ! functionName . trim ( ) ||
348434 ! fee . trim ( )
@@ -354,6 +440,11 @@ export function ExecuteTransaction() {
354440 < Loader2 className = "mr-2 h-4 w-4 animate-spin" />
355441 Executing Transaction...
356442 </ >
443+ ) : isPollingStatus ? (
444+ < >
445+ < Loader2 className = "mr-2 h-4 w-4 animate-spin" />
446+ Waiting for Confirmation...
447+ </ >
357448 ) : (
358449 < >
359450 < Zap className = "mr-2 h-4 w-4" />
@@ -362,36 +453,54 @@ export function ExecuteTransaction() {
362453 ) }
363454 </ Button >
364455
365- { transactionHash && (
456+ { ( transactionStatus || onchainTransactionId ) && (
366457 < Alert >
367- < CheckCircle className = "h-4 w-4 text-green-500 dark:text-green-400" />
458+ { transactionStatus ?. toLowerCase ( ) === TransactionStatus . ACCEPTED . toLowerCase ( ) ? (
459+ < CheckCircle className = "h-4 w-4 " />
460+ ) : transactionStatus ?. toLowerCase ( ) === TransactionStatus . REJECTED . toLowerCase ( ) ||
461+ transactionStatus ?. toLowerCase ( ) === TransactionStatus . FAILED . toLowerCase ( ) ? (
462+ < XCircle className = "h-4 w-4" />
463+ ) : (
464+ < Loader2 className = "h-4 w-4 animate-spin" />
465+ ) }
368466 < AlertDescription >
369467 < div className = "space-y-2" >
370- < p className = "font-medium" > Transaction Executed Successfully!</ p >
371- < div className = "flex items-center justify-between bg-muted p-2 rounded text-xs font-mono break-all border" >
372- < span className = "truncate" > Tx Hash: { transactionHash } </ span >
373- < Button
374- variant = "ghost"
375- size = "sm"
376- onClick = { ( ) => copyToClipboard ( transactionHash ) }
377- className = "transition-all duration-200"
378- >
379- < Copy className = "h-4 w-4" />
380- </ Button >
381- </ div >
382- < Button
383- variant = "outline"
384- size = "sm"
385- onClick = { ( ) => {
386- window . open (
387- `https://${ network === Network . TESTNET3 ? 'testnet.' : network === Network . CANARY ? 'canary.' : '' } explorer.provable.com/transaction/${ transactionHash } ` ,
388- '_blank' ,
389- ) ;
390- } }
391- className = "transition-all duration-200"
392- >
393- See on the explorer
394- </ Button >
468+ < p className = "font-medium" >
469+ Transaction status:{ ' ' }
470+ < span className = "font-bold capitalize" > { transactionStatus || 'Pending' } </ span >
471+ </ p >
472+
473+ { onchainTransactionId ? (
474+ < >
475+ < div className = "flex items-center justify-between bg-muted p-2 rounded text-xs font-mono break-all border" >
476+ < span className = "truncate" > Transaction Id: { onchainTransactionId } </ span >
477+ < Button
478+ variant = "ghost"
479+ size = "sm"
480+ onClick = { ( ) => copyToClipboard ( onchainTransactionId ) }
481+ className = "transition-all duration-200"
482+ >
483+ < Copy className = "h-4 w-4" />
484+ </ Button >
485+ </ div >
486+ < Button
487+ variant = "outline"
488+ size = "sm"
489+ onClick = { ( ) => {
490+ window . open (
491+ `https://${ network === Network . TESTNET3 ? 'testnet.' : network === Network . CANARY ? 'canary.' : '' } explorer.provable.com/transaction/${ onchainTransactionId } ` ,
492+ '_blank' ,
493+ ) ;
494+ } }
495+ className = "transition-all duration-200"
496+ >
497+ See on the explorer
498+ </ Button >
499+ </ >
500+ ) : null }
501+ { transactionError && (
502+ < div className = "text-sm text-destructive" > Error: { transactionError } </ div >
503+ ) }
395504 </ div >
396505 </ AlertDescription >
397506 </ Alert >
0 commit comments