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

# blurr (Solana)

Solana signer that implements `solana_signer::Signer`, delegating signing to a paired Sigil device through octane. Drops into any `solana-sdk` / `solana-program` pipeline as a regular signer.

## Install

```toml
[dependencies]
blurr          = "0.1"
solana-signer  = "3.0"
solana-pubkey  = "3.0"
solana-sdk     = "3.0"
tokio          = { version = "1", features = ["full"] }
```

## Configuration

You need:

|                    |                                                                     |
| ------------------ | ------------------------------------------------------------------- |
| `pubkey`           | Ed25519 public key — the device's Solana account address            |
| `octane_url`       | `https://api.sigilvault.xyz/octane` (prod)                          |
| `api_key`          | SP API key from ironhide                                            |
| `private_key_seed` | 32-byte Ed25519 seed, base64url, no padding — co-signs each request |

You typically obtain `pubkey` from the bindings returned by `pairing::await_pair_result` (the `address` field on the `solana` binding).

## Usage

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

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

// Sign arbitrary bytes (e.g. a serialised transaction):
let sig = signer.try_sign_message(b"transaction bytes")?;
```

The signer implements the full `solana_signer::Signer` trait, so it slots into any place that takes `&dyn Signer`:

```rust
use solana_sdk::transaction::Transaction;
use solana_sdk::message::Message;

let message = Message::new(&[instruction], Some(&payer));
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&[&signer], recent_blockhash)?;
```

## Sync trait, async transport

Solana's `Signer` trait is synchronous (`try_sign_message` is `fn`, not `async fn`). Octane requests are async. `blurr` bridges this by calling `tokio::task::block_in_place` on the current runtime to run the async client to completion synchronously.

This means **`BlurrSigner` must be called from within a Tokio runtime context**:

```rust
#[tokio::main]
async fn main() {
    // Inside a tokio context — fine.
    let sig = signer.try_sign_message(b"…")?;
}
```

If no runtime is current, `try_sign_message` returns a `SignerError::Custom("no tokio runtime available …")`. Most Solana SP backends already run on Tokio (most async Rust web frameworks do), so this rarely needs explicit attention.

If you're calling from a strictly-sync context, wrap with a `tokio::runtime::Handle::current()` from a worker thread, or build a dedicated runtime:

```rust
let rt = tokio::runtime::Runtime::new()?;
let _guard = rt.enter();
let sig = signer.try_sign_message(b"…")?;
```

## Pair handshake

Blurr re-exports `astrotrain::pairing`:

```rust
use blurr::pairing;

let session = pairing::create_pair_session(
    octane_url,
    &api_key,
    pairing::CreatePairSessionRequest {
        sp_user_ref: "user-42".into(),
        requested_scopes: vec![pairing::RequestedScope {
            chain: "solana".into(),
            access: pairing::ScopeAccess::Sign,
        }],
        ..Default::default()
    },
).await?;

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

let pubkey_str = result.bindings
    .iter()
    .find(|b| b.chain == "solana")
    .map(|b| b.address.clone())
    .expect("user granted solana");

let signer = BlurrSigner::new(
    Pubkey::from_str(&pubkey_str)?,
    octane_url,
    &api_key,
    &private_key_seed_b64url,
)?;
```

See [astrotrain](/rust/astrotrain.md) for the full pairing surface (events feed, revocation).

## What gets signed

Solana signatures are 64 bytes of Ed25519 over the **message bytes you pass**. There's no recovery byte (Ed25519 doesn't need one), no parity, no domain separator. `try_sign_message(payload)` returns the device's Ed25519 signature directly — bytes 0..64 of octane's response.

If you're signing a Solana transaction, the message bytes are the output of `Transaction::message_data()` — the standard `try_sign` helper handles this for you. For arbitrary signing (e.g. an off-chain attestation), pass the bytes you want signed directly.

## Errors

`BlurrSigner` surfaces errors through the standard `solana_signer::SignerError::Custom(String)` variant. Common causes:

* **"no tokio runtime available"** — call from inside a Tokio runtime.
* **"invalid device signature encoding"** — corrupt response from octane (network issue or bug); retry.
* **"device signature must be exactly 64 bytes"** — same as above.

For typed error handling, use `astrotrain::AstrotrainClient` directly and bypass the Solana trait, or wrap `BlurrSigner` and re-classify the strings.


---

# 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/rust/blurr.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.
