# Gear Non-Fungible Token
Non-fungible tokens (NFTs) are unique cryptographic tokens on a blockchain that are used to prove an ownership of a digital asset, such as digital art or gaming assets. The difference from fungible tokens is that the fungible tokens store a value, while non-fungible tokens store a cryptographic certificate.
Under the hood, a non-fungible token consists of a unique token identifier, or token ID, which is mapped to an owner identifier and stored inside a NFT smart contract. <center> token_id → address
</center>
The functions that must be supported by each non-fungible-token contract:
- `Transfer(to, token_id)` is a function that allows you to transfer a token with the `token_id` number to the to `account`;
- `Approve(approved_account, token_id)` is a function that allows you to give the right to dispose of the token to the specified `approved_account`. This functionality can be useful on marketplaces or auctions. When the owner wants to sell his token, they can put it on a marketplace/auction, so the contract sends this token to the new owner.
- `Mint(to, token_id, metadata)` is a function that creates a new token. `Metadata` can include any information about the token: it can be a link to a specific resource, a description of the token, etc.
- `Burn(from, token_id)`: This function removes the token with the mentioned token_id from the contract.
### Implementation of NFT with gear-lib
Open in gitpod: https://github.com/LouiseMedova/starter.
Install the necessary tools:
```
rustup toolchain add nightly
rustup target add wasm32-unknown-unknown --toolchain nightly
```
Add `gear-lib` and `gear-lib-derive` to `Cargo.toml`:
```
[package]
name = "nft"
version = "0.1.0"
edition = "2021"
[dependencies]
gstd = { git = "https://github.com/gear-tech/gear.git", features = ["debug"], rev = "78dfa07"}
scale-info = { version = "2", default-features = false, features = ["derive"] }
parity-scale-codec = { version = "3", default-features = false }
gear-lib = { git = "https://github.com/gear-dapps/gear-lib.git", tag = "0.3.7" }
gear-lib-derive = { git = "https://github.com/gear-dapps/gear-lib.git", tag = "0.3.7" }
[build-dependencies]
gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", rev = "78dfa07" }
[dev-dependencies]
gtest = { git = "https://github.com/gear-tech/gear.git", rev = "78dfa07"}
```
Add entry points to `lib.rs`:
```rust
#![no_std]
use gstd::{prelude::*, msg};
#[no_mangle]
extern "C" fn init() {}
#[no_mangle]
extern "C" fn handle() {}
```
First we'll create the struct for our NFT state.
```rust
#![no_std]
use gstd::{prelude::*, msg, ActorId};
use gear_lib::non_fungible_token::{state::*, token::*, nft_core::*};
pub struct MyNFT {
pub token: NFTState,
pub token_id: TokenId,
pub owner: ActorId,
}
```
```NFTState``` is the following struct from `gear-lib`:
```rust
#[derive(Debug, Default)]
pub struct NFTState {
pub name: String,
pub symbol: String,
pub base_uri: String,
pub owner_by_id: HashMap<TokenId, ActorId>,
pub token_approvals: HashMap<TokenId, HashSet<ActorId>>,
pub token_metadata_by_id: HashMap<TokenId, Option<TokenMetadata>>,
pub tokens_for_owner: HashMap<ActorId, Vec<TokenId>>,
pub royalties: Option<Royalties>,
}
```
To inherit the default logic functions, namely `mint`, `burn`, `transfer` and `approve` we need to derive `NFTCore` and `NFTStateKeeper` trait for `MyNFT` and also add `NFTStateState` above `token` field:
```rust
use gear_lib_derive::{NFTCore, NFTStateKeeper};
#[derive(NFTCore, NFTStateKeeper)]
pub struct MyNFT {
#[NFTStateField]
pub token: NFTState,
pub token_id: TokenId,
pub owner: ActorId,
}
```
So now our NFT contract has main standard functions. Let's write the whole implementation of the NFT contract.
First, let's create `nft-io` crate that will describe our contract interface:
```
cargo new io --lib
```
The `Cargo.toml` of the `nft-io`:
```
[package]
name = "nft-io"
version = "0.1.0"
edition = "2021"
[dependencies]
gstd = { git = "https://github.com/gear-tech/gear.git", features = ["debug"], rev = "78dfa07"}
gmeta = { git = "https://github.com/gear-tech/gear.git", rev = "78dfa07"}
scale-info = { version = "2", default-features = false, features = ["derive"] }
parity-scale-codec = { version = "3", default-features = false }
gear-lib = { git = "https://github.com/gear-dapps/gear-lib.git", tag = "0.3.7" }
```
In the `lib.rs` we define the struct `InitNFT` for the initialization message and `NFTAction` enum for messages that our contract will process:
```rust
#![no_std]
use gstd::{prelude::*, ActorId};
use gmeta::{Metadata, InOut};
use gear_lib::non_fungible_token::{token::*};
#[derive(Encode, Decode, TypeInfo)]
pub struct InitNFT {
pub name: String,
pub symbol: String,
pub base_uri: String,
}
#[derive(Encode, Decode, TypeInfo)]
pub enum NFTAction {
Mint {
to: ActorId,
token_metadata: TokenMetadata
},
Burn {
token_id: TokenId,
},
Transfer {
token_id: TokenId,
to: ActorId,
},
Approve {
token_id: TokenId,
to: ActorId,
}
}
```
`TokenMetadata` is the struct from the `gear_lib`:
```rust
pub struct TokenMetadata {
// ex. "CryptoKitty #100"
pub name: String,
// free-form description
pub description: String,
// URL to associated media, preferably to decentralized, content-addressed storage
pub media: String,
// URL to an off-chain JSON file with more info.
pub reference: String,
}
```
Let's define the enum `NFTEvent` for the message the contract will reply. The `mint`, `transfer` and `burn` functions returns the `NFTTransfer` struct (this struct is also defined in the `gear-lib`):
```
pub struct NFTTransfer {
pub from: ActorId,
pub to: ActorId,
pub token_id: TokenId,
}
```
where:
- `from` is the account from which the token is transfered. In case of `mint` action this field is a zero address;
- `to` is the account to which the token is transfered. In case of `burn` action this field is a zero address;
- `token_id` is the id of the indicated token.
The function `approve` returns `NFTApproval`:
```
pub struct NFTApproval {
pub owner: ActorId,
pub approved_account: ActorId,
pub token_id: TokenId,
}
```
So, the `NFTEvent` enum:
```rust
use gear_lib::non_fungible_token::{token::*, io::*};
...
#[derive(Encode, Decode, TypeInfo)]
pub enum NFTEvent {
Mint(NFTTransfer),
Burn(NFTTransfer),
Transfer(NFTTransfer),
Approve(NFTApproval)
}
```
Add `nft-io` to `Cargo.toml`:
```
[package]
name = "nft"
version = "0.1.0"
edition = "2021"
[dependencies]
...
nft-io = { path = "io" }
[build-dependencies]
...
nft-io = { path = "io" }
```
and import to the `nft` contract:
```rust
#![no_std]
use gstd::{prelude::*, msg, ActorId};
use gear_lib::non_fungible_token::{state::*, token::*, nft_core::*};
use gear_lib_derive::*;
use nft_io::*;
```
Let's declare the global variable in the contract that will store our contract state:
```rust
static mut NFT: Option<MyNFT> = None;
```
and write the `init` function:
```rust
#[derive(NFTCore, NFTStateKeeper, Default)]
pub struct MyNFT {
#[NFTStateField]
pub token: NFTState,
pub token_id: TokenId,
pub owner: ActorId,
}
static mut NFT: Option<MyNFT> = None;
#[no_mangle]
extern "C" fn init() {
let init_msg: InitNFT = msg::load().expect("Inable to decode `InitNFT");
let mut nft: MyNFT = Default::default();
nft.token.name = init_msg.name;
nft.token.symbol = init_msg.symbol;
nft.token.base_uri = init_msg.base_uri;
nft.owner = msg::source();
unsafe { NFT = Some(nft) };
}
```
Next let's write the `handle` function:
```rust
#[no_mangle]
extern "C" fn handle() {
let msg: NFTAction = msg::load().expect("Inable to decode `NFTAction`");
let nft = unsafe { NFT.as_mut().expect("The contract is no initialized") };
match msg {
NFTAction::Mint{to, token_metadata} => {
let token_id = nft.token_id;
nft.token_id = nft.token_id.saturating_add(TokenId::one());
nft.mint(&to, token_id, Some(token_metadata));
},
NFTAction::Burn{token_id} => {
nft.burn(token_id);
}
NFTAction::Transfer{to, token_id} => {
nft.transfer(&to, token_id);
},
NFTAction::Approve{to, token_id} => {
nft.approve(&to, token_id);
}
}
}
```
Since our contract should reply to the incoming message, let's write the `reply` function:
```rust
fn reply(payload: impl Encode) {
msg::reply(payload, 0).expect("Error in sending a reply");
}
```
Since `NFTTransfer` and `NFTApproval` are message payloads and therefore implement `Encode` trait, we can define the payload as argument as `impl Encode`([read more](https://doc.rust-lang.org/book/ch10-02-traits.html)).
So, the code of `handle` function:
```rust
#[no_mangle]
extern "C" fn handle() {
let msg: NFTAction = msg::load().expect("Inable to decode `NFTAction`");
let nft = unsafe { NFT.as_mut().expect("The contract is no initialized") };
match msg {
NFTAction::Mint{to, token_metadata} => {
let token_id = nft.token_id;
nft.token_id = nft.token_id.saturating_add(TokenId::one());
reply(nft.mint(&to, token_id, Some(token_metadata)));
},
NFTAction::Burn{token_id} => {
reply(nft.burn(token_id));
}
NFTAction::Transfer{to, token_id} => {
reply(nft.transfer(&to, token_id));
},
NFTAction::Approve{to, token_id} => {
reply(nft.approve(&to, token_id));
}
}
}
```
### Adding your functionality to the NFT contract
Define a trait for our new function that will extend the default `NFTCore` trait:
```rust
...
use gear_lib::non_fungible_token::{state::*, token::*, nft_core::*, io::*};
...
pub trait MyNFTCore: NFTCore {
fn mint(&mut self, to: &ActorId, token_metadata: TokenMetadata) -> NFTTransfer;
}
```
and implement our `mint` function:
```rust
impl MyNFTCore for MyNFT {
fn mint(&mut self, to: &ActorId, token_metadata: TokenMetadata) -> NFTTransfer {
assert_eq!(msg::source(), self.owner, "Only owner can mint new tokens");
let token_id = self.token_id;
self.token_id = self.token_id.saturating_add(TokenId::one());
NFTCore::mint(self, to, token_id, Some(token_metadata))
}
}
```
Then in the `handle` function the processing of the `mint` action will be the following:
```rust
#[no_mangle]
extern "C" fn handle() {
...
match msg {
NFTAction::Mint{to, token_metadata} => {
reply(MyNFTCore::mint(nft, &to, token_metadata));
},
...
}
}
```
### NFT metadata
Let's define the program interface in the `nft_io` crate:
- the input message for the `init` function is the `InitNFT` struct;
- the input message for the `handle` function is the `NFTAction` enum;
- the output message of the `handle` function is the `NFTEvent` enum.
```
pub struct NFTMetadata;
impl Metadata for NFTMetadata {
type Init = InOut<InitNFT, ()>;
type Handle = InOut<NFTAction, NFTEvent>;
...
}
```
Then let's define the struct for our contract state that will be used to read the program state. We can't use the `MyNFT` struct since the `NFTState` doesn't implement the `Encode`, `Decode` and `TypeInfo` traits. Let's declare `NftIO` struct:
```rust
impl Metadata for NFTMetadata {
type Init = InOut<InitNFT, ()>;
type Handle = InOut<NFTAction, NFTEvent>;
type State = NftIO;
type Reply = ();
type Signal = ();
type Others = ();
}
#[derive(Encode, Decode, TypeInfo)]
pub struct NftIO {
pub name: String,
pub symbol: String,
pub base_uri: String,
pub owner_by_id: Vec<(TokenId, ActorId)>,
pub token_approvals: Vec<(TokenId, Vec<ActorId>)>,
pub token_metadata_by_id: Vec<(TokenId, Option<TokenMetadata>)>,
pub tokens_for_owner: Vec<(ActorId, Vec<TokenId>)>,
pub token_id: TokenId,
pub owner: ActorId,
}
```
and write the `state` function in nft contract where we transform the struct `MyNFT` into the `NftIo`:
```rust
#[no_mangle]
extern "C" fn state() {
let nft = unsafe { NFT.as_ref().expect("The contract is not initialized") };
let state = NftIO {
name: nft.token.name.clone(),
symbol: nft.token.symbol.clone(),
base_uri: nft.token.base_uri.clone(),
owner_by_id: nft
.token
.owner_by_id
.iter()
.map(|(key, value)| (*key, *value))
.collect(),
token_approvals: nft
.token
.token_approvals
.iter()
.map(|(key, value)| (*key, value.iter().copied().collect()))
.collect(),
token_metadata_by_id: nft
.token
.token_metadata_by_id
.iter()
.map(|(key, value)| (*key, value.clone()))
.collect(),
tokens_for_owner: nft
.token
.tokens_for_owner
.iter()
.map(|(key, value)| (*key, value.clone()))
.collect(),
token_id: nft.token_id,
owner: nft.owner,
};
msg::reply(state, 0).expect("Unable to share the state");
}
```
Let's change the `build.rs` to build the program `metadata`:
```rust
use nft_io::NFTMetadata;
fn main() {
gear_wasm_builder::build_with_metadata::<NFTMetadata>();
}
```
Do not forget to add `metahash()` function to the nft contract:
```rust
#[no_mangle]
extern "C" fn metahash() {
let metahash: [u8; 32] = include!("../.metahash");
msg::reply(metahash, 0).expect("Unable to share the metahash");
}
```
### Reading program state using your own function
In the previous lesson, we learnt how to read the full program state using state function in the contract. Additionally, it's possible to create your own library with functions to read the contract state.
Let's create an independent crate for reading state:
```
cargo new state --lib
```
The `Cargo.toml` of this crate:
```
[package]
name = "nft-state"
version = "0.1.0"
edition = "2021"
[dependencies]
gstd = { git = "https://github.com/gear-tech/gear.git", features = ["debug"], rev = "78dfa07"}
gmeta = { git = "https://github.com/gear-tech/gear.git", rev = "78dfa07", features = ["codegen"]}
gear-lib = { git = "https://github.com/gear-dapps/gear-lib.git", tag = "0.3.7" }
nft-io = { path = "../io" }
[build-dependencies]
gear-wasm-builder = { git = "https://github.com/gear-tech/gear", rev = "78dfa07", features = ["metawasm"] }
```
We should create the `build.rs` file in this crate:
```
touch state/build.rs
```
and write the following function:
```rust
fn main() {
gear_wasm_builder::build_metawasm();
}
```
In the `lib.rs` file we'll import `metawasm` macros from `gmeta` lib and define `metafns` module:
```rust
#![no_std]
use gmeta::metawasm;
#[metawasm]
pub mod metafns {
...
}
```
In the `metafns` module we can write any functions we want for reading the contract state.
At first, we have to define the contract state. It's `NftIO` in our case:
```rust
#![no_std]
use gmeta::metawasm;
use nft_io::NftIO;
#[metawasm]
pub mod metafns {
pub type State = NftIO;
}
```
Then let's write a function that returns the `TokenMetadata` for the indicated token:
```rust
#![no_std]
use gear_lib::non_fungible_token::{token::*};
use gmeta::metawasm;
use nft_io::NftIO;
#[metawasm]
pub mod metafns {
pub type State = NftIO;
pub fn token_metadata(state: State, token_id: TokenId) -> Option<TokenMetadata> {
if let Some((_, metadata)) = state
.token_metadata_by_id
.iter()
.find(|(id, _)| *id == token_id)
{
return metadata.clone();
}
None
}
}
```
Note, that in `metafns` functions the first argument must always be `state` or `_state`.