Skip to content

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

Terminal window
npm install @directory/client

Resolve 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 address
const result = await client.resolve('alice@nova.org', myCapabilities);
// Check the result
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, capability matching, address resolution, Ed25519 signature verification, and route filtering.

What happens inside that call

1. DNS lookup: _drs._tcp.nova.org → find the DRS
2. DNS lookup: _drskey.nova.org → get the Ed25519 public key
3. GET /.well-known/directory.json → what does nova.org accept?
4. POST /resolve → signed routing contract with destinations
5. Verify Ed25519 signature against DNS key
6. Match contract routes against your capabilities
7. Return matched routes

You 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 contract
result.contract.address; // "alice@nova.org"
result.contract.routes; // all routes for this address
result.contract.signature; // Ed25519 signature
result.contract.expires_at; // when this contract expires
// Signature verification
result.verification.valid; // true if signature checks out
result.verification.reason; // why it failed, if it did
// Routes that match YOUR capabilities
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
// 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 alice

Try it locally

Don’t have a live DRS to test against? Run one locally:

Terminal window
git clone https://github.com/drs-xyz/drs.git
cd drs && pnpm install && pnpm dev

Then resolve against it:

// Point the client at your local dev server
const result = await resolveAddress('http://localhost:8787', 'alice@localhost.directory.dev');

Or just curl it:

Terminal window
curl -s -X POST http://localhost:8787/resolve \
-H 'Content-Type: application/json' \
-d '{"address":"alice@localhost.directory.dev"}' | jq

What’s next