Quickstart: Build an App
By the end of this guide, your app will be able to take an address like alice@nova.org, resolve it into a signed routing contract, and know exactly where to send value.
Install the SDK
npm install @directory/clientResolve an address
import { DirectoryClient } from '@directory/client';
const client = new DirectoryClient();
const myCapabilities = [ { value_type: 'USDC', transfer_type: 'ethereum' }, { value_type: 'USDC', transfer_type: 'base' }, { value_type: 'ETH', transfer_type: 'ethereum' },];
const result = await client.resolve('alice@nova.org', myCapabilities);
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}`); // -> "Send USDC on ethereum to 0xABC..."}That’s the whole integration. One call. The SDK handles DNS discovery, resolution, Ed25519 signature verification (against every published key), clock-skew-tolerant freshness checks, key-rotation retry, and route filtering.
What happens inside that call
1. DNS lookup: _drs._tcp.nova.org -> locate the DRS.2. DNS lookup: _drskey.nova.org -> fetch all Ed25519 public keys.3. POST /resolve -> signed routing contract with destinations.4. JCS-canonicalize and verify -> against each published key.5. Filter routes against myCapabilities.6. Return matchedRoutes.You don’t need to implement any of this. The SDK does it.
Handle the result
const result = await client.resolve('alice@nova.org', myCapabilities);
result.contract.address; // "alice@nova.org"result.contract.routes; // all routes for this addressresult.contract.signature; // Ed25519 signatureresult.contract.expires_at; // when this contract expires
result.verification.valid; // true if signature checks outresult.verification.reason; // why it failed, if it did
result.matchedRoutes; // routes you can actually use// e.g. [{ value_type: "USDC", transfer_type: "ethereum", destination: "0xABC...", ttl_seconds: 300 }]Handle errors
import { DirectoryClient, DrsDiscoveryError, DrsResolveError } from '@directory/client';
try { const result = await client.resolve('alice@unknown.com', myCapabilities);} catch (err) { if (err instanceof DrsDiscoveryError) { // No DRS found — this domain doesn't support Directory. } if (err instanceof DrsResolveError) { console.log(err.status); // 404 console.log(err.code); // "ADDRESS_NOT_FOUND" }}DrsResolveError fields are parsed from the server’s RFC 7807 Problem Details response.
No matching routes?
If the recipient’s DRS accepts nothing that your app can send, matchedRoutes is empty. Tell the user.
if (result.matchedRoutes.length === 0) { console.log("This recipient doesn't accept any assets you can send.");}Conversion between mismatched pairs is a separate concern. It lives outside the DRS protocol — any settlement service that fronts its own DRS node can handle it, but the sender-side contract is identical.
Try it locally
git clone https://github.com/drs-xyz/drs.gitcd drs && pnpm install && pnpm devimport { resolveAddress } from '@directory/client';
// Low-level call — returns a bare RoutingContract, no DNS lookup.const contract = await resolveAddress( 'http://localhost:8787', 'alice@localhost.directory.dev',);console.log(contract.routes);Or just curl it:
curl -s -X POST http://localhost:8787/resolve \ -H 'Content-Type: application/json' \ -d '{"address":"alice@localhost.directory.dev"}' | jqWhat’s next
- Understand routing contracts: Routing Contracts
- Full SDK API: SDK Reference
- Understand the protocol: How It Works