From 8d4b9d02ce2f579d0117c3cab922b5b3d91c29c4 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 1/2] fix(contacts): the add-contact card shows its coin on the bordered tile, as the faucet does The ERA coin sat bare on the dark card while a contact was being added, so its artwork had no edge against the panel. It now sits on the light, bordered `sb-coin-tile`, the way the Tokens faucet card shows it. Related jest (12 suites, 28 tests), type-check, lint: pass. --- .../src/components/qr/QRCodeScannerPanel.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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...'} 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.