Pubky SDK
Ergonomic building blocks for Pubky apps: one facade (Pubky) plus focused actors for sessions, storage API, signer helpers, and QR auth flow for keyless apps.
Rust implementation of Pubky SDK.
Install
[dependencies]
pubky = "0.x"
Quick start
use pubky::prelude::*;
# async fn run() -> pubky::Result<()> {
let pubky = Pubky::new()?;
let keypair = Keypair::random();
let signer = pubky.signer(keypair);
let homeserver = PublicKey::try_from("o4dksf...uyy").unwrap();
let session = signer.signup(&homeserver, None).await?;
session.storage().put("/pub/my-cool-app/hello.txt", "hello").await?;
let body = session.storage().get("/pub/my-cool-app/hello.txt").await?.text().await?;
assert_eq!(&body, "hello");
let txt = pubky.public_storage()
.get(format!("pubky{}/pub/my-cool-app/hello.txt", session.info().public_key()))
.await?
.text().await?;
assert_eq!(txt, "hello");
let caps = Capabilities::builder().write("/pub/example.com/").finish();
let flow = pubky.start_auth_flow(&caps, AuthFlowKind::signin())?;
println!("Scan to sign in: {}", flow.authorization_url());
let app_session = flow.await_approval().await?;
signer.pkdns().publish_homeserver_if_stale(None).await?;
let resolved = signer.pkdns().get_homeserver().await;
println!("Your current homeserver: {:?}", resolved);
# Ok(()) }
Mental model
Pubky - facade, 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 : handles requests to pubky public-key hosts.
Examples
Storage API (session & public)
Session (authenticated):
use pubky::{Pubky, Keypair};
# async fn run(keypair: Keypair) -> pubky::Result<()> {
let pubky = Pubky::new()?;
let session = pubky.signer(keypair).signin().await?;
let storage = session.storage();
storage.put("/pub/my-cool-app/data.txt", "hi").await?;
let text = storage.get("/pub/my-cool-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!("pubky{user_id}/pub/example.com/file.bin"))
.await?
.bytes()
.await?;
let entries = public
.list(format!("pubky{user_id}/pub/example.com/"))?
.limit(10)
.send()
.await?;
for entry in entries {
println!("{}", entry.to_pubky_url());
}
# Ok(()) }
See the Public Storage example.
Path rules:
- Session storage uses absolute paths like
"/pub/app/file.txt".
- Public storage uses addressed form
pubky<user>/pub/app/file.txt (preferred) or pubky://<user>/....
Convention: put your app’s public data under a domain-like folder in /pub, e.g. /pub/my-new-app/.
Resolve identifiers into transport URLs
Need to feed a public resource into a raw HTTP client? Use [resolve_pubky] to transform the human-facing identifier into the HTTPS homeserver URL:
# use pubky::resolve_pubky;
# fn main() -> pubky::Result<()> {
let url = resolve_pubky("pubkyoperrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo/pub/pubky.app/posts/0033X02JAN0SG")?;
assert_eq!(
url.as_str(),
"https://_pubky.operrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo/pub/pubky.app/posts/0033X02JAN0SG"
);
# Ok(())
# }
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()?;
let host = pubky.get_homeserver_of(&other).await;
let signer = pubky.signer(Keypair::random());
signer.pkdns().publish_homeserver_if_stale(None).await?;
signer.pkdns().publish_homeserver_force(Some(&new_homeserver_id)).await?;
signer.pkdns().get_homeserver().await?;
# Ok(()) }
Pubky QR auth for third-party and keyless apps
Request an authorization URL and await approval.
Typical usage:
- Start an auth flow with
pubky.start_auth_flow(&caps) (or use the PubkyAuthFlow::builder() to set a custom relay).
- Show
authorization_url() (QR/deeplink) to the signing device (e.g., Pubky Ring — iOS / Android).
- Await
await_approval() to obtain a session-bound PubkySession.
# use pubky::{Pubky, Capabilities, Keypair, AuthFlowKind};
# async fn auth() -> pubky::Result<()> {
let pubky = Pubky::new()?;
let caps = Capabilities::builder().read_write("pub/example.com/").finish();
let flow = pubky.start_auth_flow(&caps, AuthFlowKind::signin())?;
println!("Scan to sign in: {}", flow.authorization_url());
# 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::{Pubky, PubkyAuthFlow, Capabilities, AuthFlowKind};
# async fn custom_relay() -> pubky::Result<()> {
let pubky = Pubky::new()?;
let caps = Capabilities::builder().read("pub/example.com/").finish();
let auth_flow = PubkyAuthFlow::builder(&caps, AuthFlowKind::signin())
.client(pubky.client().clone())
.relay(url::Url::parse("http://localhost:8080/link/")?) .start()?;
# Ok(()) }
Tip: reuse pubky.client() when customising the relay so the flow shares
TLS and pkarr configuration with the rest of your application.
Features
json: enable Storage helpers (.get_json() / .put_json()) and serde on certain types.
[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_app();
let pubky = testnet.sdk()?;
let signer = pubky.signer(Keypair::random());
let session = signer.signup(&homeserver.public_key(), None).await?;
session.storage().put("/pub/my-cool-app/hello.txt", "hi").await?;
let s = session.storage().get("/pub/my-cool-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)