feat(wallet): offline funding and the appliance on the send screen; balance rows state their offline allocation - #1040
Merged
cryptskii merged 2 commits intoSep 27, 2026
Conversation
…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
deleted the
feat/offline-funding-and-appliance-on-send-screen
branch
September 27, 2026 16:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
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.BalanceGetResponse.offline_allocation(a message, present with its Rust-rendered display form) is filled at the enrichment boundary fromCoreSDK::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.amountis the user's decimal text, scaled by the parser the sends use;OfflineCashResponsecarries both balances rendered by Rust.gonewait kind, for an element leaving the screen). Practice mode refuses the two moves.crates/dsm-android-anchordeclares 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_sdklibhandlers::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_testson Postgres, single-threaded (every step test passed; the load goes through the changed route).balance_wire_fixtureregenerated 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.type-check,lint: pass. Mutation:load|unloaddropped from practice mode's refusal list →refuses moving offline cashred, 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.AnchorApplianceFactory installedpresent in the packagedlibdsm_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 frommainsince 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".