Bako Docs
  • Welcome
  • Developers
    • ID
      • Architecture
      • Contracts
      • SDK
        • Quickstart
        • Bako ID Client
        • Registry Contract
Powered by GitBook
On this page
  • Architecture Overview
  • Registry
  • Manager
  • Token
  • Resolver
  1. Developers
  2. ID

Contracts

PreviousArchitectureNextSDK

Last updated 4 months ago

Bako ID is a series of Smart Contracts on Fuel that enables the registration and management of @bakoid handles. Handles on Bako ID are registered in a structure that allows both accounts and contracts to have their own identity. After registration, the handle owner has full control over it, allowing them to manage all information and resolutions associated with the handle.

Architecture Overview

Bako ID is built on two main contracts: the Registry and the Manager. The Registry serves as the entry point for registering new handles, working alongside the Manager to validate ownership during interactions with the handle and to lock primary resolvers.

Registry

The Registry is responsible for implementing the methods that allow direct interaction with handles, as well as serving as the entry point for the logic behind handle registration. The main Registry implements the following ABI:

Register

Responsible for registering a new handle, the method reverts when:

  • The Asset ID ≠ ETH - Reverts with RegistryContractError::IncorrectAssetId

  • The handle has already been minted - Reverts with RegistryContractError::AlreadyMinted

  • The handle contains invalid characters (valid: A-Z, a-z, 0-9, _) - Reverts with NameValidationError

  • The Asset ID amount ≠ the handle price - Reverts with RegistryContractError::InvalidAmount

If not reverted, the method interacts with the Token to mint the NFT and registers the record in the Manager.

Set Metadata

Method responsible for adding new metadata to the handle, which directly interacts with the Token contract implementing SRC-7 to store the data.

Only the handle owner has permission to execute the method. If called by another account, the method will revert with RegistryContractError::NotOwner.

Set owner and Set resolver

Both methods are responsible for modifying the handle's addresses. They interact directly with the Manager contract, which updates the handle's structure while ensuring that the resolver address is correctly locked.

As with the Set Metadata method, only the owner has permission to execute these methods. Otherwise, the method reverts with RegistryContractError::NotOwner.

Manager

This contract, together with the Registry, forms the core of Bako ID. It is responsible for storing new handles, managing a handle's addresses, and locking resolver addresses.

To ensure security, methods are implemented to manage the Admin, which in the context of Bako ID refers to the Registry contract. Only the contract owner has permission to manage the administrator addresses.

Storage

For this contract, which is responsible for storing and managing the handles, a storage structure has been designed to work with handle data in a secure and efficient manner.

The association between the handle and its data is managed through a hash calculated from the name: handle_hash = sha256(handle).

The storage is divided into three parts. The first part is records_data, a Map where the key represents the handle_hash, and the value is a structure of RecordData:

records_data: StorageMap<b256, RecordData> = StorageMap {}
struct RecordData {
    pub owner: Identity,
    pub resolver: Identity,
    pub period: u16,
    pub timestamp: u64,
}

The records_name is a Map where the key is the handle_hash, and the value is a String representing the name:

records_name: StorageMap<b256, StorageString> = StorageMap {}

records_resolver is responsible for locking primary resolvers. It represents a Map where the key is of type Identity and the value is the handle_hash.

A value can only be inserted if it is available, ensuring that primary resolvers are not overwritten.

records_resolver: StorageMap<Identity, b256> = StorageMap {}

ABI

Set Record

Responsible for inserting the data of a new handle into Bako ID, this method reverts when:

  • The msg_sender is not an admin or owner of the Manager: reverts with ManagerError::OnlyOwner

  • The handle has already been minted: reverts with ManagerError::RecordAlreadyExists

After the validation process, if no reverts occur, the handle is inserted into the storage.

Set Resolver

This method alters the resolver address in the handle's record and ensures the lockup between the address and handle. The execution is reverted when:

  • The msg_sender is not an admin or owner of the Manager: reverts with ManagerError::OnlyOwner

  • The handle has not been minted: reverts with ManagerError::RecordNotFound

  • The resolver address is locked: reverts with ManagerError::ResolverAlreadyInUse

If no reverts occur, the method:

  • Alters the resolver in storage.records_data

  • Removes the lockup of the old resolver address

  • Locks the new resolver address in storage.records_resolver

Token

This contract follows the Fuel standards by implementing the Native Asset ABIs to represent an NFT, along with the Admin and Ownership library.

asset_id = sha256(TOKEN_CONTRACT_ID ++ handle_hash);

The method mint(recipient: Identity, sub_id: Option<SubId>, amount: u64) of the contract ensures that only one token exists per handle. It is reverted when:

  • The msg_sender is not an admin: AdminError::NotAdmin

  • The sub_id is not an Option::Some: MintError::CannotMintMoreThanOneNFTWithSubId

  • The amount is greater than 1: MintError::CannotMintMoreThanOneNFTWithSubId

  • The total supply for the AssetId is already filled: MintError::NFTAlreadyMinted

Resolver

The Resolver contract provides methods for on-chain resolution of handle addresses and names, such as resolving the address of a name and the name of an address.

It implements two ABIs:

  • AddressResolver, responsible for resolving the handle's address.

  • NameResolver, responsible for resolving the name of an address.

abi AddressResolver {
    #[storage(read)]
    fn addr(name: String) -> Option<Identity>;

    #[storage(read)]
    fn owner(name: String) -> Option<Identity>;
}

abi NameResolver {
    #[storage(read)]
    fn name(addr: Identity) -> Option<String>;
}

All methods interact with the Manager using the getter methods defined in the ManagerInfo ABI.

fn addr(name: String) -> Option<Identity>`

The method for searching a resolver address through the handle returns an Option::Some if found, and an Option::None if not found.

fn foo(contract_id: ContractId) {
     let resolver_abi = abi(AddressResolver, contract_id.bits());
     let handle_address = resolver_abi.addr(String::from_ascii_str("@bako_id"));
     require(handle_address.is_some(), "Handle address not found.");
}

fn name(addr: Identity) -> Option<String>`

The method for searching a name through the resolver returns an Option::Some if found, and an Option::None if not found.

fn foo(contract_id: ContractId) {
     let resolver_abi = abi(NameResolver, contract_id.bits());

     let b256_address: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;
     let handle_name = resolver_abi.name(
          Identity::Address(Address::from(b256_address));
     );
     require(handle_address.is_some(), "Handle name not found.");
}

The NFT is uniquely represented by a single handle. To ensure the token is unique, its is calculated based on the contract and its Sub ID, representing the handle's name:

The primary functionality of the Token in the protocol is to store the handle's metadata, enabling interaction with both on-chain and off-chain information. For the keys, the standard defined in is followed, along with additional default keys defined in the .

SRC-20: Native Asset
SRC-3: Minting and Burning Native Assets
SRC-7: Onchain Native Asset Metadata
SRC-9: Metadata keys
Admin Library
Ownership Library
Asset ID
SRC-9: Metadata keys
TS SDK
https://github.com/infinitybase/bako-id/blob/eebb303f4ac08cd30843e5f9e1949a7edbbd3853/packages/contracts/sway/contracts/registry/src/abis.sw#L6-L18
abi Registry {
    #[storage(write, read), payable]
    fn register(name: String, resolver: Identity, period: u16);

    #[storage(write, read)]
    fn set_metadata_info(name: String, key: String, value: Metadata);

    #[storage(write, read)]
    fn set_owner(name: String, owner: Identity);

    #[storage(write, read)]
    fn set_resolver(name: String, resolver: Identity);
}
https://github.com/infinitybase/bako-id/blob/eebb303f4ac08cd30843e5f9e1949a7edbbd3853/packages/contracts/sway/lib/src/abis/manager.sw#L12-L21
abi Manager {
    #[storage(read, write)]
    fn set_record(name: String, data: RecordData);

    #[storage(read, write)]
    fn set_resolver(name: String, resolver: Identity);

    #[storage(read, write)]
    fn set_owner(name: String, owner: Identity);
}