Skip to main content

NEP-17

NEP17 协议是 Neo 补充协议中的第17号协议,替代了原先的NEP5协议。其目的是为 Neo 建立标准的 token 化智能合约通用交互机制。NEP17资产是在合约存储区内记账,通过对存储区内不同账户 hash 记录余额数值的变化,完成交易。

参照 NEP17 协议的要求,在编写 NEP17 资产智能合约时必须实现以下方法:

totalSupply

public static BigInteger totalSupply()

返回部署在系统内该 token 的总数。

symbol

public static string symbol()

返回合约所管理的token的短字符串符号 . e.g. "MYT"

该符号必须是一个有效的ASCII字符串,不能有空白字符或换行符,应该限制为简短的(建议为3-8个字符长)大写拉丁字母 (即26个英文字符)。

该方法每次被调用时必须返回一样的值。

decimals

public static byte decimals()

返回token使用的小数位数 - e.g. 8,意味着把 token 数量除以 100,000,000 来获得它的表示值。

该方法每次被调用时必须返回一样的值。

balanceOf

public static BigInteger balanceOf(byte[] account)

返回account的token余额。

参数account必须是一个 20 字节的地址。如果不是,该方法应该抛出异常

如果account是个未被使用的地址,该方法必须返回0

transfer

public static bool transfer(byte[] from, byte[] to, BigInteger amount)

从账户from转移数量为amount的token到地址to

参数 fromto 必须是 20 字节长的地址。否则,该方法应该抛出异常

参数 amount 必须大于或等于0。否则,该方法应该抛出异常

如果账户from的余额不足以支付费用,该函数必须返回 false

如果方法执行成功,必须触发Transfer事件,并且必须返回 true,即使amount为 0 或者 fromto 是同一个地址。

函数应该检查当前调用该方法的合约哈希是否等于地址from。如果是,则应该对转账操作进行处理;否则,函数应该调用 SYSCALL Neo.Runtime.CheckWitness来验证转账操作。

如果转账操作没有执行成功,该函数必须返回false

如果 to 是一个已部署合约的地址哈希,函数必须在触发Transfer事件后调用合约toonNEP17Payment方法。如果合约to不想接收这笔转账,则必须调用操作码ABORT

事件 Transfer

public static event transfer(byte[] from, byte[] to, BigInteger amount)

在token转账完成后必须触发该事件,包括amount为0以及fromto为同一地址的情况。

一个创建新 token 的 token 合约在创建 token 时必须触发Transfer事件,并将地址from设置为 null

一个销毁 token 的 token 合约在销毁 token 时必须触发Transfer事件,并将地址to设置为 null

NEP17 合约方法如下,完整的合约代码可参考 GitHub 源码

using Neo;using Neo.SmartContract;using Neo.SmartContract.Framework;using Neo.SmartContract.Framework.Native;using Neo.SmartContract.Framework.Services;using System;using System.Numerics;
namespace Template.NEP17.CSharp{    public partial class NEP17 : SmartContract    {        public static BigInteger TotalSupply() => TotalSupplyStorage.Get();
        public static BigInteger BalanceOf(UInt160 account)        {            if (!ValidateAddress(account)) throw new Exception("The parameters account SHOULD be a 20-byte non-zero address.");            return AssetStorage.Get(account);        }
        public static bool Transfer(UInt160 from, UInt160 to, BigInteger amount, object data)        {            if (!ValidateAddress(from) || !ValidateAddress(to)) throw new Exception("The parameters from and to SHOULD be 20-byte non-zero addresses.");            if (amount <= 0) throw new Exception("The parameter amount MUST be greater than 0.");            if (!Runtime.CheckWitness(from) && !from.Equals(ExecutionEngine.CallingScriptHash)) throw new Exception("No authorization.");            if (AssetStorage.Get(from) < amount) throw new Exception("Insufficient balance.");            if (from == to) return true;
            AssetStorage.Reduce(from, amount);            AssetStorage.Increase(to, amount);
            OnTransfer(from, to, amount);
            // Validate payable            if (IsDeployed(to)) Contract.Call(to, "onNEP17Payment", new object[] { from, amount, data });            return true;        }    }}

NEP-17 变化#

本节总结了NEP-17相对于NEP-5协议发生的改动。

onNEP17Payment#

  • Transfer 方法需判断收款方是否是部署的合约,如果是,则调用它的 onNEP17Payment 方法。

  • 原生合约的 FungibleToken(NeoToken、GasToken) 在转账时会调用 onNEP17Tokens 方法。NonfungibleToken 在转账时会调用 onNEP11Tokens 方法。

  • TokenSale 合约需要实现 onNEP17Payment 方法,以接收资产,并修改 Manifest 文件以信任接收的资产合约对其调用。

name 方法#

移除了 name 方法,将 name 方法放到了 manifest 中,在写合约时要添加 [DisplayName("Token Name")]

[DisplayName("Token Name")][ManifestExtra("Author", "Neo")][ManifestExtra("Email", "dev@neo.org")][ManifestExtra("Description", "This is a NEP17 example")][SupportedStandards("NEP-17")]public partial class NEP17 : SmartContract{    [DisplayName("Transfer")]    public static event Action<UInt160, UInt160, BigInteger> OnTransfer;
    public static string Symbol() => "TokenSymbol";
    public static ulong Decimals() => 8;        //……}

Transfer 事件#

transfer 事件变更为 Transfer 事件 (首字母大写)。

IsPayable#

在 Neo Legacy 中部署合约时需要勾选 IsPayable 表示能否接收 NEP-17 资产。

在 Neo N3 中移除了 payable 检查,相应逻辑将放到 onNEP17Payment 方法中。

合约能否接收资产从固定不变的常量改为合约内的代码逻辑。