diff --git a/src/features/leverage-tokens/components/leverage-token-mint-modal/ErrorStep.tsx b/src/features/leverage-tokens/components/leverage-token-mint-modal/ErrorStep.tsx index ca579374..3d14a690 100644 --- a/src/features/leverage-tokens/components/leverage-token-mint-modal/ErrorStep.tsx +++ b/src/features/leverage-tokens/components/leverage-token-mint-modal/ErrorStep.tsx @@ -12,6 +12,7 @@ export function ErrorStep({ error, onRetry, onClose }: ErrorStepProps) { const { icon, title, message, showRetry, severity, technicalDetails } = getErrorDisplay( error || '', 'Transaction Failed', + 'mintLt', ) const bgClass = severity === 'warning' diff --git a/src/features/leverage-tokens/components/leverage-token-redeem-modal/ErrorStep.tsx b/src/features/leverage-tokens/components/leverage-token-redeem-modal/ErrorStep.tsx index 4606ffac..aff59631 100644 --- a/src/features/leverage-tokens/components/leverage-token-redeem-modal/ErrorStep.tsx +++ b/src/features/leverage-tokens/components/leverage-token-redeem-modal/ErrorStep.tsx @@ -12,6 +12,7 @@ export function ErrorStep({ error, onRetry, onClose }: ErrorStepProps) { const { icon, title, message, showRetry, severity, technicalDetails } = getErrorDisplay( error || '', 'Redemption Failed', + 'redeemLt', ) const bgClass = severity === 'warning' diff --git a/src/features/leverage-tokens/utils/errorDisplay.tsx b/src/features/leverage-tokens/utils/errorDisplay.tsx index 8708f43d..10da490b 100644 --- a/src/features/leverage-tokens/utils/errorDisplay.tsx +++ b/src/features/leverage-tokens/utils/errorDisplay.tsx @@ -19,11 +19,12 @@ export interface ErrorDisplayConfig { export function getErrorDisplay( error: string, defaultTitle: string = 'Transaction Failed', + txType?: 'mintLt' | 'redeemLt', ): ErrorDisplayConfig { // Try to classify the error first let classifiedError: LeverageTokenError try { - classifiedError = classifyError(error) + classifiedError = classifyError(error, txType) } catch { classifiedError = { type: 'UNKNOWN', message: error } } @@ -87,14 +88,42 @@ export function getErrorDisplay( case 'SLIPPAGE_EXCEEDED': return { icon: , - title: 'Slippage Tolerance Exceeded', - message: - 'The price moved beyond your slippage tolerance. Try increasing your slippage tolerance or retry the transaction.', + title: 'Swap Slippage Tolerance Exceeded', + message: `The spot price moved beyond your slippage tolerance. Try decreasing your swap slippage tolerance, increasing the ${txType === 'mintLt' ? 'leverage token' : 'collateral'} adjustment parameter, or retry the transaction.`, showRetry: true, severity: 'warning', technicalDetails: error, } + case 'LT_SHARE_SLIPPAGE_EXCEEDED': + return { + icon: , + title: 'Leverage Token Slippage Exceeded', + message: + 'The Leverage Token share price moved beyond your slippage tolerance. Try decreasing your swap slippage tolerance, increasing the Leverage Token slippage tolerance, or retry the transaction.', + showRetry: true, + severity: 'warning', + } + + case 'LT_COLLATERAL_SLIPPAGE_EXCEEDED': + return { + icon: , + title: 'Leverage Token Collateral Slippage Exceeded', + message: + 'The Leverage Token collateral price moved beyond your slippage tolerance. Try decreasing your swap slippage tolerance, increasing the collateral slippage tolerance, or retry the transaction.', + showRetry: true, + severity: 'warning', + } + + case 'INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT': + return { + icon: , + title: 'Insufficient Assets for Flash Loan Repayment', + message: `Unable to repay flash loan. Try decreasing your swap slippage tolerance, increasing the ${txType === 'mintLt' ? 'flash loan adjustment' : 'collateral swap adjustment'} parameter, or retry the transaction.`, + showRetry: true, + severity: 'error', + } + default: // For unknown errors, check if it's a user rejection by looking for common patterns if ( diff --git a/src/features/leverage-tokens/utils/errors.ts b/src/features/leverage-tokens/utils/errors.ts index 42720806..19af78c4 100644 --- a/src/features/leverage-tokens/utils/errors.ts +++ b/src/features/leverage-tokens/utils/errors.ts @@ -10,16 +10,19 @@ export type LeverageTokenError = | { type: 'USER_REJECTED'; code: 4001 } | { type: 'CHAIN_MISMATCH'; expected: number; actual: number } | { type: 'UNKNOWN'; message: string; originalError?: unknown } + | { type: 'LT_SHARE_SLIPPAGE_EXCEEDED' } + | { type: 'LT_COLLATERAL_SLIPPAGE_EXCEEDED' } + | { type: 'INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT' } /** * Classify errors into actionable types * User errors (4001, 4902) should not be sent to Sentry */ -export function classifyError(e: unknown): LeverageTokenError { +export function classifyError(e: unknown, txType?: 'mintLt' | 'redeemLt'): LeverageTokenError { // Handle string input directly if (typeof e === 'string') { const error = { message: e } - return classifyErrorFromObject(error) + return classifyErrorFromObject(error, txType) } const error = e as { @@ -32,18 +35,21 @@ export function classifyError(e: unknown): LeverageTokenError { message?: string } - return classifyErrorFromObject(error) + return classifyErrorFromObject(error, txType) } -function classifyErrorFromObject(error: { - code?: number - cause?: { code?: number } - expectedChainId?: number - actualChainId?: number - reason?: string - data?: { message?: string } - message?: string -}): LeverageTokenError { +function classifyErrorFromObject( + error: { + code?: number + cause?: { code?: number } + expectedChainId?: number + actualChainId?: number + reason?: string + data?: { message?: string } + message?: string + }, + txType?: 'mintLt' | 'redeemLt', +): LeverageTokenError { // User rejected transaction if (error?.code === 4001 || error?.cause?.code === 4001) { return { type: 'USER_REJECTED', code: 4001 } @@ -113,9 +119,33 @@ function classifyErrorFromObject(error: { // Check for slippage-related error signatures in the raw message const rawMessage = error?.message || '' + + if (txType === 'mintLt' && rawMessage.includes('0x76baadda')) { + return { + type: 'LT_SHARE_SLIPPAGE_EXCEEDED', + } + } + + if ( + txType === 'redeemLt' && + (rawMessage.includes('0x76baadda') || rawMessage.includes('CollateralSlippageTooHigh')) + ) { + return { + type: 'LT_COLLATERAL_SLIPPAGE_EXCEEDED', + } + } + + if ( + (txType === 'mintLt' || txType === 'redeemLt') && + rawMessage.includes('transferFrom reverted') + ) { + return { + type: 'INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT', + } + } + if ( rawMessage.includes('0x5a046737') || // Common slippage signature - rawMessage.includes('0x76baadda') || // Another slippage signature rawMessage.includes('SlippageExceeded') || rawMessage.includes('SlippageToleranceExceeded') || rawMessage.includes('PriceImpactTooHigh') || diff --git a/tests/unit/features/leverage-tokens/utils/errors.test.ts b/tests/unit/features/leverage-tokens/utils/errors.test.ts new file mode 100644 index 00000000..4c20dd81 --- /dev/null +++ b/tests/unit/features/leverage-tokens/utils/errors.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it } from 'vitest' +import { classifyError } from '@/features/leverage-tokens/utils/errors' + +describe('classifyError', () => { + describe('LT_SHARE_SLIPPAGE_EXCEEDED (mint)', () => { + it('returns LT_SHARE_SLIPPAGE_EXCEEDED when txType is mintLt and message contains 0x76baadda', () => { + const error = { + message: + 'The contract function "deposit" reverted. Error signature 0x76baadda. Some other text.', + } + expect(classifyError(error, 'mintLt')).toEqual({ + type: 'LT_SHARE_SLIPPAGE_EXCEEDED', + }) + }) + + it('does not return LT_SHARE_SLIPPAGE_EXCEEDED when txType is mintLt but message lacks 0x76baadda', () => { + const error = { message: 'transferFrom reverted' } + expect(classifyError(error, 'mintLt')).not.toEqual({ + type: 'LT_SHARE_SLIPPAGE_EXCEEDED', + }) + }) + + it('does not return LT_SHARE_SLIPPAGE_EXCEEDED when message contains 0x76baadda but txType is not mintLt', () => { + const error = { message: 'Revert 0x76baadda' } + expect(classifyError(error, 'redeemLt')).toEqual({ + type: 'LT_COLLATERAL_SLIPPAGE_EXCEEDED', + }) + expect(classifyError(error)).not.toEqual({ + type: 'LT_SHARE_SLIPPAGE_EXCEEDED', + }) + }) + }) + + describe('LT_COLLATERAL_SLIPPAGE_EXCEEDED (redeem)', () => { + it('returns LT_COLLATERAL_SLIPPAGE_EXCEEDED when txType is redeemLt and message contains 0x76baadda', () => { + const error = { message: 'Reverted with 0x76baadda' } + expect(classifyError(error, 'redeemLt')).toEqual({ + type: 'LT_COLLATERAL_SLIPPAGE_EXCEEDED', + }) + }) + + it('returns LT_COLLATERAL_SLIPPAGE_EXCEEDED when txType is redeemLt and message contains CollateralSlippageTooHigh', () => { + const error = { message: 'CollateralSlippageTooHigh' } + expect(classifyError(error, 'redeemLt')).toEqual({ + type: 'LT_COLLATERAL_SLIPPAGE_EXCEEDED', + }) + }) + + it('does not return LT_COLLATERAL_SLIPPAGE_EXCEEDED when txType is redeemLt but message is not CollateralSlippageTooHigh or 0x76baadda', () => { + const error = { message: 'Some other revert' } + expect(classifyError(error, 'redeemLt')).toEqual({ + type: 'UNKNOWN', + message: 'Some other revert', + originalError: error, + }) + }) + + it('does not return LT_COLLATERAL_SLIPPAGE_EXCEEDED when txType is not redeemLt', () => { + const error = { message: '0x76baadda' } + expect(classifyError(error, 'mintLt')).toEqual({ + type: 'LT_SHARE_SLIPPAGE_EXCEEDED', + }) + expect(classifyError(error)).toEqual({ + type: 'UNKNOWN', + message: '0x76baadda', + originalError: error, + }) + }) + }) + + describe('INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT', () => { + it('returns INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT when txType is mintLt and message contains transferFrom reverted', () => { + const error = { + message: + 'The contract function "deposit" reverted with the following reason:\ntransferFrom reverted\n\nContract Call:', + } + expect(classifyError(error, 'mintLt')).toEqual({ + type: 'INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT', + }) + }) + + it('returns INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT when txType is redeemLt and message contains transferFrom reverted', () => { + const error = { message: 'transferFrom reverted' } + expect(classifyError(error, 'redeemLt')).toEqual({ + type: 'INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT', + }) + }) + + it('does not return INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT when message does not contain transferFrom reverted', () => { + const error = { message: 'Some other error' } + expect(classifyError(error, 'mintLt')).not.toEqual({ + type: 'INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT', + }) + }) + + it('does not return INSUFFICIENT_ASSETS_FOR_FLASH_LOAN_REPAYMENT when txType is omitted', () => { + const error = { message: 'transferFrom reverted' } + expect(classifyError(error)).toEqual({ + type: 'UNKNOWN', + message: 'transferFrom reverted', + originalError: error, + }) + }) + }) +})