作者:
用 NEO 智能合约实现 NFT
NFT 是什么
NFT(Non-Fungible Token)即非同质化代币,我们经常接触的数字货币 BTC、ETH 等都可以称为同质化代币,因为每个 BTC 都是一样的,价值和属性都一样;如果我们想用数字货币做更多事情,比如货币功能以外的资产登记、权证记录等,这时候需要的每一份“资产”都是独一无二的,它可以有自己的属性,可以单独修改,因此便有了NFT 的概念,即非同质化代币。
这里就讲讲如何在 NEO 区块链上使用智能合约实现 NFT。
需求
实现一套会员系统,成为会员后可以拥有一个 NFT 的证书,每个证书有唯一的编号标识,该证书可以记录会员的贡献值,贡献值达到一定数值可以进行升级,证书还可以转让出去,卖出后新的拥有者会享有证书附带的特权。
实现
设计合约时,可以按这个思路出发:从需求中提取合约要实现的功能,由功能来决定合约要提供的接口,然后细化每个接口的实现思路和权限等。
根据前面描述的需求,我们的证书合约需要具备发行证书、给证书加积分、证书升级、转卖证书的功能,由此可以得出合约要实现的接口:
- 发行/购买证书:Deploy
- 给证书加积分:AddPoint
- 证书升级:Upgrade
- 转卖证书:Exchange
除了以上基本功能外,我们还需要一些辅助功能,比如查询某个账户上有没有证书;所拥有的证书有多少积分,等级是多少;已经发行了多少个证书等:
- 查询证书信息:GetNftInfo
- 查询发行的数量:GetNftCount
- 查询转卖信息:GetTxInfo
有了以上的设计,基本思路和实现方案已经清楚了,接下来就要开发合约了,开发前的配置准备工作可以参考 NEO 的智能合约。各接口实现代码和说明如下:
public class NFT : SmartContract { public static object Main(string method, object[] args) { var magicstr = "NFT"; if (Runtime.Trigger == TriggerType.Verification) { return false; } else if (Runtime.Trigger == TriggerType.VerificationR) { return true; } else if (Runtime.Trigger == TriggerType.Application) { //入口处取到调用脚本 var callscript = ExecutionEngine.CallingScriptHash; //传入address获取nft信息 if (method == "GetNftInfo") { byte[] address = (byte[]) args[0]; if (address.Length == 0) return false; StorageMap addressMap = Storage.CurrentContext.CreateMap("addressMap"); byte[] tokenId = addressMap.Get(address); if (tokenId.Length == 0) return false; return GetNftByTokenId(tokenId); } //传入txid 获取nft交易信息 if (method == "GetTxInfo") { byte[] txid = (byte[]) args[0]; if (txid.Length == 0) return false; return GetTxInfoByTxid(txid); } //获取已发行的nft数量 if (method == "GetNftCount") { StorageMap nftCountMap = Storage.CurrentContext.CreateMap("nftCountMap"); return nftCountMap.Get("nftCount").AsBigInteger(); } //给一个address发行nft if (method == "Deploy") { byte[] address = (byte[]) args[0]; if (address.Length == 0) return false; StorageMap addressMap = Storage.CurrentContext.CreateMap("addressMap"); //用当前交易的交易二次hash作为nft的id var tokenId = Hash256((ExecutionEngine.ScriptContainer as Transaction).Hash); var newNftInfo = CreateNft(address, tokenId); if (SaveNftInfo(newNftInfo)) { addressMap.Put(address, tokenId); AddNftCount(); SetTxInfo(null, address, tokenId); return true; } return false; } //转手交易 if (method == "Exchange") { byte[] from = (byte[]) args[0]; byte[] to = (byte[]) args[1]; if (from.Length == 0 || to.Length == 0) return false; StorageMap addressMap = Storage.CurrentContext.CreateMap("addressMap"); byte[] toTokenId = addressMap.Get(to); byte[] fromTokenId = addressMap.Get(from); var fromNftInfo = GetNftByTokenId(fromTokenId); fromNftInfo.Owner = to; if (SaveNftInfo(fromNftInfo)) { addressMap.Delete(from); addressMap.Put(to, fromTokenId); SetTxInfo(from, to, fromTokenId); return true; } return false; } //升级 if (method == "Upgrade") { byte[] address = (byte[]) args[0]; StorageMap addressMap = Storage.CurrentContext.CreateMap("addressMap"); byte[] tokenId = addressMap.Get(address); if (tokenId.Length == 0) return false; var nftInfo = GetNftByTokenId(tokenId); nftInfo.Rank += 1; SaveNftInfo(nftInfo); return true; } //加分 if (method == "AddPoint") { byte[] address = (byte[]) args[0]; BigInteger pointValue = (BigInteger) args[1]; if (address.Length == 0) return false; StorageMap addressMap = Storage.CurrentContext.CreateMap("addressMap"); byte[] tokenId = addressMap.Get(address); if (tokenId.Length == 0) return false; var nftInfo = GetNftByTokenId(tokenId); nftInfo.Point += pointValue; if (SaveNftInfo(nftInfo)) return true; return false; } } return false; } //new一个新nft public static NFTInfo CreateNft(byte[] owner, byte[] tokenId) { var nftInfo = new NFTInfo(); nftInfo.TokenId = tokenId; nftInfo.Owner = owner; nftInfo.Point= 0; nftInfo.Rank = 1; return nftInfo; } //增加已发行的数量 public static void AddNftCount() { StorageMap nftCountMap = Storage.CurrentContext.CreateMap("nftCountMap"); var oldCount = nftCountMap.Get("nftCount").AsBigInteger(); nftCountMap.Put("nftCount", oldCount + 1); } //保存nft信息 public static bool SaveNftInfo(NFTInfo nftInfo) { StorageMap userNftInfoMap = Storage.CurrentContext.CreateMap("userNftInfoMap"); byte[] nftInfoBytes = Helper.Serialize(nftInfo); userNftInfoMap.Put(nftInfo.TokenId, nftInfoBytes); return true; } //传入TokenID获得nft信息 public static NFTInfo GetNftByTokenId(byte[] tokenId) { StorageMap userNftInfoMap = Storage.CurrentContext.CreateMap("userNftInfoMap"); byte[] data = userNftInfoMap.Get(tokenId); var nftInfo = new NFTInfo(); if (data.Length > 0) nftInfo = data.Deserialize() as NFTInfo; return nftInfo; } //保存交易信息 public static void SetTxInfo(byte[] from, byte[] to, byte[] tokenId) { ExchangeInfo info = new ExchangeInfo(); info.@from = from; info.to = to; info.tokenId = tokenId; byte[] exInfo = Neo.SmartContract.Framework.Helper.Serialize(info); //当前执行的ScriptContainer的Transaction Hash作为txid var txid = (ExecutionEngine.ScriptContainer as Transaction).Hash; StorageMap ExchangeInfoMap = Storage.CurrentContext.CreateMap("txInfoMap"); ExchangeInfoMap.Put(txid, exInfo); } //通过txid获取交易信息 public static ExchangeInfo GetTxInfoByTxid(byte[] txid) { ExchangeInfo info = new ExchangeInfo(); StorageMap ExchangeInfoMap = Storage.CurrentContext.CreateMap("txInfoMap"); var data = ExchangeInfoMap.Get(txid); if (data.Length > 0) info = data.Deserialize() as ExchangeInfo; return info; } } //交易信息 public class ExchangeInfo { public byte[] from; public byte[] to; public byte[] tokenId; } //证书信息 public class NFTInfo { public byte[] TokenId; //tokenid 证书ID public byte[] Owner; //所有者 address public BigInteger Rank; //等级 public BigInteger Point; //积分值 }复制代码
这样就实现了我们前面设计的接口,使用时编译发布合约到 NEO 网络上就可以了。
本文主要介绍如何结合 NEO 智能合约实现一个 NFT 合约以及开发 NEO 智能合约的基本思路和技术,省略了许多检查的部分,实际使用中我们可以结合需求来随意扩展,比如限制等级,控制数量,付费购买检查等。这里是一个更完善的 NEO 上 NFT 合约实现:。