Commit Graph
1743 Commits
Author SHA1 Message Date
80a8c83d02 Fix missing Material ancestor on history filters and support chat pages (#3647)
* Fix missing Material ancestor on history filters page

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CQrMvFAMkzVAPBrvAwRjL

* Fix missing Material ancestor on support chat page

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CQrMvFAMkzVAPBrvAwRjL

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-23 20:14:00 +03:00
David AdegokeandGitHub dfa5165766 Integration tests (#3477)
* fix android CI

* feat: Add integration test harness and new-ui test keys

This change:
- Adds a BaseRobot with bounded waits to replace hard sleeps in integration tests
- Adds AppLauncher, TestConfig and TestWallets as the core test harness
- Adds onboarding flows composing the existing onboarding robots
- Adds robots for the new dashboard, home page and lightning username page
- Adds a tier0 onboarding create suite targeting the new UI
- Applies the declared navbar action keys that were never attached to the InkWell
- Fixes AlertWithOneAction dropping its buttonKey since the alert redesign
- Adds stable ValueKeys to the home page action row, wallet name, top bar and sync bar
- Makes the legacy tap and text entry helpers wait for widgets instead of sleeping

* fix: Test secrets drift and integration test runner parameterization

This change:
- Adds missing test wallet seed and receive address entries to the secrets generator
- Fixes trailing space typos in two secret key names
- Adds bloc_test to pubspec_base.yaml so pubspec regeneration keeps it
- Parameterizes the integration test runner with suite dir, tier, platform and device knobs
- Fixes the runner aborting on first failure and misaligning durations after retries
- Adds adb data reset so Android suites start from a fresh install

* feat: Android emulator integration test workflows for dev PRs

This change:
- Adds a reusable integration test workflow building the app with prebuilt native deps and running the suites on an Android emulator
- Adds the PR gate workflow triggering fund-free tier0 suites on pull requests into dev
- Fixes the old workflow discarding the test runner exit code so failures now fail the job
- Removes the disabled automated integration test workflow on the stale flutter image
- Removes the commit-message gated Linux desktop test steps, unit tests stay
- Removes the vestigial emulator config from the reusable build workflow

* feat: Revive fund-free integration test suites for the new UI

This change:
- Adds tier0 suites for restore, seed confirmation, wallet switching, receive address, settings navigation and the show keys auth gate
- Adds tier1 suites for sync status, swap quotes and transaction history running non-blocking on PRs
- Adds robots for the settings, receive, swap and send sheets
- Adds auth, wallet switching and restore flows
- Applies widget keys to settings rows through their route names, they were declared but never attached
- Adds keys to the swap amount fields, send inputs, confirm swiper and history tiles
- Adds an integrationTest wrapper containing background async app errors that are not test failures
- Makes taps wait out running route transitions before hitting the screen
- Adds a watchdog to the test runner so a wedged driver fails bounded instead of hanging
- Removes the old UI suites, robots, helpers and the stale widget_test counter template

* chore: Add integration testing docs and suite template

This change:
- Adds docs/INTEGRATION_TESTS.md covering the architecture, local runs, tier rules and the flakiness playbook
- Adds a copy-paste suite template teammates start new feature tests from

* feat: Manual funds integration test workflow with funded-chain auto-discovery

This change:
- Adds a funds suite sending a real self transaction on every funded chain
- Adds a funds suite creating a real swap and broadcasting its deposit
- Discovers funded chains from the funded wallet seeds map, adding a seed to the secret adds the chain
- Adds the manual workflow dispatched from the Actions tab with flow, chain and recording inputs
- Gates the run behind the funds-tests environment and posts the result to slack
- Keeps funded seeds out of PR builds, the checked in seed map is empty

* fix: Harden integration test harness from review findings

This change:
- Fixes body failures being swallowed by the guarded test zone, failing suites hung until the watchdog instead of failing fast
- Fixes multi chain CHAINS dispatch values being split apart by the define forwarding
- Fixes the swap limits wait timing out on pairs without a provider minimum
- Makes unknown chain names in CHAINS fail with a readable message
- Keys favorite token rows by index, duplicate token symbols crashed on duplicate keys
- Guards the recording encryption so a keyserver flake cannot eat the test result
- Removes the dead security and backup robot and corrects the screenshot claim in the docs

* chore: Hold two funded seeds per chain in the funded wallets map

This change:
- Changes the funded wallet seeds map to a list of seeds per chain
- Keeps the first seed as the primary for sends and swaps, the second is reserved for cross wallet scenarios

* feat: Try each funded wallet per chain until one has a balance

This change:
- Adds a funds flow restoring a chain's funded wallets in turn and using the first with a spendable balance
- Treats a wallet that finishes syncing while still empty as drained so the next seed gets its turn quickly
- Fails a chain with a top up message when every funded wallet of that chain is empty

* reformat integration test related files

* fix: Run the integration test emulator on the kvm capable runner

This change:
- Moves the test job to the linux-amd64 runner the old emulator workflow targeted, the android builders have no /dev/kvm
- Exposes /dev/kvm through a docker device flag instead of a volume, a bind mount materializes a directory when the host node is missing
- Fails the kvm check in seconds with a clear error instead of waiting out the emulator boot timeout

* fix: Queue the integration test job on the live android runner pool

This change:
- Moves the job back to the android runner labels, no runner carries the old linux-amd64 label anymore so the job queued forever
- Keeps the kvm device flag and check so a host without kvm fails immediately with a clear error

* feat: Run the integration test gate on github hosted runners

This change:
- Moves the test job to ubuntu-latest which has working kvm, the self-hosted android builders do not expose /dev/kvm yet
- Runs directly on the vm since the cake container image does not fit hosted disk, flutter is pinned through the flutter action
- Adds rust android targets for the breez cargokit build and java 17 for gradle
- Keeps the prebuilt native deps from the ghcr docker cache, they pull fine on hosted docker
- Adds pub, gradle and flutter caching plus a disk cleanup step
- Keeps every input, the runner script, tier1 soft run, recording and artifacts unchanged

* fix: Install flutter from the same source as the ci container image

This change:
- Clones the flutter 3.41.9 tag directly like the container image instead of using the release archive
- The archive ships a stricter dart that rejects the checked in mweb ffi bindings, the tag clone compiles them
- Caches the cloned sdk between runs

* fix: Regenerate the mweb ffi bindings with the workflow dart

This change:
- Regenerates cw_mweb/lib/generated_bindings.g.dart after the deps step, the docker deps builder generates it with an older dart whose ffigen output the current compiler rejects

* fix: Pin ffigen so fresh resolutions generate compliant mweb bindings

This change:
- Pins ffigen to ^20.1.1 in cw_mweb, the unconstrained dependency resolved to 7.1.0 on fresh checkouts
- Old ffigen emits ffi classes without the base or final modifiers the current dart compiler requires

* fix: Restore the pinned cw_mweb pubspec before regenerating bindings

This change:
- Restores cw_mweb/pubspec.yaml after the deps step, the deps image rsync clobbers it with the stale unpinned copy so the ffigen pin never applied
- Wipes the rsynced pubspec.lock and .dart_tool so pub resolves the pinned ffigen fresh
- Asserts the regenerated bindings carry class modifiers so a bad generation fails in seconds
- Skips the libclang install when it is already present, apt spent fourteen minutes on it

* fix: Run the emulator test logic from a file, the action splits script lines

This change:
- Moves the whole emulator script into a file written before the emulator step, the action executes every script line as a separate sh command so multi line logic broke with a syntax error
- Passes the action a single line script invoking that file with bash

* fix: Give the ci emulator a real phone profile

This change:
- Sets the avd to the pixel 5 profile, the default 320x640 skin cannot fit the seed word grid so every wallet creation suite failed its seed display checks
- Caps tier0 attempts at ten minutes, two failing suites at the thirty minute worst case blew the job timeout

* fix: Wait for pages on the ci emulator instead of asserting immediately

This change:
- Makes the legacy page assertion poll for the page, the wallet keys page arrives after the ci emulator finishes unlocking the wallet so both auth gated suites failed on a race
- Raises the tier0 attempt timeout, the first attempt for each suite compiles that target's kernel and was being killed then passing on retry
- Raises the job timeout to fit the per suite compiles

* fix: Recover from driver attach failures instead of burning the timeout

This change:
- Detects attempts where the flutter driver never attached to the app and retries them without spending the real retry, restarting adb first
- Lowers the ci attempt timeout back to ten minutes, every passing attempt finishes within four so a longer cap only makes an attach failure cost more

* fix: Bound the adb recovery and give up when the driver never attaches

This change:
- Bounds every adb call in the recovery path, adb wait-for-device blocks forever when the emulator is gone and silently ate a whole ci job
- Aborts the suite when adb cannot be brought back instead of retrying against a dead device
- Stops the run after a few driver attach failures, an environment that never attaches will not fix itself
- Dumps logcat after every failed attempt so a failure to start the app leaves evidence behind

* fix: Stop the runner failing when every suite passes

This change:
- Assigns the result arrays empty instead of only declaring them, set -u treats a declared but never assigned array as unbound so the summary died on the first run where nothing failed

* fix: Make the ci build cacheable and keep tier results when the runner dies

This change:
- Splits the flutter and pub caches into restore and save steps, actions/cache only saves when the whole job succeeds so they were never written and every run paid for a cold build
- Lets setup-gradle write its cache, it is read only on branches other than the default one
- Caps the gradle heap before the test phase, the six gigabyte daemon plus the emulator left the runner too little to stay alive
- Runs tier0 and tier1 as separate emulator steps so each uploads its logs before the next can take the runner down
- Logs memory and disk around each tier
- Documents how ci runs the suites and the two environment quirks that only bite there

* fix: token decimals defaulting to zero and breaking amount parsing in solana

(cherry picked from commit c220bbb46df9468faf9a8967cb0ef672af7cc664)

* fix: Install the test error handler after the app boots and drain benign errors

This change:
- Installs the test error handler after main() instead of before it, the app replaces FlutterError.onError while booting so the handler installed earlier never ran
- Replaces the app's async error handler during tests, it returns at its first await without recording anything so the framework asserted instead of reporting the real error
- Drains background errors the app tolerates at runtime from the wait loops, anything unrecognised still fails the test where it happened
- Collects the tolerated categories in one place, layout and semantics assertions from debug builds plus the transient network failures exception_handler.dart already ignores

* fix: Import FlutterExceptionHandler from foundation so the test build compiles

This change:
- Imports package:flutter/foundation.dart in the launcher, material.dart only re-exports Brightness and UniqueKey from foundation so FlutterExceptionHandler was not in scope
- Drops the material.dart import, every symbol the launcher uses comes from foundation

* chore: Drop the funds-tests environment from the funds workflow and docs

This change:
- Removes the environment input from the funds workflow, we no longer use a funds-tests GitHub environment so passing it would only auto create an unprotected one on the next dispatch
- Says plainly in the docs that nothing gates the dispatch, the doc still claimed the run waits for environment approval
- Names write_funds_secrets as the one thing keeping funded seeds out of a PR build, that used to be backed by environment scoping too

* chore: Trim the integration test comments down to the ones that carry weight

This change:
- Drops the doc comment sitting on top of every robot primitive, flow and config getter, they restated the method name and nothing else
- Collapses the multi paragraph docstrings on the launcher, the error drain and the funded wallet lookup into the one or two lines that actually explain why the code is shaped that way
- Removes the file and class header paragraphs, the funded wallets stub keeps a short note because an empty map needs one
- Keeps the notes worth having, the chain quirks in the onboarding flow, the route transition tap, the dust thresholds, the void attempt handling in the runner

* chore: Fold the components folder into core and clear out the leftover comments

This change:
- Moves common_test_cases.dart into core, the components folder was down to that one file after common_test_constants.dart went
- Drops six methods from it that nothing calls, dragUntilVisible alone was 95 lines of scroll logic with no caller
- Removes the comments narrating the line below them across the older robots, "Drag to the left" above a moveBy and "Confirmation for buttons 1-9" above the loop
- Deletes the commented out body of takeScreenshots and says instead that it does nothing, all fifteen callers have been no-ops
- Notes in the docs that new robots extend BaseRobot while the older ones still carry a CommonTestCases

* chore: Clear out pending items

* refactor: Move the onboarding robots onto BaseRobot and drop the sleeps

This change:
- Deletes common_test_cases.dart, the sixteen robots that held a CommonTestCases now extend BaseRobot like the newer ones do
- Moves the primitives those robots needed onto BaseRobot with the same behaviour, isSpecificPage, hasType, hasValueKey, hasText, hasTextAtLeastOnce and swipePage
- Renames each robot's page check to isDisplayed so every robot answers the same question the same way
- Removes the twenty six defaultSleepTime calls, every one of them sat in front of an action that already waits for what it needs
- Waits for the view model to move to the next word in seed verification instead of sleeping a second and hoping, and fails with a message when it does not
- Settles after opening a tab on the wallet keys page, the assertions there read the tree straight after the tap
- Drops the fifteen takeScreenshots calls, the method's body has been commented out the whole time so none of them did anything
- Formats the three files dart format was still rewriting

* fix: Wait out the navigation the removed sleeps used to cover

This change:
- Settles after a page mounts in isSpecificPage, the route being replaced stays in the tree while it animates out and assertions on text were matching both screens, seed_confirmation_test failed on finding two "Verify Seed" widgets
- Settles after the last pin digit, the gate sends itself away and returning early landed the caller's next navigation on a locked navigator, wallet_switching_test failed on the !_debugLocked assertion

Both are places where waiting for a widget to exist is not the same as waiting for the app to
be idle, which is what the sleeps were quietly doing.

* fix: Recognise every way a flutter drive attach fails and give tier1 a retry

This change:
- Matches the other wordings flutter drive uses when the driver never attaches, we had only the connect one and missed the isolate initialise, the service disappeared and the driver extension variants
- Gives tier1 the same single retry tier0 has, running it with none meant a driver that never attached was reported as a failing test

Two of the three tier1 failures on the last run were attaches that never happened, neither
of them reached any test code.

* fix: Sync status test watched the wrong status class, history scroll had no guard

This change:
- Accepts SyncronizingSyncStatus in the sync status suite, cw_core spells it without the h and it is a separate class from SyncingSyncStatus, so chains reporting it sat out the full three minute timeout and then failed saying the wallet never synced while printing Synchronizing
- Waits for the home scroll view before handing it to scrollUntilVisible, which calls single on the finder every drag and reported a missing scroll view as a bare Bad state: No element
- Skips the scroll entirely when the history tiles are already on screen

* fix: Test the transaction history the way the screen actually works

The suite looked for history tiles on the home page and scrolled for more when it found
none, which could never pass. The home page renders the history under a tab, and for a
wallet with tokens the assets tab is the one selected first, so the history section was
never built. It also shows a three item preview, itemsShort caps it at
shortHistoryLength, and everything else sits behind the All button in a modal.

This change:
- Opens the history tab before looking for tiles, wallets with no assets tab render the history with no tab bar at all and skip that step
- Asserts the preview never renders more than the three the view model hands it, instead of scrolling for tiles that were never coming
- Opens the All button and checks the full list in the modal, which nothing covered before
- Adds keys for the tab items, the All button, the single tab history bar and the modal root

* fix: Stop the runner wiping a real wallet install and document what CI does that the docs did not

Following app_config.sh and then running the tests locally clears com.cakewallet.cake_wallet
between every suite, which is a developer's own wallets. CI never hits this because the
workflow renames the app afterwards, a step that only existed in the workflow.

This change:
- Refuses to clear a package that is not named for testing, with the rename command in the message
- Adds that rename to the local prerequisites, next to the reason it matters
- Spells out the assets/images vector pass, compile_graphics.sh does not cover that folder and those vec files are neither committed nor ignored, so a fresh checkout fails on the welcome screen
- Notes that a second Gradle daemon from Android Studio deadlocks the build

* test: Make the tier0 and tier1 assertions check what they claim to

An audit of every robot against the screen it drives turned up assertions that pass without
telling you anything, the same shape as the transaction history suite looking for tiles on a
tab it never opened.

This change:
- Reads the receive address off the screen instead of the view model, the view model holding the right address says nothing about what the user is told to send to, and reads both the chunked and the plain render because only handling the first comes back empty
- Verifies a restored wallet knows the address its seed derives, restoring the wrong wallet from a correct seed used to look the same as success, and this found two stale addresses in the secrets
- Replaces the sync bar check, the key it asserted sits on a widget that renders for every status including none, so it was true whenever the home page was up
- Requires the All transaction view to render more than the home preview, it could not tell the two apart before
- Fails the keys page check when a wallet type matches none of its branches instead of passing having verified nothing

* refactor: Remove outdated items from integration tests

* test: Wait for the wallet to actually change instead of pumping a fixed half second

Tapping a wallet row does nothing when that wallet is already open, and the load runs behind
a progress overlay, so a tap that went nowhere looked the same as a successful switch.

This change:
- Waits for the app store to report the wallet before returning, and fails with a message when it never does

* test: Open wallet groups before looking for a wallet by name

Wallets that share a seed are listed as children of their group's tile and are not on screen
while it is closed, so switching to one would time out looking for a name that is only
rendered once the group is opened. Single seed wallets carry their own name as the tile
title, which is why this has not come up yet.

This change:
- Opens the group tiles when the wallet is not already on screen

* chore: Keep the receive address widget diff to the key it needed

A formatter pass rewrote the whole file, quotes, body style and trailing commas, for what was
a one line addition. Restored the original shape so the diff shows only the key.

* chore: Drop trailing whitespace left by the comment cleanup

* test: Cover the send screen refusing what it cannot send

Nothing covered the send screen, which is the one place in the app where getting it wrong
costs the user money. The property worth holding is that the swiper, the point of no return,
is only ever offered for a transaction the wallet actually built.

This change:
- Adds a tier0 suite driving an empty form, an address the chain cannot parse, and a well formed address on a wallet that has never held anything, and requires that none of the three ever produce a swiper
- Waits for the swiper and requires it not to arrive rather than checking once, the screen passes through syncing, building and failed states on the way and any of them can be the one you catch
- Notes in the docs that a merge from dev leaves the generated chain proxies and the assets/images vec files behind, which has broken the local build three times now

* test: Add send and swap dry runs and a flows mode that never spends

The funds suites were all or nothing: the only way to find out whether a funded wallet still
works was to spend from it. Almost everything worth checking happens before the swipe, the
wallet finds its balance, prices the fee and builds a transaction, or the provider returns a
live quote, and none of that costs anything.

This change:
- Adds a send dry run that stops at the swiper, reaching it means the transaction was really built
- Adds a swap dry run that stops before the swap button, creating a trade registers an order with the provider even though no funds move, so it goes no further than the quote
- Adds a dry-run value to FLOWS that runs both and neither of the spending suites, and makes it the default so a mis-click cannot send anything

* test: Cover a wallet name that is already taken being refused

Two wallets answering to the same name is how someone ends up sending from the wrong one, and
nothing checked that the form stops it.

This change:
- Adds a tier0 suite that creates a wallet, starts a second one and gives it the same name, and requires the form to refuse it and stay put
- Splits the walk to the naming form out of createAdditionalWalletFromWalletList so a suite can stop there instead of going through with the wallet

* test: Cover a seed the wallet cannot parse restoring nothing

Restoring from a seed the wallet does not understand either fails outright or, worse, derives
some other wallet the user has no keys for. The restore button is disabled until the words
check out and nothing checked that.

This change:
- Adds a tier0 suite that types twelve words outside the wordlist, presses restore, and requires the form to stay put
- Follows it with the real seed in the same run, so the refusal above cannot pass just because the form restores nothing for any seed at all
- Splits the walk to the restore form out of restoreFirstWalletFromSeed so a suite can stop there

* refactor: Make spending its own switch instead of a value hidden in flows

flows says which flow to run, so folding a dry-run value into it made the dropdown answer two
questions at once and buried the one that matters.

This change:
- Adds a spend input, off by default, and gates the broadcasting suites on it
- Puts flows back to all, send or swap
- Runs the dry runs whenever their flow is picked, they cost nothing either way

* chore: cleanup minor issues

* test: Cover one seed backing wallets on two chains

Wallet groups had no coverage at all, and the part that would break quietly is the seed: a
group member that derived its own seed instead of sharing one looks identical from the wallet
list. It is also the only way a wallet ends up inside a collapsed group tile, which is the
path switchToWallet handles and nothing exercised.

This change:
- Adds a tier0 suite that creates a solana wallet, adds an ethereum wallet to its seed, and requires both to report the same seed
- Switches back to the first afterwards, which only works by opening the group it now sits in
- Adds the flow for it, a child wallet skips the seed and verification screens for a page explaining it shares one, so it cannot reuse the normal creation steps
- Adds the one key the group page was missing, its next button

* test: Cover changing the pin replacing the one that unlocks the wallet

Nothing checked that changing the pin actually takes. Watching the setup screen close proves
nothing, the only evidence is the new pin opening something the old one guarded.

This change:
- Adds a tier0 suite that changes the pin and then unlocks the seed and keys page with the new one
- Goes through the pin gate on the way in, so it also covers the current pin being required before the change is allowed

* test: Cover changing the language, and make settings rows and pickers addressable

Writing this turned up two things in the app rather than the test.

Only ListItemRegularRow was given its keyValue as a widget key. Toggles, checkboxes,
dropdowns and selectors all took the same keyValue and dropped it, so every settings control
that is not a plain navigation row could not be found at all.

Picker named three item types and gave everything else an empty string, so a picker over
plain values, the language list among them, rendered every row with the same key.

This change:
- Passes keyValue through as a key for the other four row types
- Falls back to the item itself for the picker name, so each row is distinct
- Adds a tier0 suite that changes the language and requires the row to show the new one

* test: Cover switching the node the wallet talks to

Which node a wallet is pointed at decides what it sees, and a switch that quietly does not
take leaves someone on a node they believe they left. Nothing covered it.

This change:
- Adds a tier1 suite that reads the current node, switches to another one, confirms, and requires the wallet to be pointed somewhere new
- tier1 rather than tier0 because opening the page speed tests every node against the network

* test: Cover the address book keeping the address it was given

Pasting an address once and trusting it afterwards is the whole point of an address book, so
an entry that comes back changed is a way to lose money quietly. Nothing covered it.

This change:
- Adds a tier0 suite that saves a contact through the form and requires the stored address to match what was typed
- Checks the saved record rather than the rows, the list filters what it shows by the currency in play so a contact can be saved and correctly not on screen
- Adds keys for the add button, the name field, the currency picker, the address field and save

* fix: Key picker rows on a name that does not change with the language

Enumerable items fell through to toString, which is translated for some of them, so the
same row carried a different key depending on the language in use.

* test: Cover the fiat currency setting, transaction details and sending to a contact

This change:
- Adds a suite for turning the fiat api off and on, which is what decides whether the
  currency setting is offered at all, then changes the currency through it
- Adds a suite that taps a transaction and checks the details that open belong to it
- Adds a suite that saves a contact and picks it from the send screen
- Adds a suite for restoring a 25 word monero seed, which takes a seed type and a restore
  height the polyseed path never asks for
- Keys the address book button on the send screen so a test can reach it
- Lets SUITE_DIR take a single suite file, for iterating on one test
- Lists every suite and what it proves in the integration tests doc

* test: Cover renaming and deleting a wallet

This change:
- Adds a suite for renaming a wallet, including the refusal when the name is already held
  by another wallet
- Adds a suite for deleting a wallet, checking it leaves storage and not only the list
- Keys the edit button in the wallet list and the name field, the two buttons and both
  dialogs on the wallet edit page, none of which could be reached from a test before
- Fixes the wallets list robot, which looked for a single Wallets title even though the
  navbar carries the same word, and looked for wallet names across the whole tree even
  though every dashboard tab stays mounted in an IndexedStack

* ci: Report every integration test run to slack

This change:
- Posts a report to slack on every run, naming each tier's counts, duration and any suite
  that failed, with the full list of what passed in a reply on the same message
- Adds a SUMMARY_FILE knob to the runner, which writes what it counted in a form the
  report can be built from rather than parsing the run log
- Skips with a notice when SLACK_APP_TOKEN or SLACK_TESTS_CHANNEL is missing, and never
  fails the gate when slack is unreachable
- Leaves the funds workflow alone, the new slack_notify input defaults to off

* chore: Removing unneeded items

* ci: Say which slack scope was missing when a report is rejected

A rejection carries the scope it wanted in a field of its own, and dropping it left the
warning saying missing_scope without saying what to go and add.

* ci: Report funds runs to the tests channel and say when slack refuses them

This change:
- Sends the funds report to SLACK_TESTS_CHANNEL, so both test workflows land in one place
  and the apk channel is left to builds
- Checks the slack response, the post was going out unchecked so a rejected report looked
  like a delivered one
- Builds the message from the environment instead of interpolating workflow inputs into
  the shell, a quote in a dispatch input was enough to break the payload

* ci: Give the test reports their own slack app

Uploading apks and reporting test results are different jobs with different scopes, and
the name on a message is how anyone in the channel tells which one sent it. Both test
workflows now post through SLACK_TESTS_TOKEN, which only needs chat:write, and the apk
uploads keep SLACK_APP_TOKEN and its file scopes.

* fix: Restore the swap amount key and say when a suite never ran

The AnyPay redesign moved the deposit amount field into SwapAmountBox and it lost the key
the swap suite enters an amount through, so that suite could no longer reach it. Both
amount fields are keyed now, deposit and receive.

This change also:
- Records whether a failed suite failed on its own or because the driver never attached,
  which is what happened to send_validation_test in run 31364715120 where the suite never
  actually ran
- Rebuilds the slack report out of blocks, so the status is a heading, the tiers sit in
  their own fields, failures name their reason and the run is a button rather than a bare
  url

* perf: End an attempt early when the driver cannot attach

flutter drive says it is struggling to attach within about a minute and then, when it is
really wedged, sits there until TEST_TIMEOUT kills it. Run 31364715120 lost 20 minutes to
two attempts doing exactly that.

This change:
- Watches the attempt log and ends the attempt VOID_GRACE seconds after the driver first
  reports trouble, 120 by default, so the retry starts on a fresh app instead of waiting
  out the remaining eight minutes
- Starts that clock at the first complaint rather than at the attempt, since the attempt
  spends its first minute building and installing
- Moves the marker list into one variable, the watchdog and the after the fact check were
  going to drift apart otherwise

* perf: Drop the driver grace to 60s and take the whole drive down with it

Measured against run 31364715120: the 19 attempts that attached did so in under a second,
while the two that never attached had the warning out at 5 seconds. 60 leaves a wide
margin over a healthy attach on a runner that was already running slow, and saves another
minute on each dead attempt.

Ending the attempt now walks the process tree rather than signalling the wrapper. On linux
the drive runs under timeout, and a KILL to that cannot be forwarded, so the drive would
have been left holding the device just as the retry went looking for it.

* trigger another test run

* Triggering another run, its been green so far

* fix: Wait for the wallet to be ready before tapping send in the funds suites

The send button is disabled until isReadyForSend, which wants a synced wallet, but the
funds flow moves on as soon as a balance appears. Every funded chain was therefore tapping
a disabled button and then waiting ninety seconds for a confirm sheet that could never
open. The robot now waits on the same value the screen gates on, so a wallet that never
syncs says so instead of looking like a missing widget.

* test: Make a funds send failure say what went wrong and stop it spreading

A send that never reaches the confirm sheet was reported as a missing ConfirmSwiper, which
says nothing about the cause. It now reports the state of whichever sheet is up, what the
wallet holds, and the build error when the chain rejected the transaction.

Recovery between chains was a single pop, so the first chain to fail left the app somewhere
the next chain's restore could not start from and every chain after it failed too. It now
unwinds until the home page is back and stops the suite if it cannot get there.

* test: full testing of all the funded tests and fix issues that came up with them

* fix: update sync key to fix sync test

* fix: out of space error

* fix: integration test review issues

* fix: merge conflicts and update test suites

* fix: false test passes and add funded wallet passphrases
chore: remove unused integration test robot methods
feat: save integration test screenshots on Android
feat: pick the swap pair for the funds dispatch

* fix: Keep the runner output on the step log through a file descriptor

* fix: timeout because of runner crash

* test: handle new seed ui flow and fix hang for runner

* fix: emulator teardown not rendering succesfully
feat: use currency picker for selecting swap options for funded run selected swap currency

* test: add key for compact dot key for sync and link to robot

* minor fixes

* test: review fixes for integration tests
2026-09-23 20:11:37 +03:00
b88fbf32ae charts (#3162)
* charts (wip)

* charts (wip)

* wip

* add asset grid

* split files

* logic wip

* proper getPrices logic

* viewmodel (wip)

* charts

* remove unused import

* remove restricted import...?

* fix background gradient when scrolling

* add safeguards for broken db

* save new pin on removal

* add safeguards for concurrency

* add haptic feedback

* fix haptic feedback

* fix fiat ticker

* reduce chart padding

* add line touch indicator

* improve touch target size on range selector

* add date/time display when viewing past amount

* merge

* remove iconSvgPath reference

* merge

* fix state for date display

* merge

* auto-reformat

* only load charts when page is opened

* merge

* reformat and apply lints

* refactor to use `Money` instead of storing amount in `String`

* add slop comment

* fix exception on division by zero

* ci

* workaround toDouble being removed

* disable strict parsing and rounding

* fix abs()

* use new currency picker sheet

* safeguard for max flutter-supported decimals

* fix amount precision

* adjust to new buy/sell

* Update lib/new-ui/pages/charts_page.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* adjust buy/sell di

* replace -> ignore

* reformat

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2026-09-22 21:32:38 +03:00
malik1004xandGitHub d38c748127 remove old ui (#3629)
* remove old ui

* remove imports to nonexistent files

* remove old buy/sell

* remove unreachable pages

* readd Routes.exchange

* remove broken di
2026-09-22 16:46:42 +02:00
Konstantin UllrichandGitHub 3d03a62e21 Add thread-safety and BLE support for Ledger and Trezor devices (#3638)
* feat: support BLE device detection for Ledger and Trezor wallets, update trezor-flutter dependency

* feat: add BLE connection state listener for Trezor devices, handle disconnect events, and clean up resources on close

* feat: add mutex locking to MoneroTrezorService for thread-safe operations
2026-09-18 21:10:59 +02:00
98f58c2898 Cw 1624 trezor passphrase on trezor device instead of app (#3601)
* feat: add support for Trezor device settings configuration and `AwaitingSettings` state in pairing flow

* fix ugly widgets

* trezor options ui

* feat: add support for Trezor on device passphrases

* chore: bump trezor_flutter deps

* fix: remove unused subtitle property in proceed_on_device_sheet [skip ci]

* chore: update trezor_flutter dependency to latest commit [skip ci]

* fix: set restore height and store wallet during initialization steps

---------

Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com>
2026-09-18 02:27:32 +02:00
David AdegokeandGitHub 3a04c7ff1e feat: restrict history api calls when toggle is off in privacy settings (#3615)
* feat: restrict history api calls when toggle is off in privacy settings

* refactor: remove unnecessary transaction timer cancellation
2026-09-16 23:05:40 +02:00
Blazebrain 3f8cc0e975 Merge remote-tracking branch 'origin/dev' into CW-1529-Improved-Seed-UI
# Conflicts:
#	lib/new-ui/pages/home_page.dart
#	res/values/strings_ar.arb
#	res/values/strings_bg.arb
#	res/values/strings_cs.arb
#	res/values/strings_es.arb
#	res/values/strings_fa.arb
#	res/values/strings_fr.arb
#	res/values/strings_gn.arb
#	res/values/strings_ha.arb
#	res/values/strings_hi.arb
#	res/values/strings_hr.arb
#	res/values/strings_hy.arb
#	res/values/strings_id.arb
#	res/values/strings_it.arb
#	res/values/strings_ja.arb
#	res/values/strings_ko.arb
#	res/values/strings_my.arb
#	res/values/strings_nl.arb
#	res/values/strings_pl.arb
#	res/values/strings_pt.arb
#	res/values/strings_pt_BR.arb
#	res/values/strings_ru.arb
#	res/values/strings_th.arb
#	res/values/strings_tl.arb
#	res/values/strings_tr.arb
#	res/values/strings_uk.arb
#	res/values/strings_ur.arb
#	res/values/strings_vi.arb
#	res/values/strings_yo.arb
#	res/values/strings_zh.arb
2026-09-15 10:45:35 +01:00
ef6879b617 fix(CW-1638): include transaction notes in history CSV export (#3623)
* fix(CW-1638): include transaction notes in history CSV export

The `note` column of the transaction-history CSV export was always written
as an empty string for transaction rows, so notes a user added to a
transaction never appeared in the exported file.

Index the `TransactionDescription` Hive box once per export and look each
transaction's note up by the same keys the app writes them under
(`<txHash>_<primaryAddress>`, falling back to the legacy bare `<txHash>`),
then write it through the existing `escapeField` so multi-line notes and
notes containing commas or quotes stay valid CSV.

`CsvExportService` now takes the box, so it is resolved through `getIt` at
both export entry points.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

* perf(CW-1638): resolve the note-key address once per CSV export

`_noteFor` read `balanceViewModel.wallet.walletAddresses.primaryAddress`
once per transaction row. On Monero and Wownero that getter is not a field
read: it calls `getAddress`, which issues an FFI `numSubaddresses` call plus
four `ffiAddress` calls before its address cache is consulted. Since
`buildCsvContent` runs on the main isolate, exporting a wallet with a long
history meant thousands of synchronous FFI round-trips with no yield point.

Every item in one export belongs to the same wallet, so the value is
invariant across rows. Resolve it once in `buildCsvContent` and thread it
through to `_noteFor`. An export holding no transaction rows now skips the
read entirely.

No change to the exported output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

* fix(CW-1638): neutralise spreadsheet formulas in CSV export

The note column this PR adds is fed by TransactionDescription.transactionNote,
which is not always authored by the user: a scanned bitcoin: URI carries
tx_description / message straight through payment_request.dart into
Output.note, and an address-resolution result writes Output.note too. Both are
folded into the note persisted by SendViewModel.

escapeField only quoted for , " and \n, so a note beginning with = + - @ TAB or
CR reached the cell untouched and Excel / LibreOffice / Sheets evaluate it as a
formula (=IMPORTDATA to exfiltrate a neighbouring cell, =HYPERLINK for phishing
anchor text, =cmd|… for DDE). trade.memo and the provider-supplied trade/order
strings had the same exposure already.

Neutralise inside escapeField rather than at the two call sites so every column
is covered and a future free-text column cannot forget it. Values starting with
a trigger are prefixed with an apostrophe, the spreadsheet text marker, guarded
by a plain-number test so negative amounts are left alone — formatFixed really
can emit a leading '-'. Also quote on a bare \r, which previously passed through
unquoted and splits rows in many CSV parsers.

Tests cover the escaping rules, the neutralisation, a row built end to end, and
the note column itself, including the two lookup keys and the payment-request
injection path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* perf(CW-1638): bound the CSV note lookup to the exported rows

Review feedback: the note index copied every entry in the shared
transactionDescriptionBox, including notes belonging to other wallets,
so its memory scaled with the box rather than with the export.

Collect the keys the exported rows can ask for first — for each
transaction row both `<txHash>_<primaryAddress>` and the bare
`<txHash>` — then walk the box once and keep only matching notes.
Lookups stay O(1) per row and first-wins/key-preference behaviour is
unchanged; a box walk is skipped entirely when the export has no
transaction rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

* refactor(CW-1638): simplify the CSV note lookup to a plain scan

Notes are written into the TransactionDescriptions box with `add`
(send_view_model.dart:1257,1263 and transaction_details_view_model.dart:299),
so entries carry auto-incrementing integer keys and `box.get(descriptionKey)`
cannot find them. Drop the prebuilt lookup map and the wanted-key set in
favour of a per-row scan that mirrors the app's own read path in
`TransactionDetailsViewModel.note`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

* docs(CW-1638): drop the explanatory comments from the CSV note lookup [skip ci]

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Omar <omarh.ismail1@gmail.com>
2026-09-14 14:43:12 +02:00
Robert Malikowski 85b79a3f41 Merge branch 'dev' into cw-1440-new-buy-sell-flow
# Conflicts:
#	res/values/strings_it.arb
#	res/values/strings_ja.arb
#	res/values/strings_ko.arb
#	res/values/strings_my.arb
#	res/values/strings_nl.arb
#	res/values/strings_pl.arb
#	res/values/strings_pt.arb
#	res/values/strings_pt_BR.arb
#	res/values/strings_ru.arb
#	res/values/strings_th.arb
#	res/values/strings_tl.arb
#	res/values/strings_tr.arb
#	res/values/strings_uk.arb
#	res/values/strings_ur.arb
#	res/values/strings_vi.arb
#	res/values/strings_yo.arb
#	res/values/strings_zh.arb
2026-09-11 12:53:54 +02:00
David AdegokeandGitHub 2b2c282089 Merge pull request #3597 from cake-tech/CW-1621-Brazillian-Locale-Fix
fix: brazillian localle bug that makes app unresponsive
2026-09-10 14:36:49 +01:00
Blazebrain 873ef018b6 Merge branch 'dev' of https://github.com/cake-tech/cake_wallet into CW-1529-Improved-Seed-UI 2026-09-09 14:55:06 +01:00
Blazebrain 6bb52fd746 fix: switch "seed" to "recovery phrase" in more places and handle edgecases for seed verification reminders 2026-09-09 08:58:09 +01:00
Blazebrain 7e0f0fc3cd refactor: improve locale handling fixing issues with yoruba, hausa and guarani languages 2026-09-09 02:35:27 +01:00
Omar HatemandGitHub 9320180684 v6.4.4 Release Candidate (#3594)
- Monero sync patches
- Trezor bitcoin
- Solana NFTs sending
- Bug fixes
2026-09-05 00:16:01 +08:00
Blazebrain 239bbc4900 Merge branch 'dev' of https://github.com/cake-tech/cake_wallet into CW-1529-Improved-Seed-UI 2026-09-04 11:30:51 +01:00
PatrickandGitHub 73a739411f Delete CakePay Auth-token if the API returns 401 (#3538)
* delete cakepay auth token on 401

* fix cakepay auth refresh

* fix Cake Pay error handling
2026-09-03 01:58:45 +08:00
David AdegokeandGitHub ea4d752914 solana nft improvements and other fixes (#3581)
* feat: improvements covering solana nfts(send, ownership badge, persistent imports etc), closing emptied token accounts and other fixes from patch

* feat: more improvements for nfts including security hardening

* fix: display error in full
2026-09-03 01:54:11 +08:00
malik1004xandGitHub 68eb1c3ded auto-select custom node on wallet creation (#3592)
* auto-select custom node on wallet creation

* safeguard for wallet type
2026-09-02 19:11:57 +08:00
David AdegokeandGitHub 1f3b261e3d CW 1515 Contacts List Out Of Sync (#3550)
* fix android CI

* fix: address book wallet contacts tab not updating after wallet changes

* feat: add page open listener for contact list and handle concurrent calls to fetch contact list
2026-09-02 18:30:28 +08:00
Robert Malikowski 9d4d1220f2 Merge branch 'dev' into cw-1440-new-buy-sell-flow 2026-09-01 13:04:02 +02:00
Blazebrain 323a1df6e9 feat: remind users with a balance to back up their recovery phrase 2026-09-01 10:01:07 +01:00
Blazebrain 837c4d7d22 feat: redesign the pre-seed and seed verification screens 2026-08-28 23:07:43 +01:00
905db64f77 feat: zano hf + bip39 (#3359)
* feat: bip39 zano hf

* fix: correct monero_c hash

* lockfile [skip ci]

* fix: zano: proper bip32
fix: zano respect 24 word setting
fix: rename / delete

* fix: nonBip39WalletTypes

* fix: zano wallet rename

* fix: passphrase store zano

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2026-08-28 15:49:51 +08:00
Robert Malikowski d28619219e Merge branch 'refs/heads/dev' into cw-1440-new-buy-sell-flow 2026-08-27 12:28:17 +02:00
David AdegokeandGitHub 06e75551a7 CW-1615: Harden walletconnect auth (#3547)
* fix android CI

* fix: harden walletconnect auth so a connected session won't transfer on dApp when wallet is switched within the app

* refactor: improve session mgmt and better error handling, also added a loader to the listing page

* feat: switch to newer id for solana dapps connections and fix requests not triggering
2026-08-26 11:45:03 +08:00
cyanandGitHub 3d9b79f536 feat: bip39 decred (#3348) 2026-08-26 01:01:52 +08:00
malik1004xandGitHub 9b50011e75 Hide broken options in Bitcoin wallets without a private key (#3532)
* hide unavailable receive page options

* hide broken options in wallets without a private key

* hide exporting unavailable logs

* hide sign/verify on airgapped wallets

* hide mweb on hwws

* hide mweb ad for hww
2026-08-24 23:24:52 +03:00
David AdegokeandGitHub 703cc22b33 fix: solana wallet bugs and security issues (#3484)
* fix android CI

* fix: duplicate outgoing tx for jup swaps and stuck pending state

* fix solana security risk by handling duplicate token symbols in wallet transactions

* feat: implement additional cost handling for pending transactions in Solana wallet.

* fix: Items on security audit list for solana wallet

* refactor: streamline fee payer index handling and improve error logging

* fix: verifySignature for solana and handle wrong mint on default token

* fix: token decimals defaulting to zero and breaking amount parsing in solana

* fix: use token mask in tx history

* refactor: apply lint to modified code

* fix: merge conflicts

* fix: use Money and mint decimals when parsing sol swaps

* test: add unit tests for SPL token amount handling and parsing

* test: add more tests

* fix: update decimal handling for fetched token and remove unused fields
2026-08-24 23:21:29 +03:00
Claude a8ca70b43d fix(ui): make unchecked NewSimpleCheckbox visible in light themes
The unchecked background resolved to colorScheme.surfaceContainerHighest,
which is pure white in LightTheme, so the unselected checkbox was an
invisible white disc on near-white list rows. In light brightness use
onSurfaceVariant with alpha 80 instead; dark themes are unchanged.
2026-08-24 16:00:46 +00:00
Robert Malikowski 91330b0199 fix 2026-08-24 12:04:34 +02:00
malik1004xandGitHub c248a53a35 hide keyboard after restoring backup on ios (#3533) 2026-08-20 23:14:47 +02:00
Robert Malikowski 5a15d5644c Error: Not a constant expression. 2026-08-20 19:49:38 +02:00
Robert Malikowski b6c6f75a51 Merge branch 'dev' into cw-1440-new-buy-sell-flow
# Conflicts:
#	lib/src/widgets/cake_image_widget.dart
2026-08-20 18:31:40 +02:00
Seth For PrivacyandGitHub c9974673c1 a11y: decorative/meaningful image semantics, labeled icon buttons, audible addresses (#3465) 2026-08-20 16:40:07 +02:00
malik1004xandGitHub 775041e636 Merge branch 'dev' into cw-1440-new-buy-sell-flow 2026-08-20 16:26:13 +02:00
Konstantin UllrichandGitHub 86906a7f85 fix: move updateBleState to connection page initialization (#3539) 2026-08-19 22:11:23 +02:00
Robert Malikowski a56dab50cb Merge branch 'dev' into cw-1440-new-buy-sell-flow 2026-08-11 13:03:39 +02:00
3b16f2fb7c fix: zcash address for exchange (#3491)
* fix: zcash address for exchange

* fix: honour the restore height the user entered

- Make Zcash rescan work in both directions by resetting sync from the new birth height
- Keep the entered height when restoring a wallet from keys
- Tolerate grouping separators in typed restore heights

---------

Co-authored-by: Vik Sharma <vik@cakewallet.com>
2026-08-11 03:15:59 +02:00
Seth For PrivacyandGitHub 8ed08fb10e Give ConfirmSwiper an accessible-navigation button fallback (#3464) 2026-08-11 03:15:32 +02:00
90194f7136 CW-1523-AnyPay Redesign (#3383)
* feat: Add network selector and compatible networks screen to send page for EVMs

* feat: add redesigned payanything flow and handle cases for evms, solana and tron

* feat: adding the swap part to the entire flow

* refactor: update navigation and error handling in send and swap flows

* adjust structuring and use the chain badge icons, also fix crypto and currency values not consistent on single network picker

* fix: modify guard for non-evm chains and minor cleanup

* use multipicker layout for receive section, streamline send to available balances and adjust colors as needed

* handle btc case for picker, update style and images across

* fix all pending issues with scanning qr and network handling for both evm and non evm, plus token and amount resolving

* fix all pending issues with scanning qr and network handling for both evm and non evm, plus token and amount resolving

* fix: expand fetch for tokens across networks and fix other issues regarding scanning from homepage

* auto-reformat

* fix conflict

* handle evm qr scan from homepage and add guards for swap and switch flows

* chore: Cleanup

* chore: set estimated fee to 0 when switching wallet

* fix merge conflicts

* fix to review comments and reformatting for lints

* fix: root cause for token bug reported

* chore: cleanup

* feat: add improved anypay swap screen which focuses on the receive amount
fix: fiat input toggle erro (also present on prod)
reformat: anypay related widgets

* feat: add alert for unsupported networks in payment requests

* fix: prevent rate and provider from showing until amount is calculated and loaded
fix: remove send icon in rate widget and align to center
fix: adjust network badge in swap confirm widget

* feat: embed tokens for solana and tron in QR generated for swap from external

* test: add unit tests for Tron, Solana, and ERC681 URI handling and detection

* fix: review comments

* fix: handle more edgecases

* feat: enhance ERC681URI to support raw token amounts and decimals

* refactor: streamline EVM chain ID retrieval in send page and recipient network row

* fix: minor review comments

---------

Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com>
2026-08-11 02:16:12 +02:00
Seth For PrivacyandGitHub b60b5a59f7 feat: show Bitcoin master fingerprint in wallet keys (#3489)
* feat: show Bitcoin master fingerprint in wallet keys

Add the BIP-32 master fingerprint (XFP) to the Bitcoin wallet keys shown
under Settings > Seed & Keys > Keys, labeled "Master fingerprint".

* fix: keep xPub in Keys tab by splitting silent payment keys into a dedicated list

The Keys tab / Silent Payments tab split relied on a hardcoded item index
(`items.sublist(0, 4)` / `sublist(4)`) that assumed the first four items
were always WIF/private key/public key/xPub. Adding the master fingerprint
as the first item pushed xPub into the Silent Payments tab.

Route Bitcoin silent payment keys into their own `silentPaymentItems` list
instead, and render each tab from its list.
2026-08-07 21:47:24 +02:00
Robert Malikowski 0c343726ce Merge branch 'refs/heads/dev' into cw-1440-new-buy-sell-flow
# Conflicts:
#	lib/new-ui/widgets/coins_page/cards/cards_view.dart
#	lib/new-ui/widgets/receive_page/receive_top_bar.dart
2026-08-07 01:35:54 +02:00
David AdegokeandGitHub 14d2862b39 Add tbb to solana default tokens (#3461)
* fix android CI

* deat: add The Bitcoin Bull to solana default tokens list

* add verification status for solana tokens
2026-08-06 15:28:56 +02:00
76b04580f3 a11y: send flow and QR scanner semantics (fields, errors, status announcements) (#3466)
* Make send flow controls announce their purpose and state

Wraps the send-flow inputs and controls in localized Semantics so a screen
reader can operate them:

- address, amount, memo and manual scan fields get a persistent name and
  announce their validation errors as live regions
- the asset picker, max-balance chip, recipient dots, dropdown toggles and
  scan page buttons become real buttons with selected/expanded state
- send progress, send errors, sync status and multi-part QR progress are
  exposed as single status nodes (announced once, not per tick)
- purely visual duplicates (address overlay, memo counter, max caption,
  full-screen tap-to-exit target, hidden confirm-sheet page) stop producing
  extra nodes
- LoadingPrimaryButton/PrimaryButton keep their name while showing a spinner
  and report the disabled state
- replaces hard-coded English "Synchronizing...", "Coin Control" and 'ok'
  with localized strings

* Scope mounted guard to the new announcement only

* Address review: liveRegion success, loading tap guard, drop redundant PrimaryButton wrapper

* Stop the currency picker from swallowing the amount field node

An on-device audit (CW-1574, D1) found the send screen's amount field
missing from the Android accessibility tree entirely: without this branch
the tree held an "Amount:" text node plus an EditText, with it there was
no node for the field at all. The address field, wrapped the same way on
the same branch, survived.

The difference is FormField. Flutter wraps a FormField's builder output in
its own non-container Semantics(validationResult:) annotation, and that
annotation becomes the node parenting everything the field renders. The
currency picker's Semantics had no `container: true`, so it is not a
boundary either and its configuration was absorbed into that same wrapper
node. The result was one node carrying isButton + a tap action + the
picker's label, with the amount TextField as its child - so screen readers
surfaced the container and never reached the field.

Declaring `container: true` on the picker gives it a node of its own and
leaves the FormField wrapper unlabelled, so the field, the paste button
and the picker are three sibling nodes again. No visual change.

The address field is unaffected because every Semantics inside its
FormField is already a boundary; the memo field and the scan manual-entry
field have no FormField and no absorbing sibling, so they were never hit.

Also replaces the two send-page caption comments, whose "the field
announces this label itself" claim is what let the regression through,
with a statement of what actually guarantees it.

* Name the amount field from its caption instead of labelling the field

Closure audit of the D1 fix found the amount announced twice on device:

  [o420] ImageView "Amount: 0.00"
    [k527] EditText "Amount: 0.00"
    "BTC"

The framework tree has no such duplication. Post-D1-fix it holds exactly
one node carrying the label, and that node *is* the text field: a merge
boundary with no children, reporting no children to the engine, with
isImage false on every node in the tree. The only thing above it is
FormField's own unlabelled Semantics(validationResult:) node, whose
children are the field, the paste button and the currency. That wrapper is
[o420] - its Flutter label is empty, so the text and the ImageView role the
device shows on it are synthesized platform-side from its descendants.

We cannot delete FormField's wrapper, so the only lever is to stop
authoring a label on the field for the wrapper to reflect. This restores
the pre-#3466 shape, which the device A/B already verified as good: the
visible "Amount:" caption goes back into the semantics tree and the field
carries no label of its own, giving caption + field as two adjacent stops.

Both halves are required - un-excluding the caption while keeping the
wrapper label produces two "Amount"-bearing nodes in the framework tree
itself.

The tradeoff is that the field has no programmatic accessible name any
more; it is named by reading-order adjacency to its caption, so focusing
it directly announces the value without the word "Amount". That is exactly
how the field behaved before #3466.

The address field keeps its merged label and excluded caption, because the
device audit shows it as a single correct node. Memo and scan manual entry
are untouched. The `container: true` D1 fix on the picker stays - it is
what got the field node back in the first place.

* Set headingLevel on the confirm-sheet title for the 3.41 engine

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2026-08-05 09:30:25 +02:00
claude[bot]andGitHub 0e42f162be Wrap long passphrase value on the wallet seed/keys screen (#3480) 2026-08-05 08:42:50 +02:00
Seth For PrivacyandGitHub a6bcf7c97a a11y: dashboard and navigation semantics (navbar, cards, sync, switchers) (#3468)
* Label new-UI dashboard navigation and sync status for screen readers

Each bottom-nav tab becomes one button node carrying its localized name, its
selected state and mutual exclusion; the pill text no longer duplicates the
selected tab. The assets/history tabs gain selected state, the Bitcoin/Lightning
switcher becomes a labeled toggle, and the sync bar becomes a single button whose
label is the localized status (plus Tor/MWEB/Silent Payments badges) with a hint
pointing at node management. Compact mode's pulsing dot and the chain icon's
progress ring now have text equivalents, and the invisible pointer-absorbing
strip behind the nav bar is excluded from traversal.

* Make new-UI dashboard cards, lists and menus operable by screen readers

Collapse the wallet-name row into one labeled button (hardware-wallet glyph
folded into the label, inner accounts button no longer a second stop) and give
balance cards a labeled, selectable node with a long-press action for show/hide
balance. Balances hidden behind AnimatedOpacity, card artwork and decorative
glyphs leave the semantics tree; the 3-dots customize target and the card action
buttons are now named buttons. Assets/history rows, the history header, the MWEB
promo, long-press menu items, the account cards and the add-account and
generate-name controls become single button nodes, the phantom action chip in the
assets header is built conditionally instead of drawn at opacity 0, and app cards
get a close tooltip plus an 'opens externally' hint.

* Use expression bodies for the new a11y helpers

* Restore untouched expressions in cards_view

* Address review: state-aware balance hint, tooltip-free close label

* Address review: keep header height without the chip, name the selected index
2026-08-03 19:58:51 +02:00
Seth For PrivacyandGitHub 26a16a4eef a11y: expose ModernButton, CoinActionButton and ModalTopBar as single labeled button nodes (#3463)
* Give ModernButton and CoinActionButton one labeled a11y node

ModernButton, CoinActionButton and the ModalTopBar chrome buttons were
icon-only IconButtons with no tooltip and a detached caption sibling, so
screen readers announced an unnamed "button" plus unrelated static text.

- Add optional semanticLabel to ModernButton; wrap the control in
  MergeSemantics + Semantics(button: true, label: semanticLabel ?? label)
  and exclude both the icon and the visible caption so exactly one node
  is exposed. No visual or pointer change.
- Give CoinActionButton the same treatment, reusing the label callers
  already pass (dashboard Send / Receive / Swap / Scan).
- Add leadingSemanticLabel / trailingSemanticLabel to ModalTopBar,
  defaulting the leading button to "Close", and mark a non-empty title
  as a semantics header.
- Migrate every ModernButton / ModalTopBar call site to pass a localized
  label, and drop the now-redundant external Semantics wrapper in
  base_page.dart so the back button is not labeled twice.
- Localize the hard-coded "Settings" modal title in settings_page.dart.

New English keys: rotate_address, swap_reverse_direction, switch_camera,
switch_input_currency, test_node_speeds, turn_flash_off, turn_flash_on.

* Require explicit accessible names on ModalTopBar and ModernButton icons

ModalTopBar defaulted every leading icon's accessible name to "Close", so
the ~40 callers that pass a back arrow announced the wrong action, and
trailingSemanticLabel was optional, leaving the trailing icon controls
unnamed.

- Drop the "?? close" fallback in ModalTopBar: the leading ModernButton
  now gets exactly leadingSemanticLabel, and constructor asserts require a
  non-empty label whenever leadingIcon / trailingIcon is supplied. Callers
  that pass leadingWidget / trailingWidget instead are unaffected, and a
  null icon still builds no button at all.
- Assert in both ModernButton constructors that the control has a name:
  either semanticLabel or a non-empty visible label.
- Pass the matching localized label at every ModalTopBar call site: "Go
  back" for back arrows, "Close" for close icons, and the action name for
  trailing icons (History, Configure, Export CSV, Save, Scan, Close).
- Label the last unnamed ModernButton (wallet accounts, wallet_info.dart).

No new localization keys; all labels reuse existing strings_en.arb keys.

* fix(a11y): put ModalTopBar header flag on the title Text node

On-device audit (CW-1574, defect D2) found zero [heading] nodes in the
Android accessibility tree on the Receive page: Semantics(header:) wrapped
the AnimatedSwitcher rather than the Text inside it, so the flag never
landed on the node that carries the title label.

Move the header semantics inside the AnimatedSwitcher, directly around the
Text. The ValueKey(title) moves with it onto the Semantics wrapper, since
AnimatedSwitcher switches on its direct child's key -- keying the Semantics
by title preserves the existing switch/animation behavior exactly.

No test or robot resolves the title ValueKey through a Text-typed finder,
so the key relocation is not observable from the test suites on this branch.

* Set headingLevel so Android surfaces modal titles as headings

The Flutter 3.41 engine drives AccessibilityNodeInfo.setHeading from
headingLevel, not the IS_HEADER flag (which 3.35 used). Proven by an
on-device probe: header:-only headings print on a 3.35-engine build and
vanish on the 3.41-engine CI build.
2026-08-02 09:50:26 +02:00
f6566e3baf Enforce passphrase confirmation match on the restore and create wallet flows (#3474)
* fix: enforce passphrase confirmation in restore passphrase bottom sheet

The confirm-passphrase field in AddPassphraseBottomSheet already had a
validator comparing it to the first field, but the fields were not inside
a Form and nothing ever called validate(), so the validator never ran.
Tapping "Restore" restored the wallet with the first field's value even
when the confirmation did not match, defeating the typo check.

Wrap both fields in a Form and validate it before completing the restore,
matching the existing pattern in advanced_privacy_settings_page.dart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DtLNfYw1H81p7zZrkNDXM

* Validate passphrase confirmation when the passphrase field is empty

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-02 09:43:46 +02:00
cyanandGitHub 92c2c5e043 zec ironwood (#3425)
* initial

* wip

* ironwood detection, migration

* dev: zec address validation on rt

* fix: ironwood spend

* ironwood regressions

* fix: tx history

* bump: zkool2

* fix: tx amounts on pending
fix: ironwood pending balance
fix: orchard->ironwood migration
fix: orchard->any pre ironwood txs
fix: ironwood -> any txs
fix: WrongSpendAuthorizingKey
fix: tx history
fix: autoshield to ironwood
fix: zkool to latest version
break: my sleep schedule :sweating: :worksonmymachine:

* thx fable

* downgrade frb to 2.11.1

* fix: tx amounts in history

* Migating... / migration label for O->I txs
2026-07-27 10:20:40 +02:00