Skip to content

Sender SDK

The @directory/client SDK handles the full resolution flow: DNS discovery, address resolution, Ed25519 verification against every published key, clock-skew-tolerant freshness checks, key-rotation retry, and route matching.

Install

Terminal window
npm install @directory/client

Basic Usage

import { DirectoryClient } from '@directory/client';
const client = new DirectoryClient();
const result = await client.resolve('alice@gmail.com', [
{ value_type: 'USDC', transfer_type: 'ethereum' },
{ value_type: 'USDC', transfer_type: 'base' },
]);
if (result.verification.valid && result.matchedRoutes.length > 0) {
const route = result.matchedRoutes[0];
console.log(`Send ${route.value_type} on ${route.transfer_type} to ${route.destination}`);
}

What the SDK Does

The resolve() call performs these steps automatically:

  1. Normalize the address (IDN punycode, NFC, whitespace, length checks).
  2. Discover — DNS-over-HTTPS lookups for _drs._tcp.<domain> SRV and _drskey.<domain> TXT. All key records are returned; the client tries each during verification.
  3. Optional manifest — fetches /.well-known/directory.json if published; treats 404 as “no manifest”.
  4. ResolvePOST /resolve with { address }.
  5. Verify — JCS-canonicalize, check Ed25519 signature against each published key, check issued_at / expires_at with ±60 s skew. On INVALID_SIGNATURE, the client refetches DNS keys once and retries to absorb key-rotation overlap.
  6. Match — filter the contract’s routes against the sender’s capabilities.

Step-by-Step (Lower Level)

import {
discoverDrs,
fetchCapabilities,
resolveAddress,
verifyContract,
matchRoutes,
} from '@directory/client';
const { baseUrl, publicKeys } = await discoverDrs('gmail.com');
const manifest = await fetchCapabilities(baseUrl); // may be null
const contract = await resolveAddress(baseUrl, 'alice@gmail.com');
const verification = await verifyContract(contract, publicKeys);
if (!verification.valid) throw new Error(verification.reason);
const senderCapabilities = [
{ value_type: 'USDC', transfer_type: 'ethereum' },
];
const routes = matchRoutes(contract.routes, senderCapabilities);

Error Handling

import { DrsDiscoveryError, DrsResolveError } from '@directory/client';
try {
const result = await client.resolve('alice@unknown.com', capabilities);
} catch (err) {
if (err instanceof DrsDiscoveryError) {
// No DRS found for domain — no SRV record.
}
if (err instanceof DrsResolveError) {
// DRS returned an RFC 7807 Problem Details response.
err.status; // 404
err.code; // "ADDRESS_NOT_FOUND"
err.message; // detail || title — parsed from the response body
}
}

Verification Outcomes

verification.valid; // boolean
verification.reason; // "INVALID_SIGNATURE" | "EXPIRED_CONTRACT" | "NOT_YET_VALID" | undefined

A client that treats valid === false as a hard failure is correct. The SDK has already retried once on signature failure.

Runtime Compatibility

  • Node.js 20+ — native Web Crypto API.
  • Cloudflare Workers — native Web Crypto API.
  • Modern browsers — native Web Crypto API.

Zero external dependencies for cryptographic operations.