Commit Graph
290 Commits
Author SHA1 Message Date
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 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 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 64642818f8 ci: update Windows build workflow 2026-08-07 10:29:15 -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 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 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 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 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 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 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 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 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 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
jwinterm 8f583b14ea iOS: strip Zano/Zcash xcframework refs + generate Monero/Wownero XCFrameworks in CI
Two fixes that together address the 'There is no XCFramework found at...'
build failure:

1) ios/Runner.xcodeproj/project.pbxproj: remove 10 lines referencing
   ZanoWallet.xcframework + WarpApiFFI.xcframework (zcash). Those were
   stale — both chains were removed in commit 4eb7db0b7 weeks ago, but
   the Xcode project still listed them in BuildFile, CopyFiles, FileReference,
   and Group sections. xcode error referenced them as missing.

2) Both iOS workflows now run scripts/ios/gen_framework.sh after staging
   the monero_c bundle. The script reads
   aarch64-apple-ios/lib<wallet>_wallet2_api_c.dylib and
   aarch64-apple-ios-simulator/lib<wallet>_wallet2_api_c.dylib from the
   bundle, wraps each as a .framework with an Info.plist, then bundles
   device + simulator into ios/MoneroWallet.xcframework +
   ios/WowneroWallet.xcframework via xcodebuild -create-xcframework.
   These are exactly the paths project.pbxproj's CopyFiles phase
   references.

   The 'Inspect iOS targets' debug step was expanded to print bundle
   subdirs + sample target contents, so if the monero_c bundle doesn't
   actually ship those exact dirs we'll see it in the log.
2026-05-19 10:54:48 -04:00
jwinterm e277d1ba33 CI: run compile_graphics.sh to generate .svg.vec icon assets
Empty action-row buttons (Send/Receive/Swap/Scan) and empty bottom
navbar tabs are the same root cause on Linux + Android (+ likely iOS):
the workflows weren't running Cake's compile_graphics.sh, which is
what converts res/pictures/*.svg sources → assets/new-ui/*.svg.vec
precompiled vector assets that CakeImageWidget loads via
AssetBytesLoader.

History: commit 89e07fbba (Cake upstream, March 2026) moved to
precompiled SVGs, added compile_graphics.sh, and added a 'Compile
graphics' workflow step. Then 980c0278b added \*.svg.vec to .gitignore
(so the artifacts are no longer tracked). End result: CI MUST run
compile_graphics.sh to produce the .vec files, or every CakeImageWidget
silently renders nothing.

Fix:
- Add vector_graphics_compiler: ^1.1.16 to dev_dependencies (the script
  invokes 'dart run vector_graphics_compiler ...' which needs the
  package in the project's deps for 'dart run' to find it).
- Add a 'Compile SVG assets' step to all four workflows, between
  generate_localization and the build/pod-install step.

This will populate assets/new-ui/ with ~82 .svg.vec files at build
time, so every CakeImageWidget reference (action row, navbar, top bar,
settings rows, etc.) resolves to its actual icon.
2026-05-19 10:50:44 -04:00
jwinterm e21430e1d6 CI: force LANG/LC_ALL to UTF-8 in iOS workflows (CocoaPods needs it)
pod install fails with Encoding::CompatibilityError 'Unicode
Normalization not appropriate for ASCII-8BIT' when the shell locale
isn't UTF-8. CocoaPods' own warning tells you: 'CocoaPods requires
your terminal to be using UTF-8 encoding'.

Gitea Actions runner on macOS launches subprocesses with LANG unset,
which Ruby treats as ASCII-8BIT. Setting env-level LANG=en_US.UTF-8
and LC_ALL=en_US.UTF-8 fixes it for all steps in the job.
2026-05-19 08:20:17 -04:00
jwinterm bf2a35893d CI: iOS workflows clone BitBox without running Android bindings build
build_bindings.sh in bitbox_flutter unconditionally runs
'gomobile bind -target=android -androidapi 24' which produces an
api.aar — needed only for Android. The Mac runner has no Android SDK,
so gomobile fails with 'could not locate Android SDK'.

iOS doesn't need the .aar. BitBox's iOS support is a separate native
plugin under bitbox_flutter/ios/Classes/ + bitbox_flutter.podspec,
linked via flutter pub get + pod install. We just need the directory
present so the path-dep resolves.

Replace the wrapper invocation in iOS workflows with the clone steps
only; skip build_bindings.sh entirely. Android workflow keeps using
the wrapper as-is.
2026-05-19 08:02:33 -04:00
jwinterm d21c944f3f CI: manual-trigger only for all build workflows
Auto-triggers on push were noisy — every merged PR kicked off the
linux + android + ios-sim builds. Switching to workflow_dispatch only.
Trigger via Gitea Actions UI when you want a build. workflow_dispatch
runs as the authenticated user, so secrets are always present.

Adds the push trigger back when we want pre-merge gating or CI on every
commit (e.g., release-tag automation).
2026-05-19 07:57:22 -04:00
jwinterm 1e4af3dbde CI: drop pull_request triggers (Gitea strips secrets from fork PRs)
The Android workflow's 'ANDROID_KEYSTORE_BASE64 not set' failure is
secrets-not-reaching-runner, not a missing secret. The secret IS set
in Builds/hash-wallet's repo secrets. The issue: workflows are being
triggered by 'pull_request' events when github-such-software/hash-wallet
is mirrored into Builds/hash-wallet via PR. Gitea (and GitHub) Actions
deliberately strip secrets from workflows triggered by PRs from forks
to prevent rogue PRs from exfiltrating secrets.

Fix: drop the pull_request trigger from all auto-build workflows. Each
workflow now runs exactly once, on push to dev/main (post-merge), in
the destination repo's context, with secrets intact.

Also added a debug step in the Android keystore decode that prints
secret LENGTHS (never values) so future failures of this shape are
diagnosable immediately.
2026-05-19 07:54:09 -04:00
jwinterm 71da372d83 CI: install Go + gomobile if missing (for BitBox Flutter iOS bindings)
scripts/build_bitbox_flutter.sh invokes build_bindings.sh which calls
gomobile to generate iOS bindings for the Go BitBox client. Without
Go + gomobile on the Mac runner, that step fails with 'gomobile:
command not found'.

Adds an idempotent step: install Go via brew if missing, then 'go
install golang.org/x/mobile/cmd/{gomobile,gobind}@latest' if missing.
Self-hosted runner persists $GOPATH/bin between runs, so steady-state
is zero-cost after the first install.

Same conditional-install pattern as Flutter + CocoaPods.
2026-05-19 07:50:02 -04:00
jwinterm 53919f73c5 CI: tolerate missing Android i686 ABI; brew-first CocoaPods install
Android:
- monero_c prebuilt bundle doesn't ship i686-linux-android (32-bit x86
  Android — essentially extinct, sim/emulators use x86_64). Was failing
  loud on the missing target. Now: missing target = skip + log; only
  the truly-modern ABIs (arm64-v8a, x86_64) are REQUIRED. armeabi-v7a
  (32-bit ARM) is shipped if available but not required. The resulting
  APK is universal across whichever ABIs got staged.

iOS:
- 'sudo gem install cocoapods' was hitting a password prompt because
  the Gitea runner user has no passwordless sudo configured (rightly).
  Switched to brew-first install (Homebrew lives in its own prefix,
  no sudo needed on Mac). Falls back to 'gem install --user-install'
  (no sudo) if brew is somehow unavailable. The whole step is still
  no-op if 'pod' is already on PATH.
2026-05-19 07:30:13 -04:00
jwinterm e8f766dfe6 Rebrand: 'Hash Wallet' → 'Hash Bags' (visible name only)
Avoids any confusion / potential trademark friction with HashWallet
(Spanish hardware-wallet company). Hash Bags is a more distinctive
name, harder to collide with, and we can still rank for 'hash wallet'
search via App Store / Play Store subtitle ('Hash Bags - A Wallet
for your Coins').

What changed (all user-visible strings):
- Launcher labels: android/app.properties, scripts/android/app_env.sh,
  scripts/ios/app_env.sh, linux window title (CMakeLists +
  my_application.cc)
- In-app text: lib/generated/i18n.dart + ARB sources under res/values/
- Brand docs: README.md, CONTRIBUTING.md, PRIVACY.md,
  assets/text/Terms_of_Use.txt, assets/text/Release_Notes.txt
- hash.boats website: index.html, og-image.svg + regen og-image.png,
  site.webmanifest, styles.css comment, splash/confirm/pulse/mark/
  lockup svg titles, report/index.html, server/server.js
- CI workflow display names ("Hash Bags Linux build", etc.)

What did NOT change (deliberate):
- Bundle ID / Package: com.suchsoftware.hashwallet (immutable app
  identity on App Store + Play Store — changing == orphaning the app)
- Binary name: hash_wallet (Linux executable)
- Dart import paths: package:hash_wallet/...
- Git repo: hash-wallet (Such-Software/hash-wallet)
- URL scheme: hashwallet:// (internal deep links)
- support@such.software (no domain change yet)
- App Store / Play Store listing names (whatever was registered there
  stays; we updated launcher label only)
- hash.boats domain (still the wallet site)

Also kept the '#ash Wallet' wordplay in the nav but updated to
'#ash Bags' so the # mark still serves as the H. Nav reads:
[# mark] #ash Bags

Website hero tagline now matches the App Store sub-title concept:
'A wallet for your coins. Open-source, non-custodial, multi-chain.
Privacy-leaning by default.'

108 files modified, ~674 string replacements.
2026-05-19 07:26:45 -04:00
jwinterm 93744af5ba CI: conditional Flutter install + concurrency + wget→curl for Mac runner
iOS workflows were broken on the Mac mini runner because Flutter
wasn't installed. Three fixes:

1) Conditional Flutter setup. Check 'flutter --version' first; only
   pull subosito/flutter-action@v2 if missing or wrong version. Self-
   hosted Mac runner persists SDKs between runs, so steady-state is
   zero-cost. First run on the runner triggers a full SDK download
   via subosito (with cache: true so subsequent fresh installs are
   still fast).

2) wget → curl across all iOS workflow steps. macOS doesn't ship
   wget by default; curl is always present and respects -fsSL +
   redirect properly.

3) Concurrency groups on all four workflows. A burst of commits no
   longer queues N copies of the same build — newer pushes cancel
   in-flight runs on the same branch. Groups:
     linux-<ref>, android-<ref>, ios-sim-<ref>, ios-testflight-<ref>

   This addresses the '6 runs from one merge' issue: each commit in
   the merge triggered all 3 push-listening workflows. Concurrency
   cancels the earlier 3 the moment the newer commit's runs start.

4) CocoaPods install is also conditional (was already 'if ! command -v
   pod' under the hood; just tightened the step name).
2026-05-18 21:29:59 -04:00
jwinterm c7f64f8273 CI: iOS TestFlight build workflow (signed + upload via altool)
Phase 2 of iOS CI. Full signed pipeline that:

1) Decodes secrets:
   - APPLE_DIST_CERT_P12_BASE64 + password → temp keychain
   - APPLE_PROVISIONING_PROFILE_BASE64 → ~/Library/MobileDevice/...
   - APPLE_API_KEY_P8_BASE64 → ~/.appstoreconnect/private_keys/
2) Parses the .mobileprovision for its UUID + Name (used in ExportOptions).
3) Generates ios/ExportOptions.plist dynamically with the right Team ID +
   profile name + 'app-store' method.
4) Runs flutter build ipa --release --export-options-plist=...
5) Uploads the .ipa to TestFlight via xcrun altool with the API key.
6) Always cleans up keychain + temp credentials, even on failure.

Trigger: workflow_dispatch ONLY (manual). TestFlight uploads consume a
build number with every push — we don't want auto-trigger spamming
TestFlight. Bump the build number in scripts/ios/app_env.sh before
each TestFlight push (or wire a workflow_dispatch input later).

All Apple secrets (and the Trocador ones reused from Linux/Android) are
already configured in Gitea Actions.

If signing fails on first run: most likely the profile name extracted
from the .mobileprovision doesn't match what Xcode expects, OR the
distribution cert in the .p12 doesn't match what the profile pins. The
keychain identity sanity-check + ExportOptions.plist echo in the logs
will tell us which.
2026-05-18 21:23:54 -04:00
jwinterm 6304521e92 CI: iOS Simulator build workflow (no signing)
Phase 1 of iOS CI. Validates the Mac runner end-to-end with the
simplest possible target: an unsigned simulator .app.

Runs on the mac-mini-m2 self-hosted runner (label: macos-latest).
Mirrors the Linux/Android workflows for the shared prep steps
(torch_dart, reown_flutter, bitbox_flutter, monero_c prebuilt bundle,
configure, codegen, Trocador secrets), then does flutter build ios
--simulator --no-codesign and zips Runner.app.

Artifact: hash-wallet-ios-sim-<sha>. Unzip on any Apple Silicon Mac,
drag Runner.app into Simulator to run.

Phase 2 (TestFlight, separate file) follows after Phase 1 is green —
adds keychain setup, .p12 + provisioning profile install, ipa build,
xcrun altool upload using the App Store Connect API key. All Apple
secrets are already configured in Gitea.

The 'Inspect iOS targets in monero_c bundle' debug step will reveal
which Apple targets the prebuilt bundle ships — we'll need
aarch64-apple-ios-sim (or similar) for arm64 simulator builds on
M-series Macs. If missing, follow-up will either fetch from a different
source or build the iOS .a archives from source via Cake's scripts/ios/
build_*.sh chain (slow but reliable).
2026-05-18 21:22:07 -04:00
jwinterm 083f8b7f2a CI: add Android build workflow (signed APK + AAB)
Mirrors the Linux workflow's approach — uses the Cake-Labs prebuilt
Docker image (Flutter 3.32.0 + NDK r28 + Go + Android SDK + JDK17 +
all native build deps) and the prebuilt monero_c .so bundle, so we
skip the multi-hour native cross-compile.

Stages monero + wownero .so files into android/app/src/main/jniLibs/
for all 4 Android ABIs (arm64-v8a, armeabi-v7a, x86, x86_64). Zano
libs in the bundle are ignored.

Decodes the upload keystore from secret ANDROID_KEYSTORE_BASE64,
writes android/key.properties (which build.gradle's signingConfigs
already reads), builds a universal release APK for sideload + an
AAB for Play Console. Both signed with the upload key; Play App
Signing re-signs the AAB server-side with the production key on
upload.

Required Gitea Actions secrets:
  ANDROID_KEYSTORE_BASE64
  ANDROID_KEYSTORE_PASSWORD
  ANDROID_KEY_ALIAS
  ANDROID_KEY_PASSWORD
  TROCADOR_API_KEY (already set, reused from Linux)
  TROCADOR_MONERO_API_KEY (same)
  TROCADOR_EXCHANGE_MARKUP (same)

mwebd (Litecoin MWEB privacy) is NOT built. LTC works without it,
just no MWEB feature. Add the build step + go build pipeline if we
want MWEB on Android — separate effort.

Artifacts:
  - hash-wallet-android-apk-<sha>  (single universal APK, 14-day retention)
  - hash-wallet-android-aab-<sha>  (Play Console upload, 30-day retention)
2026-05-18 20:48:44 -04:00
jwinterm 4eb7db0b72 Delete cw_zano / cw_tron / cw_solana / cw_decred packages + all references
These chains were already disabled in availableWalletTypes (none of them
were getting created), but the source dirs and the codegen scaffolding
for them still lived in tree (~20K lines of dead Dart).

Surgery:
- Delete cw_decred/ cw_solana/ cw_tron/ cw_zano/ source trees
- Strip 5 generate*() function bodies from tool/configure.dart
  (generateSolana, generateTron, generateZano, generateDecred,
  generateZcash) plus their top-level imports, args, calls,
  signatures, and conditional pubspec/walletTypes injection blocks.
  configure.dart shrinks from 2134 to 1561 lines and dart analyze
  is clean.
- Strip solana/tron secrets config + output paths from
  tool/import_secrets_config.dart
- Delete scripts/{android,ios,macos}/build_decred.sh,
  scripts/ios/build_zano{,_all}.sh
- Patch scripts/{android,ios,macos}/build_all.sh,
  scripts/android/copy_monero_deps.sh, scripts/ios/setup.sh,
  scripts/ios/gen_framework.sh to drop decred/zano steps
- Patch scripts/android/docker/build.sh + Dockerfile.final to drop the
  decred Docker stage; delete scripts/android/docker/Dockerfile.decred
- Update .github/workflows/build-linux.yml comments to reflect the
  remaining cw_* module list (evm, nano, bitcoin)

Note: WalletType.{solana,tron,zano,decred,zcash} enum values still live
in cw_core/lib/wallet_type.dart and there are dead switch cases in
lib/di.dart and lib/new-ui/pages/*.dart that reference them. Those
paths are unreachable (the enum values never get assigned to any wallet)
but cleaning them up is a separate hygiene pass.
2026-05-17 08:01:29 -04:00