NFT-Protocol

OriginByte is an ecosystem of tools, standards and smart contracts designed to make life easier for Web3 game developers and NFT creators. Ranging from simple artwork to more complex gaming assets, OriginByte’s ethos centers around aiding builders in reaching the public and providing on-chain market infrastructure.

The Protocol is divided into three critical components:

  • NFT domains, are the building blocks of logic and data that you can add to your NFTs;

  • Collection level objects, such as Collection<T> and Allowlist<T>, Quorum and AccessPolicy which help you manage and regulate your NFT collection;

  • Launchpad for the primary sale of your NFT collection;

  • NFT trading on the secondary market via the OriginByte Liquidity Layer;

  • Royalty protection and OriginByte's Kiosk Pro

NFTs and OriginByte Domains

In Move, NFTs as well as Fungible Tokens are considered objects. As the creator of those objects, you define their types and the way people can interact with them.

OriginByte is a type-free protocol, and what this means is that anyone can come in and build their own NFT types and leverage from our built-in building blocks. In OriginByte terms, Domains are structs that can be added to your NFT types as well as to the Collection object. These domains can contain both data and logic.

An example of a domain that adds data to your NFT is the Symbol domain, which allows anyone to recognize the collection symbol of your NFT. For domains that add logic, an example would be our composability domain that gives Creators the ability to define Types are composed (e.g. I as a creator can define that an NFT of type Hat can be attached to the NFT of type Avatar).

Static vs. Dynamic Fields

From an object-oriented programming standpoint, you can think of domains as objects being added as fields to a top-level object, which is the type you can define.

When it comes to adding domains to NFT, you can add them as a Static Field or as a Dynamic Field. In the example below, we have a type Submarine which has static fields, of which Attributes is an OriginByte domain:

struct Submarine has key, store {
    id: UID,
    name: String,
    index: u64,
    attributes: Attributes,
}

We can also add the Attributes domain to an NFT as a dynamic field, as illustrated below:

public fun foo(
    nft: &mut Submarine,
    attributes: VecMap<String, String>,
) {
    // ... 
    
    attributes::insert_from_vec(&mut nft.uid, attributes);
    
    // ...
}

Collection Object

Conceptually, we can think of NFTs as being organized into collections; a one-to-many relational data model that could, in a traditional database setup, be represented by two relational database tables - Collection and NFTs.

In our Protocol, the Collection object has two purposes:

  1. To serve as central object for collection level information and thus avoid redundancy (hence reducing storage costs).

  2. To serve as a configuration object for its NFTs, as configurable behaviour can be injected into the object via the addition and removal of domains.

The Collection is a shared object leverages on the design pattern of Entity Component Systems. Creators can therefore add custom domains to Collection to regulate certain aspects of the NFT collection. An example of this is using the domain Compositions to regulate how NFTs of different types can be composed together.

The collection object Collection<T> has a type parameter T, where T is the type of the NFT. Note that, if the creator wants to have multiple NFT types, then it must create a Collection for each type.

Witness Protection

An extremely useful pattern in Sui Move is the Witness Pattern. In a nutshell, a witness is a struct defined in your smart contract that contains drop-only ability. Since in Move, you can only create an object from the contract that defines its type, having drop-only ability means that an instance of such type cannot be copied, stored nor owned directly by anyone. So it means if a function's scope has access to a certain witness, it means that it must have come from the contract that defines it.

This is very powerful because as a Creator you can define what are the rules to be able to create this Witness and what OriginByte offers is an interface that is Witness Protected, in other words certain actions can only be taken if you have access to this Witness, which in turn is defined by the Creator.

Witness Protection allows creators to define how collection level objects can be managed and regulated, objects such as:

  • Collection<T>

  • Allowlist

  • Royalty Strategy objects such as BpsRoyaltyStrategy

  • Orderbook<T, FT>

Among others, and by extension any domain that lives in the context of these collection-level objects.

In practice, we use an even more powerful pattern created by OriginByte, which we call Delegated Witness. The TLDR is that Delegated Witnesses can be created from the Witness but also from the Publisher object, which we talk about below. Note that this accessibility is also configurable.

Collection Publisher

When you deploy a contract in Sui Move, you can create a Publisher object which serves to give its owner a proof of authority over that contract. You can find the object in the Package module of the Sui Framework.

In order to claim the Publisher object at deploy-time, the smart contract must use a One-Time Witness. This is a type defined in the creator's contract, and it is guaranteed to be unique, created in the module initializer and is guaranteed to be unique and have only one instance. The OTW name must be the name of the module in capital letters.

Owning the Publisher object gives you configurable access to Witness-Protected endpoints, as one way to create the Delegated Witness is through the Publisher object.

Below is an example of an init function that creates the Publisher, uses it to generate a DelegatedWitness<Submarine> to in turn create the Collection<Submarine> object. At the end of the function, we airdrop the Publisher object to the address that deploys the contract:

fun init(witness: SUIMARINES, ctx: &mut TxContext) {
    let sender = tx_context::sender(ctx);
    let publisher = sui::package::claim(witness, ctx);

    let delegated_witness = witness::from_publisher(publisher);
    
    let (collection, mint_cap) = collection::create_with_mint_cap<Submarine>(
            delegated_witness, option::none(), ctx
        );

    transfer::public_transfer(publisher, sender);
    transfer::public_share_object(collection);
}

NFT Minting

For each type T created for a Collection, there is a corresponding type MintCap<T>. It stands for Mint Capability, and it is an object that gives its owner the ability to mint NFTs from its type. As an example, if we have two types, Avatar and Hat, then there should be at least one MintCap<Avatar> and MintCap<Hat>. These objects can be created and sent to the Creator's address in the init function of the Collection contract, which runs when the contract is deployed to the Blockchain.

Note: The use of MintCap as an authority over minting is not enforced on the OriginByte protocol, but it is rather up to the NFT creator to use such pattern in its Collection contract. This is because as we've mentioned before, in Move to create instances of a type, you need to call the contract that defines it. This means that your NFT contract must always export a Mint function, and it is up to that function's signature to enforce the use of MintCap or not. For instance:

Below is an unprotected mint function:

public fun mint_nft(
    name: String,
    index: u64,
    receiver: address,
    ctx: &mut TxContext,
) {
    let nft = Submarine {
        id: object::new(ctx),
        name,
        index,
    }

    transfer::public_transfer(nft, receiver);
}

And now follows a mint function protected with MintCap:

public fun mint_nft(
    mint_cap: &MintCap<Submarine>
    name: String,
    index: u64,
    receiver: address,
    ctx: &mut TxContext,
) {
    let nft = Submarine {
        id: object::new(ctx),
        name,
        index,
    }
    
    mint_event::mint(mint_cap, &nft);
    transfer::public_transfer(nft, receiver);
}

You can learn more about NFT minting here.

Our Community

For more information, please check our repository and Sui Move package documentation.

Last updated