Resolve AT Protocol DIDs, handles, and schemas with intelligent caching for Laravel
1[](https://github.com/socialdept/atp-signals)
2
3<h3 align="center">
4 Resolve AT Protocol identities in your Laravel application.
5</h3>
6
7<p align="center">
8 <br>
9 <a href="https://packagist.org/packages/socialdept/atp-resolver" title="Latest Version on Packagist"><img src="https://img.shields.io/packagist/v/socialdept/atp-resolver.svg?style=flat-square"></a>
10 <a href="https://packagist.org/packages/socialdept/atp-resolver" title="Total Downloads"><img src="https://img.shields.io/packagist/dt/socialdept/atp-resolver.svg?style=flat-square"></a>
11 <a href="https://github.com/socialdept/atp-resolver/actions/workflows/tests.yml" title="GitHub Tests Action Status"><img src="https://img.shields.io/github/actions/workflow/status/socialdept/atp-resolver/tests.yml?branch=main&label=tests&style=flat-square"></a>
12 <a href="LICENSE" title="Software License"><img src="https://img.shields.io/github/license/socialdept/atp-resolver?style=flat-square"></a>
13</p>
14
15---
16
17## What is Resolver?
18
19**Resolver** is a Laravel package that resolves AT Protocol identities. Convert DIDs to handles, find PDS endpoints, and resolve DID documents with automatic caching and fallback support for both `did:plc` and `did:web` methods.
20
21Think of it as a Swiss Army knife for AT Protocol identity resolution.
22
23## Why use Resolver?
24
25- **Simple API** - Resolve DIDs and handles with one method call
26- **Automatic caching** - Smart caching with configurable TTLs
27- **Multiple DID methods** - Support for `did:plc` and `did:web`
28- **PDS discovery** - Find the correct PDS endpoint for any user
29- **Production ready** - Battle-tested with proper error handling
30- **Zero config** - Works out of the box with sensible defaults
31
32## Quick Example
33
34```php
35use SocialDept\AtpResolver\Facades\Resolver;
36
37// Resolve a DID to its document
38$document = Resolver::resolveDid('did:plc:ewvi7nxzyoun6zhxrhs64oiz');
39$handle = $document->getHandle(); // "user.bsky.social"
40$pds = $document->getPdsEndpoint(); // "https://bsky.social"
41
42// Convert a handle to its DID
43$did = Resolver::handleToDid('user.bsky.social');
44// "did:plc:ewvi7nxzyoun6zhxrhs64oiz"
45
46// Resolve any identity (DID or handle) to a document
47$document = Resolver::resolveIdentity('alice.bsky.social');
48
49// Find someone's PDS endpoint
50$pds = Resolver::resolvePds('alice.bsky.social');
51// "https://bsky.social"
52```
53
54## Installation
55
56```bash
57composer require socialdept/atp-resolver
58```
59
60Resolver will auto-register with Laravel. Optionally publish the config:
61
62```bash
63php artisan vendor:publish --tag=resolver-config
64```
65
66## Basic Usage
67
68### Resolving DIDs
69
70Resolver supports both `did:plc` and `did:web` methods:
71
72```php
73use SocialDept\AtpResolver\Facades\Resolver;
74
75// PLC directory resolution
76$document = Resolver::resolveDid('did:plc:ewvi7nxzyoun6zhxrhs64oiz');
77
78// Web DID resolution
79$document = Resolver::resolveDid('did:web:example.com');
80
81// Access document data
82$handle = $document->getHandle();
83$pdsEndpoint = $document->getPdsEndpoint();
84$services = $document->service;
85```
86
87### Resolving Handles
88
89Convert human-readable handles to DIDs or DID documents:
90
91```php
92// Convert handle to DID string
93$did = Resolver::handleToDid('alice.bsky.social');
94// "did:plc:ewvi7nxzyoun6zhxrhs64oiz"
95
96// Resolve handle to full DID document
97$document = Resolver::resolveHandle('alice.bsky.social');
98$handle = $document->getHandle();
99$pds = $document->getPdsEndpoint();
100```
101
102### Resolving Identities
103
104Automatically detect and resolve either DIDs or handles:
105
106```php
107// Works with DIDs
108$document = Resolver::resolveIdentity('did:plc:ewvi7nxzyoun6zhxrhs64oiz');
109
110// Works with handles
111$document = Resolver::resolveIdentity('alice.bsky.social');
112
113// Perfect for user input where type is unknown
114$actor = $request->input('actor'); // Could be DID or handle
115$document = Resolver::resolveIdentity($actor);
116```
117
118### Finding PDS Endpoints
119
120Automatically discover a user's Personal Data Server:
121
122```php
123// From a DID
124$pds = Resolver::resolvePds('did:plc:ewvi7nxzyoun6zhxrhs64oiz');
125
126// From a handle
127$pds = Resolver::resolvePds('alice.bsky.social');
128
129// Returns: "https://bsky.social" or user's custom PDS
130```
131
132This is particularly useful when you need to make API calls to a user's PDS instead of hardcoding Bluesky's public instance.
133
134### Cache Management
135
136Beacon automatically caches resolutions. Clear the cache when needed:
137
138```php
139// Clear specific DID cache
140Resolver::clearDidCache('did:plc:abc123');
141
142// Clear specific handle cache
143Resolver::clearHandleCache('alice.bsky.social');
144
145// Clear specific PDS cache
146Resolver::clearPdsCache('alice.bsky.social');
147
148// Clear all cached data
149Resolver::clearCache();
150```
151
152### Disable Caching
153
154Pass `false` as the second parameter to bypass cache:
155
156```php
157$document = Resolver::resolveDid('did:plc:abc123', useCache: false);
158$did = Resolver::handleToDid('alice.bsky.social', useCache: false);
159$document = Resolver::resolveIdentity('alice.bsky.social', useCache: false);
160$pds = Resolver::resolvePds('alice.bsky.social', useCache: false);
161```
162
163### Identity Validation
164
165Beacon includes static helper methods to validate DIDs and handles:
166
167```php
168use SocialDept\AtpResolver\Support\Identity;
169
170// Validate handles
171Identity::isHandle('alice.bsky.social'); // true
172Identity::isHandle('invalid'); // false
173
174// Validate DIDs
175Identity::isDid('did:plc:ewvi7nxzyoun6zhxrhs64oiz'); // true
176Identity::isDid('did:web:example.com'); // true
177Identity::isDid('invalid'); // false
178
179// Extract DID method
180Identity::extractDidMethod('did:plc:abc123'); // "plc"
181Identity::extractDidMethod('did:web:test'); // "web"
182
183// Check specific DID types
184Identity::isPlcDid('did:plc:abc123'); // true
185Identity::isWebDid('did:web:test'); // true
186```
187
188These helpers are useful for validating user input before making resolution calls.
189
190## Configuration
191
192Beacon works great with zero configuration, but you can customize behavior in `config/resolver.php`:
193
194```php
195return [
196 // PLC directory for did:plc resolution
197 'plc_directory' => env('RESOLVER_PLC_DIRECTORY', 'https://plc.directory'),
198
199 // Default PDS endpoint for handle resolution
200 'pds_endpoint' => env('RESOLVER_PDS_ENDPOINT', 'https://bsky.social'),
201
202 // HTTP request timeout
203 'timeout' => env('RESOLVER_TIMEOUT', 10),
204
205 // Cache configuration
206 'cache' => [
207 'enabled' => env('RESOLVER_CACHE_ENABLED', true),
208
209 // Cache TTL for DID documents (1 hour)
210 'did_ttl' => env('RESOLVER_CACHE_DID_TTL', 3600),
211
212 // Cache TTL for handle resolutions (1 hour)
213 'handle_ttl' => env('RESOLVER_CACHE_HANDLE_TTL', 3600),
214
215 // Cache TTL for PDS endpoints (1 hour)
216 'pds_ttl' => env('RESOLVER_CACHE_PDS_TTL', 3600),
217 ],
218];
219```
220
221## API Reference
222
223### Available Methods
224
225```php
226// DID Resolution
227Resolver::resolveDid(string $did, bool $useCache = true): DidDocument
228
229// Handle Resolution
230Resolver::handleToDid(string $handle, bool $useCache = true): string
231Resolver::resolveHandle(string $handle, bool $useCache = true): DidDocument
232
233// Identity Resolution
234Resolver::resolveIdentity(string $actor, bool $useCache = true): DidDocument
235
236// PDS Resolution
237Resolver::resolvePds(string $actor, bool $useCache = true): ?string
238
239// Cache Management
240Resolver::clearDidCache(string $did): void
241Resolver::clearHandleCache(string $handle): void
242Resolver::clearPdsCache(string $actor): void
243Resolver::clearCache(): void
244
245// Identity Validation (static helpers)
246Identity::isHandle(?string $handle): bool
247Identity::isDid(?string $did): bool
248Identity::extractDidMethod(string $did): ?string
249Identity::isPlcDid(string $did): bool
250Identity::isWebDid(string $did): bool
251```
252
253### DidDocument Object
254
255```php
256$document->id; // string - The DID
257$document->alsoKnownAs; // array - Alternative identifiers
258$document->verificationMethod; // array - Verification methods
259$document->service; // array - Service endpoints
260$document->raw; // array - Raw DID document
261
262// Helper methods
263$document->getHandle(); // ?string - Extract handle from alsoKnownAs
264$document->getPdsEndpoint(); // ?string - Extract PDS service endpoint
265$document->toArray(); // array - Convert to array
266```
267
268## Error Handling
269
270Beacon throws descriptive exceptions when resolution fails:
271
272```php
273use SocialDept\AtpResolver\Exceptions\DidResolutionException;
274use SocialDept\AtpResolver\Exceptions\HandleResolutionException;
275
276try {
277 $document = Resolver::resolveDid('did:invalid:format');
278} catch (DidResolutionException $e) {
279 // Handle DID resolution errors
280 logger()->error('DID resolution failed', [
281 'message' => $e->getMessage(),
282 ]);
283}
284
285try {
286 $did = Resolver::handleToDid('invalid-handle');
287} catch (HandleResolutionException $e) {
288 // Handle handle resolution errors
289}
290```
291
292## Use Cases
293
294### Building an AppView
295
296```php
297// Resolve user identity from DID
298$document = Resolver::resolveDid($event->did);
299$handle = $document->getHandle();
300
301// Make authenticated requests to their PDS
302$pds = Resolver::resolvePds($event->did);
303$client = new AtProtoClient($pds);
304```
305
306### Custom Feed Generators
307
308```php
309// Resolve multiple handles efficiently (caching kicks in)
310$dids = collect(['alice.bsky.social', 'bob.bsky.social'])
311 ->map(fn($handle) => Resolver::handleToDid($handle))
312 ->all();
313```
314
315### Profile Resolution
316
317```php
318// Get complete identity information
319$document = Resolver::resolveIdentity($username);
320
321$profile = [
322 'did' => $document->id,
323 'handle' => $document->getHandle(),
324 'pds' => $document->getPdsEndpoint(),
325];
326```
327
328### Input Validation
329
330```php
331use SocialDept\AtpResolver\Support\Identity;
332use SocialDept\AtpResolver\Facades\Resolver;
333
334// Validate user input before resolving
335$actor = request()->input('actor');
336
337if (Identity::isHandle($actor) || Identity::isDid($actor)) {
338 $document = Resolver::resolveIdentity($actor);
339} else {
340 abort(422, 'Invalid handle or DID');
341}
342
343// Or convert handle to DID
344if (Identity::isHandle($actor)) {
345 $did = Resolver::handleToDid($actor);
346}
347```
348
349## Requirements
350
351- PHP 8.2+
352- Laravel 11+
353- `ext-gmp` extension
354
355## Resources
356
357- [AT Protocol Documentation](https://atproto.com/)
358- [DID:PLC Specification](https://github.com/did-method-plc/did-method-plc)
359- [DID:Web Specification](https://w3c-ccg.github.io/did-method-web/)
360- [PLC Directory](https://plc.directory/)
361
362## Support & Contributing
363
364Found a bug or have a feature request? [Open an issue](https://github.com/socialdept/atp-resolver/issues).
365
366Want to contribute? We'd love your help! Check out the [contribution guidelines](CONTRIBUTING.md).
367
368## Credits
369
370- [Miguel Batres](https://batres.co) - founder & lead maintainer
371- [All contributors](https://github.com/socialdept/atp-resolver/graphs/contributors)
372
373## License
374
375Beacon is open-source software licensed under the [MIT license](LICENSE).
376
377---
378
379**Built for the Federation** • By Social Dept.