Back to Learn
applications

ERC-721 NFT

Create a non-fungible token (NFT) contract following the ERC-721 standard.

ERC-721 NFT

ERC-721 is the standard for non-fungible tokens. Each token has a unique ID and owner.

Core Functions

- balanceOf(owner) - Number of tokens owned

- ownerOf(tokenId) - Owner of a specific token

- transferFrom(from, to, tokenId) - Transfer token

- approve(to, tokenId) - Approve transfer

- setApprovalForAll(operator, approved) - Approve all tokens

Storage

``rust

#[storage]

pub struct Erc721 {

owners: StorageMap,

balances: StorageMap,

token_approvals: StorageMap,

operator_approvals: StorageMap>,

}

``

Code Example

#![cfg_attr(not(feature = "export-abi"), no_main)]
extern crate alloc;

use stylus_sdk::prelude::*;
use stylus_sdk::{evm, msg};
use stylus_sdk::storage::{StorageMap, StorageU256, StorageBool};
use alloy_primitives::{Address, U256};
use alloy_sol_types::sol;

sol! {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}

#[storage]
#[entrypoint]
pub struct Erc721 {
    owners: StorageMap<U256, Address>,
    balances: StorageMap<Address, U256>,
    token_approvals: StorageMap<U256, Address>,
    next_token_id: StorageU256,
}

#[public]
impl Erc721 {
    pub fn balance_of(&self, owner: Address) -> U256 {
        self.balances.get(owner)
    }

    pub fn owner_of(&self, token_id: U256) -> Address {
        self.owners.get(token_id)
    }

    pub fn mint(&mut self, to: Address) -> U256 {
        let token_id = self.next_token_id.get();
        self.next_token_id.set(token_id + U256::from(1));

        self.owners.insert(token_id, to);
        self.balances.insert(to, self.balances.get(to) + U256::from(1));

        evm::log(Transfer { from: Address::ZERO, to, tokenId: token_id });
        token_id
    }

    pub fn transfer_from(&mut self, from: Address, to: Address, token_id: U256) {
        let owner = self.owners.get(token_id);
        assert_eq!(owner, from, "Not owner");

        self.balances.insert(from, self.balances.get(from) - U256::from(1));
        self.balances.insert(to, self.balances.get(to) + U256::from(1));
        self.owners.insert(token_id, to);
        self.token_approvals.insert(token_id, Address::ZERO);

        evm::log(Transfer { from, to, tokenId: token_id });
    }
}

Key Points

  • Each token has unique ID
  • owners mapping: tokenId -> address
  • balances mapping: address -> count
  • Supports approvals and operators
  • Transfer, Approval, ApprovalForAll events