用 NEO 智能合约实现 NFT

用 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 智能合约的基本思路和技术,省略了许多检查的部分,实际使用中我们可以结合需求来随意扩展,比如限制等级,控制数量,付费购买检查等。这里是一个更标准化的 NFT 合约实现:NFT Contract

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,387评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,845评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,091评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,308评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,662评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,795评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,008评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,466评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,687评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,181评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,531评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,177评论 3 239
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,126评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,862评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,734评论 2 274

推荐阅读更多精彩内容