Cover photo

Bonfida(.sol)域名服务

众所周知,Solana是不兼容EVM的高并发公链,其智能合约是通过C++或者Rust语言进行开发的。接下来我们要了解是的域名服务(Bonfida)

1、SNS(solana name service)域名介绍

  • Solana 名称服务 (SNS) 允许用户拥有映射到地址、内容标识符、图像等的域名。 它最广为人知的功能是将钱包地址切割成用户友好的格式,类似于以太坊名称服务(ENS),但在 Solana 上。

  • 购买 .sol 域名的机制与 ENS 略有不同—— 它们不会过期,而且大多数域名销售都是通过拍卖进行的。 您还可以在 FIDA(可通过 Coinbase 购买)或 USDC 中以固定价格购买一些。(永久域名,竞拍模式)

  • 域名费用是一次性的,也就是说,您无需再支付任何费用来保留您的域名。 立即注册域名的一次性费用如下:

  • 1 个字符 .sol 名称:价值 750 美元的 $FIDA

  • 2 个字符的 .sol 名称:价值 700 美元的 $FIDA

  • 3 个字符的 .sol 名称:价值 640 美元的 $FIDA

  • 4 个字符的 .sol 名称:价值 160 美元的 $FIDA

  • 5 个以上的字符 .sol 名称:价值 20 美元的 $FIDA

2、Bonfida域名系统的结构

2.1、注册表(Name Registry)

注册表存储有关域名的信息。 它由两部分组成:

· 头(header)

· 数据(data)

域名的数据总是以头(header)为前缀.

图1
图1

以下是 Rust 和 JS 中的标头结构:

Rust

// 账户数据中剩余字节的布局由记录`class`决定
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
pub struct NameRecordHeader {
     // 名称是分层的。 `parent_name`包含父母的账户地址
     // 名称,如果不存在父项,则为 Pubkey::default()。
    pub parent_name: Pubkey,
    // 这个名字的拥有者
    pub owner: Pubkey,
    //此帐户代表的数据类别(DNS 记录、推特句柄、SPL 令牌名称/符号等)    
    // 如果 Pubkey::default()数据未指定.
    pub class: Pubkey,
}

JS

export class NameRegistryState {
  parentName: PublicKey;
  owner: PublicKey;
  class: PublicKey;
  data: Buffer | undefined;

  static HEADER_LEN = 96;

  static schema: Schema = new Map([
    [
      NameRegistryState,
      {
        kind: "struct",
        fields: [
          ["parentName", [32]],
          ["owner", [32]],
          ["class", [32]],
        ],
      },
    ],
  ]);
  constructor(obj: {
    parentName: Uint8Array;
    owner: Uint8Array;
    class: Uint8Array;
  }) {
    this.parentName = new PublicKey(obj.parentName);
    this.owner = new PublicKey(obj.owner);
    this.class = new PublicKey(obj.class);
  }

2.2、具体详见域名服务合约核心代码文件(state.rs)

#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
pub struct NameRecordHeader {
    // Names are hierarchical.  `parent_name` contains the account address of the parent
    // name, or `Pubkey::default()` if no parent exists.
    pub parent_name: Pubkey,

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

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

3、域名合约(name service)

提供创建、更新、转移、删除域名核心方法

https://github.com/Bonfida/solana-program-library.git

3.1、创建

核心是在这过程中会生成自动生成正向和反向数据账户用于相应的数据存

pub fn process_create(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        hashed_name: Vec<u8>,
        lamports: u64,
        space: u32,
    ) -> ProgramResult {
        let accounts_iter = &mut accounts.iter();

        let system_program = next_account_info(accounts_iter)?;
        let payer_account = next_account_info(accounts_iter)?;
        let name_account = next_account_info(accounts_iter)?;
        let name_owner = next_account_info(accounts_iter)?;
        let name_class = next_account_info(accounts_iter)?;
        let parent_name_account = next_account_info(accounts_iter)?;
        let parent_name_owner = next_account_info(accounts_iter).ok();

        let (name_account_key, seeds) = get_seeds_and_key(
            &program_id,
            hashed_name,
            Some(name_class.key),
            Some(parent_name_account.key),
        );

        // Verifications
        if name_account_key != *name_account.key {
            msg!("The given name account is incorrect.");
            return Err(ProgramError::InvalidArgument);
        }
        if name_account.data.borrow().len() > 0 {
            let name_record_header =
                NameRecordHeader::unpack_from_slice(&name_account.data.borrow())?;
            if name_record_header.owner != Pubkey::default() {
                msg!("The given name account already exists.");
                return Err(ProgramError::InvalidArgument);
            }
        }
        if *name_class.key != Pubkey::default() && !name_class.is_signer {
            msg!("The given name class is not a signer.");
            return Err(ProgramError::InvalidArgument);
        }
        if *parent_name_account.key != Pubkey::default() {
            if !parent_name_owner.unwrap().is_signer {
                msg!("The given parent name account owner is not a signer.");
                return Err(ProgramError::InvalidArgument);
            } else {
                let parent_name_record_header =
                    NameRecordHeader::unpack_from_slice(&parent_name_account.data.borrow())?;
                if &parent_name_record_header.owner != parent_name_owner.unwrap().key {
                    msg!("The given parent name account owner is not correct.");
                    return Err(ProgramError::InvalidArgument);
                }
            }
        }
        if name_owner.key == &Pubkey::default() {
            msg!("The owner cannot be `Pubkey::default()`.");
            return Err(ProgramError::InvalidArgument);
        }

        if name_account.data.borrow().len() == 0 {
            // Issue the name registry account
            // The creation is done in three steps: transfer, allocate and assign, because
            // one cannot `system_instruction::create` an account to which lamports have been transfered before.
            invoke(
                &system_instruction::transfer(&payer_account.key, &name_account_key, lamports),
                &[
                    payer_account.clone(),
                    name_account.clone(),
                    system_program.clone(),
                ],
            )?;

            invoke_signed(
                &system_instruction::allocate(&name_account_key, space as u64),
                &[name_account.clone(), system_program.clone()],
                &[&seeds.chunks(32).collect::<Vec<&[u8]>>()],
            )?;

            invoke_signed(
                &system_instruction::assign(name_account.key, &program_id),
                &[name_account.clone(), system_program.clone()],
                &[&seeds.chunks(32).collect::<Vec<&[u8]>>()],
            )?;
        }

        let name_state = NameRecordHeader {
            parent_name: *parent_name_account.key,
            owner: *name_owner.key,
            class: *name_class.key,
        };

        name_state.pack_into_slice(&mut name_account.data.borrow_mut());

        Ok(())
    }

3.2、更新

更新域名数据

具体详见processor.rs源码的process_update方法

3.3、转移

转移域名给新用户

具体详见processor.rs源码的process_transfer方法

3.4、删除

删除域名数据

具体详见processor.rs源码的process_delete方法

4、域名注册(Registration)

注册合约(未公开)

提供sdk调用进行调用

域名注册(Registration)可以通过 SDK 。要注册域名,您必须指定以下内容:

  • 域名

  • 空间(1kb 到 10kb 之间)

  • 买家的公钥

    4.1、SDK

    可以使用 SDK @bonfida/spl-name-service 按照以下说明注册未注册的域名:

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

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

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

const [, ix] = await registerDomainName(name, space, buyer, buyerTokenAccount);

// sign and send the instruction

4.2、正向和反向解释

图2
图2

正向(Direct)

要获取域名信息,您需要:

获取域名公钥

检索帐户信息

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

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

// Step 1
const { pubkey } = await getDomainKey(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 } = await getDomainKey(subDomain);

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

retrieve 方法返回一个由两个字段组成的对象:

  • registry 是 NameRegistryState 类型

  • nftOwner 的类型为 PublicKey | 不明确的

  • 当 nftOwner 是 PublicKey 类型时,这意味着该域已被标记化,并且当前的 NFT 持有者是 nftOwner。 当域被标记化时,registry.owner 是作为程序所有者的托管帐户。 资金应发送给 nftOwner

  • 当 nftOwner 的类型为 undefined 时,这意味着该域未被标记化,资金应发送到 registry.owner

反向(Reverse)

如果您知道域名注册机构的公钥并希望获得人性化可读的名称,则需要执行反向查找。

以下代码可用于从其公钥解析域名:

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

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

cnst domainName = await performReverseLookup(connection, domainKey); // bonfida

以下是demo

import { Connection,PublicKey,clusterApiUrl } from "@solana/web3.js";
import { getDomainKey, NameRegistryState, getAllDomains, performReverseLookup,getHashedName,getNameAccountKey  } from "@bonfida/spl-name-service";


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

export const SOL_TLD_AUTHORITY = new PublicKey("58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx") // The .sol TLD
export const ROOT_TLD_AUTHORITY = new PublicKey("ZoAhWEqTVqHVqupYmEanDobY7dee5YKbQox9BNASZzU"); //The .sol TLD is owned by the root TLD


export async function getPublicKeyFromSolDomain(domain: string):Promise<string>{
    const { pubkey } = await getDomainKey(domain);
    try{
    const {registry,nftOwner} =await NameRegistryState.retrieve(SOLANA_CONNECTION, pubkey);
    console.log(registry);
    const owner = registry? registry.owner.toBase58():"无";
    console.log(`The owner of SNS Domain: ${domain} is: `,owner);
    return owner;
    }catch(error){
        console.log(error);
    }
    return "";
}

//levi.sol
export async function getOwnerForName(domanName:string){
    const hashedName = await getHashedName(domanName.replace(".sol", ""));
    const nameAccountKey = await getNameAccountKey(
    hashedName,
    undefined,
    SOL_TLD_AUTHORITY
    );
    const owner = await NameRegistryState.retrieve(
    new Connection(clusterApiUrl("mainnet-beta")),
    nameAccountKey
    );
    console.log(owner.registry.owner.toBase58());
    return owner.registry.owner.toBase58()
}

//Reverse look up   Public key of bonfida.sol Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb
export async function reverseLookUp(publickeyStr:string){
    const domainKey = new PublicKey(publickeyStr);
    const domainName = await performReverseLookup(SOLANA_CONNECTION, domainKey); 
    return domainName;
}


export async function getSolDomainsFromPublicKey(wallet: string):Promise<string[]>{
    const ownerWallet = new PublicKey(wallet);
    const allDomainKeys = await getAllDomains(SOLANA_CONNECTION, ownerWallet);
    const allDomainNames = await Promise.all(allDomainKeys.map(key=>{return performReverseLookup(SOLANA_CONNECTION,key)}));
    console.log(`${wallet} owns the following SNS domains:`)
    allDomainNames.forEach((domain,i) => console.log(` ${i+1}.`,domain));
    return allDomainNames;
}



// Examples for our search. You can replace these with your own wallet or Solana Naming Service queries. 
const DOMAIN_TO_SEARCH = 'jerry';
const WALLET_TO_SEARCH = 'E645TckHQnDcavVv92Etc6xSWQaq8zzPtPRGBheviRAk';  

getPublicKeyFromSolDomain(DOMAIN_TO_SEARCH);
getSolDomainsFromPublicKey(WALLET_TO_SEARCH);

Bonfida在Solana链上合约:

注册程序:jCebN34bUfdeUYJT13J1yG16XWQpt5PDx6Mse9GUqhR

域名服务程序:namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX

域名NFT程序:nftD3vbNkNqfj2Sd3HZwbpw4BxxKWr4AjGb9X38JeZk

Solana链上注册defiapp.sol域名交易详见

https://solscan.io/tx/3eKXWRFdAzgHY4N3v8z3mm2ubXwR3stHnkFiaaprL1vs5cZMeaBdKZdaLAXcXiNQ8MNZw3vrip6vwrRXgzXR52rh

twittertwitter.com/defiapp_top

web3defiapp.top

telegram: t.me/defiapp_top