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();
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 address
result.contract.signature; // Ed25519 signature
result.contract.expires_at; // when this contract expires
result.verification.valid; // true if signature checks out
result.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

Terminal window
git clone https://github.com/drs-xyz/drs.git
cd drs && pnpm install && pnpm dev
import { 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:

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