Skip to content

Commit 8f9de65

Browse files
committed
feat: add dynamic high-slippage warning banner with two-tier alert (Closes #111)
- Lower SLIPPAGE_WARNING_THRESHOLD from 5% to 2% so users are warned earlier about sandwich-attack risk - Add two-tier warning styling: - Amber/yellow for 2-10%: 'High slippage warning' - Red for >10%: 'Extreme slippage — high risk' - Improve warning text to explain MEV sandwich attack risks and poor execution prices - Add unit tests for the updated constants and thresholds [fj4WqyCCw3C5ShR1RfB7MoBPTpkRrBFYP1uT35g3MvT]
1 parent 35e5fd3 commit 8f9de65

3 files changed

Lines changed: 138 additions & 9 deletions

File tree

components/SlippageSettings.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useUserSettingsStore, type SlippageValue } from '@/lib/store/userSettin
66
import {
77
PRESET_SLIPPAGE_VALUES,
88
SLIPPAGE_WARNING_THRESHOLD,
9+
SLIPPAGE_MAX,
910
validateSlippageInput,
1011
isSlippageRisky
1112
} from '@/lib/store/userSettingsStore';
@@ -280,21 +281,38 @@ export function SlippageSettings() {
280281

281282
{/* Risk warning banner */}
282283
{risky && (
283-
<div className="mx-4 mb-4 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 dark:border-amber-800 dark:bg-amber-950/40">
284+
<div className={`mx-4 mb-4 rounded-md border px-3 py-2 ${
285+
slippage > 10
286+
? 'border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950/40'
287+
: 'border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950/40'
288+
}`}>
284289
<div className="flex items-start gap-2">
285290
<AlertTriangle
286291
size={14}
287292
aria-hidden="true"
288-
className="mt-0.5 shrink-0 text-amber-600 dark:text-amber-400"
293+
className={`mt-0.5 shrink-0 ${
294+
slippage > 10
295+
? 'text-red-600 dark:text-red-400'
296+
: 'text-amber-600 dark:text-amber-400'
297+
}`}
289298
/>
290299
<div>
291-
<p className="text-xs font-medium text-amber-800 dark:text-amber-300">
292-
High slippage warning
300+
<p className={`text-xs font-medium ${
301+
slippage > 10
302+
? 'text-red-800 dark:text-red-300'
303+
: 'text-amber-800 dark:text-amber-300'
304+
}`}>
305+
{slippage > 10 ? 'Extreme slippage — high risk' : 'High slippage warning'}
293306
</p>
294-
<p className="mt-0.5 text-xs text-amber-700 dark:text-amber-400">
295-
Setting slippage above {SLIPPAGE_WARNING_THRESHOLD}% puts
296-
you at high risk of sandwich attacks. Consider a lower
297-
value.
307+
<p className={`mt-0.5 text-xs ${
308+
slippage > 10
309+
? 'text-red-700 dark:text-red-400'
310+
: 'text-amber-700 dark:text-amber-400'
311+
}`}>
312+
{slippage > 10
313+
? `Setting slippage above 10% exposes you to extreme MEV sandwich attacks and near-total loss of funds. Consider a much lower value.`
314+
: `Setting slippage above ${SLIPPAGE_WARNING_THRESHOLD}% puts you at risk of sandwich attacks and poor execution prices. Consider a lower value.`
315+
}
298316
</p>
299317
</div>
300318
</div>
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { describe, expect, it } from 'vitest';
2+
import {
3+
DEFAULT_SLIPPAGE,
4+
PRESET_SLIPPAGE_VALUES,
5+
SLIPPAGE_WARNING_THRESHOLD,
6+
SLIPPAGE_MAX,
7+
SLIPPAGE_MIN,
8+
validateSlippageInput,
9+
isSlippageRisky
10+
} from './userSettingsStore';
11+
12+
describe('constants', () => {
13+
it('has a default slippage of 0.5 %', () => {
14+
expect(DEFAULT_SLIPPAGE).toBe(0.5);
15+
});
16+
17+
it('has preset values [0.1, 0.5, 1.0]', () => {
18+
expect(PRESET_SLIPPAGE_VALUES).toEqual([0.1, 0.5, 1.0]);
19+
});
20+
21+
it('has a warning threshold of 2 %', () => {
22+
expect(SLIPPAGE_WARNING_THRESHOLD).toBe(2);
23+
});
24+
25+
it('has a max of 50 %', () => {
26+
expect(SLIPPAGE_MAX).toBe(50);
27+
});
28+
29+
it('has a min of 0 %', () => {
30+
expect(SLIPPAGE_MIN).toBe(0);
31+
});
32+
});
33+
34+
describe('validateSlippageInput', () => {
35+
it('rejects empty input', () => {
36+
const result = validateSlippageInput('');
37+
expect(result.valid).toBe(false);
38+
if (!result.valid) expect(result.message).toContain('empty');
39+
});
40+
41+
it('rejects bare dot', () => {
42+
const result = validateSlippageInput('.');
43+
expect(result.valid).toBe(false);
44+
});
45+
46+
it('rejects multiple dots', () => {
47+
const result = validateSlippageInput('1.2.3');
48+
expect(result.valid).toBe(false);
49+
});
50+
51+
it('rejects negative values', () => {
52+
const result = validateSlippageInput('-1');
53+
expect(result.valid).toBe(false);
54+
});
55+
56+
it('rejects values above max', () => {
57+
const result = validateSlippageInput('51');
58+
expect(result.valid).toBe(false);
59+
if (!result.valid) expect(result.message).toContain('50');
60+
});
61+
62+
it('accepts 0', () => {
63+
expect(validateSlippageInput('0').valid).toBe(true);
64+
});
65+
66+
it('accepts 0.5', () => {
67+
expect(validateSlippageInput('0.5').valid).toBe(true);
68+
});
69+
70+
it('accepts 50 (the max)', () => {
71+
expect(validateSlippageInput('50').valid).toBe(true);
72+
});
73+
74+
it('accepts trailing decimal point (in-progress edit)', () => {
75+
expect(validateSlippageInput('5.').valid).toBe(true);
76+
});
77+
});
78+
79+
describe('isSlippageRisky', () => {
80+
it('returns false for default slippage (0.5 %)', () => {
81+
expect(isSlippageRisky(0.5)).toBe(false);
82+
});
83+
84+
it('returns false for 1 %', () => {
85+
expect(isSlippageRisky(1)).toBe(false);
86+
});
87+
88+
it('returns false for exactly the threshold (2 % — not yet risky)', () => {
89+
expect(isSlippageRisky(SLIPPAGE_WARNING_THRESHOLD)).toBe(false);
90+
});
91+
92+
it('returns true for 3 % (above threshold)', () => {
93+
expect(isSlippageRisky(3)).toBe(true);
94+
});
95+
96+
it('returns true for 5 %', () => {
97+
expect(isSlippageRisky(5)).toBe(true);
98+
});
99+
100+
it('returns true for 10 %', () => {
101+
expect(isSlippageRisky(10)).toBe(true);
102+
});
103+
104+
it('returns true for 50 % (the max)', () => {
105+
expect(isSlippageRisky(SLIPPAGE_MAX)).toBe(true);
106+
});
107+
108+
it('returns false for values above max (clamped by store)', () => {
109+
expect(isSlippageRisky(51)).toBe(false);
110+
});
111+
});

lib/store/userSettingsStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const DEFAULT_SLIPPAGE: SlippageValue = 0.5;
2626
export const PRESET_SLIPPAGE_VALUES: SlippageValue[] = [0.1, 0.5, 1.0];
2727

2828
/** Anything above this is considered risky (sandwich-attack territory). */
29-
export const SLIPPAGE_WARNING_THRESHOLD = 5;
29+
export const SLIPPAGE_WARNING_THRESHOLD = 2;
3030

3131
/** Hard upper bound — values above this are rejected by validation. */
3232
export const SLIPPAGE_MAX = 50;

0 commit comments

Comments
 (0)