Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 69 additions & 51 deletions hooks/useTransaction.hook.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"use client";

import { useCallback, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { useWallet } from '@/hooks/useWallet.hook';
import { toast } from '@/hooks/use-toast';
import {
pollForConfirmation,
submitTransaction,
} from '@/lib/stellar/transaction';
import {
normalizeContractError,
normalizeFromFailureType,
} from '@/lib/stellar/contract-error-normalizer';

export type TransactionStatus =
| 'idle'
Expand Down Expand Up @@ -76,7 +80,7 @@ export const useTransaction = (): UseTransactionResult => {
setTransactionError(null);
setFailureType(null);
setTransactionHash(null);

lastBuildXdrRef.current = buildXdr;
lastSignedXdrRef.current = null;
lastSubmittedHashRef.current = null;
Expand All @@ -101,21 +105,20 @@ export const useTransaction = (): UseTransactionResult => {
const signResult = await signTransaction(xdr);

if (!signResult.success) {
const error = signResult.error ?? 'Transaction signing failed';
const userRejected = isUserRejectedError(error);
const rawError = signResult.error ?? 'Transaction signing failed';
const userRejected = isUserRejectedError(rawError);
const ft: TransactionFailureType = userRejected ? 'userRejected' : 'signFailed';
// Normalize for toast display; keep raw error in state for diagnostics
const normalized = normalizeFromFailureType(ft, rawError);
setStatus('failed');
setTransactionError(error);
setFailureType(userRejected ? 'userRejected' : 'signFailed');
setTransactionError(rawError);
setFailureType(ft);
toast({
title: userRejected ? 'Transaction rejected' : 'Signing failed',
description: error,
title: normalized.title,
description: normalized.description,
variant: 'destructive',
});
return {
success: false,
error,
failureType: (userRejected ? 'userRejected' : 'signFailed') as TransactionFailureType,
};
return { success: false, error: rawError, failureType: ft };
}

lastSignedXdrRef.current = signResult.signedTxXdr!;
Expand All @@ -128,13 +131,15 @@ export const useTransaction = (): UseTransactionResult => {

const submissionResult = await submitTransaction(signResult.signedTxXdr!);
if (!submissionResult.success) {
// error from submitTransaction is already normalized by transaction.ts
const error = submissionResult.error;
setStatus('failed');
setTransactionError(error);
setFailureType('submitFailed');
const normalized = normalizeFromFailureType('submitFailed', error);
toast({
title: 'Submission failed',
description: error,
title: normalized.title,
description: normalized.description,
variant: 'destructive',
});
return {
Expand All @@ -154,21 +159,23 @@ export const useTransaction = (): UseTransactionResult => {

const confirmationResult = await pollForConfirmation(submissionResult.hash);
if (!confirmationResult.success) {
const failure = confirmationResult.status === 'confirmationTimeout'
? 'confirmationTimeout'
: 'confirmationFailed';
const ft: TransactionFailureType =
confirmationResult.status === 'confirmationTimeout'
? 'confirmationTimeout'
: 'confirmationFailed';
setStatus('failed');
setTransactionError(confirmationResult.error);
setFailureType(failure as TransactionFailureType);
setFailureType(ft);
const normalized = normalizeFromFailureType(ft, confirmationResult.error);
toast({
title: failure === 'confirmationTimeout' ? 'Confirmation timed out' : 'Confirmation failed',
description: confirmationResult.error,
title: normalized.title,
description: normalized.description,
variant: 'destructive',
});
return {
success: false,
error: confirmationResult.error,
failureType: failure as TransactionFailureType,
failureType: ft,
};
}

Expand All @@ -180,16 +187,17 @@ export const useTransaction = (): UseTransactionResult => {
});
return { success: true, hash: confirmationResult.hash };
} catch (error: unknown) {
const message = (error as Error)?.message || 'Unknown transaction error';
const rawMessage = (error as Error)?.message || 'Unknown transaction error';
const normalized = normalizeContractError(rawMessage);
setStatus('failed');
setTransactionError(message);
setTransactionError(rawMessage);
setFailureType('requestFailed');
toast({
title: 'Transaction failed',
description: message,
title: normalized.title,
description: normalized.description,
variant: 'destructive',
});
return { success: false, error: message, failureType: 'requestFailed' as TransactionFailureType };
return { success: false, error: rawMessage, failureType: 'requestFailed' as TransactionFailureType };
}
},
[isConnected, signTransaction],
Expand All @@ -213,21 +221,23 @@ export const useTransaction = (): UseTransactionResult => {

const confirmationResult = await pollForConfirmation(lastSubmittedHashRef.current);
if (!confirmationResult.success) {
const failure = confirmationResult.status === 'confirmationTimeout'
? 'confirmationTimeout'
: 'confirmationFailed';
const ft: TransactionFailureType =
confirmationResult.status === 'confirmationTimeout'
? 'confirmationTimeout'
: 'confirmationFailed';
setStatus('failed');
setTransactionError(confirmationResult.error);
setFailureType(failure as TransactionFailureType);
setFailureType(ft);
const normalized = normalizeFromFailureType(ft, confirmationResult.error);
toast({
title: failure === 'confirmationTimeout' ? 'Confirmation timed out' : 'Confirmation failed',
description: confirmationResult.error,
title: normalized.title,
description: normalized.description,
variant: 'destructive',
});
return {
success: false,
error: confirmationResult.error,
failureType: failure as TransactionFailureType,
failureType: ft,
};
}

Expand All @@ -238,8 +248,8 @@ export const useTransaction = (): UseTransactionResult => {
description: `Hash: ${confirmationResult.hash}`,
});
return { success: true, hash: confirmationResult.hash };
}
}

if ((failureType === 'submitFailed' || failureType === 'confirmationFailed') && lastSignedXdrRef.current) {
setStatus('submitting');
toast({
Expand All @@ -253,9 +263,10 @@ export const useTransaction = (): UseTransactionResult => {
setStatus('failed');
setTransactionError(error);
setFailureType('submitFailed');
const normalized = normalizeFromFailureType('submitFailed', error);
toast({
title: 'Submission failed',
description: error,
title: normalized.title,
description: normalized.description,
variant: 'destructive',
});
return {
Expand All @@ -275,21 +286,23 @@ export const useTransaction = (): UseTransactionResult => {

const confirmationResult = await pollForConfirmation(submissionResult.hash);
if (!confirmationResult.success) {
const failure = confirmationResult.status === 'confirmationTimeout'
? 'confirmationTimeout'
: 'confirmationFailed';
const ft: TransactionFailureType =
confirmationResult.status === 'confirmationTimeout'
? 'confirmationTimeout'
: 'confirmationFailed';
setStatus('failed');
setTransactionError(confirmationResult.error);
setFailureType(failure as TransactionFailureType);
setFailureType(ft);
const normalized = normalizeFromFailureType(ft, confirmationResult.error);
toast({
title: failure === 'confirmationTimeout' ? 'Confirmation timed out' : 'Confirmation failed',
description: confirmationResult.error,
title: normalized.title,
description: normalized.description,
variant: 'destructive',
});
return {
success: false,
error: confirmationResult.error,
failureType: failure as TransactionFailureType,
failureType: ft,
};
}

Expand All @@ -313,20 +326,25 @@ export const useTransaction = (): UseTransactionResult => {
return { success: false, error: message, failureType: 'requestFailed' as TransactionFailureType };

} catch (error: unknown) {
const message = (error as Error)?.message || 'Unknown transaction error';
const rawMessage = (error as Error)?.message || 'Unknown transaction error';
const normalized = normalizeContractError(rawMessage);
setStatus('failed');
setTransactionError(message);
setTransactionError(rawMessage);
setFailureType('requestFailed');
toast({
title: 'Retry failed',
description: message,
title: normalized.title,
description: normalized.description,
variant: 'destructive',
});
return { success: false, error: message, failureType: 'requestFailed' as TransactionFailureType };
return { success: false, error: rawMessage, failureType: 'requestFailed' as TransactionFailureType };
}
}, [status, failureType, executeTransaction]);

const canRetry = status === 'failed' && (lastBuildXdrRef.current !== null || lastSignedXdrRef.current !== null || lastSubmittedHashRef.current !== null);
const canRetry = status === 'failed' && (
lastBuildXdrRef.current !== null ||
lastSignedXdrRef.current !== null ||
lastSubmittedHashRef.current !== null
);

return {
status,
Expand Down
30 changes: 18 additions & 12 deletions hooks/useWallet.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
import { useState } from "react";
import { getKit } from "../constants/wallet-kits.constant";
import { getClientConfig } from "@/lib/config";
import { normalizeContractError } from "@/lib/stellar/contract-error-normalizer";

export const useWallet = () => {
const walletState = useWalletContext();
Expand Down Expand Up @@ -99,11 +100,13 @@ export const useWallet = () => {

return { success: true, address };
} catch (error: unknown) {
const errorMessage =
(error as Error)?.message || "Error connecting wallet";
setError(errorMessage);
const rawMessage = (error as Error)?.message || "Error connecting wallet";
// Use normalized message for user-facing error state; log raw for diagnostics
const normalized = normalizeContractError(rawMessage);
const userMessage = normalized.description;
setError(userMessage);
console.error("Error connecting wallet:", error);
return { success: false, error: errorMessage };
return { success: false, error: userMessage };
} finally {
setIsConnecting(false);
}
Expand All @@ -117,11 +120,12 @@ export const useWallet = () => {
walletState.disconnect();
return { success: true };
} catch (error: unknown) {
const errorMessage =
(error as Error)?.message || "Error disconnecting wallet";
setError(errorMessage);
const rawMessage = (error as Error)?.message || "Error disconnecting wallet";
const normalized = normalizeContractError(rawMessage);
const userMessage = normalized.description;
setError(userMessage);
console.error("Error disconnecting wallet:", error);
return { success: false, error: errorMessage };
return { success: false, error: userMessage };
}
};

Expand All @@ -146,11 +150,13 @@ export const useWallet = () => {

return { success: true, signedTxXdr };
} catch (error: unknown) {
const errorMessage =
(error as Error)?.message || "Error signing transaction";
setError(errorMessage);
const rawMessage = (error as Error)?.message || "Error signing transaction";
// Use normalized message for user-facing error state; log raw for diagnostics
const normalized = normalizeContractError(rawMessage);
const userMessage = normalized.description;
setError(userMessage);
console.error("Error signing transaction:", error);
return { success: false, error: errorMessage };
return { success: false, error: userMessage };
}
};

Expand Down
Loading
Loading