Skip to content

fix(swap): make the AdvancedMode path an opt-in, and pin the routing - #419

Merged
BitHighlander merged 1 commit into
developfrom
fix/advanced-mode-opt-in-not-reject
Aug 15, 2026
Merged

fix(swap): make the AdvancedMode path an opt-in, and pin the routing#419
BitHighlander merged 1 commit into
developfrom
fix/advanced-mode-opt-in-not-reject

Conversation

@BitHighlander

Copy link
Copy Markdown
Collaborator

A user should never get a dead reject for a transaction AdvancedMode would
allow. They should be offered the switch.

That machinery already exists and I under-sold it in #417. SwapDialog.tsx:2307
routes on the error CONTENT:

if (/AdvancedMode/i.test(raw)) { setBlindSignCause('device')
                                 setPhase('blind-signing-required') }

which opens the panel whose Enable button calls applyPolicy. So #417's message
was already reaching the opt-in, not a reject — the reject is the PRE-#417 path,
where hdwallet flattens the firmware's "Blind signing disabled by policy" into a
bare "Action cancelled" that matches nothing and falls through to the generic
"Cancelled on device".

Two things wrong with leaving it there.

  1. The copy contradicted the affordance. It said "Enable AdvancedMode on your
    KeepKey, then try the swap again" — sending the user to device settings for
    something the dialog was about to offer as a button. Reworded to "Turn on
    AdvancedMode to continue", and it still states the setting dies on reboot
    (firmware feat(hive): serialize limit_order_create / limit_order_cancel #373 made it session state, which is why users hit this repeatedly).

  2. The routing depended on an unpinned phrase inside prose. Rewording the copy
    is a normal, innocuous-looking edit that would silently turn the prompt back
    into a reject, with no test failing.

So the message moves to evmAdvancedModeRequiredMessage() in shared/types.ts,
beside SOLANA_BLIND_SIGNING_REQUIRED, documented as content-routed and
load-bearing. tests/advanced-mode-routing.test.ts asserts it matches the
SwapDialog predicate for every chain name, does NOT tell the user to go
elsewhere, and mentions the reboot. It also pins the predicate from the other
side: a bare "Action cancelled" must NOT match (that is the dead end), while
firmware-worded refusals must.

Content-based routing is deliberate, per the comment above that branch: it also
catches refusals phrased by firmware versions this code has never seen. The
tests protect the phrase without giving that up.

make test-unit 488 pass, 0 fail across 33 files, +35 btc-backend, +10 cosmos
tsc --noEmit 627, unchanged, 2 below baseline 629

A user should never get a dead reject for a transaction AdvancedMode would
allow. They should be offered the switch.

That machinery already exists and I under-sold it in #417. SwapDialog.tsx:2307
routes on the error CONTENT:

    if (/AdvancedMode/i.test(raw)) { setBlindSignCause('device')
                                     setPhase('blind-signing-required') }

which opens the panel whose Enable button calls applyPolicy. So #417's message
was already reaching the opt-in, not a reject — the reject is the PRE-#417 path,
where hdwallet flattens the firmware's "Blind signing disabled by policy" into a
bare "Action cancelled" that matches nothing and falls through to the generic
"Cancelled on device".

Two things wrong with leaving it there.

1. The copy contradicted the affordance. It said "Enable AdvancedMode on your
   KeepKey, then try the swap again" — sending the user to device settings for
   something the dialog was about to offer as a button. Reworded to "Turn on
   AdvancedMode to continue", and it still states the setting dies on reboot
   (firmware #373 made it session state, which is why users hit this repeatedly).

2. The routing depended on an unpinned phrase inside prose. Rewording the copy
   is a normal, innocuous-looking edit that would silently turn the prompt back
   into a reject, with no test failing.

So the message moves to evmAdvancedModeRequiredMessage() in shared/types.ts,
beside SOLANA_BLIND_SIGNING_REQUIRED, documented as content-routed and
load-bearing. __tests__/advanced-mode-routing.test.ts asserts it matches the
SwapDialog predicate for every chain name, does NOT tell the user to go
elsewhere, and mentions the reboot. It also pins the predicate from the other
side: a bare "Action cancelled" must NOT match (that is the dead end), while
firmware-worded refusals must.

Content-based routing is deliberate, per the comment above that branch: it also
catches refusals phrased by firmware versions this code has never seen. The
tests protect the phrase without giving that up.

make test-unit   488 pass, 0 fail across 33 files, +35 btc-backend, +10 cosmos
tsc --noEmit     627, unchanged, 2 below baseline 629
@BitHighlander
BitHighlander merged commit 2676776 into develop Aug 15, 2026
4 checks passed
@BitHighlander
BitHighlander deleted the fix/advanced-mode-opt-in-not-reject branch August 15, 2026 07:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant