Show / Hide Table of Contents

NEP-11 合约编写

NEP11 协议是 Neo 补充协议中的第 11 号协议,定义了 NFT(非同质化资产)的合约编写标准。本文将以 C# 代码为例,讲解如何编写一个简单的 NFT 合约。

定义 NFT 属性

开发者编写 NFT 合约最方便的方法是直接继承 Nep11Token<Nep11TokenState> 类。其中, Nep11TokenState 是保存 NFT 所有属性的类,这里除了默认的 Name 和 Owner,还可以添加自定义的属性,如图片、视频、类别、网址、攻击力、防御力等。

字段示例描述
NameHarryPotter #001NFT 名称
Owner0x4578060c29f4c03f1e16c84312429d991952c94cNFT 拥有者
Type0类型(自定义)
Imagehttps://neo.org/images/HarryPotter.jpg图片(自定义)
ATK3000攻击力(自定义)
DEF3000防御力(自定义)

每个 NFT 资产都需要一个唯一的标识符号,如果你的合约中每个 NFT 资产名字都不相同,可以将 Nep11TokenState 中的 Name 字段当做 TokenID。如果有重名的 NFT 资产,需要自己添加一个新的字段,例如 ID 或 TokenID。

特别说明的是,为了使得钱包正确显示 NFT 的图片,建议合约开发者将图片字段命名为 Image 。钱包的开发者也可根据 NFT 的 Image 属性来抓取 NFT 的图片。

TokenState 的示例代码如下:

public class MyTokenState : Nep11TokenState
{
    public string Image { get; set; }

    public MyTokenState(string name)
    {
        //TODO: Replace it with your own URL.
        Image = "https://neo.org/images/" + name + ".jpg";
    }
}

继承 Nep11Token<Nep11TokenState> 后,需要重写 Symbol 方法,如下:

public override string Symbol() => "MNFT";

分发方法

Nep11Token 基类中并不包含如何分发 NFT 的方法,开发者可以根据需求定制。本示例中为了让合约拥有者有权限发行 NFT 资产,创建了 Airdrop 方法,其功能是合约拥有者可以向指定地址空投一个 NFT。

public static bool Airdrop(UInt160 to, string name)
{
    if (!IsOwner()) throw new Exception("No authorization.");
    if (!to.IsValid) throw new Exception("Amount is invalid.");

    Mint(name, new MyTokenState(name));
    return true;
}

其中,Mint 方法继承于 Nep11Token,调用的时候,只需传入 NFT 的 TokenID(本示例中用 Name 表示) 和 TokenState 对象即可。

代码示例

完整的合约示例代码如下:

using Neo;
using Neo.SmartContract;
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Attributes;
using Neo.SmartContract.Framework.Services;
using System;

namespace Contract1
{
    [SupportedStandards("NEP-11")]
    public class Contract1 : Nep11Token<MyTokenState>
    {
        //TODO: Replace it with your own address.
        [InitialValue("NiNmXL8FjEUEs1nfX9uHFBNaenxDHJtmuB", ContractParameterType.Hash160)]
        static readonly UInt160 Owner = default;

        private static bool IsOwner() => Runtime.CheckWitness(Owner);

        public override string Symbol() => "MNFT";

        public static bool Airdrop(UInt160 to, string name)
        {
            if (!IsOwner()) throw new Exception("No authorization.");
            if (!to.IsValid) throw new Exception("Amount is invalid.");

            Mint(name, new MyTokenState(name));
            return true;
        }
    }

    public class MyTokenState : Nep11TokenState
    {
        public string Image { get; set; }

        public MyTokenState(string name)
        {
            //TODO: Replace it with your own URL.
            Image = "https://neo.org/images/" + name + ".jpg";
        }
    }
}

如果想让用户通过 GAS 购买 NFT,可以添加如下方法:

public static void OnNEP17Payment(UInt160 from, BigInteger amount, object _)
{

    if (Runtime.CallingScriptHash != GAS.Hash)
        throw new Exception("Please pay with GAS");
    amount /= 100000000;
    for (int i = 0; i < amount; i++)
    {
        //TODO: Please replace with your own naming logic. TokenId is not allowed to be the same
        var name = "HarryPotter #001";
        Mint(name, new MyTokenState(name));
    }
}

到此,一个简单的 NFT 合约就编写好了。

基类方法和事件

Nep11Token 基类还提供了以下方法和事件。

NEP-11 方法

名称参数返回值说明
symbol--String返回合约符号,如 "MNFT"
decimals--Integer返回整数 0
totalSupply--IntegerNFT 总发行量。总发行量 = 铸币的总量 - 销毁的总量
balanceOfHash160(owner)Integer该用户持有的 NFT 的总量
ownerOfByteArray(tokenId)Hash160查询某个 NFT 的所有者
propertiesByteArray(tokenId)Map查询某个 NFT 的属性
tokensInteropInterface查询所有已发行的 NFT
tokensOfHash160(owner)InteropInterface查询某个人拥有的 NFT
transferHash160(to) ByteArray(tokenId) Any(data)Boolean转账,通过 TokenId,将 NFT 转给他人,该方法需要 NFT 的所有者签名。

事件

名称参数说明备注
transferHash160(from) Hash160(to) Integer(amount) ByteArray(tokenId)转账事件from 为 null 即铸币 to 为 null 即销毁 amount 始终为 1

相关参考

NEP-11 规范

基类 Nep11TokenState 源码

基类 Nep11Token 源码

NeoVerse 合约文档