Back to Freedom.Tech Back
All Ledger Live Mobile releasesAll versions
Release Fri, Oct 2, 2026 9 min read

Ledger Live Mobile 4.23.0

Original release notes

4.23.0

Minor Changes

  • #22217 63960b1 Thanks @vpenskyi-ledger! - Fix the Swap receive field staying empty when reaching Swap from the Earn no-funds screen. Desktop now always identifies a token by toTokenId (plus its toToken alias), which toCurrencyId alone could not do; mobile passed no parameters at all and now forwards the asset. On both, an account the Earn live app synthesised for a token the user does not hold is no longer sent as an unresolvable id
  • Add getCardCashback endpoint, resolving the asset's currency/network pair (both nullable) to its ledgerId
  • Remove getRewardWallet (GET /v1/wallet/reward), its schema, types, mock and handlers
  • useCardCashback replaces useCardRewardWallet in the reward banner
  • Banner subtitle now shows the rate and ticker: "Total cashback 1% in BTC"
  • cardRewardsAvailable / cardRewardCurrency analytics now read the cashback (amount > 0)

Removes the llmThai / lldThai feature flags. Flags resolve on their enabled: false default until the first remote fetch settles, so on every cold start the locale guard saw Thai as unsupported and persisted English over the user's choice.

  • #22667 d90780a Thanks @LucasWerey! - Fix the receive screen "Need a Tag/Memo?" link hit area overlapping the Copy address button (LIVE-38004).
  • #22840 e4c24f3 Thanks @LucasWerey! - Name the default Me contact "Me" in the Pay tab contacts list instead of "My addresses (Me)"
  • #22381 c5964f8 Thanks @ysitbon! - Rename @domain/api-market-sentiment to @domain/api-market-index-fear-and-greed, and its exported api from marketSentimentApi to fearAndGreedApi, so the name states which CoinMarketCap index the package serves
  • #22453 370f955 Thanks @LucasWerey! - Track the add-password flow through @shared/analytics, per the Password tracking plan.

The protection drawer reports its enable button as button_clicked with the variant it offers, and a stored password reports encryption_activated with type: "password" and the entry point it came from. That source is passed by each caller - settings from the Settings row, card from the Pay tab - and carried through the flow's route params instead of being guessed from where the user happens to be. password_enabled and biometrics_enabled join every event and the user's traits, read from whichever scheme protects the app, and the traits are refreshed as soon as a password is stored. No payload carries the password or anything derived from it.

The Settings password and biometrics toggles report toggle_clicked with enabled set to the state the toggle had when it was tapped, on both the revamped and the legacy rows, and the revamped rows now track through @shared/analytics too.

The gate used to lock the moment the app left. It now notes when the app leaves and decides when it comes back: away for 15 seconds or more, the app locks; back sooner, it opens where the user left it. The lock on launch is unchanged, so an app the system ended while away still asks on the way back.

While the app is away, protected content is covered, so the app switcher and the moment of return show a cover rather than the wallet. Under Detox the grace is one second, so e2e specs do not wait out the real one. On Android the return comes from the process lifecycle, like the departure, through a new appDidEnterForeground event on AppVisibilityModule; on iOS it is AppState turning active.

Both protection rows in Settings let anyone turn protection off, so someone holding a card could strip the app of its last lock. Without a card that stays allowed - the app lock is opt-in - but a card turns "at least one protection" into a rule.

Removing the last one is now refused, and a sheet says why at the moment the user asks, rather than a disabled row that explains nothing. Removing one of two is still allowed: at least one protection is the invariant, and which one is the user's choice - so a password can still be swapped for biometrics, or the reverse.

What counts as holding a card is the card session stored on the device, which @features/platform-card owns - not Pay's feature flag, and not whether the Pay tab has been opened. A holder who has not been near Pay on this launch is still a holder, and turning Pay's flag off does not turn the rule off. The session is read when the user asks to remove a protection, not when Settings opens, so a tap can never land before the answer, and a session that started or ended with Settings open is seen. A session that cannot be read counts as a card: a refusal can be retried, while a removal cannot be taken back.

The rule applies to the revamped rows. The legacy rows shown while lwmPasswordRevamp is off are left as they are.

  • #22347 e012138 Thanks @LucasWerey! - Require a password of at least six characters from users who set a shorter one, the next time they get in.

Any length was accepted before this epic - "1" among them - so the minimum the new screens enforce would otherwise apply to new passwords only. The prompt cannot be dismissed: there is no close button, the backdrop does not take a press, and the Android back button is swallowed while it holds the screen. A prompt that can be put off is one that short passwords outlive.

It runs after a successful unlock, not at boot, because that is when the password has been proven and its length is known. Four steps: a sheet that says why, the new password, its confirmation, and a sheet that says it worked. The old password is never asked for again - they just typed it to get in.

The requirement is stored, beside the verifier it describes. The protection state is deliberately not persisted, since a second source of truth about whether a password exists is a lockout risk, and this mark cannot be recomputed from a digest: a verifier says nothing about the length of the password behind it. So it rides in the same keychain record, written and cleared by the single write that sets the password it describes, and the two cannot disagree.

It is also re-derived at every password unlock, which is what heals a record written before the mark existed, and what corrects one whose password was changed elsewhere. The stored mark still earns its place: a biometric unlock never sees a password, and the prompt is owed on that boot too.

A write the keychain declines is reported as a failure rather than as a password change, so the confirmation holds instead of sending the user off with a verifier that is still the old one. The mark's own repair at unlock is best-effort by contrast: metadata must never cost somebody an unlock they have just earned.

An overlay rather than a route: a route is presented over the app in its own window, and the sheets - which present into the app's window - would be visible through it while taking none of the taps aimed at them.

Clearing the cache reboots the React tree without restarting the process, and the gate took its remount for a launch and locked again. Whether the launch lock was decided now lives in the app-lock state, which a reboot leaves in place, so it is decided once per process.

Sharing an address or logs, or saving a file from the share sheet, hands the screen to another app, which Android reports as the app leaving. Those flows now go through leaveAppFor, which holds the background lock off until Android resumes the app. The share promise settles before the user is back, so the hold ends on the resume rather than on the promise, and at once if the task fails before anything was shown. iOS keeps its share sheet inside the app and is unaffected.

React Native's AppState reports background on Android as soon as the activity pauses, and a permission dialog is enough to pause it, so the app lock read a prompt shown over the app as the user leaving. Granting the camera put the user back on the unlock screen instead of the camera.

On Android, the lock now follows the process lifecycle, which only stops once no screen of the app is visible - the signal native apps use for "the app went to the background". iOS keeps AppState, whose background already means the app has left. The platform split lives in one adapter, so the gate asks a single question on both.

isAppBackgrounded, which only the gate used, is removed from @features/platform-app-lock.

  • #22417 8b387f4 Thanks @LucasWerey! - Resume after a biometric prompt only once the app is active again, and let the enable button show it is waiting.

On iOS the system answers the prompt while the Face ID sheet is still leaving, with the app not yet back in the foreground, and native UI presented in that window never appears. The Card login hit it first: pressing Continue straight after enabling Face ID opened no browser, and the web browser module then refused every later attempt until the app was relaunched, because it had recorded a session that never started.

Every biometric prompt in the app now waits for the app to be active before answering, whatever the answer. The wait is bounded, so an event that never arrives cannot hold anyone up, and it does not apply to an app the user has sent to the background.

The system prompt also leaves the app pressable, so pressing "Enable Face ID" again while it was up opened a second one. The button now shows its spinner while the prompt is pending and ignores further presses. InfoState CTAs gain a loading flag for it.

  • #22653 e8d5e1b Thanks @OlivierFreyssinet! - Bump DMK dependencies: device-management-kit 1.10.0, device-signer-kit-solana 1.13.3, device-signer-kit-ethereum 1.18.1, context-module 2.6.0, dmk-ledger-wallet 0.6.0, device-contacts-kit 0.5.0, signer-utils 1.3.0, device-transport-kit-mockserver 1.1.2
  • #22615 7bc8d65 Thanks @mcayuelas-ledger! - Open the Pay request flow from the Add stablecoin "Crypto address" option instead of the standard Receive flow
  • #21976 26e51d9 Thanks @claudiiafg! - Add mobile E2E coverage for creating a contact, registering EVM addresses on the device and deleting both, and let device intents run against Speculos by registering it as a discoverable transport. Speculos specs can now pin an OS version so the run uses the Ethereum build the contacts intents require, which the app catalog does not serve yet.