feat: add friction for malicious transactions instead of blocking#104
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Stellar Snap confirmation UX to add friction (rather than outright blocking) for malicious transaction-scan results: the primary action becomes “Review alert”, users are routed to a second acknowledgement screen, and only then can they proceed to confirm.
Changes:
- Introduces a shared malicious acknowledgement screen + event handlers, and routes malicious confirmations through this flow.
- Refactors rendering to a shared
renderConfirmationViewand adds a sharedConfirmationFooterto centralize primary-button behavior. - Narrows
isConfirmDisabledByScanto only disable confirmation while a scan is actively fetching, and addsrequiresMaliciousAcknowledgementto drive the new UX.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/snap/src/ui/confirmation/views/render.tsx | Centralizes confirmation view rendering and adds acknowledgement-screen override. |
| packages/snap/src/ui/confirmation/views/MaliciousAcknowledgement/MaliciousAcknowledgementScreen.tsx | New acknowledgement “friction” screen UI for malicious scan results. |
| packages/snap/src/ui/confirmation/views/MaliciousAcknowledgement/MaliciousAcknowledgementScreen.test.tsx | Unit tests for acknowledgement screen rendering and button/checkbox state. |
| packages/snap/src/ui/confirmation/views/MaliciousAcknowledgement/events.tsx | New shared user-input handlers for review/ack/back/proceed flow. |
| packages/snap/src/ui/confirmation/views/MaliciousAcknowledgement/events.test.tsx | Unit tests for acknowledgement event handlers and interface updates. |
| packages/snap/src/ui/confirmation/views/MaliciousAcknowledgement/constants.ts | Shared form element names for acknowledgement flow. |
| packages/snap/src/ui/confirmation/views/ConfirmSignTransaction/ConfirmSignTransaction.tsx | Uses shared footer and malicious acknowledgement gating. |
| packages/snap/src/ui/confirmation/views/ConfirmSignChangeTrustOptOut/ConfirmSignChangeTrustOptOut.tsx | Uses shared footer and malicious acknowledgement gating; scan disable logic updated. |
| packages/snap/src/ui/confirmation/views/ConfirmSignChangeTrustOptIn/ConfirmSignChangeTrustOptIn.tsx | Uses shared footer and malicious acknowledgement gating; scan disable logic updated. |
| packages/snap/src/ui/confirmation/views/ConfirmSendTransaction/ConfirmSendTransaction.tsx | Uses shared footer and malicious acknowledgement gating; scan disable logic updated. |
| packages/snap/src/ui/confirmation/utils.ts | Adjusts scan-based disable rules and adds requiresMaliciousAcknowledgement. |
| packages/snap/src/ui/confirmation/utils.test.ts | Updates tests for scan-disable changes and adds tests for acknowledgement requirement logic. |
| packages/snap/src/ui/confirmation/controller.tsx | Persists interfaceKey in context and uses shared render path. |
| packages/snap/src/ui/confirmation/components/index.ts | Re-exports new ConfirmationFooter. |
| packages/snap/src/ui/confirmation/components/ConfirmationFooter.tsx | New shared footer with malicious “Review alert” primary-action behavior. |
| packages/snap/src/ui/confirmation/components/ConfirmationFooter.test.tsx | Unit tests for shared footer behavior. |
| packages/snap/src/ui/confirmation/api.ts | Extends base confirmation context with interface key + acknowledgement screen state. |
| packages/snap/src/handlers/user-input/userInput.ts | Registers acknowledgement event handlers alongside existing confirmation handlers. |
| packages/snap/snap.manifest.json | Updates bundle shasum for the new build output. |
| packages/snap/messages.json | Adds new i18n strings and updates banner copy. |
| packages/snap/locales/es.json | Mirrors new i18n strings and updated banner copy. |
| packages/snap/locales/en.json | Mirrors new i18n strings and updated banner copy. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ction-acknowledgement
stanleyyconsensys
left a comment
There was a problem hiding this comment.
TBH, the logic has no issue
but it is a bit hard to understand by the naming of those condition check
let me know if we can update the name?
i also have a small question for onProcessClick
| scan, | ||
| scanFetchStatus, | ||
| }); | ||
| const shouldDisableConfirmButton = isConfirmBlocked({ scanFetchStatus }); |
There was a problem hiding this comment.
nit: can we add a comment saying sign txn does not apply local simluation?
| transactionsFetchStatus?: FetchStatus; | ||
| }): boolean { | ||
| return ( | ||
| isConfirmDisabledByScan({ |
There was a problem hiding this comment.
nit:
shall we update the name to
isRemoteTransactionScanLoading
| isConfirmDisabledByScan({ | ||
| scanFetchStatus: params.scanFetchStatus ?? FetchStatus.Initial, | ||
| }) || | ||
| isConfirmDisabledByTransactionValidation(params.transactionsFetchStatus) |
There was a problem hiding this comment.
nit:
should we rename to
isLocalTransactionValidateFail
| scan, | ||
| scanFetchStatus, | ||
| }) || isConfirmDisabledByTransactionValidation(transactionsFetchStatus); | ||
| const shouldDisableConfirmButton = isConfirmBlocked({ |
There was a problem hiding this comment.
nit:
should we rename to shouldDisableConfirmation
becoz isConfirmBlocked, mean if we check the confrim is blocked or not
but actually we are referring to should we do xxxxx
| | undefined, | ||
| }) | ||
| ) { | ||
| return; |
There was a problem hiding this comment.
if return means the screen will freeze ?
shall we go back instead?
There was a problem hiding this comment.
Good catch. Fixed it to go back to the confirmation view instead of returning a silent click
Summary
We don't outright block users from any confirmation today, and per Blockaid the user should always be able to decide. This PR replaces the implicit block on malicious transactions with friction, mirroring EVM:
Applies to all four scanned flows: send, change-trust opt-in, change-trust opt-out, and sign-transaction.
Changes
isConfirmDisabledByScanto only block while a scan is fetching.requiresMaliciousAcknowledgement({ preferences, scan })to drive the review flow.MaliciousAcknowledgementScreenand acknowledgement event handlers.ConfirmationFooterto remove button duplication across views.renderConfirmationViewintoviews/render.tsxfor a single rendering path.interfaceKeyand acknowledgement state in the interface context.Implementation notes
Screenshots