| Crates.io | pubky |
| lib.rs | pubky |
| version | 0.6.0-rc.3 |
| created_at | 2024-09-06 08:06:37.282342+00 |
| updated_at | 2025-09-17 03:57:05.216477+00 |
| description | Pubky SDK |
| homepage | https://github.com/pubky/pubky-core |
| repository | https://github.com/pubky/pubky-core |
| max_upload_size | |
| id | 1365642 |
| size | 194,895 |
Ergonomic building blocks for Pubky apps: a tiny HTTP/PKARR client, session-bound agent, a storage API, signer helpers, and a pairing-based auth flow for keyless apps.
Rust implementation of Pubky SDK.
# Cargo.toml
[dependencies]
pubky = "0.x" # this crate
# Optional helpers used in examples:
# pubky-testnet = "0.x"
use pubky::prelude::*; // pulls in the common types
# async fn run() -> pubky::Result<()> {
// 1) Create a new random key user bound to a Signer
let signer = PubkySigner::new(Keypair::random())?;
// 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) Session-scoped storage I/O
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 (unauthenticated) read by user-qualified path
let txt = PubkyStorage::new_public()?
.get(format!("{}/pub/my.app/hello.txt", session.public_key()))
.await?
.text().await?;
assert_eq!(txt, "hello");
// 5) Publish / resolve your PKDNS (_pubky) record
signer.pkdns()?.publish_homeserver_if_stale(None).await?;
let resolved = Pkdns::new()?.get_homeserver(&signer.public_key()).await;
println!("current homeserver: {:?}", resolved);
// 6) Keyless third-party app: pairing auth -> session
let caps = Capabilities::builder().write("/pub/pubky.app/").finish();
let (sub, url) = PubkyAuthRequest::new(&caps)?.subscribe();
// show `url` (QR/deeplink); on the signing device call:
// signer.approve_auth_request(&url).await?;
let app_session = sub.wait_for_approval().await?;
# Ok(()) }
High level actors:
PubkySession session-bound identity agent. This is the heart of your Pubky application. Use session.storage() for reads/writes to the user's homeserver storage. You can get a working session in two ways: (1) requesting auth via PubkyAuthRequest (keyless apps) or (2) by signin from a PubkySigner (keychain apps).PubkyAuthRequest auth flow for authenticating keyless apps from a PubkySigner.PubkySigner high-level signer (keypair holder) with signup, signin, publishing, and auth request approval.PubkyStorage simple file-like API: get/put/delete, plus exists(), stats() and list().Pkdns resolve/publish _pubky Pkarr records (read-only via Pkdns::new(), publishing when created from a PubkySigner).Transport:
PubkyHttpClient stateless transport: handles requests to pubky public-key hosts.Use a PubkyStorage to access and write data into Pubky Homeservers.
Use to access public data from any user without need to authenticate.
# use pubky::prelude::*;
# async fn run(user: PublicKey) -> pubky::Result<()> {
let storage = PubkyStorage::new_public()?;
// read someone’s public file
let text = storage.get(format!("{}/pub/my.app/data.txt", user)).await?.text().await?;
// writes are not allowed in public mode!
# Ok(()) }
Use to write data into your own homeserver.
# use pubky::prelude::*;
# async fn run(session: &PubkySession) -> pubky::Result<()> {
let storage = session.storage(); // Equivalent to PubkyStorage::new_from_session(session);
// read from your own homeserver (no need to specify user id)
let text = storage.get("/pub/my.app/data.txt").await?.text().await?;
// writing to your own homeserver
storage.put("/pub/my.app/data.txt", "hi").await?;
# Ok(()) }
| Property | Session mode (PubkyStorage::new_from_session()) |
Public mode (PubkyStorage::new_public()) |
|---|---|---|
| Auth | Yes (session cookie auto-attached for this user’s homeserver) | No |
| Path form | Absolute, session-scoped (e.g. /pub/my.app/file.txt) |
User-qualified (e.g. <user>/pub/my.app/file.txt) |
| Reads | Yes | Yes (public data only) |
| Writes | Yes | No (server will reject with 401/403) |
| Cookie leakage across users | Never | N/A |
| Typical use | Your app acting “as the user” | Fetch someone else’s public content without a session |
Use absolute paths for session-scoped I/O ("/pub/…"), or user-qualified forms when public:
# use pubky::prelude::*;
# fn addr_examples(user_pubky: PublicKey) -> pubky::Result<()> {
let a = PubkyResource::new(Some(user_pubky), "/pub/my.app/file.txt")?;
let b: PubkyResource = "{user_public_key}/pub/my.app/file.txt".into_pubky_resource()?;
# Ok(()) }
By convention, prefer to place your app data in a new public /pub top level folder with domain-like name. Example /pub/mycoolnew.app/
_pubky) Pkarr publishingPublish and retrieve pkarr record.
# use pubky::prelude::*;
# async fn pkdns(signer: &PubkySigner) -> pubky::Result<()> {
// Republish only if stale (recommended in app start)
signer.pkdns()?.publish_homeserver_if_stale(None).await?;
// Force a homeserver record publish (e.g., migration)
let homeserver = PublicKey::try_from("homeserver_pubky").unwrap();
signer.pkdns()?.publish_homeserver_force(Some(&homeserver)).await?;
# Ok(()) }
Request auth url and await approval. Typical usage:
PubkyAuthRequest::new].PubkyAuthRequest::subscribe] to start background polling and obtain the pubkyauth:// URL.AuthSubscription::wait_for_approval] to obtain a session-bound [PubkySession].# use pubky::prelude::*;
# async fn auth() -> pubky::Result<()> {
// Read/Write capabilities for acme.app route
let caps = Capabilities::builder().rw("/pub/acme.app/").finish();
// Easiest: uses the default relay (see “Relay” notes below)
let (sub, url) = PubkyAuthRequest::new(&caps)?.subscribe();
// This is the moment to send `url` to the user (QR or deeplink). On the signer device
// for example Pubky Ring application on mobile. The signer will perform this call:
// signer.approve_auth_request(&url).await?;
# PubkySigner::random()?.approve_auth_request(&url).await?;
let session = sub.wait_for_approval().await?; // background long-polling started by `subscribe`
# Ok(()) }
See this fully working Auth Flow Example
PubkyAuthRequest] defaults to a Synonym-hosted instance. If that relay is down, logins won’t complete.base64url(hash(secret)); the token is end-to-end encrypted with the secret. See PubkyAuthRequest::new_with_relay docs for further info.json enable storage::json helpers (get_json / put_json) and serde on certain types.Spin up an ephemeral testnet (DHT + homeserver + relay) and run your tests fully offline:
# use pubky_testnet::{EphemeralTestnet, pubky::prelude::*};
# async fn test() -> pubky_testnet::pubky::Result<()> {
let testnet = EphemeralTestnet::start().await.unwrap();
let homeserver = testnet.homeserver();
let signer = PubkySigner::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(()) }
Export a compact bearer token and import it later to avoid re-auth:
# use pubky::prelude::*;
# async fn persist(session: &PubkySession) -> pubky::Result<()> {
// Save
let token = session.export_secret(); // "<pubkey>:<cookie_secret>"
// store `token` securely (env, keychain, vault). DO NOT log it.
// Restore
let restored = PubkySession::import_secret(&token).await?;
// Optional sanity check:
restored.revalidate().await?;
# Ok(()) }
Security: the cookie secret is a bearer token. Anyone holding it can act as the user within the granted capabilities. Treat it like a password.
subscribe()/wait_for_approval() (starts polling immediately when you get the URL) to avoid missing approvals. If you manually fetch the URL before polling, you can race the signer and miss the one-shot response.PubkyHttpClient never holds identity; PubkySession does.Check more examples using the Pubky SDK.
Find a wrapper of this crate using wasm_bindgen in pubky-sdk/bindings/js.
License: MIT Relay: https://httprelay.io (open source; run your own for production)