Appearance
Linked devices
Device linking creates a distinct signing lane for another user-controlled device.
Flow
- A new device presents a QR link session with a link public key.
- An existing owner device authenticates the user and approves permissions.
- The owner worker creates a distinct holder share for the linked device.
- The server creates the matching server share for the linked-device lane.
- The linked device receives an encrypted holder-share package.
- The lane activates after delivery receipt and address parity checks.
The linked device has its own laneId, laneShareEpoch, holder-share envelope, permission policy, revocation status, and audit history.
Owner-equivalent linked-device lanes should require local user presence for signing. Scoped linked-device lanes should use the same mandate admission model as delegated agents.
Link from the app
The new device displays a short-lived QR payload. An authenticated owner device scans and approves it through the linking hook.
Runnable React example
tsx
import { useState } from 'react';
import { QRScanMode, ShowQRCode, useDeviceLinking } from '@seams/wallet/react';
import type { LinkDeviceFlowEvent, QrLinkedDeviceSessionPayloadV5 } from '@seams/wallet';
function logLinkEvent(event: LinkDeviceFlowEvent): void {
console.log(event.phase, event.status, event.message);
}
// Device 2: `ShowQRCode` runs the whole start/display/expire cycle, including
// picking the target factor and cancelling an abandoned session.
export function NewDeviceLinkCode() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Show link code</button>
<ShowQRCode
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onEvent={logLinkEvent}
onError={(error) => console.error('Device link failed', error)}
/>
</>
);
}
// Device 1: scan the code and approve.
export function ApproveLinkedDevice(props: { qrData: QrLinkedDeviceSessionPayloadV5 }) {
const { linkDevice } = useDeviceLinking({
onEvent: logLinkEvent,
onError: (error) => console.error('Device link failed', error),
});
return (
<button onClick={() => void linkDevice(props.qrData, QRScanMode.CAMERA)}>Approve device</button>
);
}