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();
// What can your app send?const myCapabilities = [ { value_type: 'USDC', transfer_type: 'ethereum' }, { value_type: 'USDC', transfer_type: 'base' }, { value_type: 'ETH', transfer_type: 'ethereum' },];
// Resolve the addressconst result = await client.resolve('alice@nova.org', myCapabilities);
// Check the resultif (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, capability matching, address resolution, Ed25519 signature verification, and route filtering.
What happens inside that call
1. DNS lookup: _drs._tcp.nova.org → find the DRS2. DNS lookup: _drskey.nova.org → get the Ed25519 public key3. GET /.well-known/directory.json → what does nova.org accept?4. POST /resolve → signed routing contract with destinations5. Verify Ed25519 signature against DNS key6. Match contract routes against your capabilities7. Return matched routesYou don’t need to implement any of this. The SDK does it.
Handle the result
The result object contains everything:
const result = await client.resolve('alice@nova.org', myCapabilities);
// The signed routing contractresult.contract.address; // "alice@nova.org"result.contract.routes; // all routes for this addressresult.contract.signature; // Ed25519 signatureresult.contract.expires_at; // when this contract expires
// Signature verificationresult.verification.valid; // true if signature checks outresult.verification.reason; // why it failed, if it did
// Routes that match YOUR capabilitiesresult.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 // The domain has no _drs._tcp SRV record } if (err instanceof DrsResolveError) { console.log(err.status); // 404 console.log(err.code); // "ADDRESS_NOT_FOUND" }}No matching routes?
If the recipient’s DRS doesn’t accept what your app can send, matchedRoutes will be empty. You have two options:
Option A: Tell the user there’s no route.
if (result.matchedRoutes.length === 0) { console.log("This recipient doesn't accept any assets you can send.");}Option B: Route through an exchange node that can convert.
// Your app has USDC. Recipient wants USD via ACH.// Route through an exchange that bridges the gap.const result = await client.resolve('alice@nova.org', myCapabilities, { exchangeDomain: 'bridge.money', pathwayTo: { value_type: 'USD', transfer_type: 'ach' },});// → exchange returns a crypto deposit address// → you send USDC there// → exchange converts and delivers USD via ACH to aliceTry it locally
Don’t have a live DRS to test against? Run one locally:
git clone https://github.com/drs-xyz/drs.gitcd drs && pnpm install && pnpm devThen resolve against it:
// Point the client at your local dev serverconst result = await resolveAddress('http://localhost:8787', 'alice@localhost.directory.dev');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
- Verify asset identity via sponsors: Sponsor References
- Understand routing contracts: Routing Contracts
- Full SDK API: SDK Reference
- Understand the protocol: How It Works