Resolve AT Protocol DIDs, handles, and schemas with intelligent caching for Laravel

Resolver Header

Resolve AT Protocol identities in your Laravel application.



What is Resolver?#

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.

Think of it as a Swiss Army knife for AT Protocol identity resolution.

Why use Resolver?#

  • Simple API - Resolve DIDs and handles with one method call
  • Automatic caching - Smart caching with configurable TTLs
  • Multiple DID methods - Support for did:plc and did:web
  • PDS discovery - Find the correct PDS endpoint for any user
  • Production ready - Battle-tested with proper error handling
  • Zero config - Works out of the box with sensible defaults

Quick Example#

use SocialDept\AtpResolver\Facades\Resolver;

// Resolve a DID to its document
$document = Resolver::resolveDid('did:plc:ewvi7nxzyoun6zhxrhs64oiz');
$handle = $document->getHandle(); // "user.bsky.social"
$pds = $document->getPdsEndpoint(); // "https://bsky.social"

// Convert a handle to its DID
$did = Resolver::handleToDid('user.bsky.social');
// "did:plc:ewvi7nxzyoun6zhxrhs64oiz"

// Resolve any identity (DID or handle) to a document
$document = Resolver::resolveIdentity('alice.bsky.social');

// Find someone's PDS endpoint
$pds = Resolver::resolvePds('alice.bsky.social');
// "https://bsky.social"

Installation#

composer require socialdept/atp-resolver

Resolver will auto-register with Laravel. Optionally publish the config:

php artisan vendor:publish --tag=resolver-config

Basic Usage#

Resolving DIDs#

Resolver supports both did:plc and did:web methods:

use SocialDept\AtpResolver\Facades\Resolver;

// PLC directory resolution
$document = Resolver::resolveDid('did:plc:ewvi7nxzyoun6zhxrhs64oiz');

// Web DID resolution
$document = Resolver::resolveDid('did:web:example.com');

// Access document data
$handle = $document->getHandle();
$pdsEndpoint = $document->getPdsEndpoint();
$services = $document->service;

Resolving Handles#

Convert human-readable handles to DIDs or DID documents:

// Convert handle to DID string
$did = Resolver::handleToDid('alice.bsky.social');
// "did:plc:ewvi7nxzyoun6zhxrhs64oiz"

// Resolve handle to full DID document
$document = Resolver::resolveHandle('alice.bsky.social');
$handle = $document->getHandle();
$pds = $document->getPdsEndpoint();

Resolving Identities#

Automatically detect and resolve either DIDs or handles:

// Works with DIDs
$document = Resolver::resolveIdentity('did:plc:ewvi7nxzyoun6zhxrhs64oiz');

// Works with handles
$document = Resolver::resolveIdentity('alice.bsky.social');

// Perfect for user input where type is unknown
$actor = $request->input('actor'); // Could be DID or handle
$document = Resolver::resolveIdentity($actor);

Finding PDS Endpoints#

Automatically discover a user's Personal Data Server:

// From a DID
$pds = Resolver::resolvePds('did:plc:ewvi7nxzyoun6zhxrhs64oiz');

// From a handle
$pds = Resolver::resolvePds('alice.bsky.social');

// Returns: "https://bsky.social" or user's custom PDS

This is particularly useful when you need to make API calls to a user's PDS instead of hardcoding Bluesky's public instance.

Cache Management#

Beacon automatically caches resolutions. Clear the cache when needed:

// Clear specific DID cache
Resolver::clearDidCache('did:plc:abc123');

// Clear specific handle cache
Resolver::clearHandleCache('alice.bsky.social');

// Clear specific PDS cache
Resolver::clearPdsCache('alice.bsky.social');

// Clear all cached data
Resolver::clearCache();

Disable Caching#

Pass false as the second parameter to bypass cache:

$document = Resolver::resolveDid('did:plc:abc123', useCache: false);
$did = Resolver::handleToDid('alice.bsky.social', useCache: false);
$document = Resolver::resolveIdentity('alice.bsky.social', useCache: false);
$pds = Resolver::resolvePds('alice.bsky.social', useCache: false);

Identity Validation#

Beacon includes static helper methods to validate DIDs and handles:

use SocialDept\AtpResolver\Support\Identity;

// Validate handles
Identity::isHandle('alice.bsky.social'); // true
Identity::isHandle('invalid');           // false

// Validate DIDs
Identity::isDid('did:plc:ewvi7nxzyoun6zhxrhs64oiz'); // true
Identity::isDid('did:web:example.com');              // true
Identity::isDid('invalid');                          // false

// Extract DID method
Identity::extractDidMethod('did:plc:abc123'); // "plc"
Identity::extractDidMethod('did:web:test');   // "web"

// Check specific DID types
Identity::isPlcDid('did:plc:abc123'); // true
Identity::isWebDid('did:web:test');   // true

These helpers are useful for validating user input before making resolution calls.

Configuration#

Beacon works great with zero configuration, but you can customize behavior in config/resolver.php:

return [
    // PLC directory for did:plc resolution
    'plc_directory' => env('RESOLVER_PLC_DIRECTORY', 'https://plc.directory'),

    // Default PDS endpoint for handle resolution
    'pds_endpoint' => env('RESOLVER_PDS_ENDPOINT', 'https://bsky.social'),

    // HTTP request timeout
    'timeout' => env('RESOLVER_TIMEOUT', 10),

    // Cache configuration
    'cache' => [
        'enabled' => env('RESOLVER_CACHE_ENABLED', true),

        // Cache TTL for DID documents (1 hour)
        'did_ttl' => env('RESOLVER_CACHE_DID_TTL', 3600),

        // Cache TTL for handle resolutions (1 hour)
        'handle_ttl' => env('RESOLVER_CACHE_HANDLE_TTL', 3600),

        // Cache TTL for PDS endpoints (1 hour)
        'pds_ttl' => env('RESOLVER_CACHE_PDS_TTL', 3600),
    ],
];

API Reference#

Available Methods#

// DID Resolution
Resolver::resolveDid(string $did, bool $useCache = true): DidDocument

// Handle Resolution
Resolver::handleToDid(string $handle, bool $useCache = true): string
Resolver::resolveHandle(string $handle, bool $useCache = true): DidDocument

// Identity Resolution
Resolver::resolveIdentity(string $actor, bool $useCache = true): DidDocument

// PDS Resolution
Resolver::resolvePds(string $actor, bool $useCache = true): ?string

// Cache Management
Resolver::clearDidCache(string $did): void
Resolver::clearHandleCache(string $handle): void
Resolver::clearPdsCache(string $actor): void
Resolver::clearCache(): void

// Identity Validation (static helpers)
Identity::isHandle(?string $handle): bool
Identity::isDid(?string $did): bool
Identity::extractDidMethod(string $did): ?string
Identity::isPlcDid(string $did): bool
Identity::isWebDid(string $did): bool

DidDocument Object#

$document->id;                    // string - The DID
$document->alsoKnownAs;           // array - Alternative identifiers
$document->verificationMethod;    // array - Verification methods
$document->service;               // array - Service endpoints
$document->raw;                   // array - Raw DID document

// Helper methods
$document->getHandle();           // ?string - Extract handle from alsoKnownAs
$document->getPdsEndpoint();      // ?string - Extract PDS service endpoint
$document->toArray();             // array - Convert to array

Error Handling#

Beacon throws descriptive exceptions when resolution fails:

use SocialDept\AtpResolver\Exceptions\DidResolutionException;
use SocialDept\AtpResolver\Exceptions\HandleResolutionException;

try {
    $document = Resolver::resolveDid('did:invalid:format');
} catch (DidResolutionException $e) {
    // Handle DID resolution errors
    logger()->error('DID resolution failed', [
        'message' => $e->getMessage(),
    ]);
}

try {
    $did = Resolver::handleToDid('invalid-handle');
} catch (HandleResolutionException $e) {
    // Handle handle resolution errors
}

Use Cases#

Building an AppView#

// Resolve user identity from DID
$document = Resolver::resolveDid($event->did);
$handle = $document->getHandle();

// Make authenticated requests to their PDS
$pds = Resolver::resolvePds($event->did);
$client = new AtProtoClient($pds);

Custom Feed Generators#

// Resolve multiple handles efficiently (caching kicks in)
$dids = collect(['alice.bsky.social', 'bob.bsky.social'])
    ->map(fn($handle) => Resolver::handleToDid($handle))
    ->all();

Profile Resolution#

// Get complete identity information
$document = Resolver::resolveIdentity($username);

$profile = [
    'did' => $document->id,
    'handle' => $document->getHandle(),
    'pds' => $document->getPdsEndpoint(),
];

Input Validation#

use SocialDept\AtpResolver\Support\Identity;
use SocialDept\AtpResolver\Facades\Resolver;

// Validate user input before resolving
$actor = request()->input('actor');

if (Identity::isHandle($actor) || Identity::isDid($actor)) {
    $document = Resolver::resolveIdentity($actor);
} else {
    abort(422, 'Invalid handle or DID');
}

// Or convert handle to DID
if (Identity::isHandle($actor)) {
    $did = Resolver::handleToDid($actor);
}

Requirements#

  • PHP 8.2+
  • Laravel 11+
  • ext-gmp extension

Resources#

Support & Contributing#

Found a bug or have a feature request? Open an issue.

Want to contribute? We'd love your help! Check out the contribution guidelines.

Credits#

License#

Beacon is open-source software licensed under the MIT license.


Built for the Federation • By Social Dept.