第十八课 【ERC875】Hiblock黑客马拉松门票从定制到编码实现

1,摘要

【本文目标】 通过本文,可以从一个HiBlock黑客马拉松活动门票定制,转让,出售和签到为例,说明ERC875的设计初心,ERC875的标准接口分析,也给出了官网的ERC875的代码和本地测试,便于更多项目使用ERC875解决区块链业务中遇到的实际问题。 【前置条件】 (1)体验门票受让的用户不需要有任何技术门槛; (2)做门票定制和开发的需要本地已安装好MetaMASK,在Reposton Test Net获取了几个测试ETH(免费)的,要懂Solidity语言。 不熟悉的建议参考文档《第六课 技术小白如何开发一个DAPP区块链应用(以宠物商店为例)》的“5. 安装 MetaMask和配置区块链网络”章节。

2,Hiblock黑客马拉松区块链门票全体验

2.1 门票定制创建 - [辉哥]

ALPHA WALLET团队已经封装好了ERC785协议实现,可以通过浏览器完成票务类ERC875的智能合约创建。对应的TOKEN工厂网址为https://alpha-wallet.github.io/ERC875-token-factory/index.html 测试使用,MetaMASK选择的测试网络为"Ropsten Test Net"。

1) “Deploy Contract” 定义名称和标识,对应的地址是以太坊钱包地址。Owner Address必须为MetaMast的当前账号地址,然后点击“Deploy Contract”按钮。[名称和标识命名跟一般使用的搞反了,将就用吧] Contract Name: HHT Ticket Symbol: Hiblock Hackathon Ticket Owner Address:0xB51Fa936B744CFEbAeD8DbB79d2060903e689F89 Recipient Address:0xB51Fa936B744CFEbAeD8DbB79d2060903e689F89

1. 提交合约部署

2)“Submmit”按钮 “Gas Price”设置为30,点击“Submmit”按钮。该账号要有一定的ETH测试币,否则点击"Buy"找平台免费买点。

2. 确认交易

3)购买成功确认 购买成功的会有弹出提示。点击“确定”按钮后,拉到下方的按钮可以查看智能合约部署链接和ABI合约信息。

3.合约部署成功

4. 查看ABI信息和合约记录

4)查看部署合约成功地址 点击可知其部署成功:https://ropsten.etherscan.io/address/0x07fc44d796d30b317013cb907fadb6d738f5779e

2.2 安装APP,导入钱包,导入门票 - [辉哥]

1) 安装APP 辉哥在官网(https://awallet.io/)下载APP完成安装。

2) 导入钱包 点击配置页面,更换网络为"Ropsten(Test)"网络,导入创建门票的钱包私钥。

3)添加代币 输入之前的智能合约地址,符号和名称会自动联想出来的。

导入成功后,钱包页面可以看到对应的通证信息。如果是没有这个资产的钱包导入这个通证,钱包页面是看不到这个通证门票的。

2.3 转让门票 - [辉哥-欧阳哥哥]

通过报名渠道,辉哥知道欧阳哥哥已报名参加HiBlock黑客马拉松,所以把区块链门票转给他。 1) 辉哥点击“转让”按钮 选择HHT后,点击右下角的“转让”按钮进行票务转让。

2)点击“转让”按钮 选择“现在直接转让门票”,

获取欧阳哥哥的钱包地址,输入:

输入欧阳哥哥的钱包地址

3)确认转让

转让门票按钮

转账成功

2.4 出售门票 - [欧阳哥哥-小辉]

1)导入通证 欧阳哥哥在AlphaWallet钱包中输入HHT的合约地址(0x07fc44d796d30b317013cb907fadb6d738f5779e)即可查看到辉哥转账过来的门票通证。

2) 出售门票 小辉同学知道了黑客马拉松的事情,也很想参加。欧阳哥哥刚好弄了2张票,就同意把一张票低价转让给小辉。双方协商好价格是0.2个ETH。 欧阳哥哥点击出售按钮,设置好价格,最后链接通过微信发给小辉。

设置价格

设置截止时间

确认出售,把链接微信发给小辉

3) 导入支付 小辉安装好APP。复制链接打开APP时,会提示导入门票。点击购买,支付了0.2个ETH后即可完成支付。

门票

确认购买

购买成功

4) 导入代币地址完成呈现 小辉在钱包导入HHT智能合约的地址(0x07fc44d796d30b317013cb907fadb6d738f5779e)后,即可在APP上呈现购买的HHT门票一张。

2.5 兑现门票

欧阳哥哥和小辉到达HiBlock黑客马拉松现场,点击门票的“兑换”按钮,主办方Bob根据他们展示的二维码扫描完成。该门票的状态会变更为已兑换。

【后记】他们组队参加黑客马拉松,依靠其过硬的技术实力,获得了一个二等奖!

3,ERC875设计目标

AlphaWallet团队核心成员(左二:CEO张中南;右二:创始人兼CTO张韡武)

ERC875协议是由AlphaWallet团队提出的,他们希望基于ERC875协议族,能够实现人、事、物、权token化。

在创始人张中南看来,人、事、物、权全部token化,即可以用token来替代物理世界里面的任何商品。在此其中,token替代的是一个权益,可以指代各种各样的权益。比如,「人」的token化,「跟吴亦凡今天晚上6点钟到8点钟一起吃饭的权益,可以做成一个token」,「事」的token化,「用信用卡在商店买了一瓶水,也可以做成一个token」,而「物」、「权」的token化,就更好理解了。 将人、事、物、权token化,可以有不同层级的愿景和意义。张中南介绍: 第一层级,简单的来说,就是把 人、事、物、权做成token,放到区块链上面流通,或者说放到钱包里,做成APP,能够使用token做流转。 再往上一个级别,是这些token和token之间的交互。比如,可能有一件事,可以同时调用7、8个token,不再是简单的转让或流通。 再往上一个级别,「我们能够看到最远的地方就是这些token用来指代人、事、物、权之后,它们本身可以变成一个集成点,可以在用户端集成各种各样的服务和应用。比如,租车服务、保险、信用卡公司等,当需要调用他们的服务时,不再通过微信来使用,而是直接在用户端就能集成。 现阶段,为了实现初级目标,AlphaWallet选择从一款可编程钱包切入。今年5月23日,该公司正式发布了这款筹备已久的钱包产品——AlphaWallet 1.0版。 公开资料显示,这是一款直接支持不可替代性token的钱包,可作为连接虚拟世界和真实世界的网关。基于该钱包之上,真实世界内的生活服务可利用区块链技术而具备强有力的基础技术平台,从而拥有无限想象的可能性。 通常来说,大量token广泛使用的是ERC20协议。遵循ERC20的token可以跟踪任何人在任何时候拥有多少token。在一些开源组织的推动下,目前第三方基于ERC20接口5分钟即能发行一个ERC20的token。不过,相对来说,ERC20还存在两个问题: 第一,ERC20无法代表现实世界中无法拆分、独一无二的资产; 第二,现有的打包、转账流程复杂,ERC20缺乏可扩展性,无法实现更复杂的功能。 基于此,AlphaWallet自主开发了ERC875协议族。该协议不仅会让数字资产变得具有收藏价值,同时也能帮助现实世界中不可拆分替代、具有物权唯一性的资产上链,这就能为线下服务的链上操作提供了可能性。 虽然另一种协议ERC721也能实现token的不可置换性,但其存在需要交易双方支付gas费用、无法简单实现原子化交易等一些不易于用户使用的问题。 张中南向雷锋网AI金融评论介绍称,ERC875内置了两个密码学协议, 一方面能够简单实现原子化交易(atomic swap)——直接搭建去中心化市场、降低普通用户使用门槛,卖家无需持有以太币,买家支付一次gas即能完成;另外一方面可以简单打包处理大量交易。 拿基于ERC721的加密猫来说,换用ERC875协议的话,能够实现。用户在商家网站法币购猫,通过MagicLink免费把猫导入用户的钱包,之后用户还可以在不需要持有以太币的情况下,通过MagicLink把猫售出或者免费转让,全部过程都是无中心的原子化交易。另外商家可以一次批发100只猫给分销商。

首个落地应用:体育票务 或许与张中南在票务业务的经历有关,AlphaWallet选择从ERC875和钱包切入的第一个use case就是俄罗斯世界杯门票。 相较人、事而言,「票务」由于具备物理和权益属性,利用区块链技术来实现不可置换的token的流转,更具操作性和可行性。 目前 AlphaWallet 已与盛开体育达成合作。今年的俄罗斯世界杯,二者联合引入区块链技术以测试新的票务解决方案,将盛开体育世界杯票库内的部分门票转化为以太坊上的ERC875的token。由于这些token具有不可置换性,用户通过AlphaWallet钱包的动态二维码,以及线下的现场扫描,即可获得世界杯门票。考虑到进一步安全的问题,AlphaWallet钱包显示的动态二维码,每隔10s就会变一次。

AlphaWallet钱包兑换俄罗斯世界杯门票(test)流程体验

据张中南介绍,这次合作,「盛开那边做了10张票,AlphaWallet则拿了10张开幕式的VIP门票,所以一共只有20张门票」。经过雷锋网AI金融评论现场测试体验,通过AlphaWallet钱包流转一张世界杯门票,所花时间在4-7s以内。而买方从卖方手里通过支付以太坊的方式买入一张门票,所需时间则在10s左右。 「这应该是目前世界上首个不可替代通证与现实物权交互的落地案例。」团队向雷锋网AI金融评论表示。 除票务外,AlphaWallet近期还会继续考虑在「物」上面开发use case,主要专注在物理商品这一块,如 奢侈手表和限量球鞋等等。 不过,也有业内人士指出,通过不可置换协议,从token到实物的映射,可能还是难以避免实物造假的情况,这点又该如何防范?在张中南看来,给物理商品配备数字身份证,是通过经济学原理来实现防伪的。这点与溯源、防伪等又不一样。

4,ERC875标准

function name() constant returns (string name)

返回智能合约的名字,例如CarLotContract。

function symbol() constant returns (string symbol)

返回智能合约通证的标识符。

function balanceOf(address _owner) public view returns (uint256[] balance)

返回一组账户余额的数组。

function transfer(address _to, uint256[] _tokens) public;

通过包含通证索引的数组参数,把一组独一无二的通证转移给一个账户地址。相比ERC721一次只能转账一个通证,ERC875更显友好,它可以一次批量转账一组通证。这样既便利又能节约大量的GAS消耗。

function transferFrom(address _from, address _to, uint256[] _tokens) public;

从一个账户给另一个账户转账批量通证。这个可由一个获得特定KEY例如合同创建者的授权的账号来完成。

【以下为可选函数】

function totalSupply() constant returns (uint256 totalSupply);

返回给定合同的通证总数。这个通证总数可能是可变的。

function ownerOf(uint256 _tokenId) public view returns (address _owner);

返回特定通证的拥有者。这个函数是可选的,因为并不是每一个通证合约都需要跟踪每一个独一无二通知的拥有者,并且每次查询需要消耗GAS用于遍历和匹配token id于拥有者的关系。

function trade(uint256 expiryTimeStamp, uint256[] tokenIndices, uint8 v, bytes32 r, bytes32 s) public payable

该函数允许用户出售一组非同质通证而不需要支付GAS费,只需要购买者支付。这是通过签署包含要销售的代币数量,合同地址,到期时间戳,价格和包含ERC规范名称和链ID的前缀的证明来实现的。然后,买方可以通过附加适当的以太币(ether)来满足交易,从而在一次交易中支付交易。 这种设计也更有效,因为它允许订单在离线前完成,而不是在智能合约中创建订单并更新订单。到期时间戳保护卖方免受使用旧订单的人的影响。 这为点对点(p2p)原子交换(atomic swap)打开了大门,但对于这个标准应该是可选的,因为有些可能没有用它。 需要在消息中添加一些保护,例如编码链ID,合同地址和ERC规范名称,以防止重放和欺骗人们签署允许交易的消息。

5,ERC875样例代码

官方给出的ERC875代码样例如下,函数含义参考第4章。

contract ERC
{
  event Transfer(address indexed _from, address indexed _to, uint256[] tokenIndices);
 
  function name() constant public returns (string name);
  function symbol() constant public returns (string symbol);
  function balanceOf(address _owner) public view returns (uint256[] _balances);
  //function ownerOf(uint256 _tokenId) public view returns (address _owner);
  function transfer(address _to, uint256[] _tokens) public;
  function transferFrom(address _from, address _to, uint256[] _tokens) public;
 
  //optional
  //function totalSupply() public constant returns (uint256 totalSupply);
  function trade(uint256 expiryTimeStamp, uint256[] tokenIndices, uint8 v, bytes32 r, bytes32 s) public payable;
}

pragma solidity ^0.4.17;
contract Token is ERC
{
    uint totalTickets;
    mapping(address => uint256[]) inventory;
    uint16 ticketIndex = 0; //to track mapping in tickets
    uint expiryTimeStamp;
    address owner;   // the address that calls selfdestruct() and takes fees
    address admin;
    uint transferFee;
    uint numOfTransfers = 0;
    string public name;
    string public symbol;
    uint8 public constant decimals = 0; //no decimals as tickets cannot be split

    event Transfer(address indexed _from, address indexed _to, uint256[] tokenIndices);
    event TransferFrom(address indexed _from, address indexed _to, uint _value);
    
    modifier adminOnly()
    {
        if(msg.sender != admin) revert();
        else _;
    }

    function() public { revert(); } //should not send any ether directly

    // example: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "MJ comeback", 1603152000, "MJC", "0x007bEe82BDd9e866b2bd114780a47f2261C684E3"
    function Token(
        uint256[] numberOfTokens,
        string evName,
        uint expiry,
        string eventSymbol,
        address adminAddr) public
    {
        totalTickets = numberOfTokens.length;
        //assign some tickets to event admin
        expiryTimeStamp = expiry;
        owner = msg.sender;
        admin = adminAddr;
        inventory[admin] = numberOfTokens;
        symbol = eventSymbol;
        name = evName;
    }

    function getDecimals() public pure returns(uint)
    {
        return decimals;
    }
    
    // price is 1 in the example and the contract address is 0xfFAB5Ce7C012bc942F5CA0cd42c3C2e1AE5F0005
    // example: 0, [3, 4], 27, "0x2C011885E2D8FF02F813A4CB83EC51E1BFD5A7848B3B3400AE746FB08ADCFBFB", "0x21E80BAD65535DA1D692B4CEE3E740CD3282CCDC0174D4CF1E2F70483A6F4EB2"
    // price is encoded in the server and the msg.value is added to the message digest,
    // if the message digest is thus invalid then either the price or something else in the message is invalid
    function trade(uint256 expiry,
                   uint256[] tokenIndices,
                   uint8 v,
                   bytes32 r,
                   bytes32 s) public payable
    {
        //checks expiry timestamp,
        //if fake timestamp is added then message verification will fail
        require(expiry > block.timestamp || expiry == 0);
        //id 1 for mainnet
        bytes12 prefix = "ERC800-CNID1";
        bytes32 message = encodeMessage(prefix, msg.value, expiry, tokenIndices);
        address seller = ecrecover(message, v, r, s);
        
        for(uint i = 0; i < tokenIndices.length; i++)
        { // transfer each individual tickets in the ask order
            uint index = uint(tokenIndices[i]);
            require((inventory[seller][index] > 0)); // 0 means ticket sold.
            inventory[msg.sender].push(inventory[seller][index]);
            inventory[seller][index] = 0; // 0 means ticket sold.
        }
        seller.transfer(msg.value);
    }


    //must also sign in the contractAddress
    //prefix must contain ERC and chain id
    function encodeMessage(bytes12 prefix, uint value, 
        uint expiry, uint256[] tokenIndices)
        internal view returns (bytes32)
    {
        bytes memory message = new bytes(96 + tokenIndices.length * 2);
        address contractAddress = getContractAddress();
        for (uint i = 0; i < 32; i++)
        {   // convert bytes32 to bytes[32]
            // this adds the price to the message
            message[i] = byte(bytes32(value << (8 * i)));
        }

        for (i = 0; i < 32; i++)
        {
            message[i + 32] = byte(bytes32(expiry << (8 * i)));
        }
        
        for(i = 0; i < 12; i++)
        {
            message[i + 64] = byte(prefix << (8 * i));    
        }

        for(i = 0; i < 20; i++)
        {
            message[76 + i] = byte(bytes20(bytes20(contractAddress) << (8 * i)));
        }

        for (i = 0; i < tokenIndices.length; i++)
        {
            // convert int[] to bytes
            message[96 + i * 2 ] = byte(tokenIndices[i] >> 8);
            message[96 + i * 2 + 1] = byte(tokenIndices[i]);
        }

        return keccak256(message);
    }

    function name() public view returns(string)
    {
        return name;
    }

    function symbol() public view returns(string)
    {
        return symbol;
    }

    function getAmountTransferred() public view returns (uint)
    {
        return numOfTransfers;
    }

    function isContractExpired() public view returns (bool)
    {
        if(block.timestamp > expiryTimeStamp)
        {
            return true;
        }
        else return false;
    }

    function balanceOf(address _owner) public view returns (uint256[])
    {
        return inventory[_owner];
    }

    function myBalance() public view returns(uint256[])
    {
        return inventory[msg.sender];
    }

    function transfer(address _to, uint256[] tokenIndices) public
    {
        for(uint i = 0; i < tokenIndices.length; i++)
        {
            require(inventory[msg.sender][i] != 0);
            //pushes each element with ordering
            uint index = uint(tokenIndices[i]);
            inventory[_to].push(inventory[msg.sender][index]);
            inventory[msg.sender][index] = 0;
        }
    }

    function transferFrom(address _from, address _to, uint256[] tokenIndices)
        adminOnly public
    {
        bool isadmin = msg.sender == admin;
        for(uint i = 0; i < tokenIndices.length; i++)
        {
            require(inventory[_from][i] != 0 || isadmin);
            //pushes each element with ordering
            uint index = uint(tokenIndices[i]);
            inventory[_to].push(inventory[msg.sender][index]);
            inventory[_from][index] = 0;
        }
    }

    function endContract() public
    {
        if(msg.sender == owner)
        {
            selfdestruct(owner);
        }
        else revert();
    }

    function getContractAddress() public view returns(address)
    {
        return this;
    }
}

【函数说明】 1,trade函数是发起批量转让的智能合约函数 trade(uint256 expiry,/超时时间,以s计算/ uint256[] tokenIndices, /通证索引/ uint8 v, /*v,r,s是卖家签名的3个部分,产生的方法参考文件 */ bytes32 r, bytes32 s )

6,ERC875测试(REMIX+MetaMASK环境)

6.1 创建合约

[1] 管理员(0xca35b7d915458ef540ade6068dfe2f44e8fa733c)构建函数CREATE

 [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115], "DJ Family", 1603152000, "DJ", "0xca35b7d915458ef540ade6068dfe2f44e8fa733c"

【结果】 智能合约创建成功,得到智能合约地址:0x692a70d2e424a56d2c6c27aa97d1a86395877b3a

6.2 门票转让

管理员(0xca35b7d915458ef540ade6068dfe2f44e8fa733c)转移2张座位号为101,102的门票给李四(0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db)

transfer("0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db", [0,1])

*【结果】:门票已转让给李四,李四并没有消耗GAS,是管理员消耗了GAS。

6.3 trade门票

管理员(0xca35b7d915458ef540ade6068dfe2f44e8fa733c)把门票trade给赵六(0xdd870fa1b7c4700f2bd7f44238821c26f7392148) 当智能合约地址为0xfFAB5Ce7C012bc942F5CA0cd42c3C2e1AE5F0005,price is 1时,

trade(0, [3, 4], 27, "0x2C011885E2D8FF02F813A4CB83EC51E1BFD5A7848B3B3400AE746FB08ADCFBFB", "0x21E80BAD65535DA1D692B4CEE3E740CD3282CCDC0174D4CF1E2F70483A6F4EB2")

【结果】操作失败了,也无法触发购买。 【官方答复】 那个Trade function的功能是,在卖家发了签名信息给买家,然后买家联合卖家的签名信息和自己的签名信息一起call trade fundction来完成交易。你在现在的模式,是创建不出来卖家签名信息的, 你需要参考AlphaWallet的代码。 源码参考地址: https://github.com/alpha-wallet/AlphaWallet-Mobile-Apps

7 参考

1) 2018世界杯门票的一笔交易记录 2) 深入浅出以太坊ERC875标准(不可替代性通证标准) 3) AlphaWallet野心有点大:基于ERC875协议族,实现人、事、物、权token化 4)ERC875 for non fungible tokens and simple atomic swaps 5) AlphaWallet代码 6) ERC875智能合约案例 (TradeImplementationExample.java 和ERCTokenImplementation.sol) 7) AlphaWallet钱包下载-支持测试网络代币

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏企鹅号快讯

投资钛值的你,知道钛链是什么吗?

技术 钛链技术到底有多牛?钛值从技术考量到底有多值?为普及和推广钛值背后的区块链技术,我们的钛值极客团队来给大家上课啦! 搬好小板凳,拿出笔记本,开始学习! 1...

27780
来自专栏区块链大本营

“冰封”合约背后的老牌劲敌——拒绝服务漏洞 | 漏洞解析连载之二

眼观目前区块链发展的步伐越来越急促,似乎我们已无暇回首当初那些辉煌与挫败,只能低着头继续跟从与追赶。

6910
来自专栏区块链大本营

区块链开发实战:如何从零打造一个去中心化应用

1.1K60

简单介绍区块链技术

重要提示:如果你拥有价值超过1000美元的加密货币,那么你肯定应该使用硬件钱包,而不是在交易所保留硬币。 我推荐使用Trezor,你可以直接从他们的网站花费€8...

1.8K40
来自专栏IPFS

IPFS和Filecoin的挖矿原理

检索矿工对于带宽和性能要求较高,而存储矿工则是大家都可以参与的,所以,我们平时所说的挖矿通常指的是Filecoin的存储矿工。

29900
来自专栏北京马哥教育

看完此文再不懂区块链算我输:手把手教你用Python从零开始创建区块链

来源:51CTO技术栈 ID:blog51cto “三点钟区块链”无疑成为了大家春节期间焦虑的根源,而“区块链”注定是 2018 年被持续讨论、关注的行业性热...

43780
来自专栏SeanCheney的专栏

链课堂02 | 企业应用开发为何选择Fabric?

16030
来自专栏智能计算时代

区块链101:什么是SegWit(隔离见证)?

SegWit(隔离证人的缩写)是一种协议升级,它改变了存储数据的方式。它于2017年5月10日在litecoin上被激活,在2017年8月23日被激活。 开发商...

38860
来自专栏智能计算时代

区块链:它是什么,它是如何工作的

编者按:本报告是由《威胁镜头》,Stratfor的独特保护情报产品制作的。以企业安全领导者的头脑设计,威胁镜头使行业专业人士能够预测、识别、衡量和减轻世界各地的...

11630

所以你是因此而想使用区块链么?

使用区块链有很好的理由和不好的理由。在与人们讨论区块链使用案例的对话中,我注意到一些普遍的困惑,以及源于最初狭义范围内的词语的合成词(通常用于描述比特币的区块链...

34880

扫码关注云+社区

领取腾讯云代金券