100 Commits
Author SHA1 Message Date
jwinterm d74a6aeeb6 ci(android): name the APK and AAB after the version they carry
Flutter writes app-release.{apk,aab} for every build, so each release's
artifact unzips to the same filename. A stale 1.0.3 app-release.aab in
Downloads was uploaded to the 1.0.4 Play release and refused for reusing
versionCode 13. Copy the outputs to hash-bags-<version>-build<N>.* from
pubspec_description.yaml and upload those, refusing if the version line
does not parse.
2026-10-02 13:42:11 -04:00
jwinterm b07adfff5b ci(macos): port the prerequisites instead of guessing them, and stop printing secrets
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.
2026-09-26 11:16:53 -04:00
jwinterm 374048958a chore: 1.0.4+14
1.0.3 reached both stores carrying Cake Wallet's app icon on the About screen,
Cake's wordmark on the receive QR page, and Cake's release notes in What's New.
All three were compiled or read from files the August de-cake pass never
touched. This is the release that fixes them.

Build 14: Play wants a higher versionCode and App Store Connect wants a higher
build than 13, and both read this one file.
2026-09-26 05:15:16 -04:00
jwinterm 8c33061917 fix(brand): the in-app release notes were still Cake's
The What's New screen read "Add support for Bitcoin Lightning / New Design".
That file has never been touched since it was inherited; the last three commits
against it are upstream Cake release candidates. It shipped in 1.0.3.

It now describes what this release actually changed, one line per bullet, which
is the format the screen splits on.

The screen also chose between two files based on isMoneroOnly, and the
monero.com one it named does not exist in this repository. Any build where that
flag went true would have thrown on a missing asset instead of showing notes.
Hash Bags ships eleven chains so the flag is always false, which is the only
reason it never fired. One file now, and the import that existed solely for
that branch is gone.

Also removes assets/images/cupcake.png, which nothing in the repository
references. The cupcake SVG stays: HardwareWalletType.cupcake is a third-party
airgapped signer sitting in a list beside Ledger and Trezor, so it is interop,
not somebody else's branding.
2026-09-24 21:21:41 -04:00
jwinterm 47944ed9dd feat(ui): the send celebration throws WOW and XMR, not only bitcoin
The "transaction sent" animation erupted #, $ and ₿ out of the money sack. A
Wownero-first wallet that celebrates every send by throwing somebody else's
coin is advertising for them on the one screen a user reaches directly after
parting with their own money.

Adds Monero and Wownero glyphs and re-spaces the stagger across ten particles
so the fan stays even. WOW leads and is the largest, because that is the coin
this wallet is for.

The sprites are rendered at 128px from res/pictures/crypto_full_icons, which is
the same artwork the chain icons elsewhere in the app use, so the bag throws
the coins the rest of the UI draws rather than a second opinion about what they
look like. 128x128 RGBA to match the three that were already there.
2026-09-24 19:50:10 -04:00
jwinterm fff5259860 fix(brand): de-cake the actual source, not the file it is compiled over
The About screen has been showing Cake Wallet's app icon, and the receive QR
page — the screen a user screenshots to get paid — has been showing Cake's
wordmark. In CI builds only, which is why nobody caught it.

compile_graphics.sh compiles res/pictures/*.svg into assets/new-ui/*.svg.vec,
and every platform workflow runs it. That makes res/pictures the source and
assets/new-ui the output. The August de-cake pass replaced three files under
assets/new-ui and left res/pictures untouched, so each CI build faithfully
recompiled Cake's artwork straight over the top of ours. Locally it looked
right, because locally nobody re-runs the compiler.

Three files, all of them branding: hash_squircle_icon.svg, hash-wordmark.svg,
hash-wordmark-light.svg. The sources are now the Hash Bags artwork, so the
compiler produces what the filenames have claimed since August.

This is the third time somebody else's artwork has reached users through this
repo, and the second time an audit missed it, so it gets a test rather than
another careful look: any SVG present in both res/pictures and assets/new-ui
must be byte-identical. Drift means a hand-edit that the next CI build will
silently revert. The test also asserts it compared something, because a silent
zero is how the previous two got through.

AGENTS.md has had the rule the whole time: generated files are never
hand-edited, fix the source and regenerate. The trap here is that the generated
copies sit beside the sources under identical names, and the ones the Dart code
references are the generated ones.
2026-09-24 19:50:00 -04:00
jwinterm c1f618176c ci(desktop): build macOS, and publish every desktop artifact to the release page
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.
2026-09-23 08:13:10 -04:00
jwinterm c11c79b0c5 ci(windows): refuse a drifted Flutter, and let provisioning notice one
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.
2026-09-19 14:13:39 -04:00
jwinterm 25acfe656b ci(windows): pin Flutter 3.32.0 like every other platform
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.
2026-09-19 14:09:26 -04:00
jwinterm d703455ef2 ci(windows): export the Rust env for the step that checks it
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.
2026-09-19 12:07:18 -04:00
jwinterm 0697f41c7e fix(brand): stop shipping Cake artwork, and 1.0.3+13
Audit of every surface that could show somebody else's logo. Most of it was
already ours: the transaction success screen draws hash_bag.png and the
hashbag_sym_* glyphs, cake_logo_light/dark.svg and the Android icon set were
redrawn from the Hash Bags mark in May and only kept their old filenames, no
user-visible string contains "Cake", and every cupcake reference is a
HardwareWalletType icon sitting next to Ledger and Trezor, which is interop
rather than branding.

Two places were not ours.

The Flatpak manifest installed cakewallet_icon_180.png as the Linux desktop
icon, and that file is upstream Cake artwork from 2025 that the rebrand never
touched. So the Linux build has been shipping a Cake Wallet icon. It now uses
the Hash Bags mark. The macOS icon script had the same fault, latent only
because no workflow builds macOS; it gets a proper Hash Bags set at all seven
sizes rather than a landmine for whoever turns that build on.

Deleted the Cake artwork nothing needs: cake_logo.png, cakepay.png, the three
cakewallet_icon_* files and the macOS set. assets/images/ is bundled whole, so
unreferenced files were shipping inside the binary regardless of whether any
code could draw them.

Also fixes a bug the audit turned up rather than a branding one. The theme
picker chose its preview icon by matching theme.title, which is built from the
accent colour's display name, so renaming an accent silently changed the icon.
It had: the list still named Cake Primary, Tron Red and Frosting Purple, none
of which ship, while our own Such Pink and Such Blue were absent and fell
through to the plain dark icon. It asks the theme what it is now, not what it
is called.

Version to 1.0.3+13. pubspec_description.yaml is the single source for all five
platforms; the checked-in pubspec.yaml is a build artifact.

Still there, reported not removed: cake_pay_icon.png, reachable only through
the Cake Pay order provider, which is the only provider, has an empty API key
and cannot produce an order in this app. Removing it means removing the orders
feature, which is not a branding change.
2026-09-18 19:45:17 -04:00
jwinterm 75a7193da6 fix(reports): say which build produced the error
An error report that cannot be tied to a binary tells us a bug exists and
nothing about whether we already fixed it. Of the ten reports received since
May, five carry the Wownero addresses crash that shipped fixed in 1.0.2, and we
could only tell by reading timestamps buried in the traces themselves.

Two gaps, both closed here. The body carried "App Version: 1.0.0" without the
build number, and 1.0.0 shipped as Play builds 9 and 10 which encrypted under
different keys, so the version alone cannot identify the binary that wrote a
wallet we are being asked to heal.

And the body is the part of this flow we do not control. It reaches us only if
the user pastes what we put on their clipboard. On 2026-09-17 somebody typed a
sentence into the trace box instead, and the report arrived with no version, no
platform and no stack at all. So the app now also puts its version and platform
in the query string of the link it opens, where the form reads it rather than
the user. Someone who reaches the page without the app simply reports nothing,
which is honest and stays empty rather than guessing.
2026-09-18 13:09:54 -04:00
jwinterm 8e45de5928 ci(windows): Authenticode-sign HashWallet.exe with the hardware token
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.
2026-08-31 18:00:18 -04:00
jwinterm 3a60b921f4 ui: fix grey Wownero coin logo + lift too-dark dashboard background
- res/pictures/balance_card_icons/wownero.svg had an opaque background rect
  (+#FFF5FD fill), so the ChainIcon's srcIn colorFilter flooded the whole
  circle solid grey. Replaced with a clean alpha glyph (white W-in-circle on
  transparent, like monero.svg) so the dark bg shows through the negative
  space. Source in res/pictures → compile_graphics.sh regenerates the .vec.
- dark theme surface #0B1F14 → #10281B (a bit less dark, per feedback)
2026-08-30 10:36:50 -04:00
jwinterm 6a41701656 feat(android): embedded Wownero node — run a full node on the phone
Bundles wownerod (aarch64-linux-android, built via monero depends) as
jniLibs libwownerod.so; EmbeddedNode.java execs it from nativeLibraryDir
(the exec-exempt path), binds RPC on 127.0.0.1:34568 for the wallet, pruned
mode by default, capped concurrency for phone thermals. Dart EmbeddedNode
service (start/stop/status/waitUntilReady) drives it over a method channel.

Verified: builds into the APK (binary bundled, arm64), and the daemon
executes on real hardware (moto g stylus) — inits core, binds RPC, connects
peers, and syncs blocks. UI toggle + foreground-service lifecycle are the
next layer; binary is provisioned like the wallet libs (gitignored).
2026-08-30 10:17:11 -04:00
jwinterm acccba9210 fix: remove duplicate useSSL key in polygon_node_list.yml (runtime crash)
package:yaml (used by the app) throws 'Duplicate mapping key' on the two
useSSL entries; PyYAML tolerated it so it slipped through. Adds a test that
parses every bundled YAML asset with package:yaml to prevent regression.
2026-08-30 04:55:28 -04:00
jwinterm 24face9432 fix: harden legacy wallet-password validation to exact generateKey() shape + test
- validate legacy-decoded wallet passwords are real generateKey() output
  (696 chars, base64 of 512-byte key + 8-byte iv) so a wrong AES key that
  unpads to printable bytes can't be re-saved as a bogus password
- test/legacy_decode_test.dart: proves 1.0.0->1.0.2 recovers PIN+password
  and 16k wrong keys yield zero false-accepts
2026-08-29 15:08:40 -04:00
jwinterm 7fcef4e4ce fix: add 1.0.0 (build 9/10) legacy secret candidates recovered from the Play AAB
Cross-referencing both shipped binaries' string tables also separates
per-build randoms from shared library constants, trimming false candidates.
2026-08-29 14:58:58 -04:00
jwinterm d8aab70fa7 fix: 1.0.2 hotfix — heal wallets broken by per-build random crypto secrets
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
2026-08-29 14:53:18 -04:00
jwinterm 8db45c5842 ios: raise MinimumOSVersion to 15.0
App Store Connect warning 90068 on the last two uploads: from Spring
2027 all iOS apps must set MinimumOSVersion 15.0 or later. Raising from
13.0 costs almost no devices (iOS 15 still covers iPhone 6s and SE 1).
Rides the next iOS build.
2026-08-29 05:22:17 -04:00
jwinterm d19e52d308 ci(test): generate models and localization before analyzing
generated/i18n.dart and several .g.dart parts are untracked generation
outputs too; without them a fresh checkout analyzes with thousands of
undefined_identifier errors. Run model_generator.sh and
generate_localization exactly as the build lanes do.
2026-08-21 05:25:36 -04:00
jwinterm a144d037d5 ci(test): generate the untracked workspace before resolving
pubspec.yaml, the cw_* dependency wiring, wallet_types.g.dart, and the
.secrets.g.dart files are generated and untracked, so the workflow's
bare pub get failed on every push since the workflow landed. Mirror the
build lanes: description -> generate_pubspec -> coin configure ->
generate_new_secrets (empty defaults), then analyze and test. This also
makes the CI resolve identical to release builds, so descriptor drift
like the cake_backup ref conflict fails here in minutes instead of at
release-build time.
2026-08-21 05:00:41 -04:00
jwinterm 78bd866fa6 fix(deps): pin cake_backup and bech32 refs in cw_core to match root
Pub requires identical git descriptors across the graph: the root's
pinned SHAs (pubspec_base) conflicted with cw_core's ref:main /
unpinned declarations, failing CI's pub resolve after the configure
step regenerates pubspec.yaml. Local resolution had masked it because
the untracked working-tree pubspec.yaml predated the pins. Pins now
identical everywhere (audit script confirms no remaining multi-ref git
deps); sibling lockfiles re-locked.
2026-08-18 11:07:17 -04:00
jwinterm ae824d6455 fix: WOW crash on Receive -> Addresses (AccountPreviewHeader)
User report 05ebf86e: monero?.getCurrentAccount on a Wownero wallet. The
null-aware operator guards plugin presence, not wallet type, so the
Monero bridge always ran and CWMonero's cast threw before the ?? chain
could reach the wownero branch. Dispatch by wallet.type instead. Swept
lib for the same anti-pattern: account_customizer already dispatches
(fixed 2026-08-07) and the settings_store call sites take no wallet
argument; this was the last live instance.
2026-08-18 08:28:25 -04:00
jwinterm ca771f51d5 prices: non-USD fiat display works now; update endpoint comment
Server-side forex (ECB daily reference rates) shipped in
hash-wallet-prices; the wallet needed no code change. Comment updated so
the file stops claiming USD-only.
2026-08-16 00:09:23 -04:00
jwinterm 0b548ed8ca nodes: own node as Wownero default; fix Arbitrum RPC; drop keyless NOWNodes
- Wownero default becomes node.wownero.org:443 (SSL, ours) instead of a
  third-party plaintext host
- Arbitrum's default was arbitrum.nownodes.io, which requires an API key
  we don't ship (nowNodesApiKey is empty) — every fresh install had a dead
  default RPC; now arbitrum-one-rpc.publicnode.com
- remove the other NOWNodes entries from node lists and the EVM chain
  registry for the same reason; historical migration maps keep their
  entries so old users still get migrated off
2026-08-15 19:37:34 -04:00
jwinterm 1bd91f7ab3 exchange: curate the swap picker to an explicit allowlist
Deposit side: natives of shipped chains (WOW, XMR, BTC, BCH, DOGE, XNO,
ETH, ETH-Base, ETH-Arb, BNB, POL) plus major stables (USDT/USDC variants,
DAI). Receive side: same minus BNB (inherited upstream restriction) plus
LTC as a receive-only target. ZEC, DASH, SOL, AVAX, Zano, Haven, ADA,
EOS, XLM, XRP, TRX, TON and the rest disappear from the swap UI; their
CryptoCurrency entries stay defined because the raw ids back Hive-stored
trade history.
2026-08-15 19:37:34 -04:00
jwinterm 47680c1162 Merge gitea-builds/dev: sync CI remote merge history into dev 2026-08-15 16:30:21 -04:00
jwinterm 599dcd13d5 chore: bump version to 1.0.1+11 for the post-launch hardening release 2026-08-15 16:29:20 -04:00
jwinterm 21587d0c88 refactor(cw): dedupe mnemonic wordlists into cw_core
The 10 language wordlists were byte-identical between cw_monero and
cw_wownero (16,291 duplicated lines). A single copy now lives in
cw_core/lib/mnemonics/, and the configure.dart code-gen templates import
from there.
2026-08-15 16:29:07 -04:00
jwinterm adece963c3 refactor: delete dead Cake Pay/buy/exchange code; split di setup()
- Cake Pay: 36 files (~5.3k LOC) plus DI registrations, routes, and UI
  branches (feature was hard-disabled and unreachable)
- unreachable buy providers: robinhood, dfx, onramper, meld, kryptonim
  (~1.7k LOC); MoonPay untouched
- exchange providers that only existed as commented-out wiring:
  changenow, sideshift, chainflip, exolix, swaptrade, letsexchange,
  stealthex, xoswap, swapsxyz, thorchain, simpleswap (~5.2k LOC);
  Trocador untouched; ExchangeProviderDescription values and Hive
  adapters kept so stored trades and orders still deserialize
- di.dart setup() split from one 1,452-line function into 10 sequential
  chunk functions; registration execution order verified identical
  before and after (251 register calls)
2026-08-15 16:29:03 -04:00
jwinterm 71706314fb privacy: only domain-anchored address resolvers; drop Cake endpoints
Address-field input no longer leaves the device except toward
infrastructure implied by the typed name itself:
- remove Twitter, Mastodon, Nostr, Yat, Unstoppable Domains, FIO,
  ThorChain-name, zcash.me, and Zano alias lookups (Nostr and FIO ran
  with no setting gate at all; Zano is not even a supported chain)
- keep OpenAlias, BIP-353, .well-known, LNURL, and ENS, each toggleable
  in Settings -> Domain lookups
- BTC fee/block/restore-height queries move from mempool.cakewallet.com
  to public mempool.space (also fixes two /api/v1 paths that 404 on
  standard mempool deployments)
- PRIVACY.md updated to disclose the remaining flows (mempool.space,
  name lookups, opt-in silent payments and payjoin); issue templates and
  the translation prompt de-Caked
2026-08-15 16:28:54 -04:00
jwinterm 77ae91e695 ci: run analyze + tests on every push/PR; make the suite real
- delete the stock Flutter counter widget_test (always failed) and the
  lightning invoice tests (network-dependent, and the feature is off)
- fix rotted assertions: removed-chain wallet types (solana, tron, zano,
  decred) and mainnet-only BTC/LTC pattern expectations
- new test.yml workflow: pub get + analyze + test on push/PR to dev/main
- analysis_options: drop excludes for deleted paths, stop excluding the
  five live coin facades, enable empty_catches and 7 other correctness
  lints (still 0 analyzer errors)
- integration_test_runner: clean hash_wallet data dirs, not cake_wallet
2026-08-15 16:28:44 -04:00
jwinterm 08fb925cea fix: wownero P0s, fail-closed RNG, iOS scheme, dep pins
- resetToDefault omitted Wownero: the reset button null-crashed and left
  WOW wallets with an empty node list
- accountLabel was missing a return and rendered blank for WOW wallets
- sec_random now throws instead of returning empty bytes when the native
  entropy channel fails or under-delivers
- iOS builds no longer register the leftover cakewallet:// URL scheme
- WOW fee estimate now fetched from the connected daemon's
  get_fee_estimate RPC and cached; the inherited XMR constant table
  remains only as a pre-fetch fallback
- pin cake_backup, flutter_local_authentication, and bech32 git deps to
  their locked SHAs for reproducible builds
2026-08-15 16:28:39 -04:00
jwinterm 94bf0e8c41 ci(windows): enable Developer Mode for plugin symlink support
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.
2026-08-07 12:38:36 -04:00
jwinterm 7ea02dcfda nodes: add node.wownero.org and wow.smirk.cash on :443 2026-08-07 12:33:55 -04:00
jwinterm 64642818f8 ci: update Windows build workflow 2026-08-07 10:29:15 -04:00
jwinterm 05bef0c14c chore: bump build number to 1.0.0+10 for TestFlight/Play (replaces build 9) 2026-08-07 05:12:47 -04:00
jwinterm 852f8d536d tools(demo): store-screenshot composers (App Store, 13" iPad, Play 2:1)
storeshots.py (website-dark theme), storeshots_ipad.py (2048x2732), and
storeshots_play.py (1290x2580, Play's 2:1 cap). Compose the funded demo captures
onto the Hash Bags brand background for the store listings.
2026-08-07 03:31:55 -04:00
jwinterm 4818cdf017 brand: de-cake new-ui asset filenames + drop unused cake art
Rename the new-ui assets (source in res/pictures + compiled .svg.vec in
assets/new-ui) and their 3 code refs. Content was already Hash Bags; only the
filenames were cake:
- cake_squircle_icon      -> hash_squircle_icon   (About page)
- cakewallet-wordmark(.light) -> hash-wordmark(.light) (receive QR)
- cake-setting            -> hash-setting          (settings)

Remove unused cake assets (0 refs): by-cakelabs, cakelabs-icon, new-ui cupcake
duplicate, cake_icon.png, cake_wordmark.png. (assets/images/cupcake* kept — that
is Cake's Cupcake hardware-signer device name, a real integration, not branding.)
2026-08-07 03:31:32 -04:00
jwinterm 2e33f8324e feat(send): animated hash-bag on the "Transaction Sent" screen
Replace the static success illustration with HashBagSuccessAnimation: the money
sack sits at the bottom and #/$/₿ glyphs erupt from its mouth, rising and fading
on a loop. Glyphs are PNG assets (the app bundles no font with a ₿ glyph).

InfoPage gains an overridable buildIllustration() hook (default unchanged:
static HashImageWidget); only TransactionSuccessPage overrides it, so every
other InfoPage is untouched.

NOTE: written without a local Flutter build (Dart pub can't resolve here) —
verify on the next CI/device build. Self-contained and revertible; the static
hash_bag.png remains the fallback via imageLightPath/imageDarkPath.
2026-08-07 03:02:37 -04:00
jwinterm 7e368f92e6 brand: de-cake the app (hash-bag success art, asset + widget renames, filter, icon padding)
Send-success art: replace the Cake birthday cake with a Hash Bags money sack
that has a # on it and erupts #/$/₿ (assets/images/hash_bag.png). Used by the
success page, send-confirm sheet, exchange-trade, and dEURO savings.

Asset renames (files + refs):
- birthday_cake.png            -> hash_bag.png
- restore_cupcake(.png/_dark)  -> restore_hashbag.png
- qr-cake.png                  -> qr-hashbag.png
- arrow_bottom_cake_green.png  -> arrow_bottom_green.png (+ 2.0x/3.0x)

Identifier renames:
- CakeImageWidget -> HashImageWidget (widget + file + 93 refs)
- typography _cakeRegular/_cakeBold/_cakeSemiBold -> _hash*

Transaction filter: drop the Cake Pay "Orders" row and every non-Trocador
trade provider; the swap gate is already Trocador-only, so this just aligns the
filter UI with reality.

Android launcher: add breathing room to the adaptive-foreground # (all
densities, monochrome included); iOS icon unchanged.

About page: swap the Cake easter eggs for Hash Bags ones.

Deferred (needs a build to verify): new-ui .svg.vec filename renames
(cake_squircle_icon, cakewallet-wordmark, cake-setting) — already Hash Bags in
content, only the filenames are cake, and they route through the .vec precompile.
2026-08-07 02:57:29 -04:00
jwinterm 6dc3da4621 feat(demo): App Store screenshot composer (storeshots.py)
Composes App-Store-ready 1290x2796 (6.9" iPhone) screenshots from the raw
1080x1920 harness captures: crops the mobile status bar (per-screen — deeper
for bottom-sheet modals), frames each capture with rounded corners + soft
shadow on the Hash Bags brand gradient, and burns a headline + subhead.

Copy is factual and benefit-driven (no price/return/"investment" claims) to
stay inside App Store Review 3.1.5 and the no-misleading-claims rules. Reads
brand colors/fonts from packs/hash/pack.json; needs Pillow.

Output (funded demo wallet, real 13.37 WOW): dashboard / receive / send / swap
under ~/Build/hash-wallet/demo/appstore/. Same source works for the Play
listing; frame + resize for other required device sizes as needed.
2026-08-03 15:56:12 -04:00
jwinterm 985cbf0064 fix(ios): recover wallet password across Keychain param changes; no crash
getWalletPassword force-unwrapped the secure-storage read, so a null iOS
Keychain result (item written by an older build under different Keychain
params, e.g. after a TestFlight update) hard-crashed the app and the wallet
showed "Corrupted seeds" — reported by a tester whose older Wownero and BTC
wallets broke on update.

Now it:
- falls back to the legacy readNoIOptions path (queries the OTHER iOS Keychain
  accessibility), which can AUTO-RECOVER such wallets without a seed restore;
- falls back to the #__<name>_bak__# backup password the Monero migration
  writes before re-encrypting;
- throws a catchable error instead of a null-check crash, so the loader can
  offer seed/restore recovery rather than the app dying.

Only changes behavior on the previously-crashing null path — strictly safer.

Not the file cipher (iOS is always Salsa20) nor the Monero migration
(Wownero/BTC don't run it): the wallet files are intact, only the Keychain
password was unreadable. flutter_secure_storage stays pinned (ca897a08) and the
accessibility stays first_unlock; those must not change without a migration
(see docs/internal-notes.md).
2026-08-03 14:48:29 -04:00
jwinterm 2afcf010e1 feat(demo): funded-wallet restore + build-arg fixes
Adds DEMO_WALLET_SEED/DEMO_WALLET_HEIGHT support so a demo build restores a
fixed (throwaway, funded) Wownero wallet instead of creating a fresh one, via
WalletRestoreViewModel.create. Balance lives on-chain against the seed, so it
survives any emulator wipe and is reproducible.

Three real bugs fixed on the way:
- build-apk.sh built the --dart-define args as a word-split string; the 16-word
  seed's internal spaces split into separate argv tokens and flutter treated a
  seed word as a build target ("Target file '<word>' not found"). Now a bash
  array keeps each --dart-define one token.
- WalletRestoreViewModel.getCredentials reads name/passphrase from the options
  MAP (not the vm field) and casts name as non-null String, so the restore
  options must include 'name' and 'passphrase'.
- forward DEMO_WALLET_SEED/HEIGHT into the build container; source demo.env.

Verified end to end: demo build restores the wallet, its address matches the
funded one exactly (QR-decoded), it syncs at height 861800, and the dashboard
shows the real 13.37 WOW balance + tx history. Send shows Max 13.37.

The seed lives only in gitignored tools/demo/demo.env (never committed).
docs/internal-notes.md (gitignored) documents the FailedDecryption gotcha.
2026-08-03 13:53:13 -04:00
jwinterm 4dd15704b0 feat(demo): honor SCREENSHOT_MODE as alias for the iOS UITest path
The iOS fastlane snapshot UITest already passes --dart-define=SCREENSHOT_MODE=
true; accept it as an alias for DEMO_MODE so the Mac screenshot path drives the
same demo-wallet boot without changing the UITest. Trivial const OR; validated
on next build.
2026-08-03 08:36:30 -04:00
jwinterm 4be2424486 feat(demo): DEMO_MODE — boot straight to an unlocked wallet for capture
Adds a debug-only capture mode so screenshots/video never depend on the
onboarding/PIN flow, whose synthetic taps are unreliable under the headless
emulator (both flutter_test and adb input drop taps on the PIN confirm screen).

lib/entities/demo_mode.dart: on launch, when kDebugMode AND
--dart-define=DEMO_MODE=true, create a fresh throwaway Wownero wallet through
the real WalletNewVM.create() (so walletInfo/derivation/paths/persistence and
changeCurrentWallet all happen exactly as in the UI), then set the PIN and
allow auth — landing on the dashboard. Reuses the wallet on later launches.
Both halves are compile-time const/kDebugMode, so this is tree-shaken out of
release: DEMO_MODE cannot be enabled in a store binary.

lib/main.dart: call maybeSetupDemoWallet() during bootstrap and force the
initial route to the dashboard in demo mode.

tools/demo/{build-apk,capture}.sh: DEMO=1 bakes the define into a local build;
flutter drive passes it so scenarios start at the dashboard.

Verified end to end: demo build boots to a live Wownero wallet and captured a
clean 1080x1920 store set (dashboard/receive/send/swap/settings) with no PIN.
This also implements the SCREENSHOT_MODE the iOS UITest scaffold already
references (store/SCREENSHOTS.md Path B).

Note: the API-35 emulator needs -gpu host (real GPU) for deep-page rendering;
-gpu software hard-crashes qemu on the Receive page. Stills use screencap
(screenrecord's encoder is unrelated).
2026-08-03 08:33:22 -04:00
jwinterm 731adb84f1 fix(demo): API35 capture recipe, reown unshallow, deferred recording, device match
Hardening from the first real end-to-end runs against the app:
- build-apk.sh: guard bitbox on the api.aar artifact (not the dir); unshallow
  the reown_flutter tarball so prepare_reown.sh can checkout its pinned commit
  (the tarball ships a shallow repo -> "unable to read tree"); generate reown
  uniffi bindings when absent.
- capture.sh boot: API 35 + `-gpu software -feature -Vulkan`. API 36.1's guest
  encoder is broken (screenrecord err=-38, screencap DMA assert) under both
  swiftshader_indirect and -gpu host; API 35 works.
- capture.sh: defer screenrecord until the app is foregrounded (flutter drive
  builds for minutes; the 180s cap must cover the scenario, not the build);
  match the target device by AVD name so a co-running emulator isn't hijacked;
  record to /data/local/tmp (headless /sdcard FUSE is flaky).
- README: document the API-35 requirement and reasoning.
2026-08-02 21:29:40 -04:00
jwinterm f64ee19b0c fix(integration_test): repair compile after chain removal; robust key taps
integration_test/ has not compiled since 784a082f1 removed SOL/TRX/ZEC/DCR/
ZANO/Haven: common_test_flows.dart and common_test_constants.dart still
referenced WalletType.solana/.tron/.zano/.decred/.zcash/.haven and secrets that
were never generated, and wallet_keys_robot branched on .solana/.tron. Any
`flutter drive` over these components failed at kernel compile.

- common_test_flows.dart: drop removed chains from both wallet-type switches.
- common_test_constants.dart: default test wallet SOL -> WOW (Wownero-first;
  SOL no longer exists), with a real WOW address.
- wallet_keys_robot.dart: EVM-only branch (solana/tron gone).
- common_test_cases.dart tapItemByKey: wait for the widget (up to 20s) instead
  of pumping a flat 500ms then asserting. Screens behind an async transition
  (PIN-setup success button) were a coin flip; this makes the failure honest
  ("not there after 20s") and the pass reliable. Pump loop, not pumpAndSettle,
  since the dashboard sync animation never settles.
2026-08-02 21:29:40 -04:00
jwinterm 44661ce592 fix(ios): correct Apple Team ID to D8PL9F7X33
The Appfile and Runner.xcodeproj carried 32J6BB6VUS (a stale leftover). The
real Such Software LLC team is D8PL9F7X33. This did not affect CI signing:
build-ios-testflight.yml rewrites DEVELOPMENT_TEAM to the APPLE_TEAM_ID secret
before archiving, so TestFlight builds already signed with the correct team.
This aligns local Xcode and the fastlane snapshot path (which are NOT rewritten
by CI) with what CI actually uses.

Team IDs are public identifiers (embedded in every distributed app's
provisioning profile), not secrets.
2026-08-02 15:03:04 -04:00
jwinterm c396e94b77 Add store capture harness: record real wallet flows into screenshots + video
Drives the real app through real flows on a device while recording the screen,
then cuts ONE recording into both deliverables — store screenshots and
marketing video — so a listing can't drift out of sync with its own trailer.

integration_test/demo/
  DemoSession adds what a passing test lacks: pacing (beat/hold/typeSlowly, so
  footage is watchable rather than a strobe), timing marks reported through
  binding.reportData, and seed guards. Scenarios reuse the existing 25 page
  robots rather than reimplementing navigation:
    core_store_set     dashboard, receive, send (composed, NEVER broadcast),
                       wallets, security — the store screenshot set
    onboarding_create  welcome -> PIN -> seed -> dashboard
    restore_from_seed  restore -> syncing
    swap_and_buy       Trocador quote, stopping BEFORE creating a trade; Buy
                       skipped when no provider key, matching what reviewers see

tools/demo/
  capture.sh   orchestrates boot/record/cut/shots/reel
  marks.py     maps timing marks onto the recording; cuts, extracts frames,
               builds manifests
  reel.py      branded master; reuses such_graphics video_transitions/video_qa
               when ~/src/such-graphics is present, degrades cleanly when not
               (this repo is public and must build without it)
  build-apk.sh local APK via the CI container image

Seed safety — the demo wallet is a real wallet with real funds:
  - seed comes from Vaultwarden at capture time, never the repo
  - guardNoSeedOnScreen() fails the run on a visible mnemonic, catching both
    the known seed and the generic shape of one (so freshly generated seeds
    are caught too)
  - unavoidable seed screens are wrapped in blindfold(); those spans are
    excised from the video with 0.6s padding, and screenshots honour the same
    padding
  - marks.py refuses to build a publishable master if any clip was cut without
    marks, since blindfold spans could not have been excised

Apple App Preview gets no touch indicators, captions, cards or music, and
reel.py enforces the 15-30s window rather than emitting an out-of-spec file.
Everything else keeps taps on, drawn by SurfaceFlinger via show_touches rather
than composited, so they look real.

Play sizing: the harness builds its own 1080x1920 AVD. The stock Medium Phone
AVD is 1080x2400 = 2.22:1 and Play rejects phone screenshots above 2:1.

Also gitignores android/app/upload-keystore.jks (and *.jks/*.keystore): the
Android workflow decodes ANDROID_KEYSTORE_BASE64 to exactly that path, so the
Play upload signing key was one `git add -A` away from being committed.
2026-07-31 15:25:29 -04:00
jwinterm 4be0eb9613 Bump build number to 1.0.0+9
TestFlight run #118 succeeded on 2026-07-09, and that workflow ends with
`xcrun altool --upload-app` — so 1.0.0+8 is already uploaded and App Store
Connect will reject a redundant binary at the same build number. Re-running at
+8 would have burned a full signed archive before failing at the last step.

Both toolchains read this single source (scripts/{ios,android}/app_env.sh parse
pubspec_description.yaml), verified reporting build=9.

Note there is still no auto-bump: build-ios-testflight.yml is workflow_dispatch
only and its own comment flags this as a TODO, so every TestFlight run needs a
manual bump until that is wired up.
2026-07-31 15:25:07 -04:00
jwinterm c8abee8b15 Fix builds after Wownero's migration off Codeberg
Wownero left Codeberg in July 2026 (Codeberg announced it would ban crypto
projects) and remigrated to github.com/wownero/wownero. monero_c's .gitmodules
still points at the dead Codeberg URLs, which now fail with "remote: Bye" /
HTTP 403. That broke the iOS TestFlight build on 2026-07-31 during "Fetch
prebuilt monero_c bundle".

The clone never needed to happen. Every workflow (android/ios-sim/
ios-testflight/linux/windows) consumes PREBUILT .so/.a files from
release-bundle.zip; nothing outside release/ references the monero/wownero
source trees. The only thing needed from the checkout is `git describe --tags`
to resolve the release/<tag>/ path. prepare_moneroc.sh was cloning ~1GB of
source plus deep transitive submodules purely to discard it.

So skip the submodules by default. Source checkout is now opt-in behind
MONERO_C_BUILD_FROM_SOURCE=1, and when requested it redirects the dead
Codeberg URLs:
  wownero   -> github.com/wownero/wownero    (canonical after the migration)
  randomwow -> github.com/mrcyjanek/randomwow (what monero_c's apply_patches.sh
               already switches the remote to; NOT wownero/RandomWOW, which is
               a tevador/RandomX fork on a different lineage without the
               pinned commit)

Both submodules stay pinned to exact SHAs, so git verifies the object hash on
fetch and a different host cannot substitute content.

Verified clean-room with Codeberg still 403ing: prebuilt mode exits 0 with
`git describe --tags` = v0.18.4.6-RC1-2-gbc8d1a0 and zero Codeberg access;
source mode lands randomwow at the pinned cd137b1ea.
2026-07-31 15:24:59 -04:00
jwinterm fa49834820 Fix build: repin ledger_usb_plus to pub.dev ^1.0.4 (dead git commit)
The dependency_override pinned ledger_usb_plus to
konstantinullrich/ledger-usb-plus@dad482c847..., a commit that was force-pushed
away and no longer exists — so 'flutter pub get' fails ('bad object') on any
cold runner (the Android/Linux containers). It passed on iOS only because the
Mac-mini runner had a warm pub cache. cake-tech's ledger-flutter-plus fork
depends on the published ledger_usb_plus ^1.0.4, so pin the override to that.
2026-07-10 07:29:31 -04:00
jwinterm eee5ae83c2 CI: resolve app deps before generate_new_secrets (Android + Linux)
tool/generate_new_secrets.dart imports app packages (encrypt/convert/intl).
pubspec_gen.sh runs `flutter pub get` without set -e, so a silent hiccup there
surfaced downstream as 'Couldn't resolve the package encrypt' at secrets-gen.
Run `flutter pub get` explicitly (set -e) right before the tool on Android +
Linux, matching what the iOS flow effectively guarantees. The build container
already pins Flutter 3.32.0, so this resolves cleanly.
2026-07-09 21:49:08 -04:00
jwinterm bd9312839a Bump build number to 1.0.0+8 for TestFlight/Play submission
Build 7 is already on TestFlight. Both iOS (CFBundleVersion) and Android
(versionCode) source the build number from pubspec_description.yaml at build
time (scripts/{ios,android}/app_env.sh), so this single bump moves both to 8.
2026-07-09 19:48:42 -04:00
jwinterm d9897f13ea MoonPay: key-gated enablement + inject MOONPAY_API_KEY/EXCHANGE_HELPER_API_KEY in CI
getAvailableBuyProviderTypes() now returns [moonpay] only when moonPayApiKey is
non-empty, so MoonPay's Buy button appears only in builds where the CI secret is
present (and the exchange-helper signer is live). Both build workflows now inject
MOONPAY_API_KEY -> moonPayApiKey and EXCHANGE_HELPER_API_KEY -> exchangeHelperApiKey
alongside Trocador + WalletConnect. moonPaySecretKey is server-side and never injected.
2026-07-02 16:41:45 -04:00
jwinterm 26a554c3f0 privacy: disclose opt-in error reports (match hash.boats/privacy + store labels) 2026-07-02 16:26:14 -04:00
jwinterm 803be5feea CI: inject WalletConnect projectId into Android + iOS TestFlight builds
Both build workflows now set walletConnectProjectId in lib/.secrets.g.dart from
the WALLETCONNECT_PROJECT_ID secret (alongside the Trocador keys), so
WalletConnect pairing works in signed builds. Add WALLETCONNECT_PROJECT_ID
(your reown Wallet projectId) to the CI secrets before building.
2026-07-02 15:49:54 -04:00
jwinterm 65887ea9db Move hash.boats site out to its own repo (~/src/hash.boats-site)
The site is now maintained/deployed from the standalone hash.boats-site repo,
so drop the gitignored local mirror and its tracked comment.
2026-07-02 14:19:08 -04:00
jwinterm 784a082f15 Legal + release-readiness pass for Hash Bags launch
Legal (aligned with the hosted hash.boats Terms & Privacy):
- Rewrite in-app Terms (assets/text/Terms_of_Use.txt): drop the false 'No Fiat
  Operations'/'No Embedded Applications' clauses; add MoonPay (optional, where
  available), Trocador swaps + ~1% fee disclosure, WalletConnect, sanctions/18+,
  Apple EULA rider, and PA + class/jury-waiver dispute terms.
- Reconcile PRIVACY.md with the canonical hosted policy.
- Add THIRD_PARTY_LICENSES.md; correct the blanket 'MIT' claim in README (app
  code MIT; bundles BSD-3/Apache-2.0/LGPL-3.0); fix README build-doc + LICENSE.md
  links and copyright year.
- Standardize in-app legal naming to 'Terms of Service'; add in-app links to
  hash.boats/terms and /privacy.

Release fixes:
- Align URL scheme hashwallet:// -> hashbags:// (matches registered scheme) so
  WalletConnect/Trezor/quick-action deep links work.
- Store-review gating: hide Buy action until a provider is available; hide the
  empty 'Apps' tab.
- Exclude removed chains (SOL/TRX/ZEC/DCR/ZANO + tokens) from swap pickers.
- Residual Cake branding: macOS CFBundleDisplayName and Windows Runner.rc
  (Hash Wallet -> Hash Bags); remove Android cakewallet://y.at leftover.
- Genericize internal-infra comments in CI workflows.
2026-07-02 14:07:24 -04:00
jwinterm 86b4040c9b Linux: package release as self-contained AppImage
The previous Linux artifact was a zip of the Flutter bundle/ directory.
That's awkward to distribute — users have to unzip, find the binary,
chmod +x, and figure out the lib/ layout themselves. Replace with a
proper AppImage: single file, double-click to run, no install, no
Flutter knowledge required.

Build steps:
- Stage AppDir with the Flutter bundle under usr/bin/ (the binary's
  RPATH=\$ORIGIN/lib already finds plugin libs there).
- Add .desktop file (Hash Bags / Office;Finance;) and 256x256 hash
  icon scaled from the iOS marketing PNG.
- Download appimagetool and invoke with --appimage-extract-and-run
  so it works inside the build container without FUSE.

Artifact name: hash-wallet-linux-appimage-<sha>.
2026-06-03 20:11:43 -04:00
jwinterm 2abff1fe06 Android version-from-pubspec + proper iOS tinted icons
Android had the same hardcoded CAKEWALLET_BUILD_NUMBER=1 bug we just
fixed for iOS — bumping pubspec_description.yaml would not have moved
Play Store versionCode at all, and the second AAB upload would have
been rejected for duplicate versionCode. Mirror the iOS fix: parse
the build number from pubspec_description.yaml in scripts/android/app_env.sh.

Regenerate the 16 Tinted iOS icon slices as black bg + white hash mark.
The previous slices reused the green-bg light variant, which iOS 18+
cannot meaningfully tint (green-on-green collapses when the system
applies the home-screen accent color). Black background + white shape
gives iOS a clean luminance map to tint against.

Wownero monero! audit: cleared. Audit flagged two callsites in
fees_view_model.dart, but both wownero!.getWowneroTransactionPriority*
and monero!.getMoneroTransactionPriority* return the same
MoneroTransactionPriority singleton (cw_wownero.dart:158 +
cw_monero.dart:169), so the cross-call is inelegant but not buggy.

Bump 1.0.0+6 -> 1.0.0+7.
2026-06-03 09:31:24 -04:00
jwinterm 21f3a75aa5 Wownero: dispatch getCurrentAccount by wallet.type in AccountCustomizer
The "Wallet Accounts" reorder modal threw on open for Wownero wallets:
account_customizer.dart unconditionally called monero!.getCurrentAccount,
which casts the wallet to MoneroWallet internally. WowneroWallet is a
sibling type, not a subtype, so the cast blew up before the active-card
swap logic ran.

Dispatch on wallet.type and use the wownero! proxy for Wownero, matching
the pattern already in card_customizer_bloc.dart, addresses_page.dart,
and dashboard_view_model.dart. Skip the swap entirely for any other
wallet type rather than guessing.

Bump 1.0.0+5 -> 1.0.0+6.
2026-06-03 09:15:10 -04:00
jwinterm a1b3add2a6 iOS: source CAKEWALLET build number from pubspec_description.yaml
Bumping version in pubspec_description.yaml has had zero effect on
the iOS CFBundleVersion: app_env.sh hardcoded CAKEWALLET_BUILD_NUMBER=1,
app_config.sh substituted that literal into Info.plist's
\$(CURRENT_PROJECT_VERSION) placeholder via PlistBuddy before flutter
even saw it, and every TestFlight upload shipped as build 1 regardless
of what we bumped in pubspec. Apple rejected the +4 and +5 IPAs as
duplicates of the +3 that got through as build 1.

Make pubspec_description.yaml the single source of truth by parsing
its `version: X.Y.Z+N` line in app_env.sh.
2026-06-03 08:02:55 -04:00
jwinterm 5ff5ebf910 iOS: declare ITSAppUsesNonExemptEncryption=false (open-source exemption)
Hash Bags' crypto is either OS-provided (TLS / CryptoKit / CommonCrypto)
or standard published algorithms shipped as MIT-licensed open source
(secp256k1, ed25519, SHA-2/3, AES, Argon2, ChaCha20). All of that
qualifies as "exempt" under BIS export controls (§740.17(b)(3)).
Declaring the answer in Info.plist skips the per-build App Store Connect
encryption questionnaire and removes the annual BIS self-classification
filing requirement.

Bump 1.0.0+4 -> 1.0.0+5 in case +4 already claimed an Apple build slot.
2026-06-02 20:16:27 -04:00
jwinterm 6e6cf33bf1 iOS icons: replace Cake artwork with Hash Bags # mark, rename dir
The iOS build was still pulling Cake Wallet's purple/orange wallet
icon from assets/images/ios_icons/cakewallet_ios_icons/ — visible
in TestFlight as the wrong brand. Replace all 69 light/dark/tinted
slices with the Hash Bags green hash mark from hash.boats/icon-512.png,
flattened against #1a5c38 so the 1024 marketing icon is opaque (no
alpha, as App Store requires) and iOS applies its own corner mask.

Rename the directory cakewallet_ios_icons -> hashwallet_ios_icons so
the repo doesn't carry Cake-branded paths. The build-time flavor is
still named "cakewallet" internally (huge blast radius to rename),
so app_icon.sh just maps that flavor to the new directory name.

Bump build 1.0.0+3 -> 1.0.0+4 for re-upload.
2026-06-02 20:11:07 -04:00
jwinterm ac294874bf iOS TestFlight: patch LibTorch Info.plist to match binary LC_BUILD_VERSION
LibTorch.xcframework ships with each slice's Info.plist claiming
MinimumOSVersion=12.0, but the Mach-O LC_BUILD_VERSION inside the
binaries actually requires 13.0 (device) and 14.0 (simulator). Apple's
validator catches the lie and rejects TestFlight uploads with ITMS-90208
("LibTorch.framework does not support the minimum OS Version specified
in the Info.plist") regardless of the app's deployment target.

Revert the previous 15.0 deployment-target bump (which was based on the
wrong hypothesis) back to 13.0 — that's actually what LibTorch supports.
Add a workflow step that plutil-rewrites both slice Info.plists to match
the value their binary actually requires. Bump build number 1.0.0+2 ->
1.0.0+3 for the re-upload.
2026-06-02 13:39:30 -04:00
jwinterm 52fd1d784f iOS: bump deployment target 12/13 → 15 (Apple ITMS-90208 fix)
First TestFlight upload was accepted and processed all the way through,
then Apple's binary validation kicked back ITMS-90208 Invalid Bundle:

  The bundle Runner.app/Frameworks/LibTorch.framework does not support
  the minimum OS Version specified in the Info.plist.

torch_dart-v1.0.17 ships a LibTorch.xcframework whose Info.plist
declares a higher MinimumOSVersion than our app's 13.0. Apple's
notarization-style check rejects an app that claims to run on iOS
versions where a bundled framework refuses to load.

Bump the four spots iOS deployment target lives:
  - ios/Podfile (was 13.0)
  - ios/Flutter/AppFrameworkInfo.plist MinimumOSVersion (was 12.0)
  - ios/Runner.xcodeproj/project.pbxproj — 6 IPHONEOS_DEPLOYMENT_TARGET
    entries (mixed 12.0 / 13.0)
all → 15.0. iPhone 6s (2015) and newer; very wide 2026 coverage.

Also bump build number 1.0.0+1 → 1.0.0+2 in pubspec_description.yaml.
Apple rejects re-uploads with the same build number (ITMS-90478), so
the next TestFlight upload after this fix needs a fresh +N.

Workflow diagnostic step that dumps LibTorch.xcframework's Info.plist
MinimumOSVersion now lives in the "Fetch prebuilt torch_dart" step. If
15.0 still isn't enough, the log tells us exactly what to bump to next.
2026-06-02 13:02:08 -04:00
jwinterm be4cf0e49a iOS TestFlight: validate .p8 AuthKey before altool upload
altool bailed with "Failed to load AuthKey file. (-39) The file ...
does not contain a valid authentication key." — same shape as the
.p12 truncation we hit earlier. The .p8 stored secret is probably
truncated or has stray characters from the original web-UI paste.

Add fail-fast validation right after decoding the .p8 to disk:
prints the first + last lines (just the PEM markers, no key body)
and runs `openssl pkey -in file -noout` to actually parse it. If
either check fails the step bombs immediately with a clear "re-paste
via Gitea API" instruction, saving 30+ seconds before altool fails
the same way with a less useful error.
2026-06-01 21:01:38 -04:00
jwinterm fc813368cc iOS TestFlight: also install nightly Rust + iOS targets
One of the Flutter plugins in our dep chain (sp_scanner / payjoin /
something in the cargokit family) compiles its rust component with
+nightly. The archive step ran for 27s then bombed with:

  Error (Xcode): Missing manifest in toolchain 'nightly-aarch64-apple-darwin'

We were installing only stable + iOS targets. Add nightly toolchain
(minimal profile) + iOS targets for it alongside the stable ones.
~250 MB extra download once per runner workspace lifetime.

build-ios-sim.yml has the same stable-only setup but never tripped on
this — likely because the failing plugin's release-only build path
isn't exercised in the unsigned sim build. Leaving sim workflow
alone for now; copy the nightly steps over if it ever surfaces there.
2026-06-01 20:42:11 -04:00
jwinterm 2294bfabe7 iOS TestFlight: patch signing in pbxproj, not Release.xcconfig
Xcode build-setting precedence puts TARGET-LEVEL pbxproj settings
ABOVE the target's xcconfig. So the earlier append to Release.xcconfig
(CODE_SIGN_STYLE = Manual etc.) was being silently overridden by the
pbxproj's CODE_SIGN_STYLE = Automatic. The build did Automatic
signing anyway and bombed with:

  Error (Xcode): No Accounts: Add a new account in Accounts settings.
  Error (Xcode): No profiles for 'com.suchsoftware.hashwallet' were
  found: Xcode couldn't find any iOS App Development provisioning
  profiles matching 'com.suchsoftware.hashwallet'.

(Note "App Development" not "App Distribution" — confirming the
project was still asking for a dev profile via Automatic signing.)

sed-patch the Runner target's Release config block in
project.pbxproj directly, scoped by the stable Release config UUID
(97C147071CF9000F007C117D) so we don't touch Debug/Profile configs.
Sets:
  CODE_SIGN_IDENTITY  = "Apple Distribution"
  CODE_SIGN_STYLE     = Manual
  DEVELOPMENT_TEAM    = ${APPLE_TEAM_ID}
  PROVISIONING_PROFILE_SPECIFIER = ${PROFILE_NAME}

Patch runs only at CI time; the committed pbxproj stays on Automatic
signing for local Xcode dev. The dumped block at the end of the
step confirms which lines actually changed, so the next failure
mode (if any) is debuggable.
2026-06-01 20:25:22 -04:00
jwinterm 2dd7c84246 iOS TestFlight: drop openssl conversion, import .p12 directly
The openssl pkcs12 round-trip step was speculative defense against
"modern macOS Keychain exports incompatible with security import" —
a problem we made up. What was actually happening:
  1. The original failure ("SecKeychainItemImport: Unknown format")
     was caused by a TRUNCATED base64 secret value. security wasn't
     rejecting the format; it was rejecting bytes it couldn't parse
     because the file was 29 bytes short.
  2. The follow-on failure ("RC2-40-CBC unsupported") was the openssl
     conversion step bombing because openssl 3.x has RC2-40-CBC
     disabled by default. macOS Keychain Access actually exports
     PKCS12 with RC2-40-CBC + PBE-SHA1 — exactly what `security
     import` reads natively.

With the truncated-secret fixed (re-pasted via Gitea API), the .p12
on disk is now correct. Import it directly without round-tripping
through openssl. Less code, fewer failure modes, faster.
2026-06-01 19:59:50 -04:00
jwinterm 5a43cc1114 iOS TestFlight: re-export .p12 as legacy PKCS12 before security import
macOS Keychain Access on Sonoma+ exports .p12 with modern crypto
(AES-256-GCM + PBKDF2 + SHA-256). The `security import` binary on
some macOS releases — including ours apparently — rejects it with:
  SecKeychainItemImport: Unknown format in import.

The openssl decode itself was fine (now using openssl base64 -d -A),
but security can't import the resulting PKCS12 envelope.

Pipe the .p12 through openssl: unpack with the user's password, then
re-export using legacy PBE-SHA1-3DES for both certs and keys, plus
SHA1 MAC. The resulting .p12 uses 90s-era crypto for the OUTER
envelope, which is what security import wants — the actual signing
keys + cert inside are unchanged. No security implication; the legacy
format encryption only matters for the few seconds the .p12 lives in
$RUNNER_TEMP before being imported and deleted.

Also added diagnostic output (file size, magic bytes, file(1)) so the
next failure mode (if any) is easier to characterize.
2026-06-01 19:17:04 -04:00
jwinterm 91dadfd35e iOS TestFlight: use openssl base64 -d -A, stop fighting BSD base64
`base64 --decode` also failed on the Mac runner with the same
"(null): error decoding base64 input stream" — so either this macOS
version's BSD base64 doesn't honor --decode (only -D) or it chokes
on the input format. Either way, swap the three secret decodes to
`openssl base64 -d -A`:

  - openssl ships with macOS by default; no extra install
  - -d = decode (same on BSD and Linux, no platform skew)
  - -A treats the input as one continuous line (matches our
    `base64 -i x | tr -d '\n'` encoding flow)

If openssl base64 ever bites us we'll fall back to python3's
base64.b64decode, but openssl has been bulletproof for ~25 years.
2026-06-01 19:08:20 -04:00
jwinterm 4f38322c92 iOS TestFlight: macOS base64 wants --decode, not -d
macOS uses BSD base64 where -d historically means *debug*, not
*decode* like GNU base64. On the Mac runner that bit us — the
first base64 -d invocation ran in debug mode, consumed no stdin,
and produced an empty .p12 file, then the keychain import bombed
with the now-classic "(null): error decoding base64 input stream"
once a downstream tool tried to read the empty file.

Switch all three secret decodes (.p12 cert, .mobileprovision,
.p8 API key) to base64 --decode. Long form is identical between
GNU coreutils and BSD base64.

The Apple secrets themselves were fine — sanity-check step
confirmed all non-zero lengths (TEAM_ID/KEY_ID showed as ***0
in the log, which is the actual literal 10 with the 1 redacted
because some other secret value contains the substring "1").
2026-06-01 18:59:37 -04:00
jwinterm 5e6a9d5743 iOS TestFlight: sanity-check Apple secrets reach the runner
First TestFlight trigger failed at the .p12 import step with
"base64: stdin: (null): error decoding base64 input stream" — meaning
$APPLE_DIST_CERT_P12_BASE64 was empty when reaching the keychain step.

Add a length-only debug step at the top of the Apple-signing section
that prints the length of every required Apple secret and bails
fast with a clear "set this secret on Builds/hash-wallet" message if
any is zero-length. Same pattern build-android.yml uses for the
Android keystore + Trocador secrets.

The likely cause is the Apple secrets being set on github-such-
software/hash-wallet but not on the Builds fork the workflow runs
in — Gitea secrets are repo-scoped, not org-scoped.
2026-06-01 18:54:42 -04:00
jwinterm b6f6f3f4da Wire error reporting to hash.boats/report, surface Wownero primary address
Two unrelated wallet-UX fixes in one commit:

1) lib/utils/exception_handler.dart — switch the "Report Error" flow
   from "open prefilled GitHub issue URL" to "copy error trace to
   clipboard + open https://hash.boats/report/". The hash.boats backend
   (Node service + nginx proxy + Telegram relay) was already deployed
   to the suchwow server; the in-app handler just never got wired to
   use it. No GitHub account required, works on every platform.

2) lib/wownero/cw_wownero.dart — add 'primaryAddress' to the keys map
   returned by getKeys(). The lib/monero/cw_monero.dart equivalent
   already had this; without it, WalletKeysViewModel renders zero
   primary-address entry for Wownero wallets and users can't easily
   read off the main address for sharing (e.g. to set up a view-only
   wallet on another device). The underlying WowneroWalletBase.keys
   already populated primaryAddress — only the getKeys map projection
   was missing it.
2026-06-01 18:39:57 -04:00
jwinterm e6195d1e98 iOS TestFlight: override signing in Release.xcconfig for CI
The committed project.pbxproj Release config uses CODE_SIGN_STYLE =
Automatic + CODE_SIGN_IDENTITY = "Apple Development" — both set up for
local Xcode-signed-into-an-Apple-ID development workflow. CI has only
the Distribution cert + provisioning profile we install into a temp
keychain, no Apple ID session, so Automatic signing fails at archive
time.

Append manual-signing overrides to ios/Flutter/Release.xcconfig in CI
(xcconfig values take precedence over pbxproj defaults, so this
overrides without modifying the committed project file):
  CODE_SIGN_IDENTITY = Apple Distribution
  CODE_SIGN_STYLE = Manual
  DEVELOPMENT_TEAM = ${APPLE_TEAM_ID}
  PROVISIONING_PROFILE_SPECIFIER = ${PROFILE_NAME}

PROFILE_NAME is parsed earlier from the .mobileprovision Name field.
APPLE_TEAM_ID comes from the existing Gitea secret. Local Debug/Profile
builds remain on Automatic signing — unchanged.
2026-06-01 18:30:34 -04:00
jwinterm 59d7833668 Windows CI: revert SFX wrapper, ship as zip artifact
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.
2026-05-24 19:00:10 -04:00
jwinterm 0ad4a4eb8a Windows CI: fetch SFX modules from LZMA SDK, not "7-Zip Extra"
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).
2026-05-23 20:44:56 -04:00
jwinterm bdbd3388a3 Windows CI: look in x64/ subdir for SFX modules
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.
2026-05-23 20:03:26 -04:00
jwinterm 3bf9509f08 Windows CI: pin 7-Zip Extra URL to 23.01 (only version still hosted)
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.
2026-05-22 21:57:40 -04:00
jwinterm b8ca749e1e Windows CI: download 7-Zip Extra to get installer-mode SFX module
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.
2026-05-22 21:03:53 -04:00
jwinterm 8c4fbed869 Drop zano_node_list.yml from pubspec_base.yaml assets list
The previous commit deleted assets/zano_node_list.yml (Zano isn't
shipped in Hash Bags) but pubspec_base.yaml still listed it under
flutter.assets. Flutter assemble failed during the asset-bundling
stage:

  Error detected in pubspec.yaml:
  No file or variants found for asset: assets/zano_node_list.yml.

Remove the line — same intent as the original deletion, just the
second half of the change.
2026-05-22 19:22:47 -04:00
jwinterm 1ad70a210a Windows CI: fix YAML break — replace SFX-config heredoc with printf
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.
2026-05-22 18:09:59 -04:00
jwinterm 713eba2a53 Windows CI: ship single-file portable HashBags.exe via 7-Zip SFX
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.
2026-05-22 12:18:15 -04:00
jwinterm bab0d252ee Rebrand: Cake Pay / Cake 2FA / Cake Wallet → Hash Bags across all 31 locale ARBs
Replaced upstream Cake brand references in user-visible ARB *values*
(keys untouched, so generated S.current.<key> references still work):

  - "Cake 2FA" → "Hash Bags 2FA"
  - "Cake Pay" → "Hash Bags Pay" (still gated off via
    FeatureFlag.isCakePayEnabled = false; rebranding the string so it
    doesn't ship literal "Cake" copy if the flag ever flips)
  - "Cake Wallet" → "Hash Bags"
  - "Cake Labs" → "Such Software"
  - "by Cake Pay" → "by Hash Bags Pay"
  - "cakewallet.com" → "such.software"

English-only adjustments (other locales had their own translations):
  - new_first_wallet_text "Keeping your crypto safe is a piece of
    cake" → "Keeping your crypto safe shouldn't be hard" (drops the
    Cake pun, which now reads odd post-rename)
  - outdated_electrum_wallet_description + _receive_warning rewritten
    to Hash-Bags-neutral phrasing: "Hash Bags now uses Electrum V2
    wallets. Bitcoin wallets created in older builds need to be
    replaced; receiving to old wallets may lose funds." Same Hash-Bags-
    neutral rewrite propagated to all locales that had a translated
    version of the original Cake-Electrum-V2 message.

Deliberately not touched:
  - ARB *keys* (cake_2fa_preset, cake_pay_*, cakepay_*, disable_cake_2fa,
    etc.) — keys must stay stable so generated S.current.x references
    keep resolving.
  - "Cupcake" / "cupcake" — a separate companion app referenced in
    restore_*_from_cupcake strings, not part of the Cake brand we're
    rebranding from.
  - Internal Dart identifiers like CakeImageWidget class name.

Also fixed hardcoded titles in lib/src/screens/setup_2fa/setup_2fa.dart:
  - Line 18: title 'Cake 2FA' → 'Hash Bags 2FA'
  - Line 22: cake2FAGuideTitle 'Cake 2FA Guide' → 'Hash Bags 2FA Guide'
  (Local variable name cake2FAGuideTitle kept as-is — it's a code
  identifier, not user-visible.)

33 files touched: 31 ARBs (ar, bg, cs, de, en, es, fa, fr, gn, ha, hi,
hr, hy, id, it, ja, ko, my, nl, pl, pt, pt_BR, ru, th, tl, tr, uk, ur,
vi, yo, zh, zh_tw) + lib/src/screens/setup_2fa/setup_2fa.dart.
2026-05-22 12:16:32 -04:00
jwinterm 20ad49a403 Icon: redraw # as vector path for true centering
User flagged the Windows .exe icon as having the hash sign visually
biased toward the top. Root cause: the previous icon source SVG used
<text> with dominant-baseline="central" — the rasterizer fell back to
whatever monospaced font it found, and that font's # glyph isn't
positioned at the geometric center of its em-box (most monospaced
fonts have the # mark biased upward in their em-square).

Replace the text-rendered # with 4 vector <line> elements forming a
mathematically symmetric # centered at (512, 512) of the 1024 canvas.
No font dependency, deterministic across rasterizers.

Re-rendered:
  - assets/images/app_logo.png — master 1024x1024 PNG
  - windows/runner/resources/app_icon.ico — 7-size .ico (16..256)
  - assets/images/cakewallet_android_icon/mipmap-*/ic_launcher.png
    (5 densities, the legacy launcher PNG; CI symlinks from here)

NOT regenerated in this commit (follow-up):
  - assets/images/cakewallet_android_icon/mipmap-*/ic_launcher_{foreground,background,monochrome}.png
    (adaptive icons used on Android 8+ — need separate foreground/
    background composition logic)
  - assets/images/ios_icons/cakewallet_ios_icons/* (70 files)
  - assets/images/macos_icons/cakewallet_macos_icons/* (7 files)
2026-05-22 12:16:07 -04:00
jwinterm a2a9c1b0aa Pre-release rebrand: core platform branding + versions + node lists
Cake Wallet → Hash Bags / Such Software LLC across the user-facing
platform configs:
  - macos/Runner/InfoBase.plist: CFBundleDisplayName + CFBundleName
  - windows/runner/main.cpp: top-level window title
  - ios/Runner/InfoBase.plist: URL scheme cakewallet → hashbags,
    CFBundleURLName y.at → com.suchsoftware.hashwallet
  - android/app/src/main/AndroidManifestBase.xml: URL scheme
  - scripts/windows/build_exe_installer.iss: full rebrand (name,
    publisher, URL, exe name, AppId, DefaultDirName, output filename)
  - assets/text/disclaimer_deuro.txt: legal disclaimer text

Version unification → 1.0.0 build 1 across all platforms:
  - pubspec_description.yaml (was 0.0.0, description was "Cake Wallet.")
  - scripts/android/app_env.sh (was 6.1.2 build 4410)
  - scripts/ios/app_env.sh (was 6.1.2 build 418)
  - scripts/macos/app_env.sh (was 6.1.0 build 147)
  - scripts/windows/build_exe_installer.iss (was 6.1.0)
  env.json (Windows) was already 1.0.0.

FAQ contact info → support@such.software / @such_software across all
13 locale faq_*.json files (was support@cakewallet.com /
@CakeWalletXMR / @cakewallet_bot).

Node lists:
  - Delete assets/zano_node_list.yml — Zano not enabled in any
    configure flag; file was dead code.
  - Remove zcashDefaultNodeUri const from
    lib/entities/default_settings_migration.dart — Zcash not in
    product, const was unreferenced anywhere.

Note on BTC + LTC defaults: audit suggested these were Cake-by-default
but they were already non-Cake (blockstream and electrum-ltc.bysh.me).
Cake stays in the auto-switch pool — flagged for separate decision on
whether to disable auto-switching to Cake nodes for privacy.
2026-05-22 11:26:18 -04:00
jwinterm bba8a975cf CI: add Hash Bags Windows build workflow (manual trigger)
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.
2026-05-22 10:48:38 -04:00
jwinterm a6bb3c595d Branding: Hash Wallet → Hash Bags for Windows + Linux app name
Final public name is Hash Bags (Such Software). iOS CFBundleDisplayName
and Android app.properties (generated by scripts/android/app_env.sh) were
already on Hash Bags; this catches the two remaining user-facing places:

  - env.json CW_WIN_APP_NAME (Windows launcher label)
  - linux/com.suchsoftware.HashWallet.desktop Name= (Linux app menu label)

Bundle IDs, Dart package name, and the .desktop file's app-id all stay
on hashwallet — those are internal identifiers that nobody sees.
2026-05-19 17:58:22 -04:00
jwinterm 52b8cd090d Wownero: fix missing icon in wallet picker
The wow CryptoCurrency had iconPath pointing at assets/images/crypto/wownero.svg,
which doesn't exist — so the row in Create New Wallet rendered with no logo.
The actual asset lives at assets/new-ui/crypto_full_icons/wownero.svg (same
location every other coin uses). Point iconPath there and add flatIconPath
for the balance-card-flat icon, mirroring the Monero definition.
2026-05-19 17:58:22 -04:00
jwinterm c506679086 iOS sim: drop sudo /etc/hosts rewrite, add upload diagnostic
The runtime sudo rewrite step hung 51m waiting for a password the runner
can't enter. The Mac mini's /etc/hosts already points git.such.software
at 127.0.0.1, and the host-mode act_runner inherits it directly, so no
runtime modification is needed. Replace the rewrite with a passive grep
assertion that fails fast with instructions if the entry is missing.

Add a diagnostic step before the upload that prints ACTIONS_RUNTIME_URL,
ACTIONS_RESULTS_URL, GITHUB_SERVER_URL + probes each with curl -kIv. The
last run never reached the upload step, so we still don't know whether
upload-artifact targets git.such.software (uses our hosts override) or
a different address injected by act_runner.
2026-05-19 17:37:00 -04:00
jwinterm 6c765a6e0e iOS sim: write /etc/hosts at job runtime so upload-artifact can reach Gitea
The Mac runner doesn't honor changes to the host's /etc/hosts (whether
because of act_runner sandboxing, container isolation, or DNS caching).
Instead of fighting that, override /etc/hosts INSIDE the runner's job
environment, right before the upload step.

Resolution priority:
  1. host.docker.internal — magic name in Docker Desktop containers
     that resolves to the Mac host. Use this if available.
  2. 127.0.0.1 — works for host-mode runners (runs natively on Mac).

NODE_TLS_REJECT_UNAUTHORIZED=0 (already set) bypasses the self-signed
cert Gitea presents on its internal listener.

Brought back actions/upload-artifact so the .app lands in Gitea's
Actions UI like APK + AAB.
2026-05-19 15:30:13 -04:00
jwinterm ee4ccacab0 iOS sim: skip artifact upload, just copy .app to workspace/dist
Mac runner spawns each job in a Docker container (via Docker Desktop).
The container has its own /etc/hosts and DNS — the host's hosts entries
don't propagate. DNS for git.such.software resolves to the public IP,
the container can't NAT-hairpin back to its own WAN IP, ECONNREFUSED.

actions/upload-artifact is doubly silly when the runner IS the Gitea
server. Copy the .zip to $GITHUB_WORKSPACE/dist/ instead. That dir
is mounted from the host, so it persists at:
  /Users/such-git/.cache/act/<runner-id>/hostexecutor/dist/

Find latest:
  find /Users/such-git/.cache/act -name 'hash_wallet_ios_sim_*.zip'     -exec ls -lt {} + 2>/dev/null | head

Step also echoes the exact path + a find command in the build log.
2026-05-19 15:01:59 -04:00
jwinterm 0aa9f03f77 Android: bump gradle JVM heap to 10G + cap workers to fix AAB OOM
Verbose log of yesterday's AAB build revealed:
  java.lang.OutOfMemoryError: Java heap space
    at java.util.zip.DeflaterOutputStream.write
    at com.android.zipflinger.StreamSource.<init>
    at com.android.builder.internal.packaging.AabFlinger$writeZip$1.call

AAB packaging streams compressed native libs through ByteArrayOutputStream
buffers. With universal-bundle (all 3 ABIs in one AAB) and parallel
ForkJoinPool workers all drawing from the same 6G heap, the peak exceeds
budget mid-zip. APK build doesn't hit this because per-ABI APKs are
packaged separately, much smaller per-file working set.

Two changes:
  1) org.gradle.jvmargs: -Xmx6144M → -Xmx10240M, + MaxMetaspaceSize=1024M
     (the daemon-side metaspace also creeps up over a long build).
  2) org.gradle.workers.max=2 — fewer parallel workers = lower simultaneous
     buffer footprint. Build still parallel, just gentler peak.
2026-05-19 14:38:57 -04:00
jwinterm ba5c81e78b CI: clarify NODE_TLS_REJECT_UNAUTHORIZED is a fallback now
Gitea is on the same Mac mini as the runner, behind NPM in a separate
container. The proper fix is a hosts entry on the Mac that points
git.such.software at NPM's LAN IP so the runner reaches Gitea via
NPM's Let's Encrypt cert.

The env var stays in the workflow as a belt-and-suspenders fallback —
no harm if certs already validate cleanly, saves the workflow from
breaking again if the hosts entry ever gets blown away (system reset,
container migration, etc).
2026-05-19 12:52:29 -04:00
jwinterm 9f832633b9 CI: skip TLS verify on iOS artifact upload + verbose AAB build
iOS:
- Build itself succeeds (Runner.app sits in build/ios/iphonesimulator/).
- actions/upload-artifact's Node.js HTTP client hits git.such.software
  via LAN (NAT hairpin), bypassing the public NPM/Let's Encrypt cert
  and landing on Gitea's internal self-signed cert. Node refuses with
  DEPTH_ZERO_SELF_SIGNED_CERT.
- Setting NODE_TLS_REJECT_UNAUTHORIZED=0 at job env disables verification
  for all Node-based actions in the job. Acceptable here because we're
  talking to our own server on our own LAN — no external MITM surface.
- Long-term cleaner fix: install Gitea's CA cert into the runner's
  system trust store, or have the runner reach Gitea via the public
  hostname so NPM's Let's Encrypt cert is presented.

Android AAB:
- APK built + signed + uploaded fine (~14 min). AAB build then failed
  at FinalizeBundleTask$BundleToolRunnable / :app:signReleaseBundle
  with no visible error.
- Added --verbose to 'flutter build appbundle' so the next run prints
  the actual gradle stacktrace. Once we see WHY signReleaseBundle is
  failing, we can target the real fix.
2026-05-19 12:29:53 -04:00
jwinterm 1c844c58cf CI: pre-install Rust + iOS targets to fix rustup-during-podbuild race
Build was failing with:
  component download failed for rust-std-aarch64-apple-ios-sim:
  could not rename downloaded file from '.../9362b66...partial' to
  '.../9362b66...': No such file or directory

One of the iOS pods (likely reown_walletkit's yttrium FFI or similar)
invokes rustup mid-build to fetch iOS targets. When the pod build runs
multiple targets in parallel, multiple rustup processes race on the
same .partial download file — one finishes/renames, the next looks
for the .partial that's no longer there.

Fix: add a workflow step that runs before pod install + flutter build:
  - Install rustup if missing (idempotent — self-hosted runner persists).
  - Wipe any stale *.partial files (debris from previous racing).
  - rustup target add the three iOS triples serially.

That way the pod's mid-build rustup invocation finds everything already
installed and just no-ops.
2026-05-19 11:11:14 -04:00