ERC-721

我们已经讨论了如何使用 ERC-20 创建一个 可替代的 token,但如果不是所有 token 都一样呢? 这会在 房地产投票权收藏品 等情况下出现,在这些情况下,由于它们的实用性、稀有性等原因,某些物品的价值高于其他物品。 ERC-721 是表示 不可替代的 token 所有权的标准,也就是说,每个 token 都是独一无二的。

ERC-721 是比 ERC-20 更复杂的标准,具有多个可选扩展,并分布在多个合约中。 OpenZeppelin Contracts 在如何组合这些合约方面提供了灵活性,以及自定义的有用扩展。 查看 API 参考 以了解更多信息。

构建 ERC-721 Token 合约

我们将使用 ERC-721 来跟踪我们游戏中的物品,每个物品都有其独特的属性。 每当要奖励给玩家一个物品时,它将被铸造并发送给他们。 玩家可以自由地保留他们的 token 或与其他玩家进行交易,就像他们在区块链上交易任何其他资产一样! 请注意,任何帐户都可以调用 awardItem 来铸造物品。 为了限制每个物品可以铸造哪些帐户。 我们可以使用 访问控制 扩展。

以下是 token 化物品的合约示例:

use openzeppelin_stylus::{
    token::erc721::{
        self,
        extensions::{Erc721Metadata, IErc721Metadata},
        Erc721, IErc721,
    },
    utils::introspection::erc165::IErc165,
};

#[entrypoint]
#[storage]
struct GameItem {
    erc721: Erc721,
    metadata: Erc721Metadata,
    next_token_id: StorageU256,
}

#[public]
#[implements(IErc721<Error = Error>, IErc721Metadata<Error = erc721::Error>, IErc165)]
impl GameItem {
    #[constructor]
    fn constructor(&mut self, name: String, symbol: String, base_uri: String) {
        self.metadata.constructor(name, symbol);
        self.metadata.base_uri.set_str(base_uri);
    }

    fn award_item(&mut self, player: Address) -> Result<U256, erc721::Error> {
        let token_id = self.next_token_id.get() + uint!(1_U256);
        self.next_token_id.set(token_id);

        self.erc721._mint(player, token_id)?;

        Ok(token_id)
    }
}

#[public]
impl IErc721 for GameItem {
    type Error = erc721::Error;

    fn balance_of(&self, owner: Address) -> Result<U256, Self::Error> {
        self.erc721.balance_of(owner)
    }

    fn owner_of(&self, token_id: U256) -> Result<Address, Self::Error> {
        self.erc721.owner_of(token_id)
    }

    fn safe_transfer_from(
        &mut self,
        from: Address,
        to: Address,
        token_id: U256,
    ) -> Result<(), Self::Error> {
        self.erc721.safe_transfer_from(from, to, token_id)
    }

    fn safe_transfer_from_with_data(
        &mut self,
        from: Address,
        to: Address,
        token_id: U256,
        data: Bytes,
    ) -> Result<(), Self::Error> {
        self.erc721.safe_transfer_from_with_data(from, to, token_id, data)
    }

    fn transfer_from(
        &mut self,
        from: Address,
        to: Address,
        token_id: U256,
    ) -> Result<(), Self::Error> {
        self.erc721.transfer_from(from, to, token_id)
    }

    fn approve(
        &mut self,
        to: Address,
        token_id: U256,
    ) -> Result<(), Self::Error> {
        self.erc721.approve(to, token_id)
    }

    fn set_approval_for_all(
        &mut self,
        to: Address,
        approved: bool,
    ) -> Result<(), Self::Error> {
        self.erc721.set_approval_for_all(to, approved)
    }

    fn get_approved(&self, token_id: U256) -> Result<Address, Self::Error> {
        self.erc721.get_approved(token_id)
    }

    fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool {
        self.erc721.is_approved_for_all(owner, operator)
    }
}

#[public]
impl IErc721Metadata for GameItem {
    type Error = erc721::Error;

    fn name(&self) -> String {
        self.metadata.name()
    }

    fn symbol(&self) -> String {
        self.metadata.symbol()
    }

    #[selector(name = "tokenURI")]
    fn token_uri(&self, token_id: U256) -> Result<String, Self::Error> {
        self.metadata.token_uri(token_id, &self.erc721)
    }
}

#[public]
impl IErc165 for GameItem {
    fn supports_interface(&self, interface_id: B32) -> bool {
        self.erc721.supports_interface(interface_id)
            || <Self as IErc721Metadata>::interface_id() == interface_id
    }
}

Erc721Metadata 合约是 ERC-721 的扩展合约。 它使用 token 的名称、符号和基本 URI 扩展了合约本身。

另请注意,与 ERC-20 不同,ERC-721 缺少 decimals 字段,因为每个 token 都是不同的并且无法分割。

有关 erc721 架构的更多信息,请查看 ERC-721 规范

您会注意到物品的信息包含在元数据中,但该信息不在链上! 因此,游戏开发者可以更改底层的元数据,从而更改游戏规则!

扩展

此外,还有多个自定义扩展,包括:

  • ERC-721 Burnable:token 持有者销毁他们自己的 token 的一种方式。

  • ERC-721 Consecutive:https://eips.ethereum.org/EIPS/eip-2309[ERC2309] 的一个实现,用于在构造期间按照 ERC721 铸造批量的 token。

  • ERC-721 Enumerable:可选扩展,允许在链上枚举 token,通常不包括,因为它需要大量的 Gas 开销。

  • ERC-721 Metadata:可选扩展,添加名称、符号和 token URI,几乎总是包含在内。

  • ERC-721 Pausable:暂停合约操作的原语。

  • ERC-721 Uri Storage:一种更灵活但更昂贵的存储元数据的方式。

  • ERC-721 Wrapper:包装器,用于创建由另一个 ERC-721 支持的 ERC-721,带有存款和取款方法。