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,带有存款和取款方法。