-
+ {/* The coin on its light, bordered tile, as the faucet shows it: bare
+ on the dark card its artwork has no edge. */}
+
+
+
Adding Contact
{phase.alias ? <>Saving "{phase.alias}" to your contacts...> : 'Saving the contact...'}
From be4bc9f0779d1ce5fbab93db904428b2e6d555f0 Mon Sep 17 00:00:00 2001
From: Cryptskii <47649969+cryptskii@users.noreply.github.com>
Date: Sun, 27 Sep 2026 13:01:52 -0400
Subject: [PATCH 2/2] fix(android): the app registers the anchor appliance's
transport at start, so Offline connects it by itself
Only the debug self-test screen, launched over adb with an extra, registered the
USB anchor transport with Rust. A normal start never did, so the Appliance
pop-up and Offline Funding answered "no anchor appliance connected" with the
appliance plugged in. `DsmInitProvider` now registers it once at start in any
build that carries it; registering opens nothing, and the first offline read
attaches the appliance (Android asks for the USB permission once). A build
without the capability logs that offline sends are unavailable, and Rust
refuses them saying so. The debug screen's trigger for it goes.
Kotlin main, unit-test and instrumented-test sources compile.
---
crates/dsm-android-anchor/src/install.rs | 7 ++++---
.../src/main/java/com/dsm/wallet/DsmInitProvider.kt | 11 +++++++++++
.../src/main/java/com/dsm/wallet/bridge/Unified.kt | 2 +-
.../com/dsm/wallet/debug/PicoSelfTestActivity.kt | 12 ------------
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/crates/dsm-android-anchor/src/install.rs b/crates/dsm-android-anchor/src/install.rs
index 220c9e651..962953792 100644
--- a/crates/dsm-android-anchor/src/install.rs
+++ b/crates/dsm-android-anchor/src/install.rs
@@ -9,9 +9,10 @@
//! v2 needs NOTHING receiver-side (no relay, no counter reader, no verifier slot) — the receiver
//! accepts from the release alone. So this is the ENTIRE device-layer install story.
//!
-//! NOT auto-called from `initDsmSdk`: the install is gated behind an explicit device-layer trigger
-//! (feature `on_device_installs`, bench builds; the production flip is the owner's call). The
-//! factory is called once per send-session and fails CLOSED if the Pico/chip is absent — an
+//! Registered once at app start by the Kotlin `DsmInitProvider`, in every .so that carries it
+//! (feature `on_device_installs`; the production flip of that feature is the owner's call).
+//! Registering opens nothing. The factory is called once per send-session and fails CLOSED if
+//! the Pico/chip is absent — an
//! uninstalled factory or an unreachable chip means every offline-bearer send errors
//! ("offline = chips"); nothing falls back to a mock.
diff --git a/dsm_client/android/app/src/main/java/com/dsm/wallet/DsmInitProvider.kt b/dsm_client/android/app/src/main/java/com/dsm/wallet/DsmInitProvider.kt
index b04daa1c8..ea408d884 100644
--- a/dsm_client/android/app/src/main/java/com/dsm/wallet/DsmInitProvider.kt
+++ b/dsm_client/android/app/src/main/java/com/dsm/wallet/DsmInitProvider.kt
@@ -27,6 +27,17 @@ class DsmInitProvider : ContentProvider() {
// it only opens the Pico lazily on the first counter read, and accepts no value by itself.
context?.let { com.dsm.wallet.bridge.LocalPicoUsb.init(it) }
+ // The sender's anchor appliance: register its USB transport with Rust, so the
+ // first offline read attaches the appliance by itself and every later one reuses
+ // it. Registering opens nothing; Android asks for the USB permission once, on first
+ // use. A build without the anchor capability has no such export: offline sends are
+ // unavailable there, and Rust refuses them saying so.
+ try {
+ com.dsm.wallet.bridge.Unified.installAnchorTransport()
+ } catch (e: UnsatisfiedLinkError) {
+ Log.w("DsmInitProvider", "this build carries no anchor appliance transport; offline sends are unavailable")
+ }
+
// Library loaded successfully - clear any previous incompatibility flag
context?.let {
it.getSharedPreferences("dsm_system", android.content.Context.MODE_PRIVATE)
diff --git a/dsm_client/android/app/src/main/java/com/dsm/wallet/bridge/Unified.kt b/dsm_client/android/app/src/main/java/com/dsm/wallet/bridge/Unified.kt
index b0badb0e5..bf12879af 100644
--- a/dsm_client/android/app/src/main/java/com/dsm/wallet/bridge/Unified.kt
+++ b/dsm_client/android/app/src/main/java/com/dsm/wallet/bridge/Unified.kt
@@ -104,7 +104,7 @@ object Unified {
// installs the USB anchor appliance factory so offline-bearer sends drive the phone's own
// physical RP2350/TROPIC01 (v2: the receiver needs NO hardware — this is the entire device
// install story). Fail-closed: without it every offline-bearer send errors ("offline = chips").
- // Catch UnsatisfiedLinkError.
+ // `DsmInitProvider` registers it once at app start. Catch UnsatisfiedLinkError.
@Keep @JvmStatic external fun installAnchorTransport(): Boolean
@Keep @JvmStatic fun dispatchStartup(requestBytes: ByteArray): ByteArray =
UnifiedNativeApi.dispatchStartup(requestBytes)
diff --git a/dsm_client/android/app/src/main/java/com/dsm/wallet/debug/PicoSelfTestActivity.kt b/dsm_client/android/app/src/main/java/com/dsm/wallet/debug/PicoSelfTestActivity.kt
index 4b37fa318..e834c5e4b 100644
--- a/dsm_client/android/app/src/main/java/com/dsm/wallet/debug/PicoSelfTestActivity.kt
+++ b/dsm_client/android/app/src/main/java/com/dsm/wallet/debug/PicoSelfTestActivity.kt
@@ -111,18 +111,6 @@ class PicoSelfTestActivity : Activity() {
Log.e(TAG, "counter-init REFUSED: confirm must be 'yes-init-counter-max' (got '$confirm')")
}
}
- // GATED sender-transport install (for the 2-phone test): the USB anchor appliance
- // factory — the ONLY v2 device install (the receiver needs no hardware). Runs ONLY
- // when launched with `--ez install_anchor_transport true`. Absent from the default .so.
- if (intent?.getBooleanExtra("install_anchor_transport", false) == true) {
- val ok = try {
- com.dsm.wallet.bridge.Unified.installAnchorTransport()
- } catch (e: UnsatisfiedLinkError) {
- Log.e(TAG, "installAnchorTransport not in this .so (needs on_device_installs): ${e.message}")
- false
- }
- Log.i(TAG, "*** installAnchorTransport = $ok ***")
- }
// GATED IRREVERSIBLE slot-0 birth burn. Runs ONLY when launched with
// `--ez run_birth_cage true --es confirm yes-birth-cage-slot0`. Run LAST in device
// setup, AFTER counter-init. A normal launch never reaches this.