AT Protocol OCaml Libraries#
A comprehensive implementation of the AT Protocol (Authenticated Transfer Protocol) in OCaml. This library suite enables developers to build decentralized social networking applications using OCaml's strong type system and functional programming paradigm.
Features#
- Complete AT Protocol support - Syntax validation, cryptography, repositories, identity resolution, and more
- Runtime-agnostic I/O - Uses OCaml 5.4 algebraic effects for pluggable async runtimes (eio, lwt, etc.)
- Fully tested - Passes all 272 tests from the official atproto-interop-tests
- Modular design - 11 independent packages, use only what you need
- Pure OCaml - No external processes or services required for core functionality
Installation#
From opam (when published)#
opam install atproto
From source#
git clone https://github.com/gdiazlo/atproto.git
cd atproto
opam install . --deps-only
dune build
Package Overview#
The library is organized into layered packages following the AT Protocol architecture:
Foundation Layer#
| Package | Description |
|---|---|
atproto-multibase |
Base encoding (base32-sortable, base58btc) |
atproto-syntax |
Identifier parsing: handles, DIDs, NSIDs, TIDs, AT-URIs |
atproto-crypto |
P-256/K-256 elliptic curves, did:key, JWT signing |
Data Layer#
| Package | Description |
|---|---|
atproto-ipld |
DAG-CBOR encoding, CIDs, CAR files, blobs |
atproto-mst |
Merkle Search Tree for content-addressed storage |
atproto-repo |
Repository operations, commits, record management |
Identity Layer#
| Package | Description |
|---|---|
atproto-identity |
DID resolution (did:plc, did:web), handle resolution |
Network Layer#
| Package | Description |
|---|---|
atproto-effects |
I/O effect types for runtime abstraction |
atproto-xrpc |
XRPC HTTP API client and server |
atproto-sync |
Firehose event streams, repository sync |
Application Layer#
| Package | Description |
|---|---|
atproto-lexicon |
Lexicon schema parsing and validation |
atproto-api |
High-level client API, rich text facets |
Quick Start#
Parsing AT Protocol Identifiers#
open Atproto_syntax
(* Parse a handle *)
let handle = Handle.of_string "alice.bsky.social" |> Result.get_ok
let () = assert (Handle.to_string handle = "alice.bsky.social")
(* Parse a DID *)
let did = Did.of_string "did:plc:z72i7hdynmk6r22z27h6tvur" |> Result.get_ok
let () = assert (Did.method_ did = "plc")
(* Parse an AT-URI *)
let uri = At_uri.of_string "at://did:plc:xyz/app.bsky.feed.post/abc123" |> Result.get_ok
let () = assert (At_uri.collection uri = Some "app.bsky.feed.post")
(* Generate a TID (timestamp-based identifier) *)
let tid = Tid.make ()
let () = Printf.printf "New TID: %s\n" (Tid.to_string tid)
Working with Cryptography#
open Atproto_crypto
(* Generate a P-256 key pair *)
let keypair = P256.generate ()
(* Sign data *)
let data = Bytes.of_string "Hello, AT Protocol!"
let signature = P256.sign keypair.private_key data
(* Verify signature *)
let valid = P256.verify keypair.public_key data signature
let () = assert valid
(* Encode public key as did:key *)
let did_key = Did_key.encode_p256 keypair.public_key
let () = Printf.printf "DID: %s\n" did_key
DAG-CBOR and CIDs#
open Atproto_ipld
(* Create a record *)
let record = Dag_cbor.Map [
("$type", Dag_cbor.String "app.bsky.feed.post");
("text", Dag_cbor.String "Hello from OCaml!");
("createdAt", Dag_cbor.String "2024-01-01T00:00:00.000Z");
]
(* Encode to DAG-CBOR *)
let bytes = Dag_cbor.encode record
(* Compute CID *)
let cid = Cid.of_dag_cbor bytes
let () = Printf.printf "CID: %s\n" (Cid.to_string cid)
Firehose Event Stream#
open Atproto_sync
open Atproto_effects
(* Define an effect handler for real I/O *)
let run_with_eio f =
(* Implementation depends on your async runtime *)
...
(* Subscribe to the firehose *)
let () = run_with_eio (fun () ->
let config = Firehose.{
endpoint = "wss://bsky.network";
cursor = None;
} in
Firehose.subscribe config (fun event ->
match event with
| Firehose.Commit { repo; ops; _ } ->
Printf.printf "Commit from %s with %d ops\n" repo (List.length ops)
| Firehose.Handle { did; handle } ->
Printf.printf "Handle update: %s -> %s\n" did handle
| _ -> ()
)
)
Identity Resolution#
open Atproto_identity
open Atproto_effects
(* Resolve a DID to get the DID document *)
let () = run_with_effects (fun () ->
match Did_resolver.resolve "did:plc:z72i7hdynmk6r22z27h6tvur" with
| Ok doc ->
Printf.printf "Handle: %s\n" (Option.value ~default:"none" doc.also_known_as)
| Error e ->
Printf.printf "Resolution failed: %s\n" e
)
(* Resolve a handle to get the DID *)
let () = run_with_effects (fun () ->
match Handle_resolver.resolve "bsky.app" with
| Ok did -> Printf.printf "DID: %s\n" did
| Error e -> Printf.printf "Resolution failed: %s\n" e
)
Effects System#
The libraries use OCaml 5.4 algebraic effects for I/O operations, making them runtime-agnostic. You provide effect handlers for:
Http_request/Http_get- HTTP requestsDns_txt/Dns_a- DNS lookupsWs_connect/Ws_recv/Ws_close- WebSocket operationsNow/Sleep- Time operationsRandom_bytes- Cryptographic randomness
Example handler with eio:
open Atproto_effects.Effects
let run_with_eio ~env f =
let open Effect.Deep in
try_with f () {
effc = fun (type a) (eff : a Effect.t) ->
match eff with
| Http_get { url } -> Some (fun (k : (a, _) continuation) ->
let response = Eio_client.get ~env url in
continue k (Ok response))
| Now -> Some (fun k ->
continue k (Ptime_clock.now ()))
| _ -> None
}
Running Tests#
# Run all tests
dune runtest
# Run tests for a specific package
dune runtest test/syntax
# Run with verbose output
dune runtest --force --verbose
Examples#
See the examples/ directory for complete examples:
- firehose_demo - Connect to the Bluesky firehose and print events
dune exec examples/firehose_demo/firehose_demo.exe
Requirements#
- OCaml >= 5.1 (5.4 recommended for full effects support)
- dune >= 3.20
Dependencies#
mirage-crypto-ec- Elliptic curve cryptographydigestif- SHA-256 hashingzarith- Big integers for low-S normalizationcbor- CBOR encoding (wrapped for DAG-CBOR)yojson- JSON parsinguri- URI handlingptime- Time handling
Architecture#
+------------------+
| atproto-api | High-level client
+--------+---------+
|
+--------------------+--------------------+
| | |
+-------v-------+ +-------v-------+ +-------v-------+
| atproto-xrpc | |atproto-identity| | atproto-sync |
+-------+-------+ +-------+-------+ +-------+-------+
| | |
+--------------------+--------------------+
|
+--------v---------+
| atproto-effects | I/O abstraction
+------------------+
|
+--------------------+--------------------+
| | |
+-------v-------+ +-------v-------+ +-------v-------+
| atproto-repo | |atproto-lexicon| | |
+-------+-------+ +---------------+ | |
| | |
+-------v-------+ | |
| atproto-mst | | |
+-------+-------+ | |
| | |
+-------v-------+ | |
| atproto-ipld | | |
+-------+-------+ | |
| +---------------+ | |
+----------->| atproto-crypto|<---+ |
| +-------+-------+ |
| | |
+-------v-------+ +-------v-------+ |
|atproto-syntax |<---+atproto-multibase|<-----------------+
+---------------+ +---------------+
License#
ISC License - see LICENSE file.
Contributing#
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Resources#
- AT Protocol Specification
- Bluesky Documentation
- AT Protocol Interop Tests
- Reference Implementation (TypeScript)
Status#
This implementation is feature-complete for core AT Protocol functionality. It passes all official interoperability tests. However, it has not yet been extensively tested against production AT Protocol infrastructure.