# Welcome

Welcome to the Solana Name Service!

The Solana Name Service (SNS) aims to provide a decentralized and cost-effective solution for mapping human-readable .sol domain names to on-chain data. This data can include Solana (SOL) wallet addresses, IPFS content identifiers (CIDs), images, text, and more.

One of the core benefits of SNS is enabling easily recognizable and user-friendly domain names that represent otherwise complex wallet addresses. This simplifies interactions on the Solana blockchain by improving accessibility and reducing the friction associated with traditional alphanumeric addresses. By enabling users to associate a meaningful identity with their wallet, SNS helps facilitate more intuitive and efficient payments.

The utility of .sol domains extends well beyond payments. Most decentralized applications (dApps) today rely on traditional domain name systems (DNS), which are centralized and susceptible to censorship. In contrast, SNS offers fully on-chain domain names that are resistant to takedowns or control by centralized authorities. When paired with decentralized storage solutions like IPFS or Arweave, these domains enable censorship-resistant websites and a more robust, decentralized web infrastructure.

### Jump right in

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>SNS Basics</td><td></td><td><a href="https://1246390684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlDSt3ON3ZgisfqsL3Jdb%2Fuploads%2F1dqZOROoPWDqWgnOmHN9%2FSNS_Gitbook_Basics.png?alt=media&amp;token=c37d521e-41ca-47fe-bbde-76b4eb29b7f4">SNS_Gitbook_Basics.png</a></td><td></td><td><a href="/dev/sns-basics/quickstart">SNS Basics</a></td></tr><tr><td><strong>SNS SDKs</strong></td><td></td><td><a href="https://1246390684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlDSt3ON3ZgisfqsL3Jdb%2Fuploads%2F1Zkzk8tnXd396todFCWj%2FSNS_Gitbook_SDK.png?alt=media&amp;token=5b7650ae-0f0a-4c11-a45a-e32361ca2564">SNS_Gitbook_SDK.png</a></td><td></td><td><a href="/dev/sns-sdk/introduction">SNS SDK</a></td></tr><tr><td><strong>SNS API</strong></td><td></td><td><a href="https://1246390684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlDSt3ON3ZgisfqsL3Jdb%2Fuploads%2F188PMDhhH7k8q7MCNs6D%2FSNS_Gitbook_API.png?alt=media&amp;token=60062941-1143-4e84-abc3-33e3346a39dc">SNS_Gitbook_API.png</a></td><td></td><td><a href="/dev/sns-api/introduction">SNS API</a></td></tr></tbody></table>


# The Name Registry

Overview of 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.

<figure><img src="https://1246390684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlDSt3ON3ZgisfqsL3Jdb%2Fuploads%2Fwo8ayLKMraKPpcUSzaO9%2Fregistry-2.png?alt=media&amp;token=6e7b77b3-c449-4e6e-95f3-36b8c94981b9" alt=""><figcaption><p>Name Registry</p></figcaption></figure>

### Name Registry Struct

#### 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.

#### Data: Flexible Data Storage <a href="#data-flexible-data-storage" id="data-flexible-data-storage"></a>

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.


# 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`.

<figure><img src="https://1246390684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlDSt3ON3ZgisfqsL3Jdb%2Fuploads%2Fy3M8mjuSKG6nyDVsUPtJ%2Fimage.png?alt=media&amp;token=80962c32-8348-4ffb-915d-5aa04c9b3531" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}

### TLD List <a href="#tld-list" id="tld-list"></a>

* Root TLD: `ZoAhWEqTVqHVqupYmEanDobY7dee5YKbQox9BNASZzU`
* .sol TLD: `58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx`
  {% endhint %}

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.


# X (formerly Twitter)

Since SNS is a primitive of Solana that allows the mapping of names to a Solana account. This permits many things including associating your X handle to your Solana address.

<figure><img src="https://1246390684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlDSt3ON3ZgisfqsL3Jdb%2Fuploads%2F0JK2oEG14bNj80Y4s5YJ%2Ftwitter-tld.png?alt=media&amp;token=af207b48-63a0-4c1c-a38d-3d883f509034" alt=""><figcaption></figcaption></figure>

\
Both X handles and .sol domain names are a part of SNS, yet are slightly different. An X handle can only be claimed by the owner of the X 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. For a detailed guide on how to use X handles as a domain, check out this guide: [Set up X handle](https://docs.sns.id/collection/solana-name-service-twitter).&#x20;


# On-Chain Resolution

## Resolve domains on Solana&#x20;

The flow outlined below is the logic used to resolve .sol domains.

#### Step 1: Check if the domain name is tokenized

* **Yes:** The correct destination of funds is the token holder
* **No:** Go to step 2

#### Step 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

#### Step 3: The correct destination of funds is the domain owner

If the domain is not tokenized, and there is no valid SOL record, then domain resolves to the wallet address holding the domain

<figure><img src="https://1246390684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlDSt3ON3ZgisfqsL3Jdb%2Fuploads%2FYE2M8E5U7rfNbMc7D0Kf%2Fimage.png?alt=media&amp;token=64fc4be3-33a8-4ca4-9d16-78c88cff1bc2" alt=""><figcaption></figcaption></figure>

### FAQ <a href="#faq" id="faq"></a>

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.

2. **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.


# Web Resolution

## Web Resolvers <a href="#web-resolvers" id="web-resolvers"></a>

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 two such methods for resolving .sol domain names:

* Natively in the [Brave browser](https://brave.com/)
* Through a proxy service called [sol-domain.org](https://github.com/Bonfida/name-resolver)

### Brave browser <a href="#brave-browser" id="brave-browser"></a>

The [Brave browser](https://brave.com/) 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](https://pyth.network/), you would enter `pyth.sol` in the URL bar.

### Resolution via sol-domain.org Proxy Service

The [sol-domain.org](https://github.com/Bonfida/name-resolver) 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](https://pyth.sol-domain.org/) in the URL bar of your browser.

### Resolving .sol domains in practice <a href="#resolving-sol-domain-names-in-practice" id="resolving-sol-domain-names-in-practice"></a>

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 proccess <a href="#step-by-step-process" id="step-by-step-process"></a>

#### 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.

<br>


# Domain 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.

| Name     | Value                                           |
| -------- | ----------------------------------------------- |
| IPFS     | An IPFS CID                                     |
| ARWV     | An Arweave address                              |
| SOL      | A concatenation of a public key and a signature |
| ETH      | An ETH public key                               |
| BTC      | A BTC public key                                |
| LTC      | An LTC public key                               |
| DOGE     | A DOGE public key                               |
| email    | An email address                                |
| url      | A website URL                                   |
| discord  | A discord username                              |
| github   | A github username                               |
| reddit   | A reddit username                               |
| twitter  | A twitter username                              |
| telegram | A telegram username                             |
| pic      | A profile picture                               |
| SHDW     | A Shadow drive address                          |
| POINT    | A Point network record                          |
| BSC      | A BSC public key                                |
| INJ      | A Cosmos (Injective) public key                 |
| IPNS     | A IPNS CID                                      |

### Records V1 vs V2 <a href="#records-v1--v2" id="records-v1--v2"></a>

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](https://github.com/Bonfida/sns-ip/blob/master/proposals/sns-ip-1.md). 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](https://github.com/Bonfida/sns-ip/blob/master/proposals/sns-ip-3.md). 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.

{% hint style="info" %}
`\0` and `\1` are convenient notations for:

* `\0 = \x00`
* `\1 = \x01`
  {% endhint %}

### Differences between Records and Subdomains <a href="#difference-between-records-and-subdomains" id="difference-between-records-and-subdomains"></a>

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.


# Tokenization

Domain names are **not** SPL tokens and are not tokenized by default, however they can be tokenized into NFTs that follow the [Metaplex standard](https://developers.metaplex.com/token-metadata/token-standard). The terms "wrapped domain", "tokenized domain", or "NFT domain" may be used interchangeably throughout our platforms, and indicate the same tokenized domain status.&#x20;

Although tokenized domains will still successfully resolve to their owner's wallet address as specified in our [On-Chain Resolution](/dev/on-chain-resolution) section, some domain functionality may be unavailable. It's only recommended to tokenize your domain if you want to resell your domain on NFT marketplaces such as [Magic Eden](https://magiceden.us/marketplace/sns) and [Tensor](https://www.tensor.trade/trade/sns).

For an in-depth guide on wrapping domains directly from our [sns.id](https://www.sns.id/) app, check out our guide here: [Wrap your domain](https://docs.sns.id/collection/sns-v2/manage-your-domains#wrap-your-domain).


# SNS Improvement Protocol

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 <a href="#accepted" id="accepted"></a>

* [SNS-IP-1](https://github.com/Bonfida/sns-ip/blob/master/proposals/sns-ip-1.md): Proposal to standardize data encoding in SNS records
* [SNS-IP-3](https://github.com/Bonfida/sns-ip/blob/master/proposals/sns-ip-3.md): 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 resource when relevant

### Draft <a href="#draft" id="draft"></a>

* [SNS-IP-4](https://github.com/Bonfida/sns-ip/blob/master/proposals/sns-ip-4.md): This SNS-IP describes the derivation of SNS subdomains (eg. `a.b.sol`). A subdomain is under the complete control of the parent domain's controlling entity. However, this control can be limited through the use of a community program called `sub-registrar`. This protects subdomain ownership while allowing the parent domain owner more freedom in how registration proceeds.

### Rejected <a href="#rejected" id="rejected"></a>

* [SNS-IP-2](https://github.com/Bonfida/sns-ip/blob/master/proposals/sns-ip-2.md): Proposal to improve the handling of staleness and authenticy of records


# Introduction

### SNS SDKs

SNS maintains a list of SDKs that make integrating and interacting with SNS domains simple. Although many of the code examples throughout our documentation will be in JavaScript/TypeScript a full list of SDKs including other language support can be found below. <br>

* [JS/TS for web3.js v1](https://github.com/Bonfida/sns-sdk/tree/f4e4c2150ed85b1225a7921808db8aa8bbbdf388/js)
* [JS/TS for web3.js v2 (@solana/kit)](https://github.com/Bonfida/sns-sdk/tree/f4e4c2150ed85b1225a7921808db8aa8bbbdf388/js-kit)
* [Rust](https://github.com/Bonfida/sns-sdk/tree/main/rust-crates)
* [React](https://github.com/Bonfida/sns-sdk/tree/main/react)
* [Vue](https://github.com/Bonfida/sns-sdk/tree/main/vue)


# Quick Start - Integration

Quickly integrate SNS domains into your dApps to build a more personalized, and secure user experience. The methods below can be used to integrate the most popular SNS functionalities. \
\
1.) **Resolve domains** - As outlined in our [resolve](/dev/sns-sdk/editor) section, resolution allows a user entered .sol domain to resolve to its corresponding wallet address. Use this for functionality like transfers, tracking, sharing, etc.&#x20;

{% tabs %}
{% tab title="web3.js v1" %}

```typescript
import { resolve } from "@bonfida/spl-name-service";

const domain = "sns.sol" // With or without .sol

// The config argument to resolve is optional
// If allowPDA is true, a list of program IDs is required
const resolveConfig = { allowPda: true, programIds: ["..."] }

const resolvedAddress = await resolve(connection, domain, resolveConfig)
```

{% endtab %}

{% tab title="web3.js v2 (@solana/kit)" %}

<pre class="language-typescript"><code class="lang-typescript"><strong>import { resolveDomain } from "@solana-name-service/sns-sdk-kit";
</strong>
const domain = "sns.sol" // With or without .sol

// The options argument is not required to be passed
// If allowPDA is true, a list of program IDs is required.
const resolveOptions = { allowPda: true, programIds: ["..."] }

const resolvedAddress = await resolveDomain({ rpc, domain, options:resolveOptions });
</code></pre>

{% endtab %}
{% endtabs %}

2.) **Primary domains** - In identity elements of your dApp, we always recommend that you use primary domains as outlined in our [primary domains](/dev/sns-sdk/integrations) section. Primary domains are specifically set by users are their preferred on chain identity.&#x20;

{% tabs %}
{% tab title="web3.js v1" %}

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

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

{% endtab %}

{% tab title="web3.js v2 (@solana/kit)" %}

<pre class="language-typescript"><code class="lang-typescript">import { getPrimaryDomain } from "@solana-name-service/sns-sdk-kit";
import { Address } from "@solana/kit";

const userWallet = "36Dn3RWhB8x4c83W6ebQ2C2eH9sh5bQX2nMdkP2cWaA4" as Address

const { 
  domainAddress, // Address of domain account, not the user wallet address
<strong>  domainName: primaryDomain, 
</strong>  stale: isPrimaryDomainStale 
} = await getPrimaryDomain({ rpc, walletAddress:userWallet });
</code></pre>

{% endtab %}
{% endtabs %}

3.) **Domain records** - Domain records can be used as a powerful tool in social graphing and bridging a traditional social presence to Web3 identities. Records are feature rich, and the methods below are a quick integration guide, a full breakdown can be found in the [records](/dev/domain-records) section.&#x20;

{% tabs %}
{% tab title="web3.js v1" %}

```typescript
import { Record, getMultipleRecordsV2, verifyStaleness } from "@bonfida/spl-name-service";

const domain = "sns.sol"

const recordsToFetch = [Record.Discord, Record.Twitter, Record.Telegram];

const recordOptions = { deserialize: true };

const domainRecords = getMultipleRecordsV2(
  connection,
  domain,
  recordsToFetch,
  recordOptions,
 );

// This example arbitrarily selects the first record of the list
// In practice you should filter based on your needs
const isFreshRecord = await verifyStaleness(connection, domainRecords[0], domain);
```

{% endtab %}

{% tab title="web3.js v2 (@solana/kit)" %}

```typescript
import {
  Record,
  getDomainRecords,
} from "@solana-name-service/sns-sdk-kit";

const domain = "sns.sol"

const recordsToFetch = [Record.Discord, Record.Twitter, Record.Telegram];

// Optionally chose to deserialize the returned content
// Optionally pass verifiers to use custom oracle during right of association verification
const recordOptions = { deserialize: true, verifiers: undefined }; 

const domainRecords = getRecords({
  rpc,
  domain,
  records: recordsToFetch,
  options: recordOptions,
 });
 
// This example arbitrarily selects the first record of the list 
// In practice you should filter based on your needs
const recordToCheck = domainRecords[0];

const recordContent = recordToCheck.deserializedContent;
const isRecordVerified = recordToCheck.verification.roa;
const isRecordStale = recordToCheck.verification.staleness;
```

{% endtab %}
{% endtabs %}


# Registration

### SNS Widget

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 at the link below -

* <https://github.com/Bonfida/sns-widget>

```typescript
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} />;
};
```

### SNS Widget Walkthrough

The video walk through below details integration using the SNS Widget.&#x20;

{% embed url="<https://youtu.be/ZlBZjNUvuxc?feature=shared>" %}

### Register Domain Name

If you opt not to use our out of the box SNS Widget React component, the `registerDomainNameV2`function can be used instead.&#x20;

The last 2 arguments to the function are optional in both  the web.js v1 and v2 (@solana/kit) versions. The fifth argument is the token mint to purchase the domain, which defaults to USDC. The sixth argument is the referrers key. If you've been approved as a registration referrer, add the approved public key as the last argument.

If you'd like to integrate registrations into your dApp and would like to discuss becoming a referrer, please reach out to the SNS team via [Discord](http://discord.sns.id) or [Twitter](https://x.com/sns).&#x20;

{% tabs %}
{% tab title="web3.js v1" %}

```typescript
import { registerDomainNameV2, USDC_MINT } from "@bonfida/spl-name-service";

const name = "sns"; // We want to register sns.sol
const space = 0; // Default to 0 (Max 10kb)

const buyer = new PublicKey("..."); // Publickey of the buyer
const buyerTokenAccount = new PublicKey("..."); // Publickey of the token account of the buyer (USDC)
const refKey = new PublicKey("...") // If approved as a registration referrer, add your referral key

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

// sign and send the instruction
```

{% endtab %}

{% tab title="web3.js v2 (@solana/kit)" %}

```typescript
import { registerDomain } from "@solana-name-service/sns-sdk-kit";

const domain = "sns"; // We want to register sns.sol
const space = 0; // Default to 0 (Max 10kb)

const buyer = new PublicKey("..."); // Publickey of the buyer
const buyerTokenAccount = new PublicKey("..."); // Publickey of the token account of the buyer (USDC)
const refKey = new PublicKey("...") // If approved as a registration referrer, add your referral key

const ix = await registerDomain({
rpc, 
domain,
space,
buyer,
buyerTokenAccount,
});
```

{% endtab %}
{% endtabs %}


# Sales & Listings

## SNS Marketplace <a href="#sns-marketplace" id="sns-marketplace"></a>

The SNS Marketplace program makes it possible to integrate the purchase and sale of SNS domains directly into your own dApps. The methods outlined in this section should be imported from the `@bonfida/name-offers` NPM package as linked below.&#x20;

#### Deployment: <a href="#deployment" id="deployment"></a>

* Program ID: `85iDfUvr3HJyLM2zcq5BXSiDvUWfw6cSE1FfNBo8Ap29`
* Auditor: Halborn
* Audit report: [here](https://github.com/HalbornSecurity/PublicReports/blob/master/Solana%20Program%20Audit/Bonfida_SNS_Solana_Program_Security_Assessment_Report_Halborn_Final.pdf)
* JS SDK: [NPM](https://www.npmjs.com/package/@bonfida/name-offers)

This smart contract supports these different types of sales:

* Fixed price
* Unsolicited
* Category
* P2P

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

All of these listings can be accessed on [sns.id](https://www.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`.\ <br>


# Fixed Price Offers (Listings)

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.

### &#x20;Struct

```rust
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,
};
```

### Make Fixed Price Offer

Creating a fixed price offer is handled by the `makeFixedPriceOffer` function, as shown in the code snippet below.&#x20;

```typescript
import { NAME_OFFERS_ID, makeFixedPriceOffer } from "@bonfida/name-offers";
import { getDomainKeySync } from "@bonfida/spl-name-service";

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 
);
```

### Buy Fixed Price Offer

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:

```typescript
import { 
NAME_OFFERS_ID, 
buyFixedPrice, 
getFixedPriceOffersForName 
} from "@bonfida/name-offers";
import { getDomainKeySync } from "@bonfida/spl-name-service";

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

// Util function 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
);
```


# Unsolicited Offers

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.

### Struct

```rust
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,
}
```

### Make Offer

<pre class="language-typescript"><code class="lang-typescript">import { NAME_OFFERS_ID, makeOffer } from "@bonfida/name-offers";
import { getDomainKeySync } from "@bonfida/spl-name-service";
<strong>
</strong><strong>const mint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC mint
</strong>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
);

// ... sign and send instruction
</code></pre>

### Accept Offer

```typescript
import { 
NAME_OFFERS_ID, 
getOffersForName, 
acceptOffer 
} from "@bonfida/name-offers";
import { getDomainKeySync } from "@bonfida/spl-name-service";

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
);

// ... sign and send instruction
```

### Cancel Offer

```typescript
import { 
NAME_OFFERS_ID, 
cancelOffer 
} from "@bonfida/name-offers";
import { getDomainKeySync } from "@bonfida/spl-name-service";
import { getAssociatedTokenAddress } from "@solana/spl-token";

// Offer token mint is the mint address of the token the offer was made in.
const tokenDestination = getAssociatedTokenAddress(offerTokenMint, offerOwner);

// See acceptOffer example above for offerKey description
const ix = await cancelOffer(domainOwner, tokenDestination, offerKey, NAME_OFFERS_ID)

// ... sign and send instruction
```


# Category Offers

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

### Struct

```rust
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,
}
```

### Make Category Offer

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.

```typescript
import { CATEGORIES } from "@bonfida/sns-categories"; // Map of current categories
import { NAME_OFFERS_ID, makeCategoryOffer } from "@bonfida/name-offers";

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
);

// ... sign and send instruction
```

### Take (Accept) Category Offer

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

```typescript
import { CategoryMember } from "@bonfida/sns-categories"; // Map of current categories
import { getDomainKeySync } from "@bonfida/spl-name-service";
import { 
NAME_OFFERS_ID, 
getCategoryOffer, 
takeCategoryOffer 
} from "@bonfida/name-offers";
import { getDomainKeySync } from "@bonfida/spl-name-service";
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 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
);

// ... sign and send instruction
```


# P2P Offers

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.

### Struct

```rust
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,
}
```

### Make P2P Offer

```typescript
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
import { NAME_OFFERS_ID, makeP2p } from "@bonfida/name-offers";

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
);

// ... sign and send instruction
```

### Accept P2P Offer

```typescript
import { useConnection } from "@solana/wallet-adapter-react";
import { PublicKey } from "@solana/web3.js";
import { useQuery } from "@tanstack/react-query";
import { getAllP2pOffersForOwner, acceptP2p } from "@bonfida/name-offers";


// Example hook to get P2P offers for an owner address
const useP2pOfferOwner = (address: string | null | undefined) => {
  const { connection } = useConnection();
  const fn = async () => {
    if (!address) return null;
    const res = await getAllP2pOffersForOwner(
      connection,
      new PublicKey(address),
    );
    res.sort((a, b) => a.pubkey.toBuffer().compare(b.pubkey.toBuffer()));
    return res;
  };
  return useQuery({ queryKey: ["useP2pOfferOwner", address], queryFn: fn });
};

const { data: p2pOffers } = useP2pOfferOwner(ownerPublicKey);

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

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

// ... sign and send instruction
```

### Cancel P2P Offer

```typescript
import { cancelP2p } from "@bonfida/name-offers";

const ix = await cancelP2p(
          connection,
          NAME_OFFERS_ID,
          p2pKey, // See above for example of how to retrieve
          ownerPublicKey,
);

// ... sign and send instruction
```


# Resolve Domains

Resolving a domain involves finding its corresponding owner wallet address. Learn more about SNS resolution methodology and the steps involved to finally resolve an address from a domain here: [On-Chain Resolution](/dev/on-chain-resolution). \ <br>

{% tabs %}
{% tab title="web3.js v1" %}

```typescript
import { resolve } from "@bonfida/spl-name-service";

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

// The config argument to resolve is optional
// If allowPDA is true, a list of program IDs is required
const resolveConfig = { allowPDA: true, programIds: ["..."] }

// Final argument is optional
const resolvedAddress = await resolve(connection, domain, resolveConfig)
```

{% endtab %}

{% tab title="web3.js v2 (@solana/kit)" %}

```typescript
import { resolveDomain } from "@solana-name-service/sns-sdk-kit";

const domain = "sns.sol" // With or without .sol

// The options argument is not required to be passed
// If allowPDA is true, a list of program IDs is required.
const resolveOptions = { allowPda: true, programIds: ["..."] }

const resolvedAddress = await resolveDomain({ rpc, domain, options:resolveOptions });
```

{% endtab %}
{% endtabs %}


# Lookup Methods

Lookups take a certain piece of domain information such as its human readable name, or the domain registry's public key and use that information to retrieve other data about the domain.&#x20;

<figure><img src="https://1246390684-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlDSt3ON3ZgisfqsL3Jdb%2Fuploads%2FuriAHaPYwmrQOEorZYDH%2Fimage.png?alt=media&amp;token=5f32f10f-67d1-41f8-bb83-3fc53aa9001f" alt=""><figcaption></figcaption></figure>


# Direct Lookup

Lookup domain information directly based on the human readable domain name.&#x20;

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

```typescript
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`

`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 Lookup

Lookup the human readable domain name based on the public key of a domain name registry.

```typescript
import { reverseLookup, getDomainKeySync} from "@bonfida/spl-name-service";

// Public key of bonfida.sol
const { pubkey: domainKey } = getDomainKeySync('bonfida.sol')

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

{% hint style="info" %}
Please note the owner's wallet public key and the public key of the domain registry differ. A reverse lookup uses the domain registry's public key as demonstrated above.&#x20;
{% endhint %}


# Get all registered domains

Use the methods below to get all registered domain names in existence right now.&#x20;

To avoid issues from the large payload response using the `getAllDomains`method from the JS/TS SDK, only the domain owner information is returned.&#x20;

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

const registeredDomains = await getAllRegisteredDomains(connection);
```

If you prefer to use a call to `getProgramAccounts` an example of the call and the required filters is below.&#x20;

```typescript
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;
};
```


# Get all domains of wallet

Use the methods below to get all the domain names owned by a specific wallet address.

{% tabs %}
{% tab title="web3.js v1" %}

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

const domains = await getAllDomains(connection, user);
```

{% endtab %}

{% tab title="web3.js v2 (@solana/kit)" %}

<pre class="language-typescript"><code class="lang-typescript"><strong>import { getDomainsForAddress } from "@solana-name-service/sns-sdk-kit";
</strong>
const domainsForAddress = await getDomainsForAddress({ rpc, address, });
</code></pre>

{% endtab %}
{% endtabs %}

The web3.js v1 version of the function above will return an array of public keys. Use the toBase58() method to convert the public keys into base 58 encoded strings. The web3.js v2 solana/kit version of the method returns an array of objects containing both the domain address as well as the domains in human readable string format.&#x20;

Another option to retrieve public keys, as well as their corresponding domain names in a  human readable string format is to use the function below which returns an array of objects including public keys, and their corresponding domain name strings.

```typescript
import { getDomainKeysWithReverses } from "@bonfida/spl-name-service";

const domainsWithReverses = await getDomainKeysWithReverses(connection, user);
```

If you prefer to use a call to \`getProgramAccounts\`, you'll want to use the filters below.

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


# Primary Domains

Users have the ability to select a domain name as their "primary". 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.

{% hint style="info" %}
Primary domains were previously called favorite domains. The naming change was made in version 3.0.0 of the SDK.
{% endhint %}

### Get a primary domain

{% tabs %}
{% tab title="web3.js v1" %}

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

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

{% endtab %}

{% tab title="web3.js v2 (@solana/kit)" %}

<pre class="language-typescript"><code class="lang-typescript">import { getPrimaryDomain } from "@solana-name-service/sns-sdk-kit";
import { Address } from "@solana/kit";

const userWallet = "36Dn3RWhB8x4c83W6ebQ2C2eH9sh5bQX2nMdkP2cWaA4" as Address

const { 
  domainAddress, // Address of domain account, not the user wallet address
<strong>  domainName: primaryDomain, 
</strong>  stale: isPrimaryDomainStale 
} = await getPrimaryDomain({ rpc, walletAddress:userWallet });
</code></pre>

{% endtab %}
{% endtabs %}

### 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.

```typescript
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);
```

### Walkthrough

For a walkthrough tutorial on integrating Primary Domains into your dApps, check out the video below.

{% embed url="<https://youtu.be/WDneF3u5sB4>" %}


# Domain Record Methods

This section includes methods to resolve both V1 and V2 records. Please note, V1 records are currently deprecated.&#x20;

### Records V2 Walkthrough

The video walkthrough below details the update from V1 to V2 Records as well as discusses new methods added to the our SDK specific to the change.&#x20;

{% embed url="<https://youtu.be/hmAaAiZJVJs?feature=shared>" %}

For a list of domain records, as well as more information on the differences between V1 and V2 records, check out the section below.

{% content-ref url="/pages/Rm50FV2cxtCUvqeCEmUC" %}
[Domain Records](/dev/domain-records)
{% endcontent-ref %}


# V1 Records (Deprecated)

## Records V1

**V1 records are deprecated.**

### V1 Record Derivation

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

```js
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

### V1 Record Resolution

The methods below can be used to retrieve specific V1 records.&#x20;

* `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 the functions above can be passed a Connection and record to be resolved. An example with the IPFS record is below.

```typescript
import { getIpfsRecord } from "@bonfida/spl-name-service";

const domain = 'bonfida.sol';

const ipfsRecordContent = await getIpfsRecord(connection, domain);
```

A more generic resolving function `getRecord` is also exported with the below signature. The final argument to the function is an optional boolean indicating if the response should be deserialized.

```js
import { getRecord, Record } from "@bonfida/spl-name-service";

const ipfsRecord = Record.IPFS //Example of the Record enum import

const ipfsRecordContent = await getRecord(connection, domain, ipfsRecord, true);
```

### Editing V1 Records <a href="#editing-records-v1" id="editing-records-v1"></a>

```typescript
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 wallet = Keypair.fromSecretKey(...) // Your wallet owning the domain

// 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}`);
};
```

### Deleting a V1 Record <a href="#deleting-a-record-v1" id="deleting-a-record-v1"></a>

Records can be deleted using the `deleteInstruction` function.

```js
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 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 V1 Record <a href="#the-sol-record-v1" id="the-sol-record-v1"></a>

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.


# V2 Records

### Get Record V2

Single records can be obtained using the `getRecordV2`function. The optional final argument to the function is an object with a key of deserialized with a boolean value to indicate if the response should be deserialized.&#x20;

```typescript
import { Record, getRecordV2 } from "@bonfida/spl-name-service";

const recordOptions = { deserialize: true };

const discordRecord = getRecordV2(connection, domain, Record.Discord, recordOptions);
```

### Get multiple V2 Records

```typescript
import { Record, getMultipleRecordsV2 } from "@bonfida/spl-name-service";

const recordsToFetch = [Record.Discord, Record.Twitter, Record.Telegram];

const recordOptions = { deserialize: true };

const domainRecords = getMultipleRecordsV2(
  connection,
  domain,
  recordsToFetch,
  recordOptions,
 );

```

## Verify Staleness and Verify Right of Association

The introduction of 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.

### 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.

```typescript
import { verifyStaleness } from "@bonfida/spl-name-service";

const isFreshRecord = 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.

```typescript
import { verifyRightOfAssociation, GUARDIANS } from "@bonfida/spl-name-service";

const verifier = GUARDIANS.get(Record.URL); 

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.

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

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

### Walkthrough

For a detailed walkthrough tutorial outlining the Records V2 upgrade, as well as examples on how to fetch stalesness and right of association, check out the video below.&#x20;

{% embed url="<https://youtu.be/hmAaAiZJVJs?feature=shared>" %}


# X Handle Methods

SNS supports the registration of Twitter handles, allowing users to connect their Twitter profile to their wallet as described in this section: [X (formerly Twitter) TLD](/dev/tld/x-formerly-twitter).

### X (formerly Twitter) root TLD

```typescript
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.

### Direct look up

To find the Twitter handle associated to a public key

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

const pubkey = new PublicKey("FidaeBkZkvDqi1GXNEwB8uWmj9Ngx2HXSS5nyGRuVFcZ");

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

### Reverse look up <a href="#reverse-look-up" id="reverse-look-up"></a>

To find the public key associated to a Twitter handle

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

const handle = "bonfida";

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

\ <br>


# 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 <a href="#key-characteristics-of-subdomains" id="key-characteristics-of-subdomains"></a>

* **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.

### Create a Subdomain

A subdomain created with `createSubdomain` will initially be owned by the parent owner. A subdomain can be created and transfered inside the same transaction.

```typescript
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...
```

### Transfer a Subdomain

```typescript
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
```

### Delete a Subdomain

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

```typescript
import { deleteInstruction, getDomainKeySync, NAME_PROGRAM_ID } 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 subdomain
const owner = new PublicKey("...");

const { pubkey: domainKey } = getDomainKeySync(domain);

const ix = deleteInstruction(
  NAME_PROGRAM_ID,
  domainKey,
  owner,
  owner
);

// sign and send the instruction...
```


# Subdomain Registrars

Subdomain registrars offer an out of the box, no code solution for allowing your community or friends to create subdomains of a parent domain that you own. You can choose to monetize these registrars, or offer the subdomain registrations for free. Along with monetization, you can customize your registrar to accept any SPL token of your choosing, NFT/Token gate access, and more! Registrars can be created created directly on [sns.id.](https://www.sns.id/) Check out the video below for a full walkthrough tutorial.&#x20;

The registrar program is open source and can be viewed here: [Subdomain Registrar Program](https://github.com/Bonfida/sub-registrar).

{% embed url="<https://youtu.be/XMO4CLqM8f4?feature=shared>" %}


# Delete Domains

Domain deletion permanently removes the domain from your ownership and returns it to the global namespace. Once deleted, the domain becomes available for anyone to register, meaning **you lose all control and access to it**.&#x20;

{% hint style="info" %}
**Deleting a domain is irreversible, and will cause you to lose ownership of the subdomains and records related to the domain. We do not recommend deleting domains, proceed at your own risk.**&#x20;
{% endhint %}

```typescript
import {
  getDomainKeySync,
  NAME_PROGRAM_ID,
  deleteInstruction,
} from "@bonfida/spl-name-service";

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

// Owner public key
const owner = new PublicKey("...")

const { pubkey: domainKey } = getDomainKeySync(domain);

const ix = deleteInstruction(
  NAME_PROGRAM_ID,
  domainKey,
  owner,
  owner
);

// sign and send instruction...
```


# Introduction

The SNS API provides developers with an HTTP-based interface to interact with the Solana Name Service (SNS) ecosystem. It offers a convenient way to retrieve domain-related data without directly querying the blockchain.

The base URL for the API is -

```
https://sns-api.bonfida.com
```

In addition to the API maintained by SNS, our friends at Quicknode also offer a marketplace plugin with many useful methods to interact with SNS. Check it out here: [SNS Quicknode API](/dev/sns-api/sns-quicknode-api).

{% hint style="info" %}
Important: APIs are **not a replacement** for the blockchain as the source of truth. All responses should be considered *snapshots* of the current on-chain state and may be delayed by a few seconds or minutes. For mission-critical operations, always rely on on-chain verification.
{% endhint %}


# Domains

### History <a href="#history" id="history"></a>

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.

`order_by` - Sorting direction. Use "ASC" to return results from oldest to newest, or "DESC" (default) to return newest to oldest.

> Request

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

> Response

```json
{
  "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`.

```ts
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)
}
```


# Primary Domains

### Primary domains <a href="#primary-domains" id="primary-domains"></a>

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.

{% hint style="info" %}
Primary domains used to be called favorite domains.
{% endhint %}

> Request

```
GET /v2/user/fav-domains/{pubkeys}
```

> Response

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


# Categories

### Get the list of categories <a href="#get-the-list-of-categories" id="get-the-list-of-categories"></a>

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

> Request

```
GET /categories/list
```

> Response

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

### Get the domains of a category <a href="#get-the-domains-of-a-category" id="get-the-domains-of-a-category"></a>

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

> Request

```
GET /categories/list/{category}
```

> Response

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

### Get categories statistics <a href="#get-categories-statistics" id="get-categories-statistics"></a>

This endpoint returns stats for all the categories.

> Request

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

> Response

```json
{
  "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 <a href="#get-statistics-for-a-category" id="get-statistics-for-a-category"></a>

This endpoint returns stats for a given category.

> Request

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

> Response

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

### Get floors <a href="#get-floors" id="get-floors"></a>

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

> Request

```
GET /categories/floors
```

> Response

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

### Get floor for a category <a href="#get-floor-for-a-category" id="get-floor-for-a-category"></a>

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

> Request

```
GET /categories/floors/{category}
```

> Response

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

### Get supply <a href="#get-supply" id="get-supply"></a>

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

> Request

```
GET /categories/supply
```

> Response

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

### Get top categories by volume <a href="#get-top-categories-by-volume" id="get-top-categories-by-volume"></a>

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

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

### Get owners <a href="#get-owners" id="get-owners"></a>

> 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

```json
{
  "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

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

### Domains For Single User <a href="#primary-domains" id="primary-domains"></a>

For an endpoint returning the list of domains owned for a single owner, check out: [Domains for owner](https://guide.sns.id/sns-api/owners#domains-for-owner).

### User Listings <a href="#user-listings" id="user-listings"></a>

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

> Request

```
GET /v2/user/listings/{pubkey}
```

> Response

```json
[
  {
    "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 <a href="#domains-and-categories" id="domains-and-categories"></a>

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

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


# Listings

### All marketplace listings <a href="#all-marketplace-listings" id="all-marketplace-listings"></a>

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

```json
{
  "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 <a href="#listing-details" id="listing-details"></a>

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.

| Key | Type                  | Description                                         |
| --- | --------------------- | --------------------------------------------------- |
| d   | String                | The domain name                                     |
| p   | Float (`f32`)         | The price of the domain                             |
| q   | String                | The token mint of the listing (i.e the currency)    |
| a   | Enum (`u8`)           | The availability ID (see `PlatformEnum` definition) |
| l   | Enum (`u8`)           | Language code (see definition below)                |
| up  | Float (`f32`)         | The price in USD                                    |
| e   | Boolean               | Indicates if the domain has an emoji                |
| r   | Boolean               | Indicates if the domain is rare                     |
| do  | Boolean               | Indicates if the domain contains digits only        |
| lo  | Boolean               | Indicates if the domain contains letters only       |
| le  | Integer               | The length of the domain name                       |
| fp  | Boolean               | Indicates if the listing is a fixed price offer     |
| me  | Boolean               | Indicates if the domain is listed on Magic Eden     |
| pa  | Boolean               | Indicates if the domain is a palindrome             |
| ca  | Array (`Vec<String>`) | A list of categories the domain belongs to          |

> Request

```
GET /v2/listings/listing/{domain}
```

> Response

```json
{
  "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": []
}
```

```rust
pub enum Language { 
English = 0,    
Cyrillic = 1,    
Chinese = 2,    
Japanese = 3,    
Emoji = 4,    
Unauthorized = 5,    
Korean = 6,    
Arabic = 7,
}
```


# Sales

### Last Sale <a href="#last" id="last"></a>

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

```json
{
  "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 <a href="#registrations" id="registrations"></a>

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

```json
{
  "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 <a href="#leaderboard" id="leaderboard"></a>

This endpoint returns the top 100 sales ever.

> Request

```
GET /sales/leaderboard
```

> Response

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


# Trade Volume

### Secondary sales volume <a href="#secondary-sales-volume" id="secondary-sales-volume"></a>

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

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

### Direct registration volume <a href="#direct-registration-volume" id="direct-registration-volume"></a>

This endpoint can be used to retrieve direct registrations volume

> Request

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

> Response

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

### Aggregated volume <a href="#aggregated-volume" id="aggregated-volume"></a>

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

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


# Owners

### Distribution <a href="#distribution" id="distribution"></a>

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

```
GET /owners/distribution
```

> Response

```json
{
  "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

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

### Domains For Owner <a href="#domains-for-owner" id="domains-for-owner"></a>

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

```
GET /owners/{owner_key}/domains
```

> Response

```json
{
  "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.

### Base URL <a href="#base-url" id="base-url"></a>

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 <a href="#get-a-list-of-domain-images" id="get-a-list-of-domain-images"></a>

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

> Request

```
GET /image?domain=foo&domain=bar
```

> Response

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


# SNS Quicknode API

In collaboration with our friends at Quicknode, we're pleased to offer the SNS Quicknode marketplace plugin with many helpful API methods that allow you to interact with the SNS ecosystem. Learn more about the Quicknode marketplace here: [Quicknode](https://marketplace.quicknode.com/).

The Solana Name Service Quicknode marketplace plugin accepts requests using the [JSON-RPC-2.0](https://www.jsonrpc.org/specification) 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:

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

The response is a JSON object. For example:

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


# sns\_getAllDomainsForOwner

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

### Parameters <a href="#parameters" id="parameters"></a>

* **owner** `string` *required*: A base58-encoded Solana Pubkey.

### Result <a href="#result" id="result"></a>

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 <a href="#example" id="example"></a>

```json
{
  "jsonrpc": "2.0",
  "method": "sns_getAllDomainsForOwner",
  "params": [
    "HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA"
  ],
  "id": 42
}
```

```json
{
  "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 <a href="#parameters" id="parameters"></a>

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

### Result <a href="#result" id="result"></a>

The result will be an RPCResponse JSON object with field:

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

### Example <a href="#example" id="example"></a>

```json
{
  "jsonrpc": "2.0",
  "method": "sns_getDomainData",
  "params": [
    "bonfida.sol",
    "github"
  ],
  "id": 42
}
```

```json
{
  "jsonrpc": "2.0",
  "result": "aHR0cHM6Ly9naXRodWIuY29tL0JvbmZpZGEvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
  "id": 42
}
```


# sns\_getDomainKey

Returns a domain's Solana account public key.

### Parameters <a href="#parameters" id="parameters"></a>

* **domain** `string` *required*: The domain name to query for.

### Result <a href="#result" id="result"></a>

The result will be an RPCResponse JSON object with field:

* **result** `string`: A base58-encoded Solana public key.

### Example <a href="#example" id="example"></a>

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

```json
{
  "jsonrpc": "2.0",
  "result": "Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb",
  "id": 42
}
```

[<br>](https://sns.guide/sns-quicknode/sns_getDomainData.html)


# 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 <a href="#parameters" id="parameters"></a>

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

### Result <a href="#result" id="result"></a>

The result will be an RPCResponse JSON object with field:

* **result** `string`: A base58-encoded Solana public key.

### Example <a href="#example" id="example"></a>

```json
{
  "jsonrpc": "2.0",
  "method": "sns_getDomainRecordKey",
  "params": [
    "bonfida.sol",
    "github"
  ],
  "id": 42
}
```

```json
{
  "jsonrpc": "2.0",
  "result": "4sQDE98ZzQ23Rygb7tx1HhXQiuxswKhSBvECCREW35Ei",
  "id": 42
}
```


# sns\_getFavouriteDomain

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

### Parameters <a href="#parameters" id="parameters"></a>

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

### Result <a href="#result" id="result"></a>

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 <a href="#example" id="example"></a>

```json
{
  "jsonrpc": "2.0",
  "method": "sns_getFavouriteDomain",
  "params": [
    "HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA"
  ],
  "id": 42
}
```

```json
{
  "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 <a href="#parameters" id="parameters"></a>

* **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 <a href="#result" id="result"></a>

The result will be an RPCResponse JSON object with field:

* **result** `string`: The base64-encoded Solana Transaction object.

[<br>](https://sns.guide/sns-quicknode/sns_getFavouriteDomain.html)


# sns\_getSubdomains

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

### Parameters <a href="#parameters" id="parameters"></a>

* **domain** `string` *required*: The domain name.

### Result <a href="#result" id="result"></a>

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 <a href="#example" id="example"></a>

```json
{
  "jsonrpc": "2.0",
  "method": "sns_getSubdomains",
  "params": [
    "bonfida.sol"
  ],
  "id": 42
}
```

```json
{
  "jsonrpc": "2.0",
  "result": [
    "naming",
    "dex",
    "test"
  ],
  "id": 42
}
```


# sns\_getSupportedRecords

Returns a list of all the currently supported record types.

### Parameters <a href="#parameters" id="parameters"></a>

None

### Result <a href="#result" id="result"></a>

The result will be an RPCResponse JSON object with field:

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

### Example <a href="#example" id="example"></a>

```json
{
  "jsonrpc": "2.0",
  "method": "sns_getSupportedRecords",
  "params": [],
  "id": 42
}
```

```json
{
  "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
}
```

[<br>](https://sns.guide/sns-quicknode/sns_getSubdomains.html)


# sns\_resolveDomain

Returns the resolved Solana Public key associated to a domain.

### Parameters <a href="#parameters" id="parameters"></a>

* **domain** `string` *required*: The domain name to resolve.

### Result <a href="#result" id="result"></a>

The result will be an RPCResponse JSON object with field:

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

### Example <a href="#example" id="example"></a>

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

```json
{
  "jsonrpc": "2.0",
  "result": "HKKp49qGWXd639QsuH7JiLijfVW5UtCVY4s1n2HANwEA",
  "id": 42
}
```

[<br>](https://sns.guide/sns-quicknode/sns_getSupportedRecords.html)


# sns\_reverse\_lookup

Returns the domain name associated with a raw SNS account.

### Parameters <a href="#parameters" id="parameters"></a>

* **domain\_key** `string` *required*: The base58-encoded public key of the Solana account to reverse lookup.

### Result <a href="#result" id="result"></a>

The result will be an RPCResponse JSON object with field:

* **value** `string`: The domain name.

### Example <a href="#example" id="example"></a>

```json
{
  "jsonrpc": "2.0",
  "method": "sns_reverseLookup",
  "params": [
    "Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb"
  ],
  "id": 42
}
```

```json
{
  "jsonrpc": "2.0",
  "result": "bonfida",
  "id": 42
}
```

[<br>](https://sns.guide/sns-quicknode/sns_resolveDomain.html)


# sns\_getDomainReverseKey

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

### Parameters <a href="#parameters" id="parameters"></a>

* **domain** `string` *required*: The domain name to query for.

### Result <a href="#result" id="result"></a>

The result will be an RPCResponse JSON object with field:

* **result** `string`: A base58-encoded Solana public key.

### Example <a href="#example" id="example"></a>

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

```json
{
  "jsonrpc": "2.0",
  "result": "DqgmWxe2PPrfy45Ja3UPyFGwcbRzkRuwXt3NyxjX8krg",
  "id": 42
}
```

[<br>](https://sns.guide/sns-quicknode/sns_reverseLookup.html)


