【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

相關文章
相關標籤/搜索