fix(frontend): Keplr + Ledger Nano signing stall (chain refresh, amino, recovery UX) #567

Closed
opened 2026-08-18 23:53:07 +00:00 by PlasticDigits · 7 comments
PlasticDigits commented 2026-08-18 23:53:07 +00:00 (Migrated from gitlab.com)

Summary

Keplr connected to a Ledger Nano X can stall on the Keplr–Ledger signing UI during a Terra Classic swap (first report: USTR). The swap itself succeeded after the user refreshed chains in Keplr, installed/opened Cosmos then switched back to Luna Classic on the device, and retried. The dApp currently has no Ledger detection, no pre-sign Keplr chain refresh, no hardware-wallet recovery copy, and no bounded wait on the split-path sign step — so first-time Ledger users look stuck.

Not USTR-specific. Same sign/broadcast stack is used for Swap, Trade market, limits, pool, wrap, and /ust1.


User report (2026-08)

When I 1st tried, it did not go forward and stucked at Kepler-ledger Interface, i refreshed my chains on ledger and kepler account, installed cosmos chain and switched back to luna classic on ledger then it went forward (refresh was needed)

Environment inferred from report: columbus-5, Keplr extension → Ledger Nano X, first USTR swap. Tx eventually succeeded.


Current codebase

Connect and chain metadata

Sign / broadcast

  • Canonical path: broadcastTerraExecuteContracts → split signTerraTxRaw + RPC broadcast for Keplr extension (#359), or atomic wallet.broadcastTx for some WC wallets.
  • terraWalletSignTxRaw.ts walletUsesAmino():
    • Station and Cosmostation → always amino.
    • Keplr → amino only if wallet.useAmino === true (cosmes KeplrExtension ctor, fed from getKey().isNanoLedger in upstream). Otherwise signDirect.
    • walletUsesAmino is not exported and has no unit tests. If useAmino is missing on the connected object, Ledger Keplr would SIGN_MODE_DIRECT, which commonly hangs on the Keplr–Ledger UI unless the Cosmos app + blind signing are in a good state.
  • Cosmes patch (patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch): Station always amino because “Ledger already required amino in upstream cosmes” (#208). Keplr still depends on isNanoLedger → useAmino.
  • Timeouts (#173): broadcastTx / LCD poll are capped (30s / 90s). Split-path signTerraTxRaw is unbounded. A Ledger hang stays on phase signing with button “Signing…” forever. If anyone later wraps sign in the 30s broadcast timeout, Ledger users who are still unlocking the device would get the wrong copy: “Could not broadcast the transaction. Check your connection…” (terraTxTimeout.ts).
  • Phase UI (#305): terraBroadcastUi.ts / TerraBroadcastPendingLink.tsx — signing has no status message. Recovery copy exists only for post-sign recovering.
  • vite-env.d.ts types getKey().isNanoLedger on Station’s Keplr shim. The dApp never reads it for UX.

Product / QA docs today

Related (do not duplicate): #127 LocalTerra fees, #208 Station amino / no second signAmino, #173 broadcast/poll timeout, #359 post-sign recovery (must not invite immediate retry after a signature exists), #429 mainnet fee guard off, #235 Station not on LocalTerra, #489 retail copy.


Why a new implementation is needed

The protocol and quote path worked. The wallet transport did not complete until the user performed a Ledger app switch + Keplr chain refresh that the dApp never mentioned.

Without this work:

  1. First-time Keplr + Ledger swaps look broken (spinner / Keplr–Ledger UI with no next step).
  2. Users may retry while a signature is still in flight (double-submit risk — must follow #359, not “just retry”).
  3. Software-Keplr and Station mitigations (pre-sign suggest, amino, timeouts) do not cover this path.
  4. Support will keep rediscovering “refresh Keplr + open Luna Classic, not Cosmos”.

Constraints / guardrails

  1. Do not treat this as a USTR or pair bug. Token choice is out of scope.
  2. Do not apply the 30s TERRA_TX_BROADCAST_TIMEOUT_MS to Ledger signing. That timeout is for RPC/transport. Ledger confirmations need a longer, separately labeled sign wait (minutes, not 30s). Wrong copy must not say “check your connection”.
  3. Do not invite immediate retry after a signature exists (#359). Pre-sign hang (no signed bytes) is safe to cancel/retry; post-sign unknown must recover, not double-execute.
  4. Do not force amino for all Keplr. Software Keplr signDirect is the working mainnet path. Amino is required for Ledger (isNanoLedger / useAmino), matching upstream cosmes and Station’s Ledger comment in the patch. Do not re-prompt signAmino after approval (#208).
  5. Do not enable the post-sign fee/gas guard on columbus-5 (#429 / SEC-E08).
  6. Pre-sign experimentalSuggestChain for Keplr must be best-effort (warn, don’t fail the swap) and must not spam an “add chain” modal every trade when the chain is already present (Station already does this pattern).
  7. Copy: retail, short, no jargon dump (#489). Say Terra Classic / LUNA app, not Cosmos, unlock Ledger, refresh Terra Classic in Keplr if stuck. Do not mention coin types 330/118 in the UI.
  8. Do not add a new wallet brand or Leap. Keplr+Ledger stays Keplr extension.
  9. Playwright / Cloud Agent cannot drive a physical Ledger. Automated tests are mocks + copy/unit; manual columbus-5 Keplr+Nano is the acceptance path. LocalTerra Keplr remains software or simulated (#235).
  10. Out of scope: Station+Ledger, Cosmostation+Ledger as separate products (shared copy helper OK if isNanoLedger is true). Do not change contract/indexer code.

Relevant files

File Role
frontend-dapp/src/services/terraclassic/wallet.ts Connect-time Keplr suggest; persist Ledger flag if needed
frontend-dapp/src/services/terraclassic/terraChainSuggestion.ts Chain metadata (coin type 330)
frontend-dapp/src/services/terraclassic/stationExtensionConfig.ts Pattern to copy: pre-sign suggest (Station-only today)
frontend-dapp/src/services/terraclassic/terraBroadcast.ts Call Keplr pre-sign prepare; sign-phase timeout vs #173 broadcast timeout
frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts walletUsesAmino — Ledger must amino; export for tests
frontend-dapp/src/utils/terraTxTimeout.ts / terraBroadcastUi.ts New sign-stall copy; do not reuse broadcast-timeout strings
frontend-dapp/src/components/ui/TerraBroadcastPendingLink.tsx Show Ledger hint during signing
frontend-dapp/src/vite-env.d.ts isNanoLedger already typed
frontend-dapp/patches/@goblinhunt+cosmes+*.patch Only if KeplrController must pass isNanoLedger more reliably — update .cosmes-patch-sha256 + cosmesPatch127.test.ts
docs/frontend.md New subsection: Keplr + Ledger signing
docs/qa-onboarding.md / QA_TEMPLATE.md / docs/runbooks/extension-fee-guard-wallet-qa.md Wallet matrix: Keplr+Ledger P1 on columbus-5
docs/user-incident-faq.md or a short user-help section Recovery steps matching the report
skills/AGENTS_FRONTEND_STATION_SIGNING.md or new AGENTS_FRONTEND_KEPLR_LEDGER.md Agent playbook
Tests terraWalletSignTxRaw (or new) amino vs direct; broadcast UI copy; make verify-issue-567

  1. Detect Ledger from Keplr getKey(chainId).isNanoLedger (and/or ConnectedWallet.useAmino). Keep it on the connected wallet for the session.
  2. prepareKeplrExtensionForTerraClassicSign (name flexible): experimentalSuggestChain(getTerraChainSuggestion()) before Keplr extension sign, same call sites as Station’s prepare. Failures → console.warn, continue.
  3. Split-path amino: walletUsesAmino is true for Keplr when useAmino or detected Nano Ledger. Unit-test both; never signDirect for Ledger.
  4. Signing-phase UX: while phase === 'signing' and Ledger (or after ~10–15s for any Keplr extension): short hint — open Terra Classic (LUNA) on the device, not Cosmos; approve in Keplr; if it stays blank, refresh Terra Classic in Keplr and retry.
  5. Sign-stall timeout (Ledger / Keplr extension sign only): long bound (e.g. 3–5 min, env-overridable). On expiry, distinct retail error (not #173 broadcast copy) with the same recovery steps. Submit re-enables only if no signed tx was produced.
  6. Docs + make verify-issue-567: invariants in docs/frontend.md, QA matrix row, FAQ recovery, playbook.

Acceptance criteria

  • Keplr software extension swap/limit/pool still signs as today (signDirect unless wallet says amino). No new modal spam.
  • Keplr Ledger (isNanoLedger): split path uses amino; never signDirect.
  • Before Keplr extension sign, dApp best-effort chain suggest (parity with Station pre-sign refresh).
  • During signing, Ledger (or delayed Keplr) users see recovery hint (LUNA app, not Cosmos; refresh chain if stuck). Button may stay Signing….
  • Sign hang with no signature eventually surfaces Ledger/Keplr recovery copy, not “check your connection”, and allows retry.
  • After a signature exists, behavior remains #359 (no immediate retry).
  • #429 mainnet fee guard stays off; #208 no second signAmino.
  • Docs: frontend subsection, QA matrix Keplr+Ledger Nano columbus-5, FAQ/help, agent playbook, make verify-issue-567.

Test plan (functional)

  1. Unit — amino vs direct: mock Keplr useAmino/isNanoLedger true → signAmino + preferNoSetFee; false → signDirect. Station/Cosmostation unchanged (always amino).
  2. Unit — pre-sign suggest: Keplr extension broadcast calls suggest (or shared prepare). Suggest rejection does not fail the tx. Station path unchanged. Software Keplr still broadcasts.
  3. Unit — signing copy: Ledger + phase=signing shows hint; software Keplr does not show Ledger-only text at t=0 (delayed generic “approve in Keplr” OK). recovering copy unchanged (#359).
  4. Unit — sign-stall timeout: fake hung signAmino past bound → Ledger recovery message; isPending clears; no TERRA_TX_BROADCAST_TIMEOUT_MESSAGE. Hung signAmino that later resolves after UI timeout must not double-broadcast (ignore late signature or lock).
  5. Unit — #173 / #359 regression: broadcast/poll timeouts and post-sign recovery tests still pass (transactions.test.ts, terraBroadcastRecovery.test.ts).
  6. Manual columbus-5 (required): Keplr + Ledger Nano X (or S Plus):
    • Happy: Terra Classic app open → USTR (or any) swap confirms on device → tx succeeds.
    • Repro: Cosmos app open (or stale HID) → signing UI stalls → dApp hint visible → switch to Luna Classic + refresh Keplr chain → retry succeeds.
    • Reject on device → Transaction rejected by user; form re-enabled; no broadcast.
    • Software Keplr (no Ledger): one swap, no Ledger copy, no extra click.
  7. Manual LocalTerra: Keplr software + simulated wallet only (#235). Do not require a Ledger on LocalTerra.
  8. Regression: make test-frontend; make verify-issue-429; Station signing tests; Swap pre-sign panel (#409) still present.

Test plan (attack / abuse)

Vector Expectation
User retries Swap while Keplr/Ledger still signing the first tx Sign lock (#208) — no second popup. No two broadcasts from one click.
Sign-stall timeout fires, then Ledger approves the old prompt Must not broadcast a late signature after the mutation settled (or must run #359 recovery if bytes already signed). No silent double-swap.
Attacker-controlled chain suggest metadata Use only getTerraChainSuggestion() (our RPC/LCD/coin type 330). Do not pass wallet-supplied chain JSON.
Phishing copy / fake “Ledger support” Hint is informational. It must not ask for seed, PIN, or “sign this message” off the swap summary. Pre-sign summary (#409) stays.
Mainnet fee-guard bypass via “Ledger path” Guard remains LocalTerra-only (#429). Ledger amino fees still use dApp Fee + preferNoSetFee.
Timeout copy that encourages immediate retry after sign Forbidden (#359). Retry CTA only when no signed tx exists.
javascript: / unexpected URLs in hint Plain text or in-app only; no extra wallet-origin links except existing explorer after hash.

Verification

  • make verify-issue-567 (new): unit tests for amino/Ledger, pre-sign suggest hook, signing hint, sign-stall copy ≠ broadcast timeout; doc markers in docs/frontend.md + QA matrix + playbook.
  • make test-frontend green; make verify-issue-429 green.
  • Manual note on this issue: columbus-5 Keplr + Ledger Nano — stall with Cosmos app, recover with Luna Classic + chain refresh, then successful swap.

Owner type

frontend

Priority

medium — funds were not lost; first-attempt Ledger UX is a production support trap.

## Summary Keplr connected to a **Ledger Nano X** can stall on the Keplr–Ledger signing UI during a Terra Classic swap (first report: USTR). The swap itself succeeded after the user **refreshed chains in Keplr**, **installed/opened Cosmos then switched back to Luna Classic on the device**, and retried. The dApp currently has **no Ledger detection, no pre-sign Keplr chain refresh, no hardware-wallet recovery copy, and no bounded wait on the split-path sign step** — so first-time Ledger users look stuck. **Not USTR-specific.** Same sign/broadcast stack is used for Swap, Trade market, limits, pool, wrap, and `/ust1`. --- ## User report (2026-08) > When I 1st tried, it did not go forward and stucked at Kepler-ledger Interface, i refreshed my chains on ledger and kepler account, installed cosmos chain and switched back to luna classic on ledger then it went forward (refresh was needed) Environment inferred from report: **columbus-5**, **Keplr extension → Ledger Nano X**, first USTR swap. Tx eventually succeeded. --- ## Current codebase ### Connect and chain metadata - [`frontend-dapp/src/services/terraclassic/wallet.ts`](frontend-dapp/src/services/terraclassic/wallet.ts) — Keplr extension `experimentalSuggestChain` runs **once at connect**. Station gets a **second post-connect suggest** (#127 / #208). **Keplr does not.** - [`frontend-dapp/src/services/terraclassic/terraChainSuggestion.ts`](frontend-dapp/src/services/terraclassic/terraChainSuggestion.ts) — Terra Classic suggestion uses **bip44 coin type 330**, `uluna` fees, chain id from `NETWORKS`. Ledger Cosmos Hub is typically **118** + Cosmos app; Terra Classic needs the **Terra / Luna Classic app** on the device. - [`frontend-dapp/src/services/terraclassic/stationExtensionConfig.ts`](frontend-dapp/src/services/terraclassic/stationExtensionConfig.ts) — `prepareStationExtensionForTerraClassicSign()` re-suggests chain **immediately before every Station sign**. [`terraBroadcast.ts`](frontend-dapp/src/services/terraclassic/terraBroadcast.ts) calls this **only when `wallet.id === STATION`**. **Keplr has no equivalent pre-sign refresh** — this is the closest code-side match to the user’s “refresh was needed”. ### Sign / broadcast - Canonical path: [`broadcastTerraExecuteContracts`](frontend-dapp/src/services/terraclassic/terraBroadcast.ts) → split `signTerraTxRaw` + RPC broadcast for Keplr extension ([#359](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/359)), or atomic `wallet.broadcastTx` for some WC wallets. - [`terraWalletSignTxRaw.ts`](frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts) `walletUsesAmino()`: - Station and Cosmostation → **always amino**. - Keplr → amino **only if** `wallet.useAmino === true` (cosmes `KeplrExtension` ctor, fed from `getKey().isNanoLedger` in upstream). Otherwise **`signDirect`**. - `walletUsesAmino` is **not exported** and has **no unit tests**. If `useAmino` is missing on the connected object, **Ledger Keplr would SIGN_MODE_DIRECT**, which commonly **hangs on the Keplr–Ledger UI** unless the Cosmos app + blind signing are in a good state. - Cosmes patch ([`patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch`](frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch)): Station **always** amino because “**Ledger already required amino in upstream cosmes**” (#208). Keplr still depends on `isNanoLedger` → `useAmino`. - Timeouts ([#173](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/173)): **`broadcastTx` / LCD poll** are capped (30s / 90s). **Split-path `signTerraTxRaw` is unbounded.** A Ledger hang stays on phase `signing` with button **“Signing…”** forever. If anyone later wraps sign in the **30s broadcast** timeout, Ledger users who are still unlocking the device would get the **wrong** copy: *“Could not broadcast the transaction. Check your connection…”* ([`terraTxTimeout.ts`](frontend-dapp/src/utils/terraTxTimeout.ts)). - Phase UI ([#305](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/305)): [`terraBroadcastUi.ts`](frontend-dapp/src/utils/terraBroadcastUi.ts) / [`TerraBroadcastPendingLink.tsx`](frontend-dapp/src/components/ui/TerraBroadcastPendingLink.tsx) — `signing` has **no status message**. Recovery copy exists only for post-sign **`recovering`**. - [`vite-env.d.ts`](frontend-dapp/src/vite-env.d.ts) types `getKey().isNanoLedger` on Station’s Keplr shim. **The dApp never reads it** for UX. ### Product / QA docs today - Pre-sign swap summary (#409 / SEC-D11) is **phishing-resistant field labels**, not hardware-wallet help ([`AGENTS_FRONTEND_SWAP_SIGNING_CONFIRMATION.md`](skills/AGENTS_FRONTEND_SWAP_SIGNING_CONFIRMATION.md)). - QA wallet matrix ([`docs/qa-onboarding.md`](docs/qa-onboarding.md)) lists Keplr **software extension** only. **No Ledger Nano row.** Launch Keplr QA ([`docs/runbooks/extension-fee-guard-wallet-qa.md`](docs/runbooks/extension-fee-guard-wallet-qa.md)) is software Keplr / simulated wallet. - [`docs/user-incident-faq.md`](docs/user-incident-faq.md) covers pause / blacklist / wrap pause — **not Ledger/Keplr HID stalls**. Related (do not duplicate): [#127](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/127) LocalTerra fees, [#208](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/208) Station amino / no second `signAmino`, [#173](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/173) broadcast/poll timeout, [#359](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/359) post-sign recovery (must not invite immediate retry after a signature exists), [#429](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/429) mainnet fee guard **off**, [#235](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/235) Station not on LocalTerra, [#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489) retail copy. --- ## Why a new implementation is needed The protocol and quote path worked. The **wallet transport** did not complete until the user performed a **Ledger app switch + Keplr chain refresh** that the dApp never mentioned. Without this work: 1. First-time **Keplr + Ledger** swaps look broken (spinner / Keplr–Ledger UI with no next step). 2. Users may **retry while a signature is still in flight** (double-submit risk — must follow #359, not “just retry”). 3. Software-Keplr and Station mitigations (pre-sign suggest, amino, timeouts) **do not cover this path**. 4. Support will keep rediscovering “refresh Keplr + open Luna Classic, not Cosmos”. --- ## Constraints / guardrails 1. **Do not treat this as a USTR or pair bug.** Token choice is out of scope. 2. **Do not apply the 30s `TERRA_TX_BROADCAST_TIMEOUT_MS` to Ledger signing.** That timeout is for RPC/transport. Ledger confirmations need a **longer, separately labeled** sign wait (minutes, not 30s). Wrong copy must not say “check your connection”. 3. **Do not invite immediate retry after a signature exists** ([#359](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/359)). Pre-sign hang (no signed bytes) is safe to cancel/retry; post-sign unknown must recover, not double-execute. 4. **Do not force amino for all Keplr.** Software Keplr `signDirect` is the working mainnet path. Amino is required for **Ledger** (`isNanoLedger` / `useAmino`), matching upstream cosmes and Station’s Ledger comment in the patch. Do **not** re-prompt `signAmino` after approval ([#208](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/208)). 5. **Do not enable the post-sign fee/gas guard on `columbus-5`** ([#429](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/429) / SEC-E08). 6. **Pre-sign `experimentalSuggestChain` for Keplr** must be best-effort (warn, don’t fail the swap) and must not spam an “add chain” modal every trade when the chain is already present (Station already does this pattern). 7. **Copy:** retail, short, no jargon dump ([#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489)). Say **Terra Classic / LUNA app**, **not Cosmos**, **unlock Ledger**, **refresh Terra Classic in Keplr** if stuck. Do not mention coin types 330/118 in the UI. 8. **Do not add a new wallet brand** or Leap. Keplr+Ledger stays **Keplr extension**. 9. **Playwright / Cloud Agent** cannot drive a physical Ledger. Automated tests are mocks + copy/unit; **manual columbus-5 Keplr+Nano** is the acceptance path. LocalTerra Keplr remains software or simulated ([#235](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/235)). 10. **Out of scope:** Station+Ledger, Cosmostation+Ledger as separate products (shared copy helper OK if `isNanoLedger` is true). Do not change contract/indexer code. --- ## Relevant files | File | Role | |------|------| | `frontend-dapp/src/services/terraclassic/wallet.ts` | Connect-time Keplr suggest; persist Ledger flag if needed | | `frontend-dapp/src/services/terraclassic/terraChainSuggestion.ts` | Chain metadata (coin type 330) | | `frontend-dapp/src/services/terraclassic/stationExtensionConfig.ts` | Pattern to copy: pre-sign suggest (Station-only today) | | `frontend-dapp/src/services/terraclassic/terraBroadcast.ts` | Call Keplr pre-sign prepare; sign-phase timeout vs #173 broadcast timeout | | `frontend-dapp/src/services/terraclassic/terraWalletSignTxRaw.ts` | `walletUsesAmino` — Ledger must amino; export for tests | | `frontend-dapp/src/utils/terraTxTimeout.ts` / `terraBroadcastUi.ts` | New **sign-stall** copy; do not reuse broadcast-timeout strings | | `frontend-dapp/src/components/ui/TerraBroadcastPendingLink.tsx` | Show Ledger hint during `signing` | | `frontend-dapp/src/vite-env.d.ts` | `isNanoLedger` already typed | | `frontend-dapp/patches/@goblinhunt+cosmes+*.patch` | Only if KeplrController must pass `isNanoLedger` more reliably — update `.cosmes-patch-sha256` + `cosmesPatch127.test.ts` | | `docs/frontend.md` | New subsection: Keplr + Ledger signing | | `docs/qa-onboarding.md` / `QA_TEMPLATE.md` / `docs/runbooks/extension-fee-guard-wallet-qa.md` | Wallet matrix: Keplr+Ledger P1 on columbus-5 | | `docs/user-incident-faq.md` or a short user-help section | Recovery steps matching the report | | `skills/AGENTS_FRONTEND_STATION_SIGNING.md` or new `AGENTS_FRONTEND_KEPLR_LEDGER.md` | Agent playbook | | Tests | `terraWalletSignTxRaw` (or new) amino vs direct; broadcast UI copy; `make verify-issue-567` | --- ## Recommended direction 1. **Detect Ledger** from Keplr `getKey(chainId).isNanoLedger` (and/or `ConnectedWallet.useAmino`). Keep it on the connected wallet for the session. 2. **`prepareKeplrExtensionForTerraClassicSign`** (name flexible): `experimentalSuggestChain(getTerraChainSuggestion())` before Keplr extension sign, same call sites as Station’s prepare. Failures → `console.warn`, continue. 3. **Split-path amino:** `walletUsesAmino` is true for Keplr when `useAmino` **or** detected Nano Ledger. Unit-test both; never `signDirect` for Ledger. 4. **Signing-phase UX:** while `phase === 'signing'` and Ledger (or after ~10–15s for any Keplr extension): short hint — open **Terra Classic (LUNA)** on the device, **not Cosmos**; approve in Keplr; if it stays blank, refresh Terra Classic in Keplr and retry. 5. **Sign-stall timeout (Ledger / Keplr extension sign only):** long bound (e.g. 3–5 min, env-overridable). On expiry, **distinct** retail error (not #173 broadcast copy) with the same recovery steps. Submit re-enables only if **no signed tx** was produced. 6. **Docs + `make verify-issue-567`:** invariants in `docs/frontend.md`, QA matrix row, FAQ recovery, playbook. --- ## Acceptance criteria - [ ] Keplr **software** extension swap/limit/pool still signs as today (`signDirect` unless wallet says amino). No new modal spam. - [ ] Keplr **Ledger** (`isNanoLedger`): split path uses **amino**; never `signDirect`. - [ ] Before Keplr extension sign, dApp **best-effort chain suggest** (parity with Station pre-sign refresh). - [ ] During `signing`, Ledger (or delayed Keplr) users see **recovery hint** (LUNA app, not Cosmos; refresh chain if stuck). Button may stay `Signing…`. - [ ] Sign hang with **no signature** eventually surfaces **Ledger/Keplr recovery copy**, not “check your connection”, and allows retry. - [ ] After a **signature exists**, behavior remains #359 (no immediate retry). - [ ] `#429` mainnet fee guard stays off; `#208` no second `signAmino`. - [ ] Docs: frontend subsection, QA matrix Keplr+Ledger Nano columbus-5, FAQ/help, agent playbook, `make verify-issue-567`. --- ## Test plan (functional) 1. **Unit — amino vs direct:** mock Keplr `useAmino`/`isNanoLedger` true → `signAmino` + `preferNoSetFee`; false → `signDirect`. Station/Cosmostation unchanged (always amino). 2. **Unit — pre-sign suggest:** Keplr extension broadcast calls suggest (or shared prepare). Suggest rejection does not fail the tx. Station path unchanged. Software Keplr still broadcasts. 3. **Unit — signing copy:** Ledger + `phase=signing` shows hint; software Keplr does not show Ledger-only text at t=0 (delayed generic “approve in Keplr” OK). `recovering` copy unchanged (#359). 4. **Unit — sign-stall timeout:** fake hung `signAmino` past bound → Ledger recovery message; `isPending` clears; **no** `TERRA_TX_BROADCAST_TIMEOUT_MESSAGE`. Hung `signAmino` that later resolves after UI timeout must not double-broadcast (ignore late signature or lock). 5. **Unit — #173 / #359 regression:** broadcast/poll timeouts and post-sign recovery tests still pass (`transactions.test.ts`, `terraBroadcastRecovery.test.ts`). 6. **Manual columbus-5 (required):** Keplr + Ledger Nano X (or S Plus): - Happy: Terra Classic app open → USTR (or any) swap confirms on device → tx succeeds. - Repro: Cosmos app open (or stale HID) → signing UI stalls → dApp hint visible → switch to Luna Classic + refresh Keplr chain → retry succeeds. - Reject on device → `Transaction rejected by user`; form re-enabled; no broadcast. - Software Keplr (no Ledger): one swap, no Ledger copy, no extra click. 7. **Manual LocalTerra:** Keplr software + simulated wallet only (#235). Do not require a Ledger on LocalTerra. 8. **Regression:** `make test-frontend`; `make verify-issue-429`; Station signing tests; Swap pre-sign panel (#409) still present. ## Test plan (attack / abuse) | Vector | Expectation | |--------|-------------| | User retries Swap while Keplr/Ledger still signing the first tx | Sign lock (#208) — no second popup. No two broadcasts from one click. | | Sign-stall timeout fires, then Ledger approves the old prompt | Must **not** broadcast a late signature after the mutation settled (or must run #359 recovery if bytes already signed). No silent double-swap. | | Attacker-controlled chain suggest metadata | Use only `getTerraChainSuggestion()` (our RPC/LCD/coin type 330). Do not pass wallet-supplied chain JSON. | | Phishing copy / fake “Ledger support” | Hint is **informational**. It must not ask for seed, PIN, or “sign this message” off the swap summary. Pre-sign summary (#409) stays. | | Mainnet fee-guard bypass via “Ledger path” | Guard remains LocalTerra-only (#429). Ledger amino fees still use dApp `Fee` + `preferNoSetFee`. | | Timeout copy that encourages immediate retry after sign | Forbidden (#359). Retry CTA only when no signed tx exists. | | `javascript:` / unexpected URLs in hint | Plain text or in-app only; no extra wallet-origin links except existing explorer after hash. | --- ## Verification - `make verify-issue-567` (new): unit tests for amino/Ledger, pre-sign suggest hook, signing hint, sign-stall copy ≠ broadcast timeout; doc markers in `docs/frontend.md` + QA matrix + playbook. - `make test-frontend` green; `make verify-issue-429` green. - Manual note on this issue: columbus-5 Keplr + Ledger Nano — stall with Cosmos app, recover with Luna Classic + chain refresh, then successful swap. ## Owner type frontend ## Priority medium — funds were not lost; first-attempt Ledger UX is a production support trap.
PlasticDigits commented 2026-08-18 23:53:22 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-08-19 01:07:34 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1091

mentioned in merge request !1091
PlasticDigits commented 2026-08-19 02:00:24 +00:00 (Migrated from gitlab.com)

mentioned in commit adc91bb474

mentioned in commit adc91bb47400a06de0ec6bd2aa240a92e5ace81d
PlasticDigits commented 2026-08-19 02:00:38 +00:00 (Migrated from gitlab.com)

mentioned in commit d89a8c7a61

mentioned in commit d89a8c7a61347aa371b22946fb2f1c7a90ca3eba
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-19 02:00:38 +00:00
PlasticDigits commented 2026-08-19 02:16:59 +00:00 (Migrated from gitlab.com)

Post-merge note (!1091 on main). K567 amino Ledger path, pre-sign suggest, 4-minute sign wait, and stall copy (not the 30s broadcast timeout) are on main.

Remaining:

  • Manual columbus-5 gate: Keplr + Ledger Nano — Cosmos-app stall, LUNA app + chain refresh, then successful swap. Physical device QA is the acceptance gate.
  • Manual: software Keplr swap — no Ledger copy. LocalTerra remains software/simulated (#235).
  • CI was not waited. Run make verify-issue-567.
**Post-merge note (!1091 on main).** K567 amino Ledger path, pre-sign suggest, 4-minute sign wait, and stall copy (not the 30s broadcast timeout) are on `main`. Remaining: - **Manual columbus-5 gate:** Keplr + Ledger Nano — Cosmos-app stall, LUNA app + chain refresh, then successful swap. Physical device QA is the acceptance gate. - Manual: software Keplr swap — no Ledger copy. LocalTerra remains software/simulated (#235). - CI was not waited. Run `make verify-issue-567`.
PlasticDigits commented 2026-08-19 02:28:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #573

mentioned in issue #573
PlasticDigits commented 2026-08-19 02:29:01 +00:00 (Migrated from gitlab.com)

marked as related to #573

marked as related to #573
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#567
No description provided.