Exosphere

Hex.pmHexDocs

Logo

Exosphere is a collection of AT Protocol clients and utilities.

Documentation

What’s inside

Getting started

Installation

Add exosphere to your dependencies:

def deps do
[
{:exosphere, "~> 0.3"}
]
end

Quickstart: XRPC client

Exosphere.XRPC.Client is a small wrapper around Exosphere.ATProto.XRPC.Client.

# Create an unauthenticated client for a PDS
client = Exosphere.XRPC.Client.new("https://bsky.social")
{:ok, %{"did" => did}} =
Exosphere.XRPC.Client.query(client, "com.atproto.identity.resolveHandle",
handle: "atproto.com"
)

Firehose (subscribeRepos)

Use Exosphere.Firehose.Consumer to connect to a relay’s com.atproto.sync.subscribeRepos WebSocket endpoint, decode frames into structured messages, and dispatch them to your callback.

Running under a supervisor

The consumer requires an :on_event callback with arity 2: (message, state) -> state.

children = [
{Exosphere.Firehose.Consumer,
relay_url: "wss://bsky.network",
cursor: nil,
on_event: &MyApp.Firehose.on_event/2,
name: MyApp.FirehoseConsumer}
]
Supervisor.start_link(children, strategy: :one_for_one)

Handling events

Messages are decoded into maps with a :type key (for example :commit, :identity, :handle). For commit messages, you can extract record data from the embedded CAR blocks via Exosphere.ATProto.Firehose.Message.extract_records/1.

defmodule MyApp.Firehose do
require Logger
alias Exosphere.ATProto.Firehose.Message
def on_event(%{type: :commit} = msg, state) do
# Persist msg.seq somewhere if you want resumable consumption (cursor).
case Message.extract_records(msg) do
{:ok, records} ->
Logger.info("commit seq=#{msg.seq} records=#{length(records)}")
state
{:error, reason} ->
Logger.warning("commit seq=#{msg.seq} extract_records failed: #{inspect(reason)}")
state
end
end
def on_event(msg, state) do
Logger.debug("firehose event: #{inspect(msg.type)}")
state
end
end

Verifying repositories

You don't have to trust a PDS's word for what's in a repository. Exosphere can fetch a full repository archive and prove it against the key the account advertises in its DID document:

{:ok, %{rev: rev, records: records}} =
Exosphere.ATProto.Repo.verify_checkout("https://bsky.network", "did:plc:abc123")

That one call downloads com.atproto.sync.getRepo, reads every record out of the Merkle Search Tree, confirms the record set matches the commit's signed root, resolves the DID document, and verifies the commit signature. If it returns {:ok, _}, the records provably come from the account controlling that DID.

For firehose events, Exosphere.ATProto.Firehose.Message.verify_commit/1 checks a #commit message's embedded blocks against its signed MST root — see the Firehose guide for when that succeeds (incremental CARs only carry new blocks) and how to build on it.

Notes

CI / Releases

This project uses GitHub Actions:

To enable publishing, add a repository secret named HEX_API_KEY (generate one via mix hex.user key generate).