Get up and running with Pkarr in 5 minutes.
[dependencies]
pkarr = "7"
tokio = { version = "1", features = ["full"] }
use pkarr::Keypair;
let keypair = Keypair::random();
// Get the public key as a z-base32 string (this is your "domain name")
let public_key_string = keypair.public_key().to_string();
println!("Public key: {}", public_key_string);
Use SignedPacket::builder() to create DNS records and sign them with your keypair.
use pkarr::{Keypair, SignedPacket};
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
// A record (IPv4 address)
.a(
"www".try_into().unwrap(),
"93.184.216.34".parse().unwrap(),
3600,
)
// TXT record
.txt(
"_foo".try_into().unwrap(),
"bar".try_into().unwrap(),
30,
)
// Sign with your keypair
.sign(&keypair)
.unwrap();
The builder supports these common record types:
.a(name, ipv4, ttl) - A record (IPv4).aaaa(name, ipv6, ttl) - AAAA record (IPv6).address(name, ip, ttl) - Auto-selects A or AAAA based on IP type.txt(name, text, ttl) - TXT record.cname(name, target, ttl) - CNAME recordUse . as the name to create records at the apex (the public key itself).
use pkarr::{Client, Keypair, SignedPacket};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder().build()?;
let keypair = Keypair::random();
let signed_packet = SignedPacket::builder()
.txt("_foo".try_into().unwrap(), "bar".try_into().unwrap(), 30)
.sign(&keypair)?;
println!("Publishing {} ...", keypair.public_key());
let stored_on = client.publish(&signed_packet).await?;
println!("Published successfully; stored on at least {stored_on} DHT nodes");
Ok(())
}
Publishing sends your signed packet to the Mainline DHT and configured
relays. The returned stored_on value means the packet was stored on at
least that many DHT nodes. When several publishing backends are configured,
the client uses the maximum count reported by any successful backend, not the
sum, because different backends may store the packet on the same DHT nodes.
use pkarr::{Client, PublicKey, ResolvePolicy};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder().build()?;
// Parse public key from z-base32 string
let public_key: PublicKey = "yqrx81zchh6aotjj85s96gdqbmsoprxr3ks6ks6y8eccpj8b7oiy"
.try_into()
.expect("Invalid public key");
match client.resolve(&public_key, ResolvePolicy::CacheFirst).await {
Ok(signed_packet) => {
println!("Resolved packet:");
println!("{}", signed_packet);
// Iterate over specific records
for record in signed_packet.resource_records("_foo") {
println!("Record: {:?}", record.rdata);
}
}
Err(pkarr::errors::ResolveError::NotFound) => {
println!("No packet found for {public_key}");
}
Err(pkarr::errors::ResolveError::InvalidSignedPacket { seq }) => {
eprintln!("The network contains an invalid signed packet at sequence {seq}");
}
Err(error) => {
eprintln!("Resolve failed: {error}");
}
}
Ok(())
}
Use resolve(&public_key, ResolvePolicy::NetworkOnly) when you need the latest
network state, for example before publishing updates. A newer mutable item that
is not a valid PKARR packet is returned as ResolveError::InvalidSignedPacket,
not ResolveError::NotFound.
ResolvePolicy::CacheFirst only returns non-expired packets. Use
ResolvePolicy::CacheOnly when expired packets are acceptable. CacheOnly
checks the local cache first and may then query configured relay caches, but it
never queries DHT nodes.
A copy-paste example that generates a keypair, publishes a record, resolves it, and prints the result.
use pkarr::{Client, Keypair, ResolvePolicy, SignedPacket};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Disable caching so resolving reads from the configured networks
let client = Client::builder().cache_size(0).build()?;
let keypair = Keypair::random();
println!("Generated keypair with public key: {}", keypair.public_key());
// 2. Create a signed packet with an A record
let signed_packet = SignedPacket::builder()
.a(
"www".try_into().unwrap(),
"93.184.216.34".parse().unwrap(),
3600,
)
.txt(
"_hello".try_into().unwrap(),
"world".try_into().unwrap(),
30,
)
.sign(&keypair)?;
// 3. Publish to DHT and relays
println!("Publishing...");
let stored_on = client.publish(&signed_packet).await?;
println!("Published successfully; stored on at least {stored_on} DHT nodes");
// 4. Resolve it from the configured networks, bypassing local caches
println!("Resolving...");
match client
.resolve(&keypair.public_key(), ResolvePolicy::NetworkOnly)
.await
{
Ok(resolved) => {
// 5. Print results
println!("\nResolved packet:\n{}", resolved);
}
Err(pkarr::errors::ResolveError::NotFound) => {
println!("No packet found on the configured networks");
}
Err(pkarr::errors::ResolveError::InvalidSignedPacket { seq }) => {
eprintln!("The network contains an invalid signed packet at sequence {seq}");
}
Err(error) => {
eprintln!("Resolve failed: {error}");
}
}
Ok(())
}
Expected output:
Generated keypair with public key: <52-character z-base32 string>
Publishing...
Published successfully; stored on at least <count> DHT nodes
Resolving...
Resolved packet:
SignedPacket (<public_key>):
last_seen: 0 seconds ago
timestamp: <timestamp> <HTTP date>,
signature: <signature>
records:
www.<public_key> IN 3600 A 93.184.216.34
_hello.<public_key> IN 30 TXT "world"