Integrating D3 Infrastructure

Building With D3

Why Use D3's SDK

D3’s SDK offers a host of benefits including:

  • Seamless transition between existing web (Internet) and Web3 infrastructure

  • Futureproof design with backward and forward compatibility at its foundation

  • Single integration without the need for endless SDKs, APIs and more

  • Accessible, compliant and secure infrastructure

D3 Connect SDK

Overview

The D3 Connect SDK aims to bring different Web3 Name Resolution services under one umbrella to simplify integration efforts for developers. It supports the following resolution methods, which can be applied in order of priority:

  1. DNS Resolution

  2. (Optional) ENS resolution

  3. (Optional) Other resolution services - either provided by D3 or community.

SDK Documentation and Sample Code

D3 Connect is a unified JavaScript SDK to resolve Web3 names, built to be modular in nature and consisting of several resolution modules, which are applied in order of priority:

  • DNS resolution module. Resolves Web3 names using DNS TXT records. This is a core module, which cannot be disabled and always applied first.

  • ENS resolution module. Resolves Web3 names using ENS. Distributed as a separate @d3-inc/d3connect-ens package.

  • Other modules...

D3 Connect SDK can be used in both NodeJS and browser environments:

  • Node 18+ (fetch api support is needed, this might be relaxed in future).

  • Browsers with ES2022 features support (basically all modern browsers).

@d3-inc/d3connect

This package provides a modular SDK itself and a DNS resolution module. It is a lightweight zero-dependency library, which can be used to perform basic Web3 Wallet address resolution using standard DNS protocol.

npm install @d3-inc/d3connect

For basic usage, no configuration is required since it comes with reasonable defaults:

import { D3Connect } from '@d3-inc/d3connect';

const d3Connect = new D3Connect();

// Resolves `example.core` name on `CORE` blockchain
const walletAddress = await d3Connect.resolve('example.core', 'CORE');
console.log(walletAddress);

// Reverse resolves wallet address `0xaaaa` on `CORE` blockchain
const domainName = await d3Connect.reverseResolve('0xaaaa', 'CORE');
console.log(domainName);

Our SDK is highly configurable, and can be customized for a variety of use cases:

const d3Connect = new D3Connect({
  // DNS resolution module options (with defaults):
  dns: {
    forwarderDomain: 'forwarder.d3.app',
    // Whether or not DNSSEC verification must be performed by a resolver
    dnssecVerification: true,
    // DNS-over-HTTPS resolver is provided by default. It uses dns-json format, which is supported by CloudFlare & Google resolvers.
    // This could be substituted with different implementations by SDK consumers.
    resolver: new DNSOverHTTPSResolver({
      dnsServer: 'https://cloudflare-dns.com/dns-query',
    }),
  },
  // Log level for the SDK log messages
  logLevel: 'info', // "trace" | "info" | "warn" | "error" | "silent"
  // If needed, custom logger implementation can be provided.
  // By default, standard console logger is used.
  logger: <ConsoleLogger>,
  // In-memory cache is used by default to cache resolution result (and intermediate resolution data)
  // To avoid memory leaks (in server scenarios), or use persistent cache (in browser scenarios), custom caching implementation can be provided.
  caching: {
    enabled: true,
    cacheProvider: <InMemoryCache>,
  },
});

Current version limitations:

  1. IDNA (Unicode) names resolution is not supported, names should be normalized and converted to Punycode before passing to SDK.

  2. Retries are not implemented for DNS queries.

  3. Need to provide a common interface to pass cross-cutting services (caching, logging) to the modules.

  4. logLevel is not respected for external logger. Need to use an internal logging abstraction.

  5. network argument is not properly validated. SLIP44 validation can be added.

@d3-inc/d3connect-ens

This is an optional module which adds support for ENS names resolution.

npm install @d3-inc/d3connect-ens

Basic usage:

import { D3Connect } from '@d3-inc/d3connect';
import { ENSModule } from '@d3-inc/d3connect-ens';

const d3Connect = new D3Connect({ modules: [new ENSModule()] });
const walletAddress = await d3Connect.resolve('test.eth', 'ETH');
console.log(walletAddress);

Under the hood, it uses the @ensdomains/ensjs package, which in turn uses viem for blockchain integration. Viem chain and transport can be provided to further customize this module:

import { D3Connect } from '@d3-inc/d3connect';
import { ENSModule } from '@d3-inc/d3connect-ens';
import { http } from 'viem';
import { mainnet } from 'viem/chains';

const d3Connect = new D3Connect({
  modules: [
    new ENSModule({
      // Be default, `viem` Ethereum Mainnet network is used.
      // Custom network can be provided for testnet or private deployments.
      chain: mainnet,
      // By default, HTTP transport used
      transport: http(),
    }),
  ],
});

Current version limitations:

  1. ENS TTL is currently not used, so resolution results are not cached. Internal @ensdomains/ensjs is used instead.

  2. Provided network is not validated for support by @ensdomains/ensjs.

Custom resolution modules

Custom resolution modules can be written by implementing the D3ConnectModule interface:

export interface CustomModuleOptions {
  // Module options here
}

export class CustomModule implements D3ConnectModule {
  // Name is used for logging purposes
  name = 'MyModuleName';

  constructor(options?: CustomModuleOptions) {
    // Initialize your module
  }

  async resolve(name: string, network: string): Promise<ResolutionResult | undefined> {
    // Perform resolution of the provided name using custom logic.
    // If name cannot be resolved by this module, return `undefined`.

    return {
      // Return resolved wallet address (MUST not be empty)
      address: resolvedAddress,
      // Return TTL for resolved address (in seconds)
      // 0 can be returned to disable caching for this name
      ttl: 30,
    };
  }
  
  async reverseResolve(address: string, network: string): Promise<ReverseResolutionResult | undefined> {
   // Perform reverse resolution of the provided address using custom logic.
   // If address cannot be reverse resolved by this module, return `undefined`.

   return {
     // Return reverse resolved name (MUST not be empty)
     name: resolvedName,
     // Return TTL for resolved address (in seconds)
     // 0 can be returned to disable caching for this name
     ttl: 30,
   };

}

To use it, pass it as a module in D3Connect options:

import { D3Connect } from '@d3-inc/d3connect';

const d3Connect = new D3Connect({ modules: [new CustomModule()] });
const walletAddress = await d3Connect.resolve('test.custom', 'ETH');
console.log(walletAddress);

DNS Wallet Address Resolution

D3 Connect provides wallet address resolution using existing DNS infrastructure. This is achieved using standard DNS TXT records on a special subdomain.

A DNS record with predefined format on a specific host must be set:

  • Type: TXT

  • Host: _w3addr - specific name mandated by this standard.

  • Value: KEY:VALUE,KEY:VALUE - comma-separated key-value list of web3 records. Format is intentionally generic to allow easy extensions with new record types in future (e.g. for DID).

Separate host for TXT records is used. This is needed for several purposes:

  • To avoid using TXT record on parent name, which might be needed for other purposes.

  • To bypass limits on TXT record value length (CloudFlare has 2048 characters limit), multiple TXT records can be specified. During resolution, key-value pairs from different TXT records are joined together. Order is not guaranteed, so records shouldn’t have duplicate keys, otherwise resolution will become non-deterministic.

For wallet address resolution the following records are used: <SYMBOL>:<ADDRESS>:

  • <SYMBOL> is a chain symbol to resolve. It should be taken from the SLIP44 spec.

  • <ADDRESS> is an address in a chain-specific format.

Example:

_w3addr IN TXT "CORE:0xaaa,MATIC:0xbbbb,SOL:0xcccc"

DNSSEC must be enabled on domain names. Without this, the resolution process MUST be aborted, since this opens the possibility for security vulnerabilities (like DNS Cache Poisoning).

For EOIs, we can’t create DNS records since the TLD is not yet available, so we use a dedicated DNS name as a parent until the TLD is registered. For example, instead of using example.core we can put records under a forwarder DNS name, e.g. example.core.vana.

With the above format in mind, the wallet address resolution process using DNS consists of the following steps:

  1. Try to resolve _w3addrTXT records under DNS name (_w3addr.example.core).

    1. If the TLD is not resolved, fallback to resolution using the forwarder address (_w3addr.example.core.vana).

    2. If the record is not found, abort.

    3. If DNSSEC is not enabled or validation fails, abort.

  2. Parse web3 records from a response.

    1. If needed, join the parsed records together from multiple TXT records.

  3. Return the resolved web3 records to a consumer.

Ideally, caching SHOULD be employed by a resolver to make sure infrequently changed data (like DNSSEC keys or TLD registration status) are not requested unnecessarily.

Reverse Wallet Address Resolution

D3 Connect provides reverse wallet address resolution using existing DNS infrastructure. This is achieved using standard DNS CNAME records on a special subdomain.

A DNS record with predefined format on a specific host must be set:

  • Type: CNAME

  • Host: <WALLET_ADDRESS>.wallet.vana - specific name mandated by this standard

  • Value: <NAME> - The resolved name for the specified wallet address

For chain-specific reverse mappings, a CNAME record in the following format must be set: <WALLET_ADDRESS>.<SYMBOL>.wallet.vana

  • <SYMBOL> is a chain symbol to resolve. It should be taken from the SLIP44 spec.

When attempting to reverse resolve a wallet address, D3 Connect SDK will first attempt to lookup the chain-specific CNAME record <WALLET_ADDRESS>.<SYMBOL>.wallet.vana and if it does not exist, it will fallback to the default <WALLET_ADDRESS>.wallet.vana CNAME record.

Examples:

0xaaa.wallet.vana IN CNAME example.core

0xbbb.core.wallet.vana IN CNAME example.core

Last updated