Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions crates/dsm-android-anchor/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 9 additions & 5 deletions dsm_client/frontend/src/components/qr/QRCodeScannerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,15 @@ export default function QRCodeScannerPanel(props: QRCodeScannerProps = {}): Reac
<div className="qr-scanner">
{phase.status === 'adding' && (
<section className="sb-card sb-card--dark sb-card--hero" aria-live="polite">
<img
src={eraTokenSrc}
alt="Adding contact..."
style={{ width: 48, height: 48, imageRendering: 'pixelated' }}
/>
{/* The coin on its light, bordered tile, as the faucet shows it: bare
on the dark card its artwork has no edge. */}
<span className="sb-coin-tile">
<img
src={eraTokenSrc}
alt="Adding contact..."
style={{ width: 48, height: 48, imageRendering: 'pixelated' }}
/>
</span>
<div className="sb-hero__label" style={{ marginTop: 6 }}>Adding Contact</div>
<div className="sb-hero__sub">
{phase.alias ? <>Saving &quot;{phase.alias}&quot; to your contacts...</> : 'Saving the contact...'}
Expand Down
Loading