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. diff --git a/dsm_client/frontend/src/components/qr/QRCodeScannerPanel.tsx b/dsm_client/frontend/src/components/qr/QRCodeScannerPanel.tsx index 1648c8bc1..8c2f3b98b 100644 --- a/dsm_client/frontend/src/components/qr/QRCodeScannerPanel.tsx +++ b/dsm_client/frontend/src/components/qr/QRCodeScannerPanel.tsx @@ -149,11 +149,15 @@ export default function QRCodeScannerPanel(props: QRCodeScannerProps = {}): Reac
{phase.status === 'adding' && (
- Adding contact... + {/* The coin on its light, bordered tile, as the faucet shows it: bare + on the dark card its artwork has no edge. */} + + Adding contact... +
Adding Contact
{phase.alias ? <>Saving "{phase.alias}" to your contacts... : 'Saving the contact...'}