【ERC875】HiBlock黑客马拉松门票从定制到编码实现

image

1

什么是代币(token)合约

【本文目标】html

经过本文,能够从一个HiBlock黑客马拉松活动门票定制,转让,出售和签到为例,说明ERC875的设计初心,ERC875的标准接口分析,也给出了官网的ERC875的代码和本地测试,便于更多项目使用ERC875解决区块链业务中遇到的实际问题。java

【前置条件】git

(1)体验门票受让的用户不须要有任何技术门槛; (2)作门票定制和开发的须要本地已安装好MetaMASK,在Reposton Test Net获取了几个测试ETH(免费)的,要懂Solidity语言。github

不熟悉的建议参考文档开发实战|3步教你在以太坊上开一家宠物店(附流程+代码)编程

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

image

提交合约部署

2)“Submmit”按钮

“Gas Price”设置为30,点击“Submmit”按钮。该帐号要有必定的ETH测试币,不然点击"Buy"找平台免费买点。

image

确认交易

3)购买成功确认

购买成功的会有弹出提示。点击“肯定”按钮后,拉到下方的按钮能够查看智能合约部署连接和ABI合约信息。

image

合约部署成功

image

查看ABI信息和合约记录

4)查看部署合约成功地址

点击可知其部署成功:https://ropsten.etherscan.io/address/0x07fc44d796d30b317013cb907fadb6d738f5779e

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

1) 安装APP

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

2) 导入钱包

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

image

3)添加代币

输入以前的智能合约地址,符号和名称会自动联想出来的。

image

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

image

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

经过报名渠道,辉哥知道欧阳哥哥已报名参加HiBlock黑客马拉松,因此把区块链门票转给他。

1) 辉哥点击“转让”按钮

选择HHT后,点击右下角的“转让”按钮进行票务转让。

image

2)点击“转让”按钮

选择“如今直接转让门票”,

image

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

image

输入欧阳哥哥的钱包地址

3)确认转让

image

转让门票按钮

image

转帐成功

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

1)导入通证

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

2) 出售门票

小辉同窗知道了黑客马拉松的事情,也很想参加。欧阳哥哥恰好弄了2张票,就赞成把一张票低价转让给小辉。双方协商好价格是0.2个ETH。

欧阳哥哥点击出售按钮,设置好价格,最后连接经过微信发给小辉。

image

设置价格

image

设置截止时间

image

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

3) 导入支付

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

image

门票

image

确认购买

image

购买成功

4) 导入代币地址完成呈现

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

image

2.5 兑现门票

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

image

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

3

ERC875设计目标

image

AlphaWallet团队核心成员

(左二:CEO张中南;右二:创始人兼CTO张韡武)

ERC875协议是由AlphaWallet团队提出的,他们但愿基于ERC875协议族,可以实现人、事、物、权token化。

在创始人张中南看来,人、事、物、权所有token化,便可以用token来替代物理世界里面的任何商品。在此其中,token替代的是一个权益,能够指代各类各样的权益。好比,「人」的token化,「跟吴亦凡今天晚上6点钟到8点钟一块儿吃饭的权益,能够作成一个token」,「事」的token化,「用信用卡在商店买了一瓶水,也能够作成一个token」,而「物」、「权」的token化,就更好理解了。

将人、事、物、权token化,能够有不一样层级的愿景和意义。张中南介绍:

  • 第一层级,简单的来讲,就是把 人、事、物、权作成token,放到区块链上面流通,或者说放到钱包里,作成APP,可以使用token作流转。

  • 再往上一个级别,是这些token和token之间的交互。好比,可能有一件事,能够同时调用七、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就会变一次。

image

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[] _toke****ns) 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个部分,产生的方法参考文件(https://github.com/alpha-wallet/ERC875-Example/blob/master/TradeImplementationExample.java) */ 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世界杯门票的一笔交易记录(https://etherscan.io/tx/0x4675bf0bddfb4a68acd224d22ca810484a82920b26365e1d39bd354fd8e76d48#decodetab)

2) 深刻浅出以太坊ERC875标准(不可替代性通证标准)(http://8btc.com/article-4614-1.html)

3) AlphaWallet野心有点大:基于ERC875协议族,实现人、事、物、权token化(http://itech.ifeng.com/45021842/news.shtml)

4)ERC875 for non fungible tokens and simple atomic swaps(https://github.com/ethereum/EIPs/issues/875)

5) AlphaWallet代码(https://github.com/alpha-wallet)

6) ERC875智能合约案例 (TradeImplementationExample.java 和ERCTokenImplementation.sol)(https://github.com/alpha-wallet/ERC875-Example)

7) AlphaWallet钱包下载-支持测试网络代币(https://awallet.io/)

本文做者:HiBlock区块链技术布道群-辉哥

原文发布于简书:https://www.jianshu.com/p/ddaa0d3643ce

加微信baobaotalk_com,加入技术布道群

线上课程推荐

线上课程:《8小时区块链智能合约开发实践》

培训讲师:《白话区块链》做者 蒋勇

课程原价:999元,现价 399元

更多福利:

  • @全部人,识别下图二维码转发课程邀请好友报名,便可得到报名费50%返利

  • @学员,报名学习课程并在规定时间内完成考试便可瓜分10000元奖金

image

相关文章
相关标签/搜索