> For the complete documentation index, see [llms.txt](https://docs.sigilvault.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sigilvault.xyz/single-device.md).

# Single-device flow

The single-device flow lets a user pair their Sigil appliance from **inside the browser on their phone** — same device that runs Sunstorm. It uses iOS / Android Universal Links to open Sunstorm directly (no QR scan), bakes the validation code into the URL so the user doesn't retype it, and routes the user back to your SP page automatically after approval.

## When to use it

* **Use it** when your SP's primary surface is mobile web. Anyone hitting your site from a phone with Sunstorm installed gets a one-tap pair.
* **Don't use it** when the SP runs on desktop. Tap-to-deeplink can't bridge from desktop to phone — that's what the QR is for.
* The widget auto-detects which to render via `navigator.userAgent`, so the correct mode lights up per session.

## What changes vs. the standard flow

Compared to two-device pair, single-device adds:

| Concern            | Two-device                                       | Single-device                                           |
| ------------------ | ------------------------------------------------ | ------------------------------------------------------- |
| Hand-off           | QR scan from desktop to phone                    | Universal Link tap on the phone itself                  |
| Validation code    | User reads it off your page, types into Sunstorm | Embedded in URL as `&v=NNNNNN`, prefilled in Sunstorm   |
| Return path        | SP page polls / SSE-watches for completion       | Sunstorm fires a `return_url` after approval            |
| SP backend changes | None                                             | One new field on session create: `returnUrl`            |
| SP infra changes   | None                                             | Pre-register your SP origin in `allowed_return_origins` |

## SP backend: pass `returnUrl`

In TypeScript:

```ts
const session = await createPairSession({
  apiKey: API_KEY,
  octaneUrl: OCTANE_URL,
  spUserRef: "user-42",
  requestedScopes: [{ chain: "ethereum", access: "sign" }],
  returnUrl: "https://acme-jazz-demo.fly.dev/paired",
});
```

In Rust (astrotrain):

```rust
let session = pairing::create_pair_session(
    octane_url,
    &api_key,
    CreatePairSessionRequest {
        sp_user_ref: "user-42".into(),
        requested_scopes: vec![RequestedScope {
            chain: "ethereum".into(),
            access: ScopeAccess::Sign,
        }],
        return_url: Some("https://acme-jazz-demo.fly.dev/paired".into()),
        ..Default::default()
    },
).await?;
```

The URL must be **HTTPS**, **≤ 2 KiB**, and its origin must be a member of your SP user's `allowed_return_origins` list. Octane returns `400` otherwise.

## Frontend: render the universal link

The `<JazzPair>` widget handles this automatically when the `validationCode` prop is supplied:

```tsx
const [validationCode, setValidationCode] = useState<string | null>(null);

const createSession = async () => {
  const r = await fetch("/api/pair", { method: "POST" }).then((x) => x.json());
  if (r.validation_code) setValidationCode(r.validation_code);
  return r;
};

<JazzPair
  octaneUrl="/api"
  apiKey="proxied-server-side"
  createSession={createSession}
  {...(validationCode ? { validationCode } : {})}
  onComplete={(pair) => persistPair(pair)}
/>
```

When `validationCode` is set, the widget's deeplink button renders `https://sigilvault.xyz/pair?session=<id>&v=<code>` instead of the bare session URI. Sunstorm reads both query params and prefills its approval card.

If you're rolling your own UI without the React widget, use the SDK's `buildPairUri` helper:

```ts
import { buildPairUri } from "@sigil/astrotrain";

const href = buildPairUri({
  sessionId: session.session_id,
  validationCode: rolledCode,    // your 6-digit code
  scheme: "https",                // universal link
});
// → "https://sigilvault.xyz/pair?session=…&v=……"
```

## Return-URL landing page

After Sunstorm approves the pair, it opens the `return_url` you supplied, with these query params appended:

| Param                  | When                      | Meaning                                                            |
| ---------------------- | ------------------------- | ------------------------------------------------------------------ |
| `?session=<id>`        | Always (you put it there) | The session you started                                            |
| `&sigil_status=denied` | Only on user-rejected     | Tells your page to flip immediately to "denied" instead of polling |

Your landing page should:

1. Read `session` from the query.
2. If `sigil_status=denied`, show the rejected state and stop.
3. Otherwise, call `awaitPairResult` (or proxy through your backend) to get the durable `pair_id`, then run `validateSpKey` to clear the appliance's pending state.

Reference implementation in `pkg/jazz-demo/web/App.tsx` — `PairedLanding` component.

## Origins must be pre-registered

Octane validates the `return_url` you supply against your SP user's `allowed_return_origins` list. Origins are HTTPS-only, scheme + host + port (no path, no trailing slash).

To register:

* **Via metroplex** — admin profile dialog has an "Allowed return origins" comma-separated input.
* **Via SQL** —

  ```sql
  UPDATE users
     SET allowed_return_origins = ARRAY['https://acme.example.com']
   WHERE id = '<sp-user-id>';
  ```

You can register multiple — typical pattern is prod + staging:

```
https://acme.example.com, https://staging.acme.example.com
```

## iOS Universal Link prerequisites

The Universal Link only opens Sunstorm if **Apple's CDN has cached the AASA file** for `sigilvault.xyz` AND **Sunstorm is installed**. If either is missing, the link opens in mobile Safari and falls back to a small "Install Sunstorm" landing page. Users can still pair from the fallback by installing first; no link is ever a dead end.

For internal testing on dev builds, the `applinks:sigilvault.xyz?mode=developer` entitlement form bypasses Apple's CDN cache. See `docs/apple-setup.md`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.sigilvault.xyz/single-device.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
