Skip to content

Linked devices

Create a distinct device credential and signing lane through short-lived linking and explicit approval.

Linked devices

Device linking creates a distinct signing lane for another user-controlled device.

Flow

  1. A new device presents a QR link session with a link public key.
  2. An existing owner device authenticates the user and approves permissions.
  3. The owner worker creates a distinct holder share for the linked device.
  4. The server creates the matching server share for the linked-device lane.
  5. The linked device receives an encrypted holder-share package.
  6. 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.

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>
  );
}