Skip to content
Closed
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
33 changes: 33 additions & 0 deletions src/__tests__/wallet-tx-error.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { AbortError } from '../util/abort-error';
import { TranslatedError } from '../util/translated-error';
import { resolveWalletTxError } from '../util/wallet-tx-error';

describe('resolveWalletTxError', () => {
it('treats an EIP-1193 user rejection (code 4001) as a silent cancel', () => {
expect(resolveWalletTxError({ code: 4001, message: 'User rejected' })).toEqual({ cancelled: true });
});

it('treats an AbortError as a silent cancel', () => {
expect(resolveWalletTxError(new AbortError('User cancelled'))).toEqual({ cancelled: true });
});

it('surfaces the message of a TranslatedError as the key to display', () => {
const message = 'There is already a request pending. Please confirm it in your MetaMask and retry.';
expect(resolveWalletTxError(new TranslatedError(message))).toEqual({ cancelled: false, messageKey: message });
});

it('keeps the generic fallback for an unknown error (no message surfaced)', () => {
expect(resolveWalletTxError(new Error('insufficient funds for intrinsic transaction cost'))).toEqual({
cancelled: false,
});
});

it('keeps the generic fallback for a TranslatedError without a message', () => {
expect(resolveWalletTxError(new TranslatedError(''))).toEqual({ cancelled: false });
});

it('does not throw on null or undefined', () => {
expect(resolveWalletTxError(null)).toEqual({ cancelled: false });
expect(resolveWalletTxError(undefined)).toEqual({ cancelled: false });
});
});
13 changes: 10 additions & 3 deletions src/screens/sell.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { useLayoutContext } from 'src/contexts/layout.context';
import { useWindowContext } from 'src/contexts/window.context';
import { useLayoutOptions } from 'src/hooks/layout-config.hook';
import { ErrorHint } from '../components/error-hint';
import { resolveWalletTxError } from '../util/wallet-tx-error';
import { ExchangeRate } from '../components/exchange-rate';
import { SellCompletion } from '../components/payment/sell-completion';
import { QuoteErrorHint } from '../components/quote-error-hint';
Expand Down Expand Up @@ -602,10 +603,16 @@ export default function SellScreen(): JSX.Element {
}
setTxDone(true);
} catch (error: any) {
const { cancelled, messageKey } = resolveWalletTxError(error);
// User rejected in wallet - silently return, user stays on form
if (error.code === 4001) return;
// Other errors - show message, user can click Retry to see deposit address for manual transfer
setErrorMessage(translate('screens/sell', 'Transaction failed. Click Retry to see the deposit address for manual transfer.'));
if (cancelled) return;
// A curated wallet error (e.g. a request still pending in the wallet) already tells the user what to
// do; surface it. Otherwise keep the generic hint that points to the manual deposit address.
setErrorMessage(
messageKey
? translate('screens/home', messageKey)
: translate('screens/sell', 'Transaction failed. Click Retry to see the deposit address for manual transfer.'),
);
} finally {
setIsProcessing(false);
}
Expand Down
14 changes: 9 additions & 5 deletions src/screens/swap.screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { useBlockchain } from '../hooks/blockchain.hook';
import { useAddressGuard } from '../hooks/guard.hook';
import { useNavigation } from '../hooks/navigation.hook';
import { useTxHelper } from '../hooks/tx-helper.hook';
import { resolveWalletTxError } from '../util/wallet-tx-error';

enum Side {
SPEND = 'SPEND',
Expand Down Expand Up @@ -660,12 +661,15 @@ export default function SwapScreen(): JSX.Element {

setTxDone(true);
} catch (error: any) {
if (error.code === 4001) return;
const { cancelled, messageKey } = resolveWalletTxError(error);
if (cancelled) return;
setErrorMessage(
translate(
'screens/swap',
'Transaction failed. Click Retry to see the deposit address for manual transfer.',
),
messageKey
? translate('screens/home', messageKey)
: translate(
'screens/swap',
'Transaction failed. Click Retry to see the deposit address for manual transfer.',
),
);
} finally {
setIsProcessing(false);
Expand Down
6 changes: 6 additions & 0 deletions src/translations/languages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,12 @@
"Please confirm the connection in your wallet.": "Bitte bestätige die Verbindung in Deiner Wallet.",
"Please confirm the connection with your Ledger.": "Bitte bestätige die Verbindung mit Deinem Ledger.",
"Connection failed!": "Verbindung fehlgeschlagen!",
"Failed to confirm sell transaction": "Der Verkauf konnte nicht bestätigt werden. Bitte versuche es erneut.",
"Failed to confirm swap transaction": "Der Tausch konnte nicht bestätigt werden. Bitte versuche es erneut.",
"Failed to execute gasless sell transaction": "Der gebührenfreie Verkauf konnte nicht ausgeführt werden. Bitte versuche es erneut.",
"Failed to execute gasless swap transaction": "Der gebührenfreie Tausch konnte nicht ausgeführt werden. Bitte versuche es erneut.",
"There is already a request pending. Please confirm it in your MetaMask and retry.": "Es ist bereits eine Anfrage offen. Bitte bestätige sie in deiner MetaMask und versuche es erneut.",
"There is already a request pending. Please reload the page and retry.": "Es ist bereits eine Anfrage offen. Bitte lade die Seite neu und versuche es erneut.",
"Please make sure that all other applications and browser extensions that are connected to the ledger are completely closed when you try to connect.\nThis includes third-party wallets (Ledger Live, Metamask, Daedalus, MyEtherWallet) or other applications that could interfere with the connection between DFX.swiss and your ledger device.": "Bitte stelle sicher, dass alle anderen Anwendungen und Browsererweiterungen, die mit dem Ledger verbunden sind, vollständig geschlossen sind, wenn Du versuchst, eine Verbindung herzustellen.\nDazu gehören Wallets von Drittanbietern (Ledger Live, Metamask, Daedalus, MyEtherWallet) oder andere Anwendungen, die die Verbindung zwischen DFX.swiss und Deinem Ledger-Gerät stören könnten.",
"Connect your {{device}} with your computer": "Verbinde Deinen {{device}} mit Deinem Computer",
"Click on \"Connect\"": "Klicke auf \"Verbinden\"",
Expand Down
6 changes: 6 additions & 0 deletions src/translations/languages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,12 @@
"Please confirm the connection in your wallet.": "Veuillez confirmer la connexion dans votre portefeuille.",
"Please confirm the connection with your Ledger.": "Veuillez confirmer la connexion avec votre Ledger.",
"Connection failed!": "Échec de la connexion !",
"Failed to confirm sell transaction": "La vente n'a pas pu être confirmée. Veuillez réessayer.",
"Failed to confirm swap transaction": "L'échange n'a pas pu être confirmé. Veuillez réessayer.",
"Failed to execute gasless sell transaction": "La vente sans frais de gas n'a pas pu être exécutée. Veuillez réessayer.",
"Failed to execute gasless swap transaction": "L'échange sans frais de gas n'a pas pu être exécuté. Veuillez réessayer.",
"There is already a request pending. Please confirm it in your MetaMask and retry.": "Une demande est déjà en attente. Veuillez la confirmer dans votre MetaMask et réessayer.",
"There is already a request pending. Please reload the page and retry.": "Une demande est déjà en attente. Veuillez recharger la page et réessayer.",
"Please make sure that all other applications and browser extensions that are connected to the ledger are completely closed when you try to connect.\nThis includes third-party wallets (Ledger Live, Metamask, Daedalus, MyEtherWallet) or other applications that could interfere with the connection between DFX.swiss and your ledger device.": "Veuillez vous assurer que toutes les autres applications et extensions de navigateur connectées au Ledger sont complètement fermées lorsque vous essayez de vous connecter.\nCela inclut les portefeuilles tiers (Ledger Live, Metamask, Daedalus, MyEtherWallet) ou d'autres applications qui pourraient interférer avec la connexion entre DFX.swiss et votre appareil Ledger.",
"Connect your {{device}} with your computer": "Connectez votre {{device}} à votre ordinateur",
"Click on \"Connect\"": "Cliquez sur \"Connecter\"",
Expand Down
6 changes: 6 additions & 0 deletions src/translations/languages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,12 @@
"Please confirm the connection in your wallet.": "Confermare la connessione nel portafoglio.",
"Please confirm the connection with your Ledger.": "Confermare il collegamento con il Ledger.",
"Connection failed!": "Connessione fallita!",
"Failed to confirm sell transaction": "Non è stato possibile confermare la vendita. Riprova.",
"Failed to confirm swap transaction": "Non è stato possibile confermare lo scambio. Riprova.",
"Failed to execute gasless sell transaction": "Non è stato possibile eseguire la vendita senza commissioni di gas. Riprova.",
"Failed to execute gasless swap transaction": "Non è stato possibile eseguire lo scambio senza commissioni di gas. Riprova.",
"There is already a request pending. Please confirm it in your MetaMask and retry.": "È già presente una richiesta in sospeso. Confermala nel tuo MetaMask e riprova.",
"There is already a request pending. Please reload the page and retry.": "È già presente una richiesta in sospeso. Ricarica la pagina e riprova.",
"Please make sure that all other applications and browser extensions that are connected to the ledger are completely closed when you try to connect.\nThis includes third-party wallets (Ledger Live, Metamask, Daedalus, MyEtherWallet) or other applications that could interfere with the connection between DFX.swiss and your ledger device.": "Assicurarsi che tutte le altre applicazioni e le estensioni del browser collegate al ledger siano completamente chiuse quando si cerca di connettersi.\nCiò include portafogli di terze parti (Ledger Live, Metamask, Daedalus, MyEtherWallet) o altre applicazioni che potrebbero interferire con la connessione tra DFX.swiss e il dispositivo ledger.",
"Connect your {{device}} with your computer": "Collegare il {{device}} al computer",
"Click on \"Connect\"": "Fare clic su \"Collegare\"",
Expand Down
31 changes: 31 additions & 0 deletions src/util/wallet-tx-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AbortError } from './abort-error';
import { TranslatedError } from './translated-error';

export interface WalletTxErrorResult {
/** The user rejected the request in their wallet — stay on the form and show nothing. */
cancelled: boolean;
/**
* A curated, translatable wallet-error key (thrown as a TranslatedError, e.g. a request that is
* still pending in the wallet). Undefined for unknown errors, where the caller keeps its generic
* "transaction failed" hint instead of surfacing a raw technical message.
*/
messageKey?: string;
}

/**
* Classifies an error thrown while sending a sell or swap transaction from the connected wallet, so
* both screens react the same way: a user cancellation stays silent, a curated TranslatedError is
* surfaced to the user (it already explains what to do), and anything else falls back to the generic
* hint that points to the manual deposit address.
*/
export function resolveWalletTxError(error: unknown): WalletTxErrorResult {
if ((error as { code?: number } | null)?.code === 4001 || error instanceof AbortError) {
return { cancelled: true };
}

if (error instanceof TranslatedError && error.message) {
return { cancelled: false, messageKey: error.message };
}

return { cancelled: false };
}