Skip to content

feat(wallet): offline funding and the appliance on the send screen; balance rows state their offline allocation - #1040

Merged
cryptskii merged 2 commits into
mainfrom
feat/offline-funding-and-appliance-on-send-screen
Sep 27, 2026
Merged

cryptskii merged 2 commits into
mainfrom
feat/offline-funding-and-appliance-on-send-screen

Conversation

@cryptskii

@cryptskii cryptskii commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

What

The offline regime had no way in from the app: nothing loaded a token into the device's offline allocation, nothing connected the anchor appliance, and a balance row said nothing about what it held offline. This puts the two controls back on the send screen, where the owner wants them, and makes the balance rows honest about the offline pot.

  • Send screen, Offline mode: below the mode toggle and above the recipient, an Offline Funding button with an i that explains the two balances, and an Appliance button whose dot shows the last read. Offline Funding opens a pop-up that moves a token between the online account and the offline allocation (Load / Unload) through wallet.loadOffline / wallet.unloadOffline, showing both balances and Rust's answer in Rust's words. Appliance opens a pop-up with the plug-in steps and what Rust read from the appliance (state, anchor, counter, frontier); Connect reads it again. Choosing Offline reads the appliance by itself, so after Android's one permission dialog it connects on its own. In Offline mode the balance card shows the offline allocation, which is what an offline send spends; the overview lists a token's offline allocation under its row once there is some.
  • Rust: BalanceGetResponse.offline_allocation (a message, present with its Rust-rendered display form) is filled at the enrichment boundary from CoreSDK::offline_allocation_of, which reads the allocation leaf under the bundle of the appliance attached in this process. The leaf is keyed by that bundle and only the appliance states it, so before one has been attached the field is absent and the wallet reads it as unknown; a zero there would claim a fact nobody holds. The native BTC row has no policy commit and so no leaf. OfflineCashRequest.amount is the user's decimal text, scaled by the parser the sends use; OfflineCashResponse carries both balances rendered by Rust.
  • Tour: after the mode step the guide has the user tap Offline, explains Offline Funding and the appliance, and has them tap Online again (a new gone wait kind, for an element leaving the screen). Practice mode refuses the two moves.
  • Anchor library: crates/dsm-android-anchor declares itself a workspace root. The repository workspace excludes it, and cargo keeps climbing past an excluding root, so from a worktree nested under another checkout it reached the outer manifest and refused to build.

Verification

  • dsm_sdk lib handlers::wallet_routes::tests + handlers::offline_cash_routes (21 passed; new: a_row_states_its_offline_allocation_only_when_the_bundle_is_known, an_amount_that_is_not_a_number_is_refused_as_an_amount); mutation: the offline mapping dropped → the allocation test red, restored. bluetooth::offline_step_tests on Postgres, single-threaded (every step test passed; the load goes through the changed route). balance_wire_fixture regenerated the TS fixture with the allocation present.
  • cargo check -p dsm_sdk --tests, cargo ndk -t arm64-v8a check -p dsm_sdk --features jni,bluetooth: clean. ./gradlew compileDebugKotlin compileDebugUnitTestKotlin: clean on the regenerated protos.
  • Frontend: related jest (58 suites, 383 tests, the wire-contract test green on the regenerated fixture), type-check, lint: pass. Mutation: load|unload dropped from practice mode's refusal list → refuses moving offline cash red, restored.
  • flow_assertions.sh, bridge_rpc_names.py, bridge_contracts_gate.sh, ci_scan.sh, enforce-guardrails.sh, ci/conformance_evidence.py: pass. make lint: passed.
  • Device: the APK (this branch with the anchor-capable library; AnchorApplianceFactory installed present in the packaged libdsm_sdk.so, the Offline Funding UI in the bundle) installs and launches on both appliance phones. The offline run itself is not done yet: both phones' stores are at client schema 24, left by an older build, and every build from main since fix: the placeholder sweep, first fixes #1038 refuses them (no migrations), so the wallet does not start. The phones need their DSM data wiped and re-provisioned (new wallet, pairing, faucet) before the load-offline and offline-send run. fix(startup): a failed startup is the session's error; a store at an older schema fails it in its own words #1041 makes that refusal show on the phone instead of an endless "starting runtime".

…screen, and a balance row states its offline allocation

The offline regime had no way in from the app: nothing loaded a token into the
device's offline allocation, nothing connected the anchor appliance, and a
balance row said nothing about what it held offline. The send screen's Offline
mode now carries, below the mode toggle and above the recipient, an Offline
Funding button with an i that explains the two balances, and an Appliance
button whose dot shows the last read. Offline Funding opens a pop-up that moves
a token between the online account and the offline allocation (Load / Unload)
through `wallet.loadOffline` / `wallet.unloadOffline` and shows Rust's answer in
Rust's words. Appliance opens a pop-up with the plug-in steps and what Rust
read from the appliance (state, anchor, counter, frontier); Connect reads it
again. Choosing Offline reads the appliance by itself, so after Android's one
permission dialog it connects on its own. In Offline mode the balance card
shows the offline allocation (what an offline send spends), and the overview
lists a token's offline allocation under its row once there is some.

Rust: `BalanceGetResponse.offline_allocation` (a message, present with its
Rust-rendered display form) is filled at the enrichment boundary from
`CoreSDK::offline_allocation_of`, which reads the allocation leaf under the
bundle of the appliance attached in this process. The leaf is keyed by that
bundle and only the appliance states it, so before one has been attached the
field is absent and the wallet reads it as unknown; a zero there would claim a
fact nobody holds. The native BTC row has no policy commit and so no leaf.
`OfflineCashRequest.amount` is the user's decimal text, scaled by the same
parser the sends use, and `OfflineCashResponse` carries both balances rendered
by Rust; the route's message is in the token's units.

Tour: after the mode step the guide has the user tap Offline, explains Offline
Funding and the appliance, and has them tap Online again (a new `gone` wait
kind, for an element leaving the screen). Practice mode refuses the two moves.

Anchor library: `crates/dsm-android-anchor` declares itself a workspace root.
The repository workspace excludes it, and cargo keeps climbing past an
excluding root, so from a worktree nested under another checkout it reached
the outer manifest and refused to build.
…cy removals

Building the on-device library from the current SDK re-resolved its lock: the
dependencies the Core and SDK dropped in the placeholder sweep leave this
lockfile too.
@cryptskii
cryptskii merged commit b829d90 into main Sep 27, 2026
20 of 24 checks passed
@cryptskii
cryptskii deleted the feat/offline-funding-and-appliance-on-send-screen branch September 27, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant