Signing requests for wallet mapping API

Introduction

The Wallet Mapping API allows users to associate blockchain wallet addresses with domain names. This document explains how to properly sign requests for various operations in the API.

Authentication

All API requests require:

  • An API key provided in the header as 'api-key'

  • A valid EIP-712 signature for the operation being performed

API Endpoints

1. Set Wallet Mapping

Associates a blockchain wallet address with a domain for a specific chain/symbol.

// Function to set a wallet mapping
async function setWalletMapping(symbol, name) {
  const payload = {
    domain: name,
    symbol,
    address: wallet.address,
    signatureExpiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
  };
  
  const signature = await wallet.signTypedData(
    SignDomain, 
    SetWeb3RecordTypes, 
    payload
  );
  
  return fetch(`https://api-public.d3.app/v1/domain/${name}/records/web3`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'api-key': API_KEY,
    },
    body: JSON.stringify({
      symbol,
      address: wallet.address,
      signature,
      signatureExpiresAt: payload.signatureExpiresAt,
    }),
  });
}

The SignDomain and SetWeb3RecordTypes constants are defined as:

const SignDomain = {
  name: 'D3 API',
  version: '1',
};

const SetWeb3RecordTypes = {
  SetWeb3Record: [
    { name: 'domain', type: 'string' },
    { name: 'symbol', type: 'string' },
    { name: 'address', type: 'string' },
    { name: 'signatureExpiresAt', type: 'uint256' },
  ],
};

Example

// Set "ETH" wallet mapping for domain "example.com"
const response = await setWalletMapping("ETH", "example.d3");
console.log(await response.json());
// Example response: { success: true }

2. Delete Wallet Mapping

Removes the association between a wallet address and a domain for a specific chain/symbol.

async function deleteWalletMapping(symbol, name) {
  const payload = {
    domain: name,
    symbol,
    signatureExpiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
  };
  
  const signature = await wallet.signTypedData(
    SignDomain, 
    DeleteWeb3RecordTypes, 
    payload
  );
  
  return fetch(`https://api-public.d3.app/v1/domain/${name}/records/web3`, {
    method: 'DELETE',
    headers: {
      'Content-Type': 'application/json',
      'api-key': API_KEY,
    },
    body: JSON.stringify({
      symbol,
      signature,
      signatureExpiresAt: payload.signatureExpiresAt,
    }),
  });
}

The DeleteWeb3RecordTypes constant is defined as:

const DeleteWeb3RecordTypes = {
  DeleteWeb3Record: [
    { name: 'domain', type: 'string' },
    { name: 'symbol', type: 'string' },
    { name: 'signatureExpiresAt', type: 'uint256' },
  ],
};

Example

// Delete "ETH" wallet mapping for domain "example.com"
const response = await deleteWalletMapping("ETH", "example.com");
console.log(await response.json());
// Example response: { success: true }

3. Set Primary Name

Sets a domain as the primary name for a wallet address (reverse resolution).

async function setPrimaryName(wallet, name) {
  const payload = {
    name,
    signatureExpiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
  };
  
  const signature = await wallet.signTypedData(
    SignDomain, 
    SetPrimaryNameTypes, 
    payload
  );
  
  return fetch(`https://api-public.d3.app/v1/reverse-registry/${wallet.address}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'api-key': API_KEY,
    },
    body: JSON.stringify({
      name,
      signature,
      signatureExpiresAt: payload.signatureExpiresAt,
    }),
  });
}

The SetPrimaryNameTypes constant is defined as:

const SetPrimaryNameTypes = {
  SetPrimaryName: [
    { name: 'name', type: 'string' },
    { name: 'signatureExpiresAt', type: 'uint256' },
  ],
};

Example

// Set "example.com" as the primary name for the wallet
const response = await setPrimaryName(wallet, "example.com");
console.log(await response.json());
// Example response: { success: true }

4. Unset Primary Name

Removes the primary name association for a wallet address.

async function unsetPrimaryName(wallet) {
  const payload = {
    signatureExpiresAt: Date.now() + 5 * 60 * 1000, // 5 minutes
  };
  
  const signature = await wallet.signTypedData(
    SignDomain, 
    UnsetPrimaryNameTypes, 
    payload
  );
  
  return fetch(`https://api-public.d3.app/v1/reverse-registry/${wallet.address}`, {
    method: 'DELETE',
    headers: {
      'Content-Type': 'application/json',
      'api-key': API_KEY,
    },
    body: JSON.stringify({
      signature,
      signatureExpiresAt: payload.signatureExpiresAt,
    }),
  });
}

The UnsetPrimaryNameTypes constant is defined as:

const UnsetPrimaryNameTypes = {
  UnsetPrimaryName: [
    { name: 'signatureExpiresAt', type: 'uint256' }
  ],
};

Example

// Remove the primary name for the wallet
const response = await unsetPrimaryName(wallet);
console.log(await response.json());
// Example response: { success: true }

Complete Working Example

Here's a complete example that demonstrates all operations:

const { ethers } = require('ethers');

// Configuration
const API_KEY = 'your-api-key-here';
const PRIVATE_KEY = 'your-private-key'; // Should be stored securely
const wallet = new ethers.Wallet(PRIVATE_KEY);

// EIP-712 domain
const SignDomain = {
  name: 'D3 API',
  version: '1',
};

// Type definitions for signatures
const SetWeb3RecordTypes = {
  SetWeb3Record: [
    { name: 'domain', type: 'string' },
    { name: 'symbol', type: 'string' },
    { name: 'address', type: 'string' },
    { name: 'signatureExpiresAt', type: 'uint256' },
  ],
};

const DeleteWeb3RecordTypes = {
  DeleteWeb3Record: [
    { name: 'domain', type: 'string' },
    { name: 'symbol', type: 'string' },
    { name: 'signatureExpiresAt', type: 'uint256' },
  ],
};

const SetPrimaryNameTypes = {
  SetPrimaryName: [
    { name: 'name', type: 'string' },
    { name: 'signatureExpiresAt', type: 'uint256' },
  ],
};

const UnsetPrimaryNameTypes = {
  UnsetPrimaryName: [
    { name: 'signatureExpiresAt', type: 'uint256' }
  ],
};

// Example usage
async function demonstrateAllOperations() {
  const domain = "example.com";
  const symbol = "ETH";
  
  // 1. Set wallet mapping
  console.log(`Setting ${symbol} wallet mapping for ${domain}...`);
  const setResponse = await setWalletMapping(symbol, domain);
  console.log(await setResponse.json());
  
  // 2. Set primary name
  console.log(`Setting ${domain} as primary name for wallet...`);
  const setPrimaryResponse = await setPrimaryName(wallet, domain);
  console.log(await setPrimaryResponse.json());
  
  // Wait to demonstrate the operations
  await new Promise(resolve => setTimeout(resolve, 2000));
  
  // 3. Delete wallet mapping
  console.log(`Deleting ${symbol} wallet mapping for ${domain}...`);
  const deleteResponse = await deleteWalletMapping(symbol, domain);
  console.log(await deleteResponse.json());
  
  // 4. Unset primary name
  console.log(`Unsetting primary name for wallet...`);
  const unsetResponse = await unsetPrimaryName(wallet);
  console.log(await unsetResponse.json());
}

// Implementation of API functions
// [Include all the function implementations from above]

// Run the demonstration
demonstrateAllOperations().catch(console.error);

Important Notes

  • All signatures expire after the time specified in signatureExpiresAt (in milliseconds since epoch)

  • The wallet address is derived from the signature, ensuring that only the wallet owner can make changes

  • Store your API key and private key securely and never expose them in client-side code

  • All endpoints will respond with 200 with an empty body in case of success

Error Handling

The API returns appropriate HTTP status codes and JSON responses for errors:

  • 400 - Bad Request: Invalid parameters or signature

  • 401 - Unauthorized: Invalid API key

  • 403 - Forbidden: Signature expired or incorrect

  • 404 - Not Found: Domain or wallet not found

Last updated