Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535
upstream commits, including the Monero silent-open fix, Monero on Trezor,
Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin
sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive
adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9.
Merged with both sides normalized first (import rename, 100-column
format), which took the conflict count from 614 paths to 322. The
procedure is now scripts/upstream_sync.sh.
Kept from Hash Bags:
- WalletType enum order and Hive IDs. SQLite stores WalletType.index and
our order differs from upstream's; taking theirs would reread saved
wallets as other chains. Our frozen adapter keeps the shipped IDs.
- The 1.0.2 legacy PIN and wallet-password decode and self-heal.
- Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new
Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out.
- Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me),
converted to upstream's isDefault node-list key; Wownero is back in the
node reset list.
- Privacy: address resolvers limited to domain-anchored sources again;
prices.neroswap.com for fiat; upstream's charts tab, which reads
prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false.
- Lightning stays behind FeatureFlag.isLightningEnabled, including
upstream's new post-seed Lightning username step.
- Our icon pipeline, branding and bundle identifiers.
Twelve new upstream widget tests are skipped: test and code are unchanged
from v6.5.0 apart from renames (not rerun on a pristine upstream checkout;
upstream's top_bar_test does not compile against its own TopBar).
flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped;
flutter build bundle compiles. Built with Flutter 3.41.9.
The first macOS run died after 88 seconds on `cd scripts/monero_c: No such file
or directory`. That was the cheap failure. The workflow had been written fresh
rather than ported, and behind that missing clone it had also skipped the step
that pins the crypto salts. Had it got further it would have shipped a build
that mints random salts, which is the 2026-08-29 corrupted-seeds incident on a
new platform: wallets created on it would not decrypt after the next upgrade.
So the prerequisites are now ported step for step from build-linux.yml, the
desktop build known to pass: torch_dart, reown_flutter, bitbox (the Apple
variant, without the gomobile Android bindings), prepare_moneroc.sh, configure,
secrets generation, salt pinning with the same refusal, Trocador injection,
codegen, localization, and compile_graphics.sh. curl for wget and BSD sed's
-i.bak form, because the runner is a Mac.
Two more things the fresh version got wrong, found while porting:
configure was run without sourcing app_env.sh, and app_config.sh writes
CFBundleName, the bundle id and the version into Info.plist from variables only
app_env.sh sets. The app would have shipped with an empty name. The step now
prints the three values it wrote.
And app_env.sh hardcoded 1.0.0 (1) on macOS, 6.1.0 (79) on Linux and 1.9.0 (29)
in the fish variant, the last two being Cake Wallet's own version numbers. On
macOS PlistBuddy writes that into the bundle, so every macOS build would have
called itself 1.0.0. Linux only escaped because its substitution looks for a
version: 0.0.0 placeholder that pubspec_description.yaml does not contain. All
three now derive from pubspec_description.yaml the way Android already does, and
the two sh variants resolve to 1.0.4 (14). The fish one is unverified: fish is
not installed here and CI does not use it.
Separately, four workflows verified their secret injection by grepping
lib/.secrets.g.dart into the log, which prints the values. Android's printed the
Trocador, WalletConnect, MoonPay and exchange-helper keys. Gitea likely masks
registered secrets, but masking is a best-effort layer and values never belong
in output. Each now reports every key's name and length and nothing else.
Windows is not in this commit. Its run refused correctly, on the guard added in
c11c79b0c: the runner has Flutter 3.38.5, not the pinned 3.32.0. That is fixed on
the runner, not in the workflow.
Three desktop targets, three different states, one destination.
macOS did not build at all. There was no workflow, only Cake's from-source
scripts and an Xcode project. It turns out most of the work was already done:
monero_c ships aarch64-apple-darwin and x86_64-apple-darwin, and
Runner.xcodeproj already references both dylibs with CodeSignOnCopy. So this
fetches the same prebuilt bundle the iOS and Android legs use, lipos the two
slices into universal libraries, and builds. One download serves Apple Silicon
and Intel, because a single-arch build looks correct on the machine that made
it and fails on half of everyone else's.
It signs with a Developer ID Application certificate, which is NOT the Apple
Distribution certificate the TestFlight workflow uses. They are not
interchangeable: an App Store cert on a directly downloaded app is refused by
Gatekeeper. Signing is inside-out by hand rather than --deep, which Apple
deprecated and which skips nested code it does not recognise. Then notarytool
with the App Store Connect key this repository already has, then stapler, so
Gatekeeper clears the app without a network round trip.
Every credential is optional and the preflight says, before spending forty
minutes, exactly which stages the run can reach and what the user will see if
it cannot reach them. Absent secrets downgrade the build loudly; they do not
fail it and they do not pass silently.
Windows and Linux already built. Windows already Authenticode-signs through the
Fleet broker. What both lacked was a way to get the artifact to anybody:
upload-artifact is the only publication and Gitea Actions artifacts here upload
and then list as zero, so the signed Windows zip has never been reachable. Both
now publish to the GitHub release, on the smirk pattern: tag-triggered, the
release must already exist so a half-populated one never goes public, and a
same-named asset from an earlier attempt is replaced rather than duplicated.
Windows does its API work in PowerShell because git-bash there has neither
python nor node guaranteed and ConvertFrom-Json is native.
Tags now build automatically on all three. A tag is the thing a release is cut
from, and the artifact that gets signed should come from it rather than from
whatever was dispatched by hand.
scripts/release/sign-release.sh detach-signs the collected artifacts and signs
SHA256SUMS, which is the signature that matters when a download reaches someone
from a mirror without its .asc. It verifies even immediately after signing: a
signature written but never checked invites trust it has not earned.
Not done here, and both need a human: GH_RELEASE_TOKEN does not exist in this
repository's Gitea secrets, and the release key's user ID still says Smirk.
Windows built 1.0.3 against Flutter 3.38.5 while every other platform used
3.32.0. Dart 3.10 removed the _macros package, so pub could not solve
hive_generator against build_resolvers and the build died in dependency
resolution, on the same commit Android and Linux had just built cleanly.
The SDK was not missing. C:\flutter is exactly where provisioning puts it and
exactly what PATH resolved; it had been upgraded in place. Provisioning never
corrected it because it skipped on Test-Path alone, so a drifted SDK was
reported as done every time it ran. It now compares the version and replaces
the SDK when it does not match, which is the actual fix and the reason this
went unnoticed for however long the machine has been ahead.
The workflow stops guessing. My first attempt installed Flutter per build via
subosito/flutter-action, which fails on this runner anyway because its setup
script needs jq, and which would have downloaded a gigabyte every run to work
around a machine provisioning already owns. It now refuses early and prints
what is wrong and which script to run.
A build machine's toolchain must not decide which dependencies resolve, and
when it has drifted the build should say so rather than fail eighty steps
later inside pub.
No app code changes; hash-bags-ios-1.0.3 stays at 0697f41c7. Android and Linux
artifacts from that commit are unaffected.
Windows took whatever Flutter the runner happened to have. On 2026-09-19 that
was 3.38.5 with Dart 3.10.4, and Dart 3.10 removed the _macros package, so pub
could not solve hive_generator against build_resolvers:
macros >=0.1.3-main.0 depends on _macros from sdk which doesn't exist
So, because hash_wallet depends on both build_resolvers ^2.4.4 and
hive_generator ^2.0.1, version solving failed.
The build died at exit 69 before compiling anything, on the same commit that
Android and Linux built successfully minutes earlier. They differ only in
that both run inside the cake_wallet debian13-flutter3.32.0 image, and iOS
installs 3.32.0 explicitly. Windows was the only platform whose dependency
resolution was decided by a machine somebody had updated.
It now checks for 3.32.0 and installs it when absent, the same two steps iOS
already uses. No app code changes; hash-bags-ios-1.0.3 stays at 0697f41c7.
The detection branch found the toolchain, wrote RUSTUP_HOME and CARGO_HOME to
$GITHUB_ENV, and then verified with `rustc --version` in the same step.
$GITHUB_ENV only reaches later steps, so that verification ran with neither
variable set and the rustup shim fell back to %USERPROFILE%\.rustup. On this
runner that path is a file, so it failed with os error 183 while a perfectly
good toolchain sat at C:\Rust.
The step's own comment already explained why the shims need RUSTUP_HOME. It
just never exported it for its own use. The install branch does export before
its check, which is why only the detection path broke, and why it broke on a
runner that already had Rust rather than on a fresh one.
No app code changes, so hash-bags-ios-1.0.3 stays where it is: Android, Linux
and iOS are building from that commit and the artifacts are unaffected.
The Windows lane already runs on the machine that physically holds the Sectigo
EV code-signing token, but the runner there is NT SERVICE\act_runner, which
cannot use it: SafeNet holds the token login per user and an operator
establishes that login interactively. So the build queues the launcher for the
signing broker that Fleet installs (such-fleet roles/windows_code_signing) and
waits for it to come back signed.
CI never holds the private key -- it is non-exportable on FIPS 140-2 Level 2
hardware -- and never holds the token password. Sectigo allows three
consecutive failed password attempts before the token needs vendor unlocking,
and a job that never supplies a password cannot spend one.
Only HashWallet.exe is signed. The bundled MSVC runtime DLLs are already
Microsoft-signed and must not be re-signed, and the plugin DLLs are loaded by
an already-trusted process; it is the launcher the user double-clicks that
SmartScreen judges.
The signature, its RFC 3161 timestamp and the expected signer are verified on
the exe that actually goes into the zip, and any of the three failing fails the
build. If the broker's circuit breaker is tripped the job says so and stops
rather than shipping an unsigned binary.
Procedure: docs platform/windows-hardware-token-signing.md.
CI ran tool/generate_new_secrets.dart unpinned, so every release binary
shipped different AES keys/salts for the stored PIN and wallet passwords.
Updating the app then failed to decrypt what the previous build stored:
'Invalid or corrupted pad block' at PIN entry and the 'Corrupted seeds'
dialog, with wallet files fully intact.
- entities/encrypt.dart: legacy-key fallback decode for PIN and wallet
password blobs (candidate sets recovered from the shipped 1.0.1 binary;
output validated to reject PKCS7 false positives)
- key_service/auth_service: on successful legacy decode, re-encrypt under
the pinned key — installs self-heal at first unlock
- all build workflows: inject the eight pinned CRYPTO_* repo secrets after
secret generation and hard-fail if any is missing, so crypto salts can
never silently rotate again (values pinned via
~keys/set-gitea-crypto-secrets.sh)
- bump 1.0.2+12
flutter build windows materialises plugins under
windows/flutter/ephemeral/.plugin_symlinks/ as NTFS symlinks. Flutter gates
this on the AppModelUnlock registry key rather than the actual privilege, so
the build failed with "Building with plugins requires symlink support".
Sets AllowDevelopmentWithoutDevLicense=1 in HKLM (what the Settings toggle
does); LocalSystem can write it, so no interactive login. Idempotent.
7-Zip SFX wrapped HashBags.exe extracted to a fresh %TEMP%\7zS<random>\
subdir on every launch. path_provider_windows derives the wallet
storage dir from the .exe location — so each launch saw a brand new
"app dir" with no prior wallets, failing with "keys corrupted" before
hanging at the PIN prompt. The multi-file zip distribution doesn't
have this problem because the .exe lives at a stable user-chosen path.
Multi-file zip is also what Firefox Portable, VS Code Portable, OBS
Portable, and ~every other Flutter Windows desktop app ship as. The
fashion in Windows is "portable = no install / no registry," not
"portable = one file."
Single-file via Inno Setup installer or Enigma Virtual Box remains
possible as a v1.1 follow-up. For v1, ship the zip.
The 23.01 "7-Zip Extra" archive (7z2301-extra.7z) was reorganized and
no longer contains the installer-mode SFX modules — only the
standalone 7za.exe/dll + Far plugin. The SFX modules were moved into
the LZMA SDK archive (lzma2301.7z), under bin/:
bin/7zSD.sfx — classic installer-mode SFX (config.txt + RunProgram)
bin/7zS2.sfx — newer variant
bin/7zS2con.sfx — console variant of the new module
Fetch the LZMA SDK instead. Use a filtered extract ('bin/*.sfx') so we
only pull ~150KB of binaries from the 1.3MB SDK archive (skipping the
700 source-code files we don't need).
7-Zip Extra 23.01 puts 7zSD.sfx / 7zS.sfx under x64/ (64-bit variants)
and x86/ subdirs, not at the archive root. The previous lookup only
checked the root and bailed even though the modules were one level
deeper. Add x64/ as the primary candidate path, fall back to root for
older Extra archive layouts.
The 24.x extra archives have been pruned from the live 7-zip.org CDN —
verified by curl HEAD over 2401/2403/2405/2406/2407/2408/2409, all
return 404. Only 7z2301-extra.7z (Jan 2023) is still served with 200.
That version's installer-mode SFX modules (7zS.sfx, 7zSD.sfx) are
backwards-compatible with any modern Windows so pinning here is fine.
choco's 7-Zip install ships 7z.sfx (GUI extract-here prompt) and
7zCon.sfx (console extractor) — but NOT 7zS.sfx / 7zSD.sfx, the
installer-mode SFX modules that support `extract to temp + auto-run
RunProgram`. Those live in the "7-Zip Extra" supplementary archive
(https://www.7-zip.org/a/7z2409-extra.7z) which has to be downloaded
separately.
Fetch + extract the Extra archive on demand in the SFX-wrap step.
Cache it under $RUNNER_TEMP so subsequent runs reuse the same module
without re-downloading. ~250 KB download, one-shot per runner working
dir lifetime.
The previous commit's bash heredoc for the 7-Zip SFX config:
cat > sfx-config.txt <<'SFXCFG'
;!@Install@!UTF-8!
...
SFXCFG
…had its body at the left margin (since heredoc lines are taken
literally). But the surrounding YAML used `run: |` which requires
every line to be indented more than the `run:` key. The unindented
heredoc body broke out of the block scalar — Gitea Actions
rejected the workflow with "could not find expected ':'" at the
;!@Install line.
Switch to a single `printf '...' > sfx-config.txt` line that fits
cleanly inside the YAML block. Same on-disk file content, no YAML
indentation conflict.
Replace the .zip artifact with a 7-Zip self-extracting .exe wrapper.
On launch, the wrapper extracts the Flutter Windows build tree to
%TEMP% and runs HashWallet.exe — user experience is one .exe to
download and double-click. Each launch re-extracts; subsequent launches
are slower than a real install but zero-touch portable is the user's
explicit preference over an installer.
SFX module probe handles 7-Zip naming variations (7zS.sfx / 7zSD.sfx /
7zSfx.sfx) so the step doesn't break across 7-Zip releases.
Self-hosted Windows runner builds an unsigned Windows Release zip with
the Flutter .exe + bundled monero/wownero DLLs + MinGW runtime DLLs +
MSVC runtime DLLs. workflow_dispatch only — triggered from the Gitea
Actions UI.
Major design points hammered out during initial bring-up:
- Use explicit Git Bash path for `shell:` rather than `shell: bash`,
since act_runner under LocalSystem resolves bare `bash` to
C:\Windows\System32\bash.exe (WSL) which refuses to run as
LocalSystem.
- Configure git identity + safe.directory before any submodule ops,
since LocalSystem has no global git config and scripts/monero_c/
apply_patches.sh does git-commit-equivalents.
- Clone torch_dart + reown_flutter from git instead of tarball — the
upstream-published .tar.gz tarballs contain example-app
.plugin_symlinks with absolute /home/runner paths that Windows tar
refuses to extract.
- Fetch monero_c prebuilt release-bundle.zip (same one Android/iOS
use) and re-stage its x86_64-w64-mingw32 wallet DLLs into the
layout that windows/CMakeLists.txt expects:
scripts/monero_c/release/<coin>/x86_64-w64-mingw32_libwallet2_api_c.dll
CMake's install() rules RENAME them to <coin>_libwallet2_api_c.dll
in the Release dir.
- Mirror those wallet DLLs under their lib<coin>_wallet2_api_c.dll
Dart-FFI-expected names — package:monero's DynamicLibrary.open()
uses a different naming convention than windows/CMakeLists.txt's
RENAME rules.
- Source MinGW runtime DLLs (libssp-0.dll, libwinpthread-1.dll) from
Git for Windows' bundled mingw64/bin — has modern symbols including
pthread_cond_timedwait64 that monero_c requires. Flutter's bundled
mingit is too old (2017-vintage libwinpthread).
setup-windows-runner.ps1 (the one-shot toolchain installer for the
runner host) is gitignored — it's runner-side infra, not part of the
app.