introduction

Introduction

Welcome to the Solana Name Service (SNS). SNS has a simple mission of providing a decentralized and yet affordable way to map domain names (represented as .sol) to on-chain data. Where on-chain data can be anything from a Solana (SOL) address to IPFS CID, images, text, and more. One of the clear benefits of the name service is a human-readable name that maps to a SOL address. Essentially, creating an identity for users in the metaverse. Why does this matter you may ask? Well, wallet address formats can be a barrier to entry, and therefore having an identifiable address can facilitate payments and its efficiency. Nonetheless, Solana domain names can have much broader applications than just payments. Most decentralized apps (dApps) have a single point of failure which is their centralized and censorable domain name. However, on-chain domain names cannot be censored or taken away! Thus a website hosted on IPFS (or Arweave) using a Solana domain name would be completely decentralized and very difficult to censor.

Twitter handles & .sol domain names

Both Twitter handles and .sol domain names are a part of SNS, yet are slightly different. A Twitter handle can only be claimed by the owner of the Twitter account. In order to guarantee this, the user needs to tweet the wallet address they want to associate with the account and then sign a transaction using the same wallet address. An oracle then verifies that the public key contained in the tweet matches with the signer. In practice, this means that Twitter names are already reserved. Twitter users just need to claim their public key by tweeting it. Still, this has privacy implications that require you to have an identifiable Twitter account. This is where .sol domain names are useful. The domain names serve the same purpose as Twitter handles except they do not require you to reveal any personal information or a Twitter account.

Deployment

The Solana Name Service program is deployed on Mainnet, Devnet and Testnet at the following address:

namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX

SNS Improvement Proposal (SNS-IP)

The Solana Name Service Improvement Protocol (SNS-IP) allows all community members to participate in the building process of SNS & makes it much more transparent.

SNS-IPs can be found on Github: https://github.com/Bonfida/sns-ip

Accepted

  • SNS-IP-1: Proposal to standardize data encoding in SNS records

  • SNS-IP-3: Amends SNS-IP-2, proposes a new specification for on-chain records which can address concerns related to the staleness of records, as well as right of association to the linked ressource when relevant

Draft

  • No proposals currently in draft

Rejected

  • SNS-IP-2: Proposal to improve the handling of staleness and authenticy of records

Video Tutorials

The following is a curated list of video tutorials that provide comprehensive guidance on using SNS and developing small-scale applications:

  • Frontend Development (React):
    • Domain Resolution: Learn how to resolve domains with this tutorial Watch Video
    • Reverse Lookup: Understand the process of reverse lookup Watch Video
    • Picture Record: Learn how to use the profile record of SNS users Watch Video
    • Records V2: Integrate V2 records into your applications Watch Video
    • SNS Widget: Learn about our SNS Widget React component Watch Video
    • ETH Record V2: Fetch verified ETH records with Records V2 Watch Video
    • URL/CNAME Records V2: Set up verified URL and CNAME records as a domain owner Watch Video

SNS SDK

The SNS SDK monorepo can be found here. It contains SDK for:

Installation

Rust SDK

cargo add sns-sdk

or

sns-sdk = "0.1.0"

JS Library

To install the JS library

npm i @bonfida/spl-name-service

or

yarn add @bonfida/spl-name-service

To install the React

React hooks

To install the React hooks library

npm i @bonfida/sns-react

or

yarn add @bonfida/sns-react

Vue SDK

To install the Vue SDK

npm i @bonfida/sns-vue

or

yarn add @bonfida/sns-vue

CLI

cargo install --git https://github.com/Bonfida/sns-sdk.git sns

Example

The following examples show how to resolve the domain bonfida.sol:

  1. With the JS SDK
const connection = new Connection(clusterApiUrl("mainnet-beta"));
const owner = await resolve(connection, "bonfida");
expect(owner.toBase58()).toBe("HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA");
  1. With the Rust SDK
#![allow(unused)]
fn main() {
let client = RpcClient::new(std::env::var("RPC_URL").unwrap());
let res = resolve_owner(&client, "bonfida").await.unwrap();
assert_eq!(res, pubkey!("HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA"));
}
  1. With the CLI
$ sns resolve bonfida

+---------+----------------------------------------------+----------------------------------------------------------------------------------+
| Domain  | Owner                                        | Explorer                                                                         |
+---------+----------------------------------------------+----------------------------------------------------------------------------------+
| bonfida | HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA | https://explorer.solana.com/address/HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA |
+---------+----------------------------------------------+----------------------------------------------------------------------------------+
  1. With the React SDK
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { useDomainOwner, useDomainsForOwner } from "@bonfida/sns-react";

export const Example = () => {
  const { connection } = useConnection();
  const { publicKey, connected } = useWallet();
  const { result } = useDomainOwner(connection, "bonfida");
  // ...
};

Sign-in With SNS (SIWSNS)

Sign-in With Solana Name Service (SIWSNS) is an authentication plugin that allows users to easily sign in to websites and applications using their Solana wallet. It utilizes Solana's blockchain and public key cryptography to provide a secure and fast identity verification and login solution. By integrating SIWSNS into your Auth0 configuration, you can enable wallet-based login for your applications, allowing users to sign in seamlessly using just their Solana wallet account. The user's domain name acts as their identity, removing the need for separate usernames and passwords. SIWSNS streamlines the login process for Solana wallet holders while also providing the security and user management benefits of Auth0.

Prerequisites

  1. An Auth0 account and tenant. Sign up for free.
  2. A registered app on SIWSNS

Set up SIWSNS

To set up SIWSNS you need to obtain a Client ID and secret, this can be done with a simple cURL command:

curl -X POST https://siwsns.bonfida.com/register -d '{"redirectUris":["https://YOUR_AUTH0_DOMAIN/login/callback"]}'

Add the Connection

  1. Select Add Integration (at the top of this page).
  2. Read the necessary access requirements, and select Continue.
  3. Configure the integration using the following fields:
    • Client ID: Public unique identifier for this application obtained when you registered your app.
    • Client secret: Secret for this application obtained when you registered your app.
  4. Select the Permissions needed for your applications.
  5. Choose and configure whether to sync user profile attributes at each login.
  6. Select Create.
  7. In the Applications view, choose the Applications that should use this Connection to log in.

Test connection

You're ready to test this Connection.

Examples

FAQ

Where can I buy a domain?

You can buy a domain on the Solana Name Service website

How do I find a domain if I only know its public key?

If you only know the public key of a domain you can do a reverse look up to find the associated domain. For example:

import { reverseLookup } from "@bonfida/spl-name-service";

// Public key of bonfida.sol
const domainKey = new PublicKey("Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb");

const domainName = await reverseLookup(connection, domainKey); // bonfida

How do I find the public key of a domain?

If you want to find the public key of a domain you need to derive it:

import { getDomainKeySync } from "@bonfida/spl-name-service";

const domain = "bonfida"; // With or without the .sol

// Step 2
const { pubkey } = getDomainKeySync(domain);

How can I find the content of a domain?

You can access the content of a domain by retrieving its registry:

const { registry } = await NameRegistryState.retrieve(connection, domainKey);
const { parentName, owner, class, data } = registry;

How do I find the twitter handle of a public key?

To find the twitter handle of a public key

import { getHandleAndRegistryKey } from "@bonfida/spl-name-service";

const pubkey = new PublicKey("FidaeBkZkvDqi1GXNEwB8uWmj9Ngx2HXSS5nyGRuVFcZ");

const [handle] = await getHandleAndRegistryKey(connection, pubkey);

How do I find the public key of a twitter handle?

To find the public key of a twitter handle

import { getTwitterRegistry } from "@bonfida/spl-name-service";

const handle = "bonfida";

const registry = await getTwitterRegistry(connection, handle);

How do I find all the subdomains of a domain?

You can find all the subdomains using the following RPC filter:

const filters = [
  {
    memcmp: {
      offset: 32,
      bytes: userAccount.toBase58(),
    },
  },
  {
    memcmp: {
      offset: 0,
      bytes: parent_key.toBase58(),
    },
  },
];

How do I find all the subdomains of a user?

You can find all the subdomains of a user by doing the following

  1. Retrieve all the domains of the user
  2. Iterate over the domains and retrieve the subdomains for each

Name Registry: Understanding Domains on Solana

Solana can be viewed as a key-value database, where everything, including domains, is uniquely identifiable by a public key. The data inside a domain account contains an object called the Name Registry. The Name Registry is made of a header and payload.

Name Registry Explained

name-registry

Name Registry Header

The header contains three public keys that define the domain's properties:

  • Parent: Represents the parent domain in the hierarchy.
  • Owner: Indicates the entity that has control over the domain.
  • Class: A special key that enables advanced use-cases, such as third-party verification in a badge system.

Below is the structure of the header in Rust:

#![allow(unused)]
fn main() {
/// The layout of the remaining bytes in the account data are determined by the record `class`
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
pub struct NameRecordHeader {
    // Names are hierarchical.  `parent_name` contains the account address of the parent
    // name, or `Pubkey::default()` if no parent exists.
    pub parent_name: Pubkey,

    // The owner of this name
    pub owner: Pubkey,

    // The class of data this account represents (DNS record, twitter handle, SPL Token name/symbol, etc)
    //
    // If `Pubkey::default()` the data is unspecified.
    pub class: Pubkey,
}
}

Data: Flexible Data Storage

The data section can hold arbitrary binary data. Its length is set during domain registration, and the domain owner can decide what to store in this part of the Name Registry.

In simple terms, the Name Registry data structure provides a way to organize and store information about a domain on Solana, including its relationships, ownership, and any additional data the owner wishes to include.

Wallet Guide

This is the step-by-step guide to help wallet providers integrate .sol domain names and Twitter handles as a way to use them to send and receive funds instead of a pubkey.

The guide lays out the process of integrating domains to both Solana and the other blockchains SNS has been bridged to. Specifically, it’ll aid you in understanding the nuanced aspects of resolving tokenized domains, those with record sections and subdomains.

The instructions are structured in a three-part layout:

7.1 Resolving domains: describes how to integrate domains on Solana

7.2 Resolving bridged domains: describes how to integrate domains on other chains

7.3 Resolving Twitter handles: describes how to integrate Twitter handles linked to a pubkey

How to resolve a .sol domain name?

💡 To resolve a domain name you can use the resolve function from the SNS SDK

Below is the correct methodology to resolve .sol domain names. It's strongly recommended to use the resolve function from the SNS SDK. If you wish to reimplement the resolution logic on your end please make sure to follow these guidelines to avoid loss of funds.

wallet-guide

  1. Check if the domain name is tokenized

    • Yes: The correct destination of funds is the token holder
    • No: Go to step 2
  2. Check the SOL record

    • If The SOL record V2 is set and the staleness & RoA ID are verified, the public key specified in the record is the correct destination
    • Else if the SOL record V1 is set and the signature is valid, the public key specified in the record is the correct destination. If the signature is invalid go to step 3
    • Else go to step 3
  3. The correct destination of funds is the domain owner

A JS implementation can be found on the SDK repository

Not resolving domains properly might lead to loss of funds ⚠️

FAQ

  1. What happens if funds are sent to the NameRegistry owner when the domain is tokenized?

As long as the user owns the tokenized domains (i.e the NFT) they will be able to withdraw from the PDA escrow that received the funds. However, if for some reason the user does not own the NFT they won't be able to withdraw the funds.

  1. Why is there a signature in the SOL record V1?

The SOL record V1 data contains a 96-byte array that is the concatenation of a public key (32 bytes) and signature (64 bytes). The first 32 bytes represent the public key (pubkey) to which funds should be sent and the next 64 bytes are the signature of pubkey_as_bytes + record_key_as_bytes signed by the owner of the domain. If the signature is invalid funds must not be transferred to the SOL record address.

The signature is required to prevent funds being sent to a stale SOL record after a domain has been transferred or sold to a new owner.

Examples

This section provides examples to assist you in testing your implementation. However, they are not exhaustive, therefore, ensure to carry out comprehensive tests beyond these examples. Use them as guidelines and adapt them to suit your specific needs.

domainowner
wallet-guide-5.solFxuoy3gFjfJALhwkRcuKjRdechcgffUApeYAfMWck6w8
wallet-guide-4.solHf4daCT4tC2Vy9RCe9q8avT68yAsNJ1dQe6xiQqyGuqZ
wallet-guide-3.solFxuoy3gFjfJALhwkRcuKjRdechcgffUApeYAfMWck6w8
wallet-guide-2.sol36Dn3RWhB8x4c83W6ebQ2C2eH9sh5bQX2nMdkP2cWaA4
wallet-guide-1.sol36Dn3RWhB8x4c83W6ebQ2C2eH9sh5bQX2nMdkP2cWaA4
wallet-guide-0.solFxuoy3gFjfJALhwkRcuKjRdechcgffUApeYAfMWck6w8
sub-0.wallet-guide-3.solFxuoy3gFjfJALhwkRcuKjRdechcgffUApeYAfMWck6w8
sub-1.wallet-guide-3.solHf4daCT4tC2Vy9RCe9q8avT68yAsNJ1dQe6xiQqyGuqZ

How to resolve a .sol domain cross-chain?

The Solana Name Service (SNS) going cross-chain means that it is expanding its functionality beyond the Solana ecosystem, enabling users to export their domain names to alternative blockchains using the Wormhole bridge. This move aims to increase the utility and adoption of SNS, while also fostering collaboration and interoperability between different blockchain networks. By allowing SNS domains to be resolved on supported chains like EVM-based chains and Injective, SNS becomes more accessible to a wider range of developers and users, promoting the growth of the Solana ecosystem and showcasing its capabilities beyond its native environment.

EVM Chains

The Solana Name Service has been bridged to the following EVM chains:

  • BNB Testnet (deployed at 0x4d50e149bb3d8c889f4ccdfffba0ef8016168d92)

  • BNB Mainnet (deployed at 0xd1Ae42Ce34E6b7ab5B41dcc851424F3cF410BF16)

  • BASESepolia (deployed at 0xc0B286f45d2D5D825aD42DcF49CB9eA39899E2c3)

  • BASE (deployed at 0x63E2FADb57BEd8A4c9c3C5a4937e7611ec88421F)

Solana domain names bridged on EVM chains can be resolved using the NPM package @bonfida/sns-warp-evm.

Installation

With Yarn:

yarn add @bonfida/sns-warp-evm

With NPM

npm i @bonfida/sns-warp-evm

Resolving a .sol domain

The following code can be used to resolve .sol domains on EVM chains

import { SupportedChains, SNS } from "@bonfida/sns-warp-evm";

/**
 *
 * BNB Example
 *
 **/

// The domain name to resolve
const domain = "mock3.sol";
// The chain on which to resolve the domain
const targetChain = SupportedChains.BNBMainnet;

const sns = new SNS(SupportedChains.BNBMainnet);
const resolved = await sns.resolveName(domain);

console.log(resolved); // <- 0x1D719d2dB763f905b1924F46a5185e001Dd93786

/**
 *
 * BASE Example
 *
 **/

const sns = new SNS(SupportedChains.BASESepolia);
const resolved = await sns.resolveName("12c8566b3e8ab8b9edac2ceab89be3bd.sol");

console.log(resolved); // <- 0x5f8901Aa3a42BCB53792CfCeDa66a7cf735Af6Db

Reverse look up

import { SupportedChains, SNS } from "@bonfida/sns-warp-evm";
import { namehash } from "@ethersproject/hash";

// The chain on which to perform the reverse lookup
const targetChain = SupportedChains.BNBMainnet;

const sns = new SNS(targetChain);
const nameHash = namehash("mock3.sol");

const resolved = await sns.resolveReverse(nameHash);
console.log(resolved); // <- mock3

Injective

The Solana Name Service has also been bridged to Injective.

  • Injective Testnet (deployed at inj1q79ujqyh72p43mhr2ldaly3x6d50rzp3354at3)
  • Injective Mainnet (deployed at inj1v7chmgm7vmuwldjt80utmw9c95jkrch979ps8z)

Solana domain names bridged to Injective can be resolved using the NPM package @bonfida/sns-warp-injective.

SNS is also supported by the Leap Wallet Name Match package.

Installation

With Yarn:

yarn add @bonfida/sns-warp-injective

With NPM

npm i @bonfida/sns-warp-injective

Resolving a .sol domain

The following code can be used to resolve .sol domains on Injective

import { resolveName } from "@bonfida/sns-warp-injective";
import { Network } from "@injectivelabs/networks";

// The domain name to resolve
const domain = "bonfida.sol";
// The network on which to resolve the domain
const network = Network.Mainnet;

const resolved = await resolveName(domain, network);

console.log(resolved); // <- inj1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqe2hm49

Resolving Twitter handles

The Solana name service supports the registration of Twitter handles, allowing users to connect their Twitter profile to their wallet.

Direct look up

To find the Twitter handle associated to a public key

import { getHandleAndRegistryKey } from "@bonfida/spl-name-service";

const pubkey = new PublicKey("FidaeBkZkvDqi1GXNEwB8uWmj9Ngx2HXSS5nyGRuVFcZ");

const [handle] = await getHandleAndRegistryKey(connection, pubkey);

Reverse look up

To find the public key associated to a Twitter handle

import { getTwitterRegistry } from "@bonfida/spl-name-service";

const handle = "bonfida";

const registry = await getTwitterRegistry(connection, handle);
const owner = registry.owner.toBase58();

Domain names

This following sections provide information on the following:

TLD

root-tld

In Solana, the hierarchy of domain names and the TLD system is organized similarly to the traditional internet domain structure. The top of the hierarchy is the Root domain, which holds the Top Level Domains (TLDs) like .sol.

Under the Root domain, you have the TLDs, such as .sol. All the domain names registered with the .sol extension are considered children (or subdomains) of the .sol TLD. For example, bonfida.sol is a child of the .sol TLD.

Further down the hierarchy, you can have subdomains of the registered domain names. For instance, dex.bonfida.sol is a child of bonfida.sol.

In simple terms, the hierarchy of Solana domain names starts with the Root domain, followed by TLDs (e.g., .sol), then the registered domain names (e.g., bonfida.sol), and finally any subdomains (e.g., dex.bonfida.sol). This hierarchical structure allows for an organized way to manage and identify domain names on the Solana network.

TLD list

  • Root TLD: ZoAhWEqTVqHVqupYmEanDobY7dee5YKbQox9BNASZzU
  • .sol TLD: 58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx

Web resolution

Web resolvers

While SNS offers numerous benefits, its separation from the traditional Domain Name System (DNS) can present challenges for adoption and accessibility. To address these challenges, we are building the necessary infrastructure to bridge the gap between SNS and DNS, making it easier for users to interact with SNS through familiar methods.

Below are three such methods for resolving .sol domain names:

Brave browser

The Brave browser supports native resolution of .sol domain names, allowing users to access Solana Name Service domains directly from the URL bar. To resolve an SNS domain in the Brave browser, simply type the domain followed by .sol in the URL bar, and press Enter. For instance, to access the Pyth network website, you would enter pyth.sol in the URL bar.

Resolution via sol-domain.org Proxy Service

The sol-domain.org service is a proxy that enables users to access SNS domains via the traditional DNS system. To resolve a .sol domain using sol-domain.org, append the domain name to the beginning of the sol-domain.org URL. For example, to resolve the pyth.sol domain, you would enter https://pyth.sol-domain.org in the URL bar of your browser.

Resolving .sol domain names in practice

The process for resolving .sol domain names follows a defined set of rules based on different types of records: URL, IPFS, Arweave, and Shadow Drive. The resolution process checks for these records in the specified order until it finds a valid record.

Step-by-step process

Step 1: URL Record

First, check if the URL record exists and is valid. If so, this is the website to resolve to. The URL record typically contains the HTTP or HTTPS link to the website hosted on the domain.

Step 2: IPFS Record

If there is no valid URL record, proceed to check for an IPFS record.

If the IPFS record exists and is valid, the user should be redirected to the corresponding IPFS content via an IPFS gateway. An IPFS gateway acts as a bridge between the traditional web and the IPFS network, allowing browsers that don't natively support IPFS to access IPFS content.

Step 3: Arweave Record

If neither a URL nor an IPFS record is found or valid, check for an Arweave record.

If the Arweave record exists and is valid, the user should be redirected to the Arweave content via an Arweave gateway. Similar to the IPFS gateway, an Arweave gateway allows users to access Arweave-hosted content through traditional web browsers.

Step 4: Shadow Drive Record

Lastly, if no URL, IPFS, or Arweave records are found or valid, check for a Shadow Drive record.

If the Shadow Drive record exists and is valid, the user should be redirected to the content via a Shadow Drive gateway. Like IPFS and Arweave gateways, a Shadow Drive gateway provides access to Shadow Drive-hosted content for traditional web browsers.

Direct look up

direct-reverse

In order to get the information of a domain name you need to:

  1. Get the domain name public key
  2. Retrieve the account info
import { getDomainKeySync, NameRegistryState } from "@bonfida/spl-name-service";

const domainName = "bonfida"; // With or without the .sol at the end

// Step 1
const { pubkey } = getDomainKeySync(domainName);

// Step 2
// The registry object contains all the info about the domain name
// The NFT owner is of type PublicKey | undefined
const { registry, nftOwner } = await NameRegistryState.retrieve(
  connection,
  pubkey
);

// Subdomain derivation
const subDomain = "dex.bonfida"; // With or without the .sol at the end
const { pubkey: subKey } = getDomainKeySync(subDomain);

// Record derivation (e.g IPFS record)
const record = "IPFS.bonfida"; // With or without the .sol at the end
const { pubkey: recordKey } = getDomainKeySync(record, true);

The retrieve method returns an object made of two fields:

  • registry is of type NameRegistryState
  • nftOwner is of type PublicKey | undefined
    • When nftOwner is of type PublicKey it means that the domain is tokenized and the current NFT holder is nftOwner. When a domain is tokenized registry.owner is an escrow account that is program owner. Funds should be sent to nftOwner
    • When nftOwner is of type undefined it means that the domain is not tokenized and funds should be sent to registry.owner

Note: NameRegistryState.retrieveBatch can be used to retrieve multiple name registries at once. Pass the connection, and an array of domain name public keys as arguments to the function.

Reverse look up

direct-reverse

If you know the public key of a domain name registry and want to get the human readable name, you need to perform a reverse lookup.

The following code can be used to resolve the domain name from its public key:

import { reverseLookup } from "@bonfida/spl-name-service";

// Public key of bonfida.sol
const domainKey = new PublicKey("Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb");

const domainName = await reverseLookup(connection, domainKey); // bonfida

Get all domains of a user

If you are using @bonfida/spl-name-service you can use the following code:

import { getAllDomains } from "@bonfida/spl-name-service";

// ...

const domains = await getAllDomains(connection, user);

The function above will return an array of public keys. Use the toBase58() method to convert the public keys into base 58 encoded strings.

domains.map((domain) => domain.toBase58());

Another option to retrieve public keys, as well as their corresponding domain names in a string format is to use the function below from the spl-name-service library. The function will return an array of objects including public keys, and their corresponding strings.

const domainsWithReverses = await getDomainKeysWithReverses(connection, user);

If you opt not to use the spl-name-service library, you can manually retrieve all the domain names of a user with the following MemcmpFilter while querying the Solana blockchain.

const filters = [
  {
    memcmp: {
      offset: 32,
      bytes: user.toBase58(),
    },
  },
  {
    memcmp: {
      offset: 0,
      bytes: ROOT_DOMAIN_ACCOUNT.toBase58(),
    },
  },
];

Get all domain names

You can retrieve all the registered domain names using a getProgramAccounts request with the following RPC filter

const filters = [
  {
    memcmp: {
      offset: 0,
      bytes: ROOT_DOMAIN_ACCOUNT.toBase58(),
    },
  },
];

If you are using @bonfida/spl-name-service you can use the following code:

import { getAllRegisteredDomains } from "@bonfida/spl-name-service";

// ...

const registeredDomains = await getAllRegisteredDomains(connection);

To avoid enormous payload response, getAllRegisteredDomains slices the data to only return the owner of the domain (i.e dataSlice = { offset: 32, length: 32 })

/**
 * This function can be used to retrieve all the registered `.sol` domains.
 * The account data is sliced to avoid enormous payload and only the owner is returned
 * @param connection The Solana RPC connection object
 * @returns
 */
export const getAllRegisteredDomains = async (connection: Connection) => {
  const filters = [
    {
      memcmp: {
        offset: 0,
        bytes: ROOT_DOMAIN_ACCOUNT.toBase58(),
      },
    },
  ];
  const dataSlice = { offset: 32, length: 32 };

  const accounts = await connection.getProgramAccounts(NAME_PROGRAM_ID, {
    dataSlice,
    filters,
  });
  return accounts;
};

Records

In addition to typical DNS records (A, AAAA, TXT, MX, etc.), the Solana Name Service introduces brand new web3-specific types. The following table will be updated as new protocols are integrated.

NameValue
IPFSAn IPFS CID
ARWVAn Arweave address
SOLA concatenation of a public key and a signature
ETHAn ETH public key
BTCA BTC public key
LTCAn LTC public key
DOGEA DOGE public key
emailAn email address
urlA website URL
discordA discord username
githubA github username
redditA reddit username
twitterA twitter username
telegramA telegram username
picA profile picture
SHDWA Shadow drive address
POINTA Point network record
BSCA BSC public key
INJA Cosmos (Injective) public key
backpackA Backpack username

Record enum

The following enum is exported from @bonfida/spl-name-service

export enum Record {
  IPFS = "IPFS",
  ARWV = "ARWV",
  SOL = "SOL",
  ETH = "ETH",
  BTC = "BTC",
  LTC = "LTC",
  DOGE = "DOGE",
  Email = "email",
  Url = "url",
  Discord = "discord",
  Github = "github",
  Reddit = "reddit",
  Twitter = "twitter",
  Telegram = "telegram",
  Pic = "pic",
  SHDW = "SHDW",
  POINT = "POINT",
  BSC = "BSC",
  Injective = "INJ",
  Backpack = "backpack",
}

Gateway

The following records can be resolved in browser using https://sol-domain.org:

  • Url
  • IPFS
  • ARWV
  • SHDW

The implementation of this resolver can be found on Github

For example https://bonfida.sol-domain.org

Records V1 & V2

There are notable differences between Records V1 and V2. Records V1 use a derivation prefix of 0x01 and encode their content based on the SNS-IP-1 guidelines. In response to challenges related to data authenticity and staleness issues prevalent in V1, Records V2 was introduced. V2 employs a class in the derivation and a distinct encoding schema detailed in SNS-IP-3. Records V2 incorporates a validation ID system to ensure data integrity and freshness. Importantly, due to the distinct derivation, Records V1 and V2 can co-exist without collisions. However, the goal of the ecosystem is a complete migration to Records V2.

Difference between records and subdomains

In practice, let us consider the name foo.sol . If we want to find the domain's A record, containing an associated IPv4 address, then we can find it by querying \1A.foo.sol, with \1 the character of code value 1. The specification makes use of this prefix in order to differentiate between actual domains and records, which means that it is still possible to use the A.foo.sol subdomain with no collision.

Note: \0 and \1 are convenient notations for:

  • \0 = \x00.
  • \1 = \x01.

Records V1

V1 records are deprecated. For current V2 records please see Records V2.

Record V1 derivation

Record keys can be derived with the getDomainKey function and the record flag set to true

const record = Record.IPFS + "." + "bonfida"; // With or without the .sol at the end
const { pubkey: recordKey } = await getDomainKey(record, true);

If the record flag is set to false, the getDomainKey function will derive the key of the subdomain

Resolving records V1

The following resolving functions are exported:

  • getIpfsRecord: This function can be used to retrieve the IPFS record of a domain name
  • getArweaveRecord: This function can be used to retrieve the Arweave record of a domain name
  • getSolRecord: This function can be used to retrieve the SOL record of a domain name
  • getEthRecord: This function can be used to retrieve the ETH record of a domain name
  • getBtcRecord: This function can be used to retrieve the BTC record of a domain name
  • getLtcRecord: This function can be used to retrieve the LTC record of a domain name
  • getDogeRecord: This function can be used to retrieve the DOGE record of a domain name
  • getEmailRecord: This function can be used to retrieve the email record of a domain name
  • getUrlRecord: This function can be used to retrieve the URL record of a domain name
  • getDiscordRecord: This function can be used to retrieve the Discord record of a domain name
  • getGithubRecord: This function can be used to retrieve the Github record of a domain name
  • getRedditRecord: This function can be used to retrieve the Reddit record of a domain name
  • getTwitterRecord: This function can be used to retrieve the Twitter record of a domain name
  • getTelegramRecord: This function can be used to retrieve the Telegram record of a domain name
  • getShdwRecord: This function can be used to retrieve the SHDW record of a domain name
  • getBscRecord: This function can be used to retrieve the BSC record of a domain name
  • getInjectiveRecord: This function can be used to retrieve the Cosmos Injective record of a domain name
  • getBackpackRecord: This function can be used to retrieve the Backpack record of a domain name

All functions have the following signature

(connection: Connection, domain: string) => Promise<NameRegistryState>

A more generic resolving function getRecord is also exported with the following signature

(connection: Connection, domain: string, record: Record) => Promise<NameRegistryState>

Editing records V1

Below is a NodeJS example of how to create and edit a record

import {
  Connection,
  TransactionInstruction,
  Keypair,
  clusterApiUrl,
} from "@solana/web3.js";
import {
  Record,
  getDomainKey,
  createNameRegistry,
  NameRegistryState,
  updateInstruction,
  NAME_PROGRAM_ID,
  Numberu32,
} from "@bonfida/spl-name-service";
import { signAndSendInstructions } from "@bonfida/utils";

const connection = new Connection(clusterApiUrl("mainnet-beta"), "processed");
const wallet = Keypair.fromSecretKey(...);

// bonfida.sol
const domain = "bonfida"; // With or without the .sol at the end

// The IPFS record of bonfida.sol
const record = Record.IPFS;

const update = async () => {
  const ixs: TransactionInstruction[] = [];
  const { pubkey: domainKey } = await getDomainKey(domain);
  const { pubkey: recordKey } = await getDomainKey(record + "." + domain, true);

  const recordAccInfo = await connection.getAccountInfo(recordKey);

  if (!recordAccInfo?.data) {
    // The record does not exist so create it first
    const space = 2_000;
    const lamports = await connection.getMinimumBalanceForRentExemption(
      space + NameRegistryState.HEADER_LEN
    );
    const ix = await createNameRegistry(
      connection,
      Buffer.from([1]).toString() + record,
      space,
      wallet.publicKey,
      wallet.publicKey,
      lamports,
      undefined,
      domainKey
    );
    ixs.push(ix);
  }

  const ix = updateInstruction(
    NAME_PROGRAM_ID,
    recordKey,
    new Numberu32(0),
    Buffer.from("Some IPFS CID"),
    wallet.publicKey
  );

  ixs.push(ix);

  const tx = await signAndSendInstructions(connection, [], wallet, ixs);
  console.log(`Updated record ${tx}`);
};

update();

Deleting a record V1

Records can be deleted using the deleteInstruction function, below is a NodeJS example

import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js";
import {
  Record,
  getDomainKey,
  NAME_PROGRAM_ID,
  deleteInstruction,
} from "@bonfida/spl-name-service";
import { signAndSendInstructions } from "@bonfida/utils";

const domain = "bonfida.sol"; // With or without .sol

const record = Record.IPFS;

const connection = new Connection(clusterApiUrl("mainnet-beta"), "processed");

const wallet = Keypair.fromSecretKey(...) // Your wallet owning the domain

const deleteRecord = async () => {
  const { pubkey: recordKey } = await getDomainKey(record + "." + domain, true);

  const ix = deleteInstruction(
    NAME_PROGRAM_ID,
    recordKey,
    wallet.publicKey,
    wallet.publicKey
  );

  const tx = await signAndSendInstructions(connection, [], wallet, [ix]);

  console.log(`Deleted record ${tx}`);
};

deleteRecord();

The SOL record V1

The SOL record can be used to receive funds to a different address than the one owning the domain. This allows people to hold the domain on a cold wallet while being able to receive funds on a hot wallet.

The SOL record data contains a 96-byte array that is the concatenation of a public key (32 bytes) and signature (64 bytes). The first 32 bytes represent the public key (pubkey) to which funds should be sent and the next 64 bytes are the signature of pubkey_as_bytes + record_key_as_bytes by the owner of the domain. If the signature is invalid funds must not be transferred.

The signature is required to prevent funds being sent to a stale SOL record after a domain has been transferred or sold.

Records V2

Records V2 improves on-chain trust by introducing the verifyStaleness method to ensure records are not stale, and the verifyRightOfAssociation to ensure records are authentic. These functions are described in detail below.

Record V2 derivation

V2 Record keys can be derived with the getRecordV2Key function.

const domain = "bonfida"; // With or without the .sol at the end
const record = Record.IPFS; // Import the Record enum from the spl-name-service library

const recordV2Key = getRecordV2Key(domain, record);

Verify Staleness

You're able to verify the staleness of a record with Records V2. A record is stale when it was set up by a previous domain owner, and is no longer relevant to the current owner. The function returns a boolean indicating if the record is fresh or not.

const freshRecord = await verifyStaleness(connection, record, domain);

Verify Right of Association

You're also able to verify the authenticity of a record with Records V2 using the verifyRightOfAssociation function. The function returns a boolean indicating if the record is authentic or not. Unique to this function is the verifier parameter, which is the known public key of the on-chain/off-chain oracle used to verify authenticity of a record. This is currently supported for SOL, ETH, INJ, BNB, URL, and CNAME records with support for further records on the way.

const verifier = GUARDIANS.get(Record.URL); // Import GUARDIANS from the spl-name-service library

const ROA = await verifyRightOfAssociation(
  connection,
  record,
  domain,
  verifier.toBuffer()
);

ETH, SOL, BNB, and INJ records are unique because they are self signing. To verify the authenticity of these records, we must pass the content of the record itself as the verifier agrument to the verifyRightOfAssociation function.

const { retrievedRecord } = await getRecordV2(connection, domain, record); // Import getRecordV2 from the spl-name-service library

const ROA = await verifyRightOfAssociation(
  connection,
  record,
  domain,
  retrievedRecord.getContent()
);

Edit domain content

To write data in a domain registry you can use the following code:

import {
  updateNameRegistryData,
  ROOT_DOMAIN_ACCOUNT,
} from "@bonfida/spl-name-service";

const data = Buffer.from("Hello, world!");

// The offset to which the data should be written into the registry, usually 0
const offset = 0;

const ix = await updateNameRegistryData(
  connection,
  name,
  offset,
  data,
  undefined,
  ROOT_DOMAIN_ACCOUNT
);

// sign and send instruction

If the data is too large to fit in a single transaction, you will have to update the domain in several transaction by slicing the buffer and increasing the offset accordingly.

Transfer domain

Domain names can be transferred using the transferNameOwnership instruction:

import {
  transferNameOwnership,
  NameRegistryState,
  ROOT_DOMAIN_ACCOUNT,
} from "@bonfida/spl-name-service";

// ..

// Domain name to transfer
const domain = "bonfida";

// New owner of the domain
const newOwner = new PublicKey("...");

const ix = await transferNameOwnership(
  connection,
  domain,
  newOwner,
  undefined,
  ROOT_DOMAIN_ACCOUNT
);

// sign and send instruction

Primary domain

💡 Primary domains used to be called favorite domains. The change was made in version 3.0.0 of the SDK

Users have the possibility to select a domain name as their primary one. If you are a developer and want to integrate SNS to your DApp it's recommended to always use the primary domain name to replace the user's public key.

Get Multiple Primary Domains

To retrieve primary domains for a group of up to 100 users, we've created the getMultiplePrimaryDomains function in our JavaScript SDK. This function is optimized for network efficiency, making only four RPC calls, three of which are executed in parallel. The function returns a promise that resolves to an array of strings or undefined representing the primary domain, or lack thereof for each wallet passed to the function.

import { getMultiplePrimaryDomains } from "@bonfida/spl-name-service";

const wallets = [
  new PublicKey("3ogYncmMM5CmytsGCqKHydmXmKUZ6sGWvizkzqwT7zb1"),
  new PublicKey("FMmaHPDL47V1gXsfh9WjgAT7Er3dfDvarQubTU1Jxc1r"),
  // Public Keys of all the wallet addresses you're looking up a primary domain for (up to 100)
];

const primaryDomains = await getMultiplePrimaryDomains(connection, wallets);

Get Single Primary Domain

The primary domain name of a single user can be retrieved with the code below.

import { getPrimaryDomain } from "@bonfida/spl-name-service";

// ...

const { domain, reverse } = await getPrimaryDomain(connection, user);

getPrimaryDomain returns the following:

  • domain: The public key of the primary domain name
  • reverse: The reverse look up of the account

For instance for FidaeBkZkvDqi1GXNEwB8uWmj9Ngx2HXSS5nyGRuVFcZ:

  • domain = Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb
  • reverse = bonfida

Get All Primary Domains

To get all primary domains currently existence, you can use the SNS API endpoint below.

GET /v2/domains/all-primary

This endpoint returns all primary domains that have been registered.

Request

No parameters are required for this request.

Response

The response is a JSON object where each key is a wallet public key and the value is an object containing the following properties:

  • domain: The primary domain name associated with the public key.
  • owner: The public key of the owner of the domain.
  • domain_key: The key associated with the domain.

Example response:

{
  "5J9BeWAZGekH71Huiro2qW6AJXSeBj7zPsHniKkmjToY": {
    "domain": "hansolana",
    "owner": "5J9BeWAZGekH71Huiro2qW6AJXSeBj7zPsHniKkmjToY",
    "domain_key": "7mNYJ8UL8YV7dpPjCREXgUaoyyULmmZxxo1raT4w9kKS"
  }
}

Caching

This endpoint is cached and may lag behind the blockchain by a few minutes. For mission-critical resolution, it is recommended not to use this endpoint and instead use the blockchain directly. However, for simple UI applications, this endpoint is perfectly suitable.

React hooks

The list below show how to implement your own React hooks using vanilla React. For production, it's recommended to use the React hooks library.

  1. useDomains
  2. useDomainRecords
  3. useDomainsForUser
  4. usePrimaryDomain
  5. useProfilePic

useDomains

This hook can be used to resolve several domain names:

import { getDomainKey, NameRegistryState } from "@bonfida/spl-name-service";
import { useEffect, useState, useRef } from "react";

type Result = (NameRegistryState | undefined)[] | undefined;

/**
 * This hook can be used to resolve several domain names
 * @param domains List of domains to resolve e.g ["bonfida", "serum"]
 * @returns
 */
export const useDomains = (domains: string[]) => {
  const { connection } = useConnection();
  const [result, setResult] = useState<Result>(undefined);
  const mounted = useRef(true);

  useEffect(() => {
    const fn = async () => {
      const keys = await Promise.all(domains.map((e) => getDomainKey(e)));

      const registries = await NameRegistryState.retrieveBatch(
        connection,
        keys.map((e) => e.pubkey)
      );

      if (mounted.current) {
        setResult(registries);
      }

      return () => (mounted.current = false);
    };

    fn().catch(console.error);
  }, [...domains]);

  return result;
};

useRecords

This hook can be used to retrieve all the V1 records of a domain. Please note, V1 records are deprecated. For current V2 records check out useRecordsV2.

type Result = (string | undefined)[] | undefined;

/**
 * This hook can be used to retrieve all the records of a domain
 * @param domains Domains to resolve records for e.g "bonfida"
 * @returns
 */
export const useRecords = (domain: string) => {
  const { connection } = useConnection();
  const [result, setResult] = useState<Result>(undefined);
  const mounted = useRef(true);

  useEffect(() => {
    const fn = async () => {
      const recordsKeys = Object.keys(Record).map((e) => Record[e]);

      const keys = await Promise.all(
        recordsKeys.map((e) => getDomainKey(e + "." + domain, true))
      );

      const registries = await NameRegistryState.retrieveBatch(
        connection,
        keys.map((e) => e.pubkey)
      );

      // Remove trailling 0s
      const records = registries.map((e) => {
        if (e?.data) {
          const idx = e.data?.indexOf(0x00);
          e.data = e.data?.slice(0, idx);

          return e.data.toString();
        }
        // Record is not defined
        return undefined;
      });

      if (mounted.current) {
        setResult(records);
      }

      return () => (mounted.current = false);
    };

    fn().catch(console.error);
  }, [domain]);

  return result;
};

useRecordsV2

This hook returns the serialized or deserialized V2 records of the given domain name.

type Result = (string | undefined)[] | undefined;

/**
 * Returns the deserialized (or not) records V2 of the given domain name
 * @param connection The Solana RPC connection object
 * @param domain The domain name
 * @param records The list of records to fetch
 * @param deserialize Whether to deserialize the record content or not. Deserialization is done according to SNS IP-1
 * @returns Returns a list of records' content
 */

export const useRecordsV2 = (
  connection: Connection,
  domain: string,
  records: Record[],
  deserialize?: boolean
) => {
  return useAsync(async () => {
    // useAsync from the react-async-hook library
    const res = await getMultipleRecordsV2(connection, domain, records, {
      deserialize,
    });
    return res;
  }, [domain, ...records]);
};

useDomainsForUser

This hook can be used to retrieve all the domains owned by a user:

interface Result {
  pubkey: PublicKey;
  registry: NameRegistryState;
  reverse: string;
}

/**
 * This hook can be used to retrieve all the domains of a user
 * @param user The user to search domains for
 * @returns
 */
export const useDomainsForUser = (user: PublicKey) => {
  const { connection } = useConnection();
  const [result, setResult] = useState<Result[] | undefined>(undefined);
  const mounted = useRef(true);

  useEffect(() => {
    const fn = async () => {
      const domains = await getAllDomains(connection, user);
      const registries = await NameRegistryState.retrieveBatch(connection, [
        ...domains,
      ]);
      const reverses = await reverseLookupBatch(connection, [...domains]);
      const _result: Result[] = [];
      for (let i = 0; i < domains.length; i++) {
        _result.push({
          pubkey: domains[i],
          registry: registries[i]!,
          reverse: reverses[i]!,
        });
      }
      if (mounted.current) {
        setResult(_result);
      }

      return () => (mounted.current = false);
    };

    fn().catch(console.error);
  }, [user.toBase58()]);

  return result;
};

usePrimaryDomain

💡 Primary domains used to be called favorite domains.

Primary domains allow users who own several domains to select one of them as their default identity.

This primary domain should be used by default by dApps.

import { useEffect, useRef, useState } from "react";
import { useConnection } from "@solana/wallet-adapter-react";
import { PublicKey } from "@solana/web3.js";
import { reverseLookup } from "@bonfida/spl-name-service";
import { FavouriteDomain, NAME_OFFERS_ID } from "@bonfida/name-offers";

type Result = string | undefined;

export const usePrimaryDomain = (user: PublicKey) => {
  const { connection } = useConnection();
  const [result, setResult] = useState<Result>(undefined);
  const mounted = useRef(true);

  useEffect(() => {
    const fn = async () => {
      const [favKey] = await FavouriteDomain.getKey(NAME_OFFERS_ID, user);
      const favourite = await FavouriteDomain.retrieve(connection, favKey);

      const reverse = await reverseLookup(connection, favourite.nameAccount);

      if (mounted.current) {
        setResult(reverse);
      }

      return () => (mounted.current = false);
    };

    fn().catch(console.error);
  }, [user.toBase58()]);

  return result;
};

useProfilePic

Users can set a profile picture using the pic record of their primary domain name. This record holds the URI to their profile picture.

import { useEffect, useRef, useState } from "react";
import { useConnection } from "@solana/wallet-adapter-react";
import { PublicKey } from "@solana/web3.js";
import { getDomainKey, NameRegistryState } from "@bonfida/spl-name-service";

export const useProfilePic = (user: PublicKey) => {
  const { connection } = useConnection();
  const primary = usePrimaryDomain(user);
  const [result, setResult] = useState<Result>(undefined);
  const mounted = useRef(true);

  useEffect(() => {
    const fn = async () => {
      if (!primary) {
        return setResult(undefined);
      }

      const registry = await getPicRecord(connection, primary.toBase58());

      if (!registry.data) {
        return setResult(undefined);
      }

      if (mounted.current) {
        setResult(registry.data.toString("utf-8"));
      }

      return () => (mounted.current = false);
    };

    fn().catch(console.error);
  }, [user.toBase58(), primary]);

  return result;
};

Vue SDK

The package @bonfida/sns-vue contains a set of useful Vue composables to help you integrate SNS into your dApp. A fully working Vue app can be found on Github with a working example for each of the composable:

Tokenization

Domain names are not SPL tokens, however, they can be tokenized in NFTs that follow the Metaplex standard. It's only recommended to tokenize your domain if you want to resell your domain on an NFT market place like Magic Eden, Hyperspace, Tensor, or SolSniper.

The smart contract handling the tokenization of domain names can be found here: Name tokenizer

JS example

To retrieve all currently tokenized domain names use the retrieveNfts function. This function returns an array of currently tokenized domains as public keys, and contains all the mints of the tokenized domain name.

import { retrieveNfts } from "@bonfida/spl-name-service";

const nfts = await retrieveNfts(connection);

To retrieve all of the tokenized domain names of a specific owner use getTokenizedDomains. Function returns an array of public keys, their reverses, and mints.

import { getTokenizedDomains } from "@bonfida/spl-name-service";

const tokenizedDomains = await getTokenizedDomains(connection, owner);

To retrieve the public key of the owner of a specific NFT that represents a tokenized domain name, use the retrieveNftOwner function.

import { retrieveNftOwner } from "@bonfida/spl-name-service";

const owner = await retrieveNftOwner(connection, nameKey);

Registration

Deprecated: The registerDomainName function is being deprecated as it relies on the older Pyth smart contract on Solana, which is scheduled to be sunsetted at the end of May 2024. Please transition to using registerDomainNameV2 for future domain registrations.

Easily integrate SNS domain registrations into your applications using our SNS Widget React Component. See a default usage example below. You can also customize the widget to fit your unique needs. More information is available here: https://github.com/Bonfida/sns-widget/blob/main/README.md

import Widget from "@bonfida/sns-widget";
// Apart from the component itself, you also need to import styles separately
import "@bonfida/sns-widget/style.css";

// Link to public RPC for Solana connection. Solana provides free public RPCs
// with rate limiters, so you might want to use your own RPC Node provider
const PUBLIC_RPC = "https://api.mainnet-beta.solana.com";

export const Component = () => {
  return <Widget endpoint={PUBLIC_RPC} />;
};

You can also opt to create registration instructions via our SDK or API, both methods are equivalent. To register a domain you will have to specify the following:

  • Domain names
  • Space (between 1kb and 10kb)
  • The public key of the buyer

Domain names can be registered with the following tokens: USDC, USDT, wSOL, FIDA, mSOL, BONK and BAT.

Note: The registration instruction does not support native SOL but wrapped SOL

Asset pricing

Pyth Network

Token pricing data during domain registration is provided to us by our friends at Pyth Network. Learn more about their Blockchain Oracle at Pyth Network.

SDK

Unregistered domains can be registered using the SDK @bonfida/spl-name-service with the following instructions:

import { registerDomainNameV2 } from "@bonfida/spl-name-service";

const name = "bonfida"; // We want to register bonfida.sol
const space = 1 * 1_000; // We want a 1kB sized domain (max 10kB)

const buyer = new PublicKey("..."); // Publickey of the buyer
const buyerTokenAccount = new PublicKey("..."); // Publickey of the token account of the buyer (USDC)

const ix = await registerDomainNameV2(name, space, buyer, buyerTokenAccount);

// sign and send the instruction

API

Registration instructions can also be created via API (equivalent to using the SDK):

GET https://sns-sdk-proxy.bonfida.workers.dev/register?buyer={buyer}&domain={domain}&space={space}&serialize={serialize}

This endpoint can be used to register domain for buyer. Additionaly, the buyer dans specify the space it wants to allocate for the domain account. In the case where serialize is true the endpoint will return the transaction serialized in the wire format base64 encoded. Otherwise it will return the instruction in the following format: { programId: string, keys: {isWritable: boolean, isSigner: boolean, pubkey: string}[], data: string } where data is base64 encoded.

This endpoint also supports the optional mint parameter to change the mint of the token used for registration (currently supports USDC, USDT, FIDA and wSOL), if mint is omitted it defaults to USDC.

Registration via CPI

Add the sns-registrar dependency to your Cargo.toml:

[dependencies]
sns-registrar = { git = "ssh://git@github.com/Bonfida/sns-registrar.git", features = ["no-entrypoint"] }

In your code make sure to import the required functions

#![allow(unused)]
fn main() {
use sns_registrar::{instruction_auto::create_split_v2, processor::create_split_v2};
}

The main function for domain registration is create_split_v2. Here's how to use it:

#![allow(unused)]
fn main() {
let registration_instruction = create_split_v2(
    sns_registrar::ID,
    create_split_v2::Accounts {
        naming_service_program: accounts.spl_name_service.key,
        root_domain: accounts.root_domain.key,
        name: accounts.name_account.key,
        reverse_lookup: accounts.reverse_lookup.key,
        system_program: accounts.system_program.key,
        central_state: accounts.sns_registrar_central_state.key,
        buyer: accounts.buyer.key,
        domain_owner: accounts.new_domain_owner.key,
        buyer_token_source: accounts.buyer_token_source.key,
        pyth_feed_account: accounts.pyth_feed_account.key,
        vault: accounts.bonfida_fee_vault.key,
        spl_token_program: accounts.spl_token_program.key,
        rent_sysvar: accounts.rent_sysvar.key,
        state: accounts.sns_registrar_state.key,
        referrer_account_opt: None,
        fee_payer: accounts.fee_payer.key,
    },
    create_split_v2::Params {
        name: domain_name,
        space: 0,
        referrer_idx_opt: None,
    },
);
}

Important Account Keys:

  • sns_registrar: Constant key jCebN34bUfdeUYJT13J1yG16XWQpt5PDx6Mse9GUqhR
  • naming_service_program: Constant key namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX
  • root_domain: Constant key 58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx
  • central_state: Constant key 33m47vH6Eav6jr5Ry86XjhRft2jRBLDnDgPSHoquXi2Z
  • name: Computed using sns_registrar::utils::get_name_key("something", None)?; for registering something.sol
  • reverse_lookup: Computed using sns_registrar::utils::get_reverse_key(accounts.name_account.key, None)?;
  • pyth_feed_account: Derived based on the mint used for registration. See Pyth price feed derivation and token utilities
  • vault: ATA of 5D2zKog251d6KPCyFyLMt3KroWwXXPWSgTPyhV22K2gR for the mint used in registration
  • state: PDA derived as: Pubkey::find_program_address(&[&accounts.name_account.key.to_bytes()], accounts.sns_registrar.key).0
  • domain_owner and buyer can be different this flexibility enables scenarios where one account pays for the registration while another becomes the owner of the domain.
  • buyer_token_source is the associated token account of buyer for the mint used for the registration and used to pay the registration

After creating the instruction, invoke it like this:

#![allow(unused)]
fn main() {
invoke_signed(
    &registration_instruction,
    &[
        accounts.sns_registrar.clone(),
        accounts.spl_name_service.clone(),
        accounts.root_domain.clone(),
        accounts.name_account.clone(),
        accounts.reverse_lookup.clone(),
        accounts.system_program.clone(),
        accounts.sns_registrar_central_state.clone(),
        accounts.central_state.clone(),
        accounts.fee_payer.clone(),
        accounts.destination_token_vault.clone(),
        accounts.pyth_feed_account.clone(),
        accounts.bonfida_fee_vault.clone(),
        accounts.spl_token_program.clone(),
        accounts.rent_sysvar.clone(),
        accounts.sns_registrar_state.clone(),
        accounts.new_domain_owner.clone(),
    ],
    &[&signer_seeds],
)?;
}
  • The space parameter in create_split_v2::Params is set to 0 in this example.
  • The referrer_account_opt and referrer_idx_opt are set to None here. Use these for referral functionality if required.

Media kit

Follow these guidelines when representing domain names on your website.

Domains names can be represented in two ways: inline and card.

Below are two examples of the inline and card representations.

  1. Inline

inline

  1. Card

card

Rare domains

Rare domains are domains with 4 or less characters. These domains must be represented with the following gradient.

Gradient:

rare-wide

Card representation

rare-card

Inline representation

rare-inline

Emoji domains

Emoji domains are domains that contain an emoji. These domains must be represented with the following gradient.

emoji-wide

Card representation

emoji-card

Inline representation

emoji-inline

Regular domains

Regular domains are domains that have 5 or more characters and do not contain an emoji. These domains must be represented with the following gradient.

regular-wide

Card representation

regular-card

Inline representation

regular-inline

Deleting a domain

🚨 Deleting a domain is irreversible

🚨 Deleting a domain will make you lose ownership of the subdomains and records related to the domain

🚨 Deleting domain names is not recommended

Domain names can be deleted using the deleteInstruction, below is a NodeJS example:

import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js";
import {
  getDomainKey,
  NAME_PROGRAM_ID,
  deleteInstruction,
} from "@bonfida/spl-name-service";
import { signAndSendInstructions } from "@bonfida/utils";

// bonfida.sol
const domain = "bonfida.sol"; // With or without .sol

const connection = new Connection(clusterApiUrl("mainnet-beta"), "processed");

const wallet = Keypair.fromSecretKey(...);

const deleteDomain = async () => {
  const { pubkey } = await getDomainKey(domain);

  const ix = deleteInstruction(
    NAME_PROGRAM_ID,
    pubkey,
    wallet.publicKey,
    wallet.publicKey
  );

  const tx = await signAndSendInstructions(connection, [], wallet, [ix]);

  console.log(`Deleted domain ${tx}`);
};

deleteDomain();

Deleting subdomains

Subdomains can be deleted using the deleteInstruction, below is a NodeJS example:

import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js";
import {
  getDomainKey,
  NAME_PROGRAM_ID,
  deleteInstruction,
} from "@bonfida/spl-name-service";
import { signAndSendInstructions } from "@bonfida/utils";

// dex.bonfida.sol
const domain = "dex.bonfida.sol"; // With or without .sol

const connection = new Connection(clusterApiUrl("mainnet-beta"), "processed");

const wallet = Keypair.fromSecretKey(...);

const deleteSubDomain = async () => {
  const { pubkey } = await getDomainKey(domain);

  const ix = deleteInstruction(
    NAME_PROGRAM_ID,
    pubkey,
    wallet.publicKey,
    wallet.publicKey
  );

  const tx = await signAndSendInstructions(connection, [], wallet, [ix]);

  console.log(`Deleted subdomain ${tx}`);
};

deleteSubDomain();

Subdomains

Subdomains in Solana Name Service (SNS) are similar to .sol domains but have a different parent. They can be considered as normal domains but from a different Top-Level Domain (TLD). For instance, something.example.sol can be considered the something subdomain of example.sol or a domain from the TLD example.sol.

Key Characteristics of Subdomains

  • Parent Ownership: The owner of the parent domain retains the ability to transfer subdomains without the signature from the owner of the subdomains. This is a unique feature of subdomains in SNS.

  • Limited Wallet Support: Subdomains have limited wallet support. This means that not all wallets may support transactions involving subdomains.

  • Feature Support: Subdomains support the same features as main domains. This includes the ability to transfer and update data.

Creating a subdomain

This code snippet creates a subdomain and its reverse lookup account:

import { createSubdomain } from "@bonfida/spl-name-service";

// The subdomain to create with or without .sol e.g something.bonfida.sol or something.bonfida
const subdomain = "something.bonfida.sol";

// The owner of the parent domain
const owner = new PublicKey("...");

const ix = createSubdomain(connection, subdomain, owner);

// Sign and send the tx...

The created subdomains will initially be owned by the parent owner. A subdomain can be created and transfered inside the same transaction.

Transferring a Subdomain

Subdomains can be transferred using the transferSubdomain instruction. Here is an example of how the subdomain owner can transfer a subdomain:

import { transferSubdomain } from "@bonfida/spl-name-service";

// ..

// Subdomains to transfer
const subdomain = "something.bonfida.sol";

// New owner of the domain
const newOwner = new PublicKey("...");

// Whether the parent name owner is signing the transfer
const isParentSigner = false;

const ix = await transferSubdomain(
  connection,
  subdomain,
  newOwner,
  isParentSigner
);

// sign and send instruction

The parent name owner can trigger a transfer by setting the isParentSigner flag to true and signing the transaction.

Resolving Subdomains

Subdomains of a .sol domain can be resolved using the findSubdomains function. Here is an example of how to resolve subdomains:

import { findSubdomains } from "@bonfida/spl-name-service";

// Public key of bonfida.sol
const parentKey = new PublicKey("Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb");

// Assuming that bonfida.sol has the following subdomains
// - sub_1.bonfida.sol
// - sub_2.bonfida.sol

const subdomains: string[] = await findSubdomains(connection, parentKey); // [sub_1, sub_2]

Deleting a Subdomain

Subdomains can be deleted using the deleteInstruction function. Here is an example of how to delete a subdomain:

import { Keypair, clusterApiUrl } from "@solana/web3.js";
import {
  getDomainKey,
  NAME_PROGRAM_ID,
  deleteInstruction,
} from "@bonfida/spl-name-service";
import { signAndSendInstructions } from "@bonfida/utils";

// dex.bonfida.sol
const domain = "dex.bonfida.sol"; // With or without .sol

const wallet = Keypair.fromSecretKey(...);

const deleteSubDomain = async () => {
  const { pubkey } = await getDomainKey(domain);

  const ix = deleteInstruction(
    NAME_PROGRAM_ID,
    pubkey,
    wallet.publicKey,
    wallet.publicKey
  );

  const tx = await signAndSendInstructions(connection, [], wallet, [ix]);

  console.log(`Deleted subdomain ${tx}`);
};

deleteSubDomain();

💡 While the deletion of a subdomain is a reversible action, it's important to be mindful of potential unintended consequences.

In conclusion, subdomains in SNS are a powerful feature that allows for more granular control and organization of domain names. However, they come with their own set of considerations such as limited wallet support and different ownership rules.

Devnet

Testing can be done on devnet with many of the same functions and methods previously described in this chapter, using the devnet module of the JavaScript SDK. A connection object created from a devnet RPC URL will need to be passed to these functions.

import { devnet } from "@bonfida/spl-name-service";

// Use Solana devnet RPC URL or a custom RPC URL
const connection = new Connection("https://api.devnet.solana.com");

Register Devnet Domains

Many of the utility and binding functions in the devnet module will require existing devnet domain names. Use the registerDomainNameV2 binding to register domains on devnet to be used in testing.

const ix = await devnet.bindings.registerDomainNameV2(
  connection,
  "devnet-test-5", // The name of the domain you want to register
  1_000,
  publicKey, // PublicKey of fee payer
  getAssociatedTokenAddressSync(NATIVE_MINT, publicKey, true), // import from @solana/spl-token
  NATIVE_MINT
);

// Sign and send instruction

Utils

The devnet module contains utility functions for lookup and derivation tasks for usage with devnet out of the box. An example of the reverseLookup function which looks up a human readable domain from the public key of a domain name registry, is below.

// Public key of bonfida.sol
const domainKey = new PublicKey("Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb");

const domainName = await devnet.utils.reverseLookup(connection, domainKey); // bonfida

Constants

A set of devnet constants are also provided for use with custom functions as well as with the bindings described below. An example of deriving the NAME_PROGRAM_ID is below.

const programId = devnet.constants.NAME_PROGRAM_ID;

Bindings

For more in depth domain name interactions like creating, updating, deleting, or transfering domains and records, bindings from the devnet module can be used. An example using the createNameRegistry function is below.

// Domain name to transfer
const domain = "devnet-test-5";

// New owner of the domain
const newOwner = new PublicKey("...");

// The .sol TLD
const nameParent = dev.constants.ROOT_DOMAIN_ACCOUNT;

const ix = await devnet.bindings.transferNameOwnership(
  connection,
  domain,
  newOwner,
  undefined, // Optional class of the domain name, if it exists
  nameParent
);

// Sign and send instruction

Examples

Below is a list of examples that can be used to test your code:

  1. Key derivations

Key derivations

The following examples can be used to test your derivations:

Domain name

  • solana.sol:
    • Public key: 9TdKztwu2cS3JConXYEwqscjuCixgQqFq1pAiPQEbkSy (Explorer link)
    • Reverse key: AceeTYYPKzfmEd9uht5cB9ATMFEjJPcG1VLCRvgiV4fy (Explorer link)
  • bonfida.sol:
    • Public key: Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb (Explorer link)
    • Reverse key: DqgmWxe2PPrfy45Ja3UPyFGwcbRzkRuwXt3NyxjX8krg (Explorer link)
  • 01.sol:
    • Public key: 8nZ7dyd6fFSiHTV5qUCNz6kMLzVcgKgHVsDvE8AvPyq9 (Explorer link)
    • Reverse key: GFG4HcxU5URRfBxFLV9xvmJo6mdHCunEm2DRHc3aNtfL (Explorer link)

Subdomain

  • dex.solana.sol:

    • Public key: F1A1iznr16YfnWAnLXLKvS3aStm4VHwkheMD786KW8Ca (Explorer link)
    • Reverse key: 9gT93HfjZVHT8xHrJvzV7eRFs5bnXhPAsEpxvgvCsDaw (Explorer link)
  • dex.bonfida.sol:

    • Public key: HoFfFXqFHAC8RP3duuQNzag1ieUwJRBv1HtRNiWFq4Qu (Explorer link)
    • Reverse key: 6tAdEpjsrzHuRqJW3XMXEV7DFyCWW4giW6mW4bgvhcYV (Explorer link)

Record

  • solana.sol IPFS record:

    • Public key: GvncrrXMGsBMtwg2uh8FShUqLS4GLtYrmBeCdX5PEbPR (Explorer link)
  • bonfida.sol URL record:

    • Public key: CvhvqcxBbA4UdWuJFDMuuC4XbpCrAd9gidpW5wxEsjg5 (Explorer link)

💡 Difference between records and subdomains

In practice, let us consider the name foo.sol . If we want to find the domain's A record, containing an associated IPv4 address, then we can find it by querying \1A.foo.sol, with \1 the character of code value 1. The specification makes use of this prefix in order to differentiate between actual domains and records, which means that it is still possible to use the A.foo.sol subdomain with no collision. In addition to this, the special \1.foo.sol is reserved to hold the list of all currently initialized records for a given subdomain

Note: \0 and \1 are convenient notations for:

  • \0 = \x00.
  • \1 = \x01.

Twitter

This following sections provide information on the following:

TLD

The Twitter handle TLD is

export const TWITTER_ROOT_PARENT_REGISTRY_KEY = new PublicKey(
  "4YcexoW3r78zz16J2aqmukBLRwGq6rAvWzJpkYAXqebv"
);

The .twitter TLD is owned by the root TLD and all twitter handles are subdomains of the .twitter TLD

twitter-tld

Twitter handle registration

Twitter handles can be registered here and a detailed guide can be found on the Community Help Center

Direct look up

direct-reverse

To find the Twitter handle associated to a public key

import { getHandleAndRegistryKey } from "@bonfida/spl-name-service";

const pubkey = new PublicKey("FidaeBkZkvDqi1GXNEwB8uWmj9Ngx2HXSS5nyGRuVFcZ");

const [handle] = await getHandleAndRegistryKey(connection, pubkey);

Reverse look up

direct-reverse

To find the public key associated to a Twitter handle

import { getTwitterRegistry } from "@bonfida/spl-name-service";

const handle = "bonfida";

const registry = await getTwitterRegistry(connection, handle);
const owner = registry.owner.toBase58();

SNS API

⚠️ This API is not meant to replace the blockchain as the source of truth. The blockchain should always be considered as the only source of truth. This API is only a snapshot of the blockchain at a certain point in time and might be stale by a few seconds/minutes.

  • The base URL of the API is:
https://sns-api.bonfida.com
  • The platform enum returned by sales endpoint is defined as below:
enum PlatformEnum {
    None = 0,
    MagicEden = 1,
    FixedPrice = 2,
    UnsolictedOffer = 3,
    AuctionClaim = 4,
    AuctionPlaceBid = 5,
    Hyperspace = 6,
    SMBMarketplace = 7,
    Solanart = 8,
    Fractal = 9,
    Holaplex = 10,
    DegenApeMarketplace = 11,
    GooseFx = 12,
    SolanartAH =13,
    CoralCube = 14,
    AlphaArt = 15,
    DigitalEyes = 16,
    SolSea = 17,
    ExchangeArt = 18,
    Grape = 19,
    OpenSea = 20,
    Metaplex = 21,
    YAWWW = 22,
    RaribleAH = 23,
    Solvent = 24,
    TiexoT0 = 25,
    TiexoT1 = 26,
    TiexoT2 = 27,
    TiexoT3 = 28,
    TiexoT4 = 29,
    CoralCubeV2 = 30,
    Elixir = 31,
    Tensor = 32,
    GoatSwap = 33,
    Hadeswap = 34,
    AuctionHouse = 35,
    CategoryOffer = 36,
    NightMarket = 37,
    Cardinal = 38,
    MECCSwap = 39,
    SniperMarket = 40,
    Okx = 41,
}
  • All timestamps are in seconds

Domains

History

This endpoint can be used to retrieve the transaction history for all registered domains. Since the response payload will be large, the below optional query parameters can be passed.

limit - This is the number of records per response up to 200. 200 is also the default if no limit parameter is passed.

start_time - The start in unix timestamp from when the domain transaction records should be obtained.

end_time - The end in unix timestamp to when the domain transaction records should be obtained.

last_token - This token is used to fetch the next set of responses and is recieved from the API response. After the initial API call, you can pass this as a parameter for further responses.

Request

GET v2/domains/history?limit={limit}&start_time={start_time}&end_time={end_time}&last_token={last_token}

Response

{
  "data": [
    {
      "operation": 1,
      "unix_timestamp": 1622592000,
      "tx_signature": "27EjmB4NdsRKMNkeYeF4rva...",
      "domain_key": "4cQ3zUeardJweGTnk...",
      "pre_tx_owner": "5fEPywJMxeP2HBo7JyBUv1G...",
      "post_tx_owner": "CUcYT9ZoBXET88o...",
      "transaction_type": 1,
      "usd_price": 152,
      "price": 1,
      "quote_mint": "So11111111111111111111111111111111111111112"
    }
  ],
  "last_token": "1622592000:abcdef1234567890:domain1"
}

The operation property in the response has the structure below. The transaction_type property correlates to operation where a value of 0 indicates a registration, and a value of 1 indicates a sale or transfer. For other operations, the transaction_type is null.

enum Operation {
  Create, // registration (0)
  Transfer, // sale or transfer (1)
  Update, // update data in a name record (2)
  Delete, // delete or burn a domain (3)
  Realloc, // change the domain storage size (4)
}

Categories

Get the list of categories

This endpoint can be used to retrieve the list of all categories.

Request

GET /categories/list

Response

{
  "success": true,
  "result": [
    "english-adjectives",
    "double-emoji"
    // ...
  ]
}

Get the domains of a category

This endpoint can be used to retrieve the list of domains of a category.

Request

GET /categories/list/{category}

Response

{
  "success": true,
  "result": [
    "qatar",
    "uganda"
    // ...
  ]
}

Get categories statistics

This endpoint returns stats for all the categories.

Request

GET /categories/stats?start_time={start_time}&end_time={end_time}

Response

{
  "success": true,
  "result": [
    {
      "category_name": "0x999-club",
      "min_sale": 7.8846874,
      "max_sale": 80.0,
      "avg_price": 43.94234371185303,
      "volume": 87.88469,
      "owners": 1,
      "supply": 2
    }
    // ...
  ]
}

Get statistics for a category

This endpoint returns stats for a given category.

Request

GET /categories/stats/{category}?start_time={start_time}&end_time={end_time}

Response

{
  "success": true,
  "result": [
    {
      "min_sale": 7.8846874,
      "max_sale": 80.0,
      "avg_price": 43.94234371185303,
      "volume": 87.88469,
      "owners": 1,
      "supply": 2
    }
  ]
}

Get floors

This endpoint returns the current floors for all categories. Floor prices are given in USD value.

Request

GET /categories/floors

Response

{
  "success": true,
  "result": {
    "0x999-club": 21.839999628067016,
    "4-letter-dictionary": 101.39999999999999
    // ...
  }
}

Get floor for a category

This endpoint returns the current floor for a given category. Floor prices are given in USD value.

Request

GET /categories/floors/{category}

Response

{
  "success": true,
  "result": 21.839999628067016
}

Get supply

This endpoint can be used to retrieve the number of registered and unregistered domains of a category.

Request

GET /categories/supply

Response

{
  "success": true,
  "result": {
    "0x999-club": {
      "total": 1000,
      "registered": 38,
      "unregistered": 962
    }
    // ...
  }
}

Get top categories by volume

This endpoint returns the top 10 categories by volume between start_time and end_time.

Request

GET /categories/top?start_time={start_time}&end_time={end_time}

Response

{
  "success": true,
  "result": [
    {
      "category_name": "10k-club",
      "volume": 177737.16
    }
  ]
}

Get owners

Request

This endpoint returns the public keys owning domains for a given category and the number of domains they own.

GET /categories/owners/{category}

Response

{
  "success": true,
  "result": [
    {
      "owner_key": "1BWutmTvYPwDtmw9abTkS4Ssr8no61spGAvW1X6NDix",
      "nb_domains": 38
    }
  ]
}

Users

Domains Owned

This endpoint can be used to retrieve the domains owned by a list of user public keys. You may include up to 20 public keys in a comma seperated list.

Request

GET /v2/user/domains/{pubkeys}

Response

{
  "FMmaHPDL47V1gXsfh9WjgAT7Er3dfDvarQubTU1Jxc1r": ["03800", "best-intern"],
  "3f9fRjLaDSDVxd26xMEm4WuSXv62cGt5qVfEVGwMfTz6": ["00378", "02112", "11441"]
}

Primary domains

💡 Primary domains used to be called favorite domains.

This endpoint can be used to retrieve the primary domains of a list of user public keys. You may include up to 20 public keys in a comma separated list. Results are cached, and refreshed every 5 minutes. If you're integrating SNS into your dApp, we strongly recommend using primary domains since users have selected these domains specifically to represent their identities apart from other domains they may own.

Request

GET /v2/user/primary-domains/{pubkeys}

Response

{
  "FMmaHPDL47V1gXsfh9WjgAT7Er3dfDvarQubTU1Jxc1r": "best-intern",
  "3f9fRjLaDSDVxd26xMEm4WuSXv62cGt5qVfEVGwMfTz6": "11441"
}

User Listings

This endpoint can be used to retrieve the domains of a user and their listing details. Please see thePlatformEnum definiton for details on availability id.

Request

GET /v2/user/listings/{pubkey}

Response

[
  {
    "domain": "00378",
    "availability_id": 0,
    "price": null,
    "quote_mint": "So11111111111111111111111111111111111111112",
    "usd_price": null,
    "categories": ["100k-club"],
    "last_activity": {
      "price": 0.18,
      "quote_mint": "So11111111111111111111111111111111111111112",
      "usd_price": 18.4365
    }
  },
  {
    "domain": "02112",
    "availability_id": 2,
    "price": 10.0,
    "quote_mint": "So11111111111111111111111111111111111111112",
    "usd_price": 810.5181,
    "categories": ["100k-club"],
    "last_activity": {
      "price": 0.25,
      "quote_mint": "So11111111111111111111111111111111111111112",
      "usd_price": 19.401249
    }
  }
]

Domains and Categories

This endpoint can be used to retrieve the list of domains owned by a user, as well as their corresponding categories.

Request

GET /v2/user/category-domains/{pubkey}

Response

[
  { "domain_name": "00378", "category_name": "100k-club" },
  { "domain_name": "3231", "category_name": "10k-club" }
]

Listings

All marketplace listings

This endpoint can be used to retrieve the details of listings across marketplaces such as Sns.id, MagicEden, Tensor, and SolSniper.

The endpoint takes a JSON body with a params object containing optional filters:

  • lang - Language filter. Language struct shared below.
  • palindrome - Boolean filter for palindromic domains.
  • emoji - Boolean filter for domains containing emojis.
  • rare - Boolean filter for rare domains.
  • digits_only - Boolean filter for domains with only digits.
  • letters_only - Boolean filter for domains with only letters.
  • min_len - Integer filter for minimum length of domain names.
  • max_len - Integer filter for maximum length of domain names.
  • mints - List of token mints as an array of strings.
  • min_price - Minimum price filter as a floating point integer.
  • max_price - Maximum price filter as a floating point integer.
  • start_with - String filter for domains that start with a specific string.
  • end_with - String filter for domains that end with a specific string.
  • contain - String filter ilter for domains that contain a specific string.
  • categories - List of categories to filter by as an array of strings.
  • page_size - Number of listings per page (default: 100, max: 100).
  • page - Page number (default: 1).
  • order_by - UsdPriceAsc, UsdPriceDesc, DomainAsc, DomainDesc, Random as strings.

Request

POST /v2/listings/listings-v3 -H 'Content-Type: application/json'  -d '{ "params": { "page_size": 10, "contain": "0" } }'

Response

{
  "total": 150,
  "data": [
    {
      "domain": "exampledomain",
      "price": 2,
      "quote_mint": "So11111111111111111111111111111111111111112",
      "availability_id": 2,
      "usd_price": 320,
      "metadata": {
        "length": 5,
        "lang": 1,
        "palindrome": false,
        "emoji": false,
        "rare": false,
        "digits_only": false,
        "letters_only": true
      }
    }
  ],
  "page_size": 100,
  "total_pages": 2,
  "page": 1
}

Listing Details

This endpoint can be used to retrieve the listing details of a specific domain. The response is a JSON object with the below structure if the domain is listed, or null if the domain is not.

KeyTypeDescription
dStringThe domain name
pFloat (f32)The price of the domain
qStringThe token mint of the listing (i.e the currency)
aEnum (u8)The availability ID (see PlatformEnum definition)
lEnum (u8)Language code (see definition below)
upFloat (f32)The price in USD
eBooleanIndicates if the domain has an emoji
rBooleanIndicates if the domain is rare
doBooleanIndicates if the domain contains digits only
loBooleanIndicates if the domain contains letters only
leIntegerThe length of the domain name
fpBooleanIndicates if the listing is a fixed price offer
meBooleanIndicates if the domain is listed on Magic Eden
paBooleanIndicates if the domain is a palindrome
caArray (Vec<String>)A list of categories the domain belongs to

Request

GET /v2/listings/listing/{domain}

Response

{
  "d": "bonfida.sol",
  "p": 100.0,
  "q": "USDC",
  "a": 2,
  "l": 0,
  "up": 100.0,
  "e": false,
  "r": true,
  "do": false,
  "lo": true,
  "le": 7,
  "fp": true,
  "me": false,
  "pa": false,
  "ca": []
}
#![allow(unused)]
fn main() {
pub enum Language {
    English = 0,
    Cyrillic = 1,
    Chinese = 2,
    Japanese = 3,
    Emoji = 4,
    Unauthorized = 5,
    Korean = 6,
    Arabic = 7,
}
}

Sales

Last

This endpoint can be used to retrieve the list of recent sales. The max limit parameter is 500.

Request

GET /sales/last?limit={limit}

Response

{
  "success": true,
  "result": [
    {
      "unix_timestamp": 1663824910,
      "slot": 151782075,
      "domain_name": "wagb👌",
      "domain_key": "FqRocnogXTAwTnhYxRc4BA3uFkAChsDefed6nVWeD1Xe",
      "domain_auction_key": "GQJyiqBXq2HWnFXUWcp3pDmBYZEw3CjiYbkUJoZC6qT2",
      "domain_token_mint": "ESPZfWYWQZ3fJaxq7GjzkCzXKJPRMfnKc1dhkVQBZyt2",
      "bidder_key": "TG41WLDXx4ofZ52up4pEKQcDj1zQ4oX9LUop5qnUwQr",
      "price": 1.55,
      "quote_mint": "So11111111111111111111111111111111111111112",
      "usd_price": 47.999626,
      "tx_signature": "2WcrNobBtLrarFNrakkMad2eyVzfBTzCnHo2cJYgWdoUHmH9eMqZe9SzJr53m1A4BqPzmXL5WcExc4t4r5DmMTC3",
      "platform_id": 1,
      "successful": true
    }
    // ...
  ]
}

Registrations

This endpoint can be used to retrieve registrations between end_time and start_time. The max limit is 500.

Request

GET /sales/registrations?limit={limit}&end_time={end_time}&start_time={end_time}

Response

{
  "success": true,
  "result": [
    {
      "unix_timestamp": 1663789595,
      "slot": 151721528,
      "tx_signature": "2D9VPjN93j7YTx13oHN9sL2RDHbytLMfNmxTCBc8a57faomn4e2iF2QyUU1DLfdA9FYEJq1SzXmaC8p9FntLckUL",
      "domain_name": "meggadao",
      "domain_key": "FoidaZVWPYNCgRkthdJqnSQ82x7SLkSpBAypR7RVtFNU",
      "domain_auction_key": "HmGENkrhkA7ekmj9kKni4CJLJyifjzohPAV1wWTWuQFX",
      "domain_token_mint": "ExankJNcWwJoS4ZYe5Xuw8r7ioqAcg5XkbzWT6NJhsiA",
      "price": 48.769577,
      "quote_mint": "EchesyfXePKdLtoiZSL8pBe8Myagyy8ZRqsACNCFGnvp",
      "usd_price": 20.184021
    }
  ]
}

Leaderboard

This endpoint returns the top 100 sales ever.

Request

GET /sales/leaderboard

Response

{
  "success": true,
  "result": [
    {
      "domain_name": "53",
      "usd_price": 15999.0
    }
    // ...
  ]
}

Volumes

Secondary sales volume

This endpoint can be used to retrieve volumes for secondary sales accross all market places

Request

GET /sales/volumes/sales?start_time={start_time}&end_time={end_time}

Response

{
  "success": true,
  "result": [
    { "day": 1667174400, "volume": 115.98516 },
    { "day": 1667088000, "volume": 658.9297 }
  ]
}

Direct registration volume

This endpoint can be used to retrieve direct registrations volume

Request

GET /sales/volumes/registrations?start_time={start_time}&end_time={end_time}

Response

{
  "success": true,
  "result": [
    { "day": 1667174400, "volume": 115.98516 },
    { "day": 1667088000, "volume": 658.9297 }
  ]
}

Aggregated volume

This endpoint can be used to retrieve aggregated volume (secondary sales + direct registrations)

Request

GET /sales/volumes/all?start_time={start_time}&end_time={end_time}

Response

{
  "success": true,
  "result": [
    { "day": 1667174400, "volume": 115.98516 },
    { "day": 1667088000, "volume": 658.9297 }
  ]
}

Owners

Distribution

This endpoint can be used to retrieve the domains distribution (includes escrow wallets)

GET /owners/distribution

Response

{
  "success": true,
  "result": [
    { "nb_owners": 1, "nb_domains": 41896 }
    // ...
  ]
}

This endpoint can be used to retrieve the domains distribution (excludes escrow wallets)

GET /owners/distribution-exclude-escrows

Response

{
  "success": true,
  "result": [
    { "nb_owners": 1, "nb_domains": 41896 }
    // ...
  ]
}

Domains for owner

This endpoint can be used to retrieve the domain owned by a public key

GET /owners/{owner_key}/domains

Response

{
  "success": true,
  "result": [
    "pharmacy",
    "softball",
    "travelagent"
    // ...
  ]
}

Images

Domain images can vary based on the rarity of a domain, if a domain contains an emoji character, or if a user has purchased a custom domain background during one of our limited time ecosystem artist collaborations.

More information about images can be found here: Media Kit

Base URL

Please note the base URL for images differs from other endpoints.

  • The base URL of the API is:
https://image-api.bonfida.com

API response times can vary based on if a request for an image has been previously made. The image is generated upon the first request and then cached, greatly reducing subsequent response times.

Get a list of domain images

This endpoint can be used to retrieve images for a list of domains.

Request

GET /image?domain=foo&domain=bar

Response

{
  "result": [
    { "domain": "foo", "image": "https://..." },
    { "domain": "bar", "image": "https://..." }
    // ...
  ]
}

Marketplace

SNS Marketplace

The SNS Marketplace program makes it possible to integrate the purchase and sale of SNS domains directly into your own applications. Interact with the SNS Marketplace smart contract using our JS SDK linked below.

Deployment:

  • Program ID: 85iDfUvr3HJyLM2zcq5BXSiDvUWfw6cSE1FfNBo8Ap29
  • Auditor: Halborn
  • Audit report: here
  • JS SDK: NPM

This smart contract supports different types of sales:

  • Fixed price
  • Unsolicited
  • Category
  • P2P

The functions from our SDK detailed below will return instructions that you can use to build transactions.

Fixed price and unsolicited offers support the following tokens as quote currency: SOL, FIDA, USDC, USDT, mSOL, BONK, BAT, PYTH and bSOL.

All these listings can be accessed on sns.id

The SNS Marketplace also supports a referral system, allowing users to share 15% of the transaction fees. This feature enables participants to earn rewards by referring new users to the marketplace. In order to earn the portion of the fees, users must pass their wallet address in buyFixedPrice, acceptOffer and takeCategoryOffer

Fixed Price Offers

#![allow(unused)]
fn main() {
pub struct FixedPriceOffer {
    /// Account tag
    pub tag: Tag,
    /// Nonce
    pub nonce: u8,
    /// Name being sold
    pub name_account: Pubkey,
    /// Offer owner
    pub owner: Pubkey,
    /// Quote token used for offer
    pub quote_mint: Pubkey,
    /// Amount of the offer
    pub offer_amount: u64,
    // Offer amount token account destination
    pub token_destination: Pubkey,
}
}

Fixed Price Offers allow sellers to list domain names for sale at a predetermined price. Buyers can purchase these domain names by paying the specified amount.

Creating a fixed price offer is handled by the makeFixedPriceOffer function, as shown in the code snippet below:

const connection = new Connection("...");
const seller = new PublicKey("..."); // Public key of the seller i.e domain owner
const mint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC mint
const amount = 1 * 1e6; // Amount with decimals, here 1 USDC
const { pubkey: domainKey } = getDomainKeySync("something.sol"); // Domain public key

const ix = await makeFixedPriceOffer(
  connection,
  amount,
  mint,
  seller,
  domainKey,
  NAME_OFFERS_ID // The program ID that can be imported directly from our SDK
);

Buying a fixed price offer is facilitated through the buyFixedPrice function, which ensures the transfer of the domain to the buyer and the payment to the seller:

const connection = new Connection("...");
const buyer = new PublicKey("..."); // Public key of the offer buyer
const source = new PublicKey("..."); // Source of the funds used to purchase the offer. In case of SOL it's the same as `buyer`. If another token is used, it's the ATA of the buyer for the given mint.
const { pubkey: domainKey } = getDomainKeySync("something.sol"); // Domain public key
const referrer: PublicKey | undefined = undefined; // Optional referrer

// Use a util function from our SDK to get fixed price offers by name, by owner, or all fixed price offers.
const fixedPriceOffers = await getFixedPriceOffersForName(
  connection,
  domainKey
);

// This example arbitrarily selects the first fixed price offer in the list. Filter offers based on the your needs.
const fixedPriceKey = fixedPriceOffers[0].pubkey;

const ix = await buyFixedPrice(
  connection,
  fixedPriceKey,
  buyer,
  source,
  NAME_OFFERS_ID,
  referrer
);

Fixed price offers can be cancelled using the cancelFixedPriceOffer function.

Unsolicited Offers

#![allow(unused)]
fn main() {
pub struct Offer {
    /// Tag
    pub tag: Tag,
    /// Nonce
    pub nonce: u8,
    /// Name account of the offer
    pub name_account: Pubkey,
    /// Offer owner
    pub owner: Pubkey,
    /// Quote token used for offer
    pub quote_mint: Pubkey,
    /// Amount of the offer
    pub offer_amount: u64,
    /// Escrow account key
    pub escrow: Pubkey,
}
}

Unsolicited offers allow buyers to propose a purchase price for a domain not listed for sale. The domain owner can then accept or ignore the offer.

Placing an unsolicited offers is handled by the makeOffer function

const mint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC mint
const amount = 1 * 1e6; // Amount with decimals, here 1 USDC
const { pubkey: domainKey } = getDomainKeySync("something.sol"); // Domain public key
const owner = new PublicKey("..."); // Owner of the unsolicited offer
const tokenSource = new PublicKey("..."); // Token source used to place the offer.

const ix = await makeOffer(
  amount,
  domainKey,
  owner,
  mint,
  tokenSource,
  NAME_OFFERS_ID
);

An unsolicited offer can be accepted by the domain owner using the acceptOffer function:

const connection = new Connection("...");
const domainOwner = new PublicKey("..."); // Current domain owner
const { pubkey: domainKey } = getDomainKeySync("something.sol"); // Domain public key
const offerEscrow = new PublicKey("..."); // PDA used to store the funds of the offer, the address is written in the state
const destination = new PublicKey("..."); // The token account used to receive the funds from the escrow
const referrer: PublicKey | undefined = undefined; // Optional referrer

// Use a util function from the SDK to get offers by domain name, by domain owner, etc.
const offers = await getOffersForName(connection, "something.sol");

// This example arbitrarily selects the first offer in the list. Filter offers based on your needs.
const offerKey = offers[0].pubkey;
const offerOwner = offers[0].owner;

const ix = await acceptOffer(
  connection,
  NAME_OFFERS_ID,
  offerKey,
  offerOwner,
  publicKey,
  domainKey,
  offerEscrow,
  destination,
  referrer
);

Unsolicited offers can be canceled using the cancelOffer function.

Category offers

#![allow(unused)]
fn main() {
pub struct CategoryOffer {
    // Account tag
    pub tag: Tag,
    // The PDA nonce
    pub nonce: u8,
    // The total number of domains requested
    pub nb_domains: u64,
    // The SOL price per domain
    pub sol_price: u64,
    // The category of the offer
    pub category: Pubkey,
    // The owner of the offer
    pub owner: Pubkey,
    // Timestamp at which the offer was created
    pub created_at: u64,
}
}

Category Offers allow buyers to bid on an entire domain category. Sellers can accept these offers, selling domains within the specified category.

The creation of a category offer is managed by the makeCategoryOffer function, which specifies the number of domains, the SOL price per domain, and the category:

import { CATEGORIES } from "@bonfida/sns-categories"; // Map of current categories

const amount = 10 * LAMPORTS_PER_SOL; // Amount of the offer in lamports here 10 SOL
const nbDomains = 10; // Number of domains the buyer wants to buy
const buyer = new PublicKey("...");

// Filter CATEGORIES to find the categoryKey which is the Public key of the category.
const categoryKey = [...CATEGORIES].find(
  ([, value]) => value === "999-club"
)?.[0];

const ix = await makeCategoryOffer(
  amount,
  nbDomains,
  categoryKey,
  NAME_OFFERS_ID,
  buyer
);

Taking a category offer is facilitated through the takeCategoryOffer function, allowing sellers to sell domains within the category at the specified price.

const connection = new Connection("...");
const { pubkey: domainKey } = getDomainKeySync("999.sol"); // Domain public key
const memberKey = CategoryMember.findKey("999", categoryKey); // Membership of the domain to the category
const seller = new PublicKey("..."); // Seller of the domain here 999.sol
const referrer: PublicKey | undefined = undefined; // Optional referrer

// Use a util function from the SDK to get category offers by category, category offers for a specific owner, etc.
const categoryOffers = await getCategoryOffer(connection, categoryKey);
// This example arbitrarily selects the first category offer in the list. Filter offers based on your needs.
const categoryOfferKey = categoryOffers[0].pubkey;

const ix = await takeCategoryOffer(
  connection,
  NAME_OFFERS_ID,
  categoryOfferKey,
  domainKey,
  memberKey,
  seller,
  referrer
);

Category offers can be cancelled using the cancelCategoryOffer function.

P2P Offers

#![allow(unused)]
fn main() {
pub struct P2pOffer {
    // Account tag
    pub tag: Tag,
    // Derivation nonce
    pub nonce: u8,
    // The owner of the p2p offer
    pub owner: Pubkey,
    // The counter party of the offer
    pub counter_party: Pubkey,
    // The domain(s) being traded
    pub domains: Vec<Pubkey>,
    // Domains against which the offer is priced
    pub quotes: Vec<Pubkey>,
    // Amount of SOL (in addition to the quote domains)
    pub amount: i64,
    // Expiry timestamp in seconds
    pub expiry_ts: u64,
}
}

P2P (Peer-to-Peer) Offers enable direct transactions between a buyer and a seller without listing the domain publicly. Sellers can create a P2P offer specifying the buyer's address, the SOL amount, the domain names involved and optionally set an expiration date to the offer.

P2P offers can be created using the makeP2p function

const amount = 10 * LAMPORTS_PER_SOL; // The SOL amount (in lamports) of the P2P offer (can be 0)
const owner = new PublicKey("..."); // The owner of the P2P offer (i.e creator)
const baseDomains: PublicKey[] = []; // The domains the owner wants to sell
const quoteDomains: PublicKey[] = []; // The domains the owner wants to buy (i.e the domains of the counter party)
const endDate: number | undefined = undefined; // The unix timestamp (in seconds) at which the P2P offer expires (optional)
const counterParty = new PublicKey("..."); // The counter party of the P2P offer

const ix = await makeP2p(
  amount,
  owner,
  baseDomains,
  quoteDomains,
  endDate,
  counterParty,
  NAME_OFFERS_ID
);

P2P offers can be accepted using the acceptP2p function

const p2pOfferKey = new PublicKey("..."); // The P2P offer key

const ix = await acceptP2p(connection, NAME_OFFERS_ID, p2pOfferKey);

P2P offers can be cancelled using cancelP2p

NFT Marketplace

Tokenized domain names can be purchased on NFT marketplaces such as Magic Eden, Tensor, Sniper etc...

SNS Suggest Introduction - Alpha

The SNS Suggest API can be used by applications looking to offer autocomplete or autosuggest for .sol domains.

⚠️ This API is not meant to replace the blockchain as the source of truth. The blockchain should always be considered as the only source of truth. This API is only a snapshot of the blockchain at a certain point in time and might be stale by a few seconds/minutes.

⚠️ This feature is currently experimental and is subject to change

This API is using MeiliSearch under the hood, you can refer to its documentation for more details https://docs.meilisearch.com

Current owner

The current owner endpoint will return the following information:

  • Domain name (String)
  • Domain key (String)
  • Domain token mint: The mint of the NFT representing the domain if it is tokenized (String)
  • Domain auction key: The key of the auction state associated to the domain (String)
  • Owner key: The key of the current owner of the domain (String)
  • Availability ID: The enum described here (Option<i16>)
  • Price: The price of the domain if the domain is available (Option<f32>)
  • Quote mint: The mint in which the domain is quoted if it is available
  • Fixed price key: The key of current fixed price offer if it exists (Option<String>)

To search for domains that match a certain keywaord (e.g 00):

curl \
  -X POST 'https://sns-suggest-proxy.bonfida.com' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "00" <- You keywords
  }'

{
  "hits": [
    {
      "domain_name": "00",
      "id": "00",
      "domain_key": "4oZe4sxw1cSbm4KoiukMs6FSG6zW8rzKgkLDPQd5Gk6Q",
      "domain_token_mint": "HWDX6pDdb3mp2223PzLccezouex1m4LLEw9GTjV85Rkx",
      "domain_auction_key": "ctPQ35SSXvti38NmNYmwy9Lk4EtyxHMZViVNSZNTPUo",
      "owner_key": "5Aw5mkykrqMj8tbqzKVrgBW79w26ha1ELe3zj6ZKYz4b",
      "availability_id": null,
      "price": null,
      "quote_mint": null,
      "fixed_price_offer_account": null
    }
    //...
  ],
  "query": "",
  "processingTimeMs": 0,
  "limit": 20,
  "offset": 0,
  "estimatedTotalHits": 1000
}

The search feature is typo tolerent

Simple UI integration

Below is a simple React hook example

import axios from "axios";
import { useState, useEffect, useRef } from "react";

export interface Item {
  domain_name: string;
  id: string;
  domain_key: string;
  domain_token_mint: string;
  domain_auction_key: string;
  owner_key: string | null | undefined;
  availability_id: number | null | undefined;
  price: number | null | undefined;
  quote_mint: string | null | undefined;
  fixed_price_offer_account: string | null | undefined;
}

export interface Result {
  hits: Item[];
  query: string;
  processingTimeMs: number;
  limit: number;
  offset: number;
  estimatedTotalHits: number;
}

const URL = "https://sns-suggest-proxy.bonfida.com";

export const useDomainAutoSuggest = (domain: string) => {
  const [result, setResult] = useState<Item[] | undefined>(undefined);
  const mounted = useRef(true);

  useEffect(() => {
    const fn = async () => {
      const payload = { q: domain };

      const { data }: { data: Result } = await axios.post(URL, payload, {
        headers: {
          "Content-type": "application/json",
        },
      });

      if (mounted.current) {
        setResult(data.hits);
      }

      return () => (mounted.current = false);
    };
    fn().catch(console.error);
  }, [domain]);

  return result;
};

SNS Quicknode API

The Solana Name Service Quicknode marketplace plugin accepts requests using the JSON-RPC-2.0 specification over HTTP POST.

This means that it accepts HTTP POST requests with the Content-Type header set to application/json, containing a json payload. For example:

{
    "jsonrpc": "2.0",
    "method": "get_domain_key",
    "params": {
        "domain": "bonfida.sol"
    },
    "id": 5678
}

The response is a JSON object. For example:

{
    "jsonrpc": "2.0",
    "result": "HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA",
    "id": 5678
}

The API supports a variety of methods which are detailed in this chapter.

sns_getAllDomainsForOwner

Returns the list of SNS domains currently owned by a given account.

Parameters

  • owner string required: A base58-encoded Solana Pubkey.

Result

The result will be an RPCResponse JSON object with field:

  • result Domain[]: A list of Domain objects

The Domain Object contains two fields :

  • name string: The domain name
  • key string: The domain's base-58 encoded public key

Example

{
  "jsonrpc": "2.0",
  "method": "sns_getAllDomainsForOwner",
  "params": [
    "HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA"
  ],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": [
    {
      "key": "Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb",
      "name": "bonfida"
    },
    {
      "key": "8xMJaFHqas1gzS7xLuWh298TDuBUw4hqLXL2ZFs376hH",
      "name": "springboks"
    },
    {
      "key": "BAW7NsKcY8SLr98ZNYcH2HeDvPBPE2EoyjuPKcJ9bW1d",
      "name": "9772"
    },
    {
      "key": "9B8y69VYEvLuwnaPdqNWL2wrV2XCLKrNAewC3FQEXptn",
      "name": "👨‍🌾"
    }
  ],
  "id": 42
}

sns_getDomainData

Returns base64 encoded contents of the domain's data payload, or those of an associated record.

Parameters

  • domain string required: The domain name to query.
  • record string optional: The associated record to get the data from instead.

Result

The result will be an RPCResponse JSON object with field:

  • result string: A base64 encoding of the record or domain's data payload.

Example

{
  "jsonrpc": "2.0",
  "method": "sns_getDomainData",
  "params": [
    "bonfida.sol",
    "github"
  ],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": "aHR0cHM6Ly9naXRodWIuY29tL0JvbmZpZGEvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
  "id": 42
}

sns_getDomainKey

Returns a domain's Solana account public key.

Parameters

  • domain string required: The domain name to query for.

Result

The result will be an RPCResponse JSON object with field:

  • result string: A base58-encoded Solana public key.

Example

{
  "jsonrpc": "2.0",
  "method": "sns_getDomainKey",
  "params": [
    "bonfida.sol"
  ],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": "Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb",
  "id": 42
}

sns_getDomainRecordKey

Returns a domain record's Solana account public key. Supported record identifier include SOL, ETH and IPFS. The get_supported_records method returns a list of all supported records.

Parameters

  • domain string required: The domain name to query for.
  • record string required: The record identifier to query for.

Result

The result will be an RPCResponse JSON object with field:

  • result string: A base58-encoded Solana public key.

Example

{
  "jsonrpc": "2.0",
  "method": "sns_getDomainRecordKey",
  "params": [
    "bonfida.sol",
    "github"
  ],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": "4sQDE98ZzQ23Rygb7tx1HhXQiuxswKhSBvECCREW35Ei",
  "id": 42
}

sns_getFavouriteDomain

Returns a user wallet's favourite (i.e. primary) domain.

Parameters

  • owner string required: The base58-encoded Solana public key of the wallet to query for.

Result

The result will be an RPCResponse JSON object with field:

  • result Domain: A Domain object describig the requested domain name.

The Domain Object contains two fields :

  • name string: The domain name
  • key string: The domain's base-58 encoded public key

Example

{
  "jsonrpc": "2.0",
  "method": "sns_getFavouriteDomain",
  "params": [
    "HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA"
  ],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": {
    "key": "Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb",
    "name": "bonfida"
  },
  "id": 42
}

sns_getRegistrationTransaction

Returns a ready-to-sign, base64-encoded transaction object to register a new SNS domain.

Parameters

  • domain string required: The domain name.
  • buyer string required: The base58-encoded Solana public key of the buyer's paying wallet.
  • buyer_token_account string required: The base58-encoded Solana public key of the buyer's paying token account.
  • space integer required: The number of bytes to allocate in the new registered domain.
  • mint string optional: The Solana public key of the Token mint used for payment, defaults to EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v (USDC).
  • referrer_key string optional: The base58-encoded Solana public key of the registration referrer.

Result

The result will be an RPCResponse JSON object with field:

  • result string: The base64-encoded Solana Transaction object.

sns_getSubdomains

Returns a list of currently registered subdomains for a given domain.

Parameters

  • domain string required: The domain name.

Result

The result will be an RPCResponse JSON object with field:

  • result string[]: The list of currently registered subdomain name account keys, encoded as base58.

Example

{
  "jsonrpc": "2.0",
  "method": "sns_getSubdomains",
  "params": [
    "bonfida.sol"
  ],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": [
    "naming",
    "dex",
    "test"
  ],
  "id": 42
}

sns_getSupportedRecords

Returns a list of all the currently supported record types.

Parameters

None

Result

The result will be an RPCResponse JSON object with field:

  • result string[]: The list of currently supported record types.

Example

{
  "jsonrpc": "2.0",
  "method": "sns_getSupportedRecords",
  "params": [],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": [
    "IPFS",
    "ARWV",
    "SOL",
    "ETH",
    "BTC",
    "LTC",
    "DOGE",
    "email",
    "url",
    "discord",
    "github",
    "reddit",
    "twitter",
    "telegram",
    "pic",
    "SHDW",
    "POINT",
    "BSC",
    "INJ",
    "backpack"
  ],
  "id": 42
}

sns_resolveDomain

Returns the resolved Solana Public key associated to a domain.

Parameters

  • domain string required: The domain name to resolve.

Result

The result will be an RPCResponse JSON object with field:

  • value string: The base-58 encoded Solana Public Key the domain resolves to.

Example

{
  "jsonrpc": "2.0",
  "method": "sns_resolveDomain",
  "params": [
    "bonfida.sol"
  ],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": "HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA",
  "id": 42
}

sns_reverse_lookup

Returns the domain name associated with a raw SNS account.

Parameters

  • domain_key string required: The base58-encoded public key of the Solana account to reverse lookup.

Result

The result will be an RPCResponse JSON object with field:

  • value string: The domain name.

Example

{
  "jsonrpc": "2.0",
  "method": "sns_reverseLookup",
  "params": [
    "Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb"
  ],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": "bonfida",
  "id": 42
}

sns_getDomainReverseKey

Returns a domain's reverse registry Solana account public key.

Parameters

  • domain string required: The domain name to query for.

Result

The result will be an RPCResponse JSON object with field:

  • result string: A base58-encoded Solana public key.

Example

{
  "jsonrpc": "2.0",
  "method": "sns_getDomainReverseKey",
  "params": [
    "bonfida.sol"
  ],
  "id": 42
}
{
  "jsonrpc": "2.0",
  "result": "DqgmWxe2PPrfy45Ja3UPyFGwcbRzkRuwXt3NyxjX8krg",
  "id": 42
}