> 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/quickstart.md).

# Quickstart

End-to-end pair handshake plus one signing request, the smallest amount of code that proves your integration works. Two stacks:

* TypeScript backend with `@sigil/astrotrain`
* Rust backend with `astrotrain` (with notes for `cliffjumper` / `blurr`)

Both stacks assume you have an SP API key, a Sigil-issued Ed25519 private seed (Rust only — TS doesn't sign envelopes itself), and a pre-existing user on the appliance side that's reachable through octane.

## TypeScript

```bash
pnpm add @sigil/astrotrain @sigil/jazz-react
```

### Backend: mint a session

```ts
// server/index.ts (Bun + Hono shown; any HTTP framework works)
import { Hono } from "hono";
import { createPairSession, awaitPairResult } from "@sigil/astrotrain";

const app = new Hono();
const OCTANE_URL = process.env.OCTANE_URL!;
const API_KEY = process.env.SIGIL_API_KEY!;

app.post("/api/pair", async (c) => {
  const session = await createPairSession({
    apiKey: API_KEY,
    octaneUrl: OCTANE_URL,
    spUserRef: "user-42",
    requestedScopes: [
      { chain: "ethereum", access: "sign" },
      { chain: "polygon", access: "read" },
    ],
  });
  return c.json(session); // { session_id, pair_uri, expires_at }
});

app.get("/api/pair/:id/result", async (c) => {
  const result = await awaitPairResult({
    apiKey: API_KEY,
    octaneUrl: OCTANE_URL,
    sessionId: c.req.param("id"),
  });
  // Persist result.pair_id against your user row.
  return c.json(result);
});
```

### Frontend: render the widget

```tsx
import { JazzPair } from "@sigil/astrotrain-react";
import "@sigil/astrotrain-react/theme.css";

export function Pair() {
  return (
    <JazzPair
      octaneUrl="/api"
      apiKey="proxied-server-side"
      createSession={async () => (await fetch("/api/pair", { method: "POST" })).json()}
      onComplete={(pair) => {
        console.log("paired", pair.pair_id, pair.bindings);
      }}
      onReject={(err) => console.warn("user declined", err.reason)}
      onExpire={() => console.warn("session timed out")}
      onError={(err) => console.error(err)}
    />
  );
}
```

That's it for pair. The widget renders a QR (desktop) or deeplink button (mobile), and the `onComplete` handler fires with the durable `pair_id` once the user approves.

For signing, see [@sigil/astrotrain](/typescript/astrotrain-ts.md) — the `AstrotrainClient` (or one-shot `sign`) drives the `/sp/sign` calls, and `getEnabledWallets` lists what addresses you currently have authz on. The server typically does the signing, not the browser.

```ts
// server: continued from above
import { AstrotrainClient, hexToBytes } from "@sigil/astrotrain";

const client = new AstrotrainClient({
  octaneUrl: OCTANE_URL,
  apiKey: API_KEY,
  privateKeySeed: process.env.SIGIL_SP_SEED!, // 32-byte Ed25519 seed
});

app.post("/api/sign", async (c) => {
  const { address, payloadHex, algo } = await c.req.json();
  const resp = await client.sign({
    address,
    payload: hexToBytes(payloadHex),
    algo, // "secp256k1" | "ed25519"
  });
  return c.json(resp); // { device_signature, octane_signature, sp_signature, ... }
});
```

## Rust

```toml
# Cargo.toml
[dependencies]
astrotrain  = "0.1"
cliffjumper = "0.1"   # only if you sign Ethereum
blurr       = "0.1"   # only if you sign Solana
tokio       = { version = "1", features = ["full"] }
```

### Pair, with `astrotrain`

```rust
use astrotrain::pairing::{
    self, CreatePairSessionRequest, RequestedScope, ScopeAccess,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let octane_url = "https://api.sigilvault.xyz/octane";
    let api_key = std::env::var("SIGIL_API_KEY")?;

    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,
            }],
            ..Default::default()
        },
    )
    .await?;

    println!("show this URL to the user: {}", session.pair_uri);

    let result = pairing::await_pair_result(
        octane_url,
        &api_key,
        &session.session_id,
    )
    .await?;

    println!("paired: {}", result.pair_id);
    Ok(())
}
```

### Sign Ethereum with `cliffjumper`

After pairing, drop the signer into any `alloy` pipeline:

```rust
use alloy_signer::Signer;
use cliffjumper::CliffjumperSigner;
use k256::ecdsa::VerifyingKey;

let signer = CliffjumperSigner::new(
    /* secp256k1 verifying key for the SP identity */ pubkey,
    "https://api.sigilvault.xyz/octane",
    &api_key,
    &private_key_seed_b64url,   // 32-byte Ed25519 seed
    "0xabc…",                    // device EVM address from the pair binding
)?;

let signature = signer.sign_message(b"hello world").await?;
```

### Sign Solana with `blurr`

```rust
use blurr::BlurrSigner;
use solana_signer::Signer;
use solana_pubkey::Pubkey;
use std::str::FromStr;

let signer = BlurrSigner::new(
    Pubkey::from_str("…device-pubkey…")?,
    "https://api.sigilvault.xyz/octane",
    &api_key,
    &private_key_seed_b64url,
)?;

let sig = signer.try_sign_message(b"transaction bytes")?;
```

`blurr`'s `try_sign_message` is synchronous (Solana's trait is sync) but runs on a current Tokio runtime under the hood — call it from a `#[tokio::main]` async context.

## Where to next

* The pair flow above is two-device by default. For mobile-web SPs, see [Single-device flow](/single-device.md).
* For real signing on a deployed system, see the per-package references: [@sigil/astrotrain](/typescript/astrotrain-ts.md), [astrotrain](/rust/astrotrain.md), [cliffjumper](/rust/cliffjumper.md), [blurr](/rust/blurr.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/quickstart.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.
