Skip to content

Commit aa2e03a

Browse files
bfullamtommasini
andauthored
feat: deprecate snap confirmation page (#16293)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Deprecates the solana snap confirmation page. Swap and bridge transactions will now be confirmed automatically after submission. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to Swap/bridge 2. Get quote 3. Submit tx 4. See snap confirmation screen does not appear ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/3422998e-9349-4b4f-b2dc-9f101ae2b895 <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: tommasini <[email protected]>
1 parent 96fd852 commit aa2e03a

File tree

13 files changed

+945
-21
lines changed

13 files changed

+945
-21
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
import { act } from '@testing-library/react-hooks';
2+
import { QuoteResponse } from '../../types';
3+
import { QuoteMetadata } from '@metamask/bridge-controller';
4+
5+
/**
6+
* Unit tests for the blockaid validation logic.
7+
* These tests isolate the validation logic to ensure it handles different response scenarios correctly.
8+
*/
9+
10+
// Type definitions for the test function
11+
type ValidateBridgeTx = (params: {
12+
quoteResponse: QuoteResponse & QuoteMetadata;
13+
}) => Promise<{
14+
error?: string;
15+
result?: {
16+
validation?: {
17+
reason?: string | null;
18+
};
19+
};
20+
}>;
21+
22+
type NavigateFunction = (route: string, params?: {
23+
screen?: string;
24+
params?: {
25+
errorType?: string;
26+
errorMessage?: string;
27+
};
28+
}) => void;
29+
30+
type DispatchFunction = (action: {
31+
type: string;
32+
payload: boolean;
33+
}) => void;
34+
35+
describe('Blockaid Validation Logic', () => {
36+
const mockNavigate = jest.fn() as jest.MockedFunction<NavigateFunction>;
37+
const mockDispatch = jest.fn() as jest.MockedFunction<DispatchFunction>;
38+
const mockValidateBridgeTx = jest.fn() as jest.MockedFunction<ValidateBridgeTx>;
39+
40+
beforeEach(() => {
41+
jest.clearAllMocks();
42+
});
43+
44+
// Simulate the handleContinue logic extracted for testing
45+
const handleContinueLogic = async (
46+
activeQuote: QuoteResponse & QuoteMetadata | null,
47+
validateBridgeTx: ValidateBridgeTx,
48+
navigate: NavigateFunction,
49+
dispatch: DispatchFunction
50+
) => {
51+
if (activeQuote) {
52+
dispatch({ type: 'setIsSubmittingTx', payload: true });
53+
54+
try {
55+
const validationResult = await validateBridgeTx({
56+
quoteResponse: activeQuote,
57+
});
58+
59+
if (validationResult.error || validationResult.result?.validation?.reason) {
60+
const isValidationError = !!validationResult.result?.validation?.reason;
61+
navigate('BRIDGE_MODALS_ROOT', {
62+
screen: 'BLOCKAID_MODAL',
63+
params: {
64+
errorType: isValidationError ? 'validation' : 'simulation',
65+
errorMessage: isValidationError
66+
? (validationResult.result?.validation?.reason || '')
67+
: validationResult.error,
68+
},
69+
});
70+
return;
71+
}
72+
73+
// If validation passes, continue with transaction submission
74+
// TODO: Implement actual transaction submission logic
75+
76+
} catch (error) {
77+
// eslint-disable-next-line no-console
78+
console.error('Error submitting bridge tx', error);
79+
} finally {
80+
dispatch({ type: 'setIsSubmittingTx', payload: false });
81+
}
82+
}
83+
};
84+
85+
// Helper to create a simple mock quote for testing
86+
const createMockQuote = (id: string) => ({
87+
id,
88+
quote: {} as QuoteResponse['quote'],
89+
approval: null,
90+
trade: {} as QuoteResponse['trade'],
91+
estimatedProcessingTimeInSeconds: 60,
92+
// Add other required properties as needed
93+
}) as unknown as QuoteResponse & QuoteMetadata;
94+
95+
it('should navigate to blockaid modal with validation error', async () => {
96+
const mockQuote = createMockQuote('test-quote');
97+
98+
mockValidateBridgeTx.mockResolvedValue({
99+
result: {
100+
validation: {
101+
reason: 'Transaction may result in loss of funds',
102+
},
103+
},
104+
});
105+
106+
await act(async () => {
107+
await handleContinueLogic(mockQuote, mockValidateBridgeTx, mockNavigate, mockDispatch);
108+
});
109+
110+
expect(mockValidateBridgeTx).toHaveBeenCalledWith({
111+
quoteResponse: mockQuote,
112+
});
113+
114+
expect(mockNavigate).toHaveBeenCalledWith('BRIDGE_MODALS_ROOT', {
115+
screen: 'BLOCKAID_MODAL',
116+
params: {
117+
errorType: 'validation',
118+
errorMessage: 'Transaction may result in loss of funds',
119+
},
120+
});
121+
122+
expect(mockDispatch).toHaveBeenCalledWith({ type: 'setIsSubmittingTx', payload: true });
123+
expect(mockDispatch).toHaveBeenCalledWith({ type: 'setIsSubmittingTx', payload: false });
124+
});
125+
126+
it('should navigate to blockaid modal with simulation error', async () => {
127+
const mockQuote = createMockQuote('test-quote');
128+
129+
mockValidateBridgeTx.mockResolvedValue({
130+
error: 'Simulation failed: Insufficient balance for transaction',
131+
result: {
132+
validation: {
133+
reason: null,
134+
},
135+
},
136+
});
137+
138+
await act(async () => {
139+
await handleContinueLogic(mockQuote, mockValidateBridgeTx, mockNavigate, mockDispatch);
140+
});
141+
142+
expect(mockNavigate).toHaveBeenCalledWith('BRIDGE_MODALS_ROOT', {
143+
screen: 'BLOCKAID_MODAL',
144+
params: {
145+
errorType: 'simulation',
146+
errorMessage: 'Simulation failed: Insufficient balance for transaction',
147+
},
148+
});
149+
});
150+
151+
it('should prioritize validation error over simulation error', async () => {
152+
const mockQuote = createMockQuote('test-quote');
153+
154+
mockValidateBridgeTx.mockResolvedValue({
155+
error: 'Simulation failed: Some simulation error',
156+
result: {
157+
validation: {
158+
reason: 'Transaction flagged as suspicious',
159+
},
160+
},
161+
});
162+
163+
await act(async () => {
164+
await handleContinueLogic(mockQuote, mockValidateBridgeTx, mockNavigate, mockDispatch);
165+
});
166+
167+
expect(mockNavigate).toHaveBeenCalledWith('BRIDGE_MODALS_ROOT', {
168+
screen: 'BLOCKAID_MODAL',
169+
params: {
170+
errorType: 'validation',
171+
errorMessage: 'Transaction flagged as suspicious',
172+
},
173+
});
174+
});
175+
176+
it('should proceed without blockaid modal when validation passes', async () => {
177+
const mockQuote = createMockQuote('test-quote');
178+
179+
mockValidateBridgeTx.mockResolvedValue({
180+
result: {
181+
validation: {
182+
reason: null,
183+
},
184+
},
185+
});
186+
187+
await act(async () => {
188+
await handleContinueLogic(mockQuote, mockValidateBridgeTx, mockNavigate, mockDispatch);
189+
});
190+
191+
expect(mockNavigate).not.toHaveBeenCalledWith('BRIDGE_MODALS_ROOT', {
192+
screen: 'BLOCKAID_MODAL',
193+
params: expect.any(Object),
194+
});
195+
});
196+
197+
it('should handle validation hook errors gracefully', async () => {
198+
const mockQuote = createMockQuote('test-quote');
199+
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
200+
201+
mockValidateBridgeTx.mockRejectedValue(new Error('Network error'));
202+
203+
await act(async () => {
204+
await handleContinueLogic(mockQuote, mockValidateBridgeTx, mockNavigate, mockDispatch);
205+
});
206+
207+
expect(consoleSpy).toHaveBeenCalledWith('Error submitting bridge tx', expect.any(Error));
208+
209+
expect(mockNavigate).not.toHaveBeenCalledWith('BRIDGE_MODALS_ROOT', {
210+
screen: 'BLOCKAID_MODAL',
211+
params: expect.any(Object),
212+
});
213+
214+
expect(mockDispatch).toHaveBeenCalledWith({ type: 'setIsSubmittingTx', payload: false });
215+
216+
consoleSpy.mockRestore();
217+
});
218+
219+
it('should handle malformed validation responses', async () => {
220+
const mockQuote = createMockQuote('test-quote');
221+
222+
mockValidateBridgeTx.mockResolvedValue({
223+
// Missing result property
224+
} as Partial<Awaited<ReturnType<ValidateBridgeTx>>>);
225+
226+
await act(async () => {
227+
await handleContinueLogic(mockQuote, mockValidateBridgeTx, mockNavigate, mockDispatch);
228+
});
229+
230+
// Should not crash and should not navigate to blockaid modal
231+
expect(mockNavigate).not.toHaveBeenCalledWith('BRIDGE_MODALS_ROOT', {
232+
screen: 'BLOCKAID_MODAL',
233+
params: expect.any(Object),
234+
});
235+
236+
expect(mockDispatch).toHaveBeenCalledWith({ type: 'setIsSubmittingTx', payload: false });
237+
});
238+
239+
it('should handle undefined validation responses', async () => {
240+
const mockQuote = createMockQuote('test-quote');
241+
242+
mockValidateBridgeTx.mockResolvedValue(undefined as unknown as Awaited<ReturnType<ValidateBridgeTx>>);
243+
244+
await act(async () => {
245+
await handleContinueLogic(mockQuote, mockValidateBridgeTx, mockNavigate, mockDispatch);
246+
});
247+
248+
// Should not crash and should not navigate to blockaid modal
249+
expect(mockNavigate).not.toHaveBeenCalledWith('BRIDGE_MODALS_ROOT', {
250+
screen: 'BLOCKAID_MODAL',
251+
params: expect.any(Object),
252+
});
253+
});
254+
});

0 commit comments

Comments
 (0)