PR #2 of the multi-device roadmap — wires the messenger pipeline
against the on-chain registry landed in v2.2.0-alpha1.
lib/api.ts:
- DeviceInfo type mirroring blockchain.DeviceInfo.
- IdentityInfo.device_count (optional; populated from /api/identity).
- fetchDevices(masterPub) → /api/devices/{master_pub}, returns [].
Errors swallowed so a downed endpoint doesn't block messaging.
- resolveRecipientKeys(masterPub) — the routing primitive. Returns
devices[] if registered, else falls back to IdentityInfo.x25519_pub
(pre-v2.2.0 path). Empty only when recipient has published nothing.
- buildLinkDeviceTx / buildUnlinkDeviceTx — signed by master Ed25519,
min-fee cost, canonical JSON payload matching the chain-side
LinkDevicePayload / UnlinkDevicePayload.
app/(app)/chats/[id].tsx:
- sendCore now fans out: encrypts once per recipient device pub
(Promise.all, any failure rejects the batch), falls back to the
cached contact.x25519Pub if the registry lookup returns nothing.
- Saved Messages short-circuit preserved; no devices lookup for self.
app/(app)/_layout.tsx:
- On every sign-in, auto-submit LINK_DEVICE for this device if its
X25519 pub isn't already in the master's registry. Device name
picks "iPhone" / "Android phone" / "Device" by Platform. Errors
(insufficient balance / legacy chain without LINK_DEVICE support)
are silent — next launch retries.
Backward compatibility: senders fall back to identity.x25519_pub when
the recipient has no registry entries, so pre-v2.2.0 clients still
receive messages. Chain-side already gates new validation on the event
types existing; old clients simply never emit LINK_DEVICE and keep
working with a single X25519.
Next — PR #3 (Settings → Devices screen + QR pairing flow + receive-side
self-wipe on revoke detection).