Documentation

Pubky SDK (Rust)

Ergonomic building blocks for Pubky apps: one façade (Pubky) plus focused actors for sessions, storage API, signer helpers, and QR auth flow for keyless apps.

Rust implementation of Pubky SDK.

Install

# Cargo.toml
[dependencies]
pubky = "0.x"            # this crate
# Optional helpers used in examples:
# pubky-testnet = "0.x"

Quick start

use pubky::prelude::*;

# async fn run() -> pubky::Result<()> {

let pubky = Pubky::new()?; // or Pubky::testnet() for local testnet.

// 1) Create a new random key user and bound to a Signer
let keypair = Keypair::random();
let signer = pubky.signer(keypair);

// 2) Sign up on a homeserver (identified by its public key)
let homeserver = PublicKey::try_from("o4dksf...uyy").unwrap();
let session = signer.signup(&homeserver, None).await?;

// 3) Read/Write as the signed-in user
session.storage().put("/pub/my.app/hello.txt", "hello").await?;
let body = session.storage().get("/pub/my.app/hello.txt").await?.text().await?;
assert_eq!(&body, "hello");

// 4) Public read of another user’s file
let txt = pubky.public_storage()
  .get(format!("{}/pub/my.app/hello.txt", session.info().public_key()))
  .await?
  .text().await?;
assert_eq!(txt, "hello");

// 5) Keyless app flow (QR/deeplink)
let caps = Capabilities::builder().write("/pub/acme.app/").finish();
let flow = pubky.start_auth_flow(&caps)?;
println!("Scan to sign in: {}", flow.authorization_url());
let app_session = flow.await_approval().await?;

// 6) Optional (advanced): publish or resolve PKDNS (_pubky) records
signer.pkdns().publish_homeserver_if_stale(None).await?;
let resolved = signer.pkdns().get_homeserver().await;
println!("Your current homeserver: {:?}", resolved);

# Ok(()) }

Mental model

  • Pubky - façade, always start here! Owns the transport and constructs actors.
  • PubkySigner - local key holder. Can signup, signin, approve QR auth, publish PKDNS.
  • PubkySession - authenticated “as me” handle. Exposes session-scoped storage.
  • PublicStorage - unauthenticated reads of others’ public data.
  • Pkdns - resolve/publish _pubky records.

Transport:

  • PubkyHttpClient stateless transport: handles requests to pubky public-key hosts.

Examples

Storage API (session & public)

Session (authenticated):

use pubky::{Pubky, Keypair};

# async fn run() -> pubky::Result<()> {

let pubky = Pubky::new()?;
let session = pubky.signer(Keypair::random()).signin().await?;

let storage = session.storage();
storage.put("/pub/my.app/data.txt", "hi").await?;
let text = storage.get("/pub/my.app/data.txt").await?.text().await?;

# Ok(()) }

Public (read-only):

use pubky::{Pubky, PublicKey};

# async fn run(user_id: PublicKey) -> pubky::Result<()> {

let pubky = Pubky::new()?;
let public = pubky.public_storage();

let file = public.get(format!("{user_id}/pub/acme.app/file.bin")).await?.bytes().await?;

# Ok(()) }

See the Public Storage example.

Path rules:

  • Session storage uses absolute paths like "/pub/app/file.txt".
  • Public storage uses addressed form <user>/pub/app/file.txt (or pubky://<user>/...).

Convention: put your app’s public data under a domain-like folder in /pub, e.g. /pub/mycoolnew.app/.

PKDNS (Pkarr)

Resolve another user’s homeserver (_pubky record), or publish your own via the signer.

use pubky::{Pubky, PublicKey, Keypair};
# async fn run(other: PublicKey, new_homeserver_id: PublicKey) -> pubky::Result<()> {
let pubky = Pubky::new()?;

// read-only resolver
let pkdns = pubky.pkdns();
let host = pkdns.get_homeserver_of(&other).await;

// publish with your key
let signer = pubky.signer(Keypair::random());
signer.pkdns().publish_homeserver_if_stale(None).await?;
// or force republish (e.g. homeserver migration)
signer.pkdns().publish_homeserver_force(Some(&new_homeserver_id)).await?;

# Ok(()) }

Pubky QR auth for third-party and keyless apps

Request an authorization URL and await approval.

Typical usage:

  1. Start an auth flow with pubky.start_auth_flow(&caps) (or use the PubkyAuthFlow::builder() to set a custom relay).
  2. Show authorization_url() (QR/deeplink) to the signing device (e.g., Pubky RingiOS / Android).
  3. Await await_approval() to obtain a session-bound PubkySession.
# use pubky::{Pubky, Capabilities, Keypair};
# async fn auth() -> pubky::Result<()> {

let pubky = Pubky::new()?;
// Read/Write capabilities for acme.app route
let caps = Capabilities::builder().read_write("pub/acme.app/").finish();

// Start the flow using the default relay (see “Relay & reliability” below)
let flow = pubky.start_auth_flow(&caps)?;
println!("Scan to sign in: {}", flow.authorization_url());

// On the signing device, approve with: signer.approve_auth(flow.authorization_url()).await?;
# pubky.signer(Keypair::random()).approve_auth(flow.authorization_url()).await?;

let session = flow.await_approval().await?;

# Ok(()) }

Approve an auth request

signer.approve_auth(authorization_url).await?;

See the fully functional Auth Flow Example.

Relay & reliability

  • If you don’t specify a relay, PubkyAuthFlow defaults to a Synonym-hosted relay. If that relay is down, logins won’t complete.
  • For production and larger apps, run your own relay (MIT, Docker): https://httprelay.io. The channel is derived as base64url(hash(secret)); the token is end-to-end encrypted with the secret and cannot be decrypted by the relay.

Custom relay example

# use pubky::{PubkyAuthFlow, Capabilities};
# async fn custom_relay() -> pubky::Result<()> {
let caps = Capabilities::builder().read("pub/acme.app/").finish();
let auth_flow = PubkyAuthFlow::builder(&caps)
    .relay(url::Url::parse("http://localhost:8080/link/")?) // your relay
    .start()?;
# Ok(()) }

Features

  • json: enable Storage helpers (.get_json() / .put_json()) and serde on certain types.
# Cargo.toml
[dependencies]
pubky = { version = "x.y.z", features = ["json"] }

Testing locally

Spin up an ephemeral testnet (DHT + homeserver + relay) and run your tests fully offline:

# use pubky_testnet::{EphemeralTestnet, pubky::Keypair};
# async fn test() -> pubky_testnet::pubky::Result<()> {

let testnet = EphemeralTestnet::start().await.unwrap();
let homeserver  = testnet.homeserver();
let pubky = testnet.sdk()?;

let signer = pubky.signer(Keypair::random());
let session  = signer.signup(&homeserver.public_key(), None).await?;

session.storage().put("/pub/my.app/hello.txt", "hi").await?;
let s = session.storage().get("/pub/my.app/hello.txt").await?.text().await?;
assert_eq!(s, "hi");

# Ok(()) }

Keypair and Session persistence

Encrypted Keypair secrets (.pkarr):

use pubky::Pubky;
# fn run() -> pubky::Result<()> {
let pubky = Pubky::new()?;
let signer = pubky.signer_from_recovery_file("/path/to/alice.pkarr", "passphrase")?;
# Ok(()) }

Session secrets (.sess):

use pubky::{Pubky, Keypair};
# async fn run() -> pubky::Result<()> {
let pubky = Pubky::new()?;
let keypair = Keypair::random();
let session = pubky.signer(keypair).signin().await?;
session.write_secret_file("alice.sess").unwrap();
let restored = pubky.session_from_file("alice.sess").await?;

# let _ = std::fs::remove_file("alice.sess");
# Ok(()) }

Security: the .sess secret is a bearer token. Anyone holding it can act as the user within the granted capabilities. Treat it like a password.

Example code

Check more examples using the Pubky SDK.

JS bindings

Find a wrapper of this crate using wasm_bindgen in npmjs.com. Or build on pubky-sdk codebase under pubky-sdk/bindings/js.


License: MIT Relay: https://httprelay.io (open source; run your own for production)