Skip to content

Instantly share code, notes, and snippets.

  • Save balduran/2d78d34ae80aa78d4bab61400f76e3a3 to your computer and use it in GitHub Desktop.
Save balduran/2d78d34ae80aa78d4bab61400f76e3a3 to your computer and use it in GitHub Desktop.

Revisions

  1. @Ankarrr Ankarrr revised this gist Jan 26, 2018. 1 changed file with 2 additions and 2 deletions.
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,10 @@
    # 【Ethereum 智能合約開發筆記】不用自己跑節點,使用 Infura 和 web3.js 呼叫合約

    ![From Consensys Medium](https://i.imgur.com/doEO8gu.jpg)
    ![Infura Logo From Consensys](https://i.imgur.com/doEO8gu.jpg)

    > Infura 提供公開的 Ethereum 主網和測試網路節點。到 [Infura 官網申請](https://infura.io/signup),只要輸入一點基本資料和 Email,就可以收到 API-key。
    ![Infura API Key](https://i.imgur.com/2F9gc5t.png)
    ![Infura API Key](https://i.imgur.com/yz9kOFo.png)

    ---

  2. @Ankarrr Ankarrr created this gist Jan 26, 2018.
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,213 @@
    # 【Ethereum 智能合約開發筆記】不用自己跑節點,使用 Infura 和 web3.js 呼叫合約

    ![From Consensys Medium](https://i.imgur.com/doEO8gu.jpg)

    > Infura 提供公開的 Ethereum 主網和測試網路節點。到 [Infura 官網申請](https://infura.io/signup),只要輸入一點基本資料和 Email,就可以收到 API-key。
    ![Infura API Key](https://i.imgur.com/2F9gc5t.png)

    ---

    ## 使用 RPC 查詢合約內儲存的狀態

    最常需要查詢的狀態就是 Token 的餘額啦。我就用 EOS 代幣合約最為範例試看看。

    ### 取得合約資訊

    可以透過 Etherscan,大部分知名的合約可以直接搜尋到。

    ![](https://i.imgur.com/vVx5L4A.png)

    要呼叫合約,至少需要:

    - **合約地址**,例如 0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0
    - **要呼叫的 function signature**,例如以 ERC 20 代幣合約來說,查詢餘額要呼叫的 function 是 `balanceOf(address)`,其對應的 function signature 是 `70a08231`

    如何取得 function signature 呢?以 `balanceOf(address)` 為例:
    1.`balanceOf(address)` 經過 sha3

    0x70a08231b98ef4ca268c9cc3f6b4590e4bfec28280db06bb5d45e689f2a360be

    2. 取除了 `0x` 外,前面的 8 位

    70a08231

    以上流程可以用任何工具完成,以 web3.js 為例:

    var functionSig = we3.sha3("balanceOf(address)").substr(2,8)

    另外也可以把 contract code 貼到 remix。在合約的 Details 中可以看到完整的合約介面和對應的 function signature。

    ![](https://i.imgur.com/YRtWIGr.png)

    ### 使用 RPC

    可以透過一個簡單的 POST 用 Infura 提供的節點呼叫一個 RPC。有哪些 RPC method 可以看 [Ethereum RPC doc](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendtransaction)

    如果要呼叫的 function 只是查詢,而沒有要更新合約的狀態,那就用 `eth_call` 這個 RPC。POST Data 如下:

    {
    "jsonrpc":"2.0",
    "method":"eth_call",
    "params":[
    {
    "to":"0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0",
    "data":"0x70a0823100000000000000000000000033b8287511ac7F003902e83D642Be4603afCd876"
    },
    "latest"
    ],
    "id":123
    }

    其中 `params` 的值包含:

    - `"to"`:合約地址
    - `"data"`:丟給合約的參數。由三個部分組成:`0x``70a08231`和一個 32 bytes 的參數 `00000000000000000000000033b8287511ac7F003902e83D642Be46`(也就是我要查詢的帳戶)
    - `"latest"`,代表使用最新的區塊鏈資料

    #### 範例

    // Request
    curl https://mainnet.infura.io/<your-api-key> -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0", "data":"0x70a0823100000000000000000000000033b8287511ac7F003902e83D642Be4603afCd876"}, "latest"],"id":123 }'

    // Result
    {
    "jsonrpc": "2.0",
    "id": 123,
    "result": "0x000000000000000000000000000000000000000000000000b1d16ec625a59d3e"
    }

    `0x000000000000000000000000000000000000000000000000b1d16ec625a59d3e` 是十六進位,換算成十進位是 `12813144212159962430`。也就是小弟只有少少的 **12.x** 個 EOS token。

    ---

    ## web3.js(JavaScript API)

    如果要更新合約的狀態,就需要送 transaction,要送 transaction 就需要錢包或是說 private key 來 sign transaction 和提供 Ether 做手續費。因為送 transaction 要手續費,為了省點錢,我就部一個合約在 **Ropsten 測試鏈**上做這次的試驗。以上步驟比較麻煩,**我就用 web3.js 寫兩個簡單的程式,一個查詢合約狀態、一個更新合約狀態**。web3.js 的功能和 RPC 差不多,但是個 JavaScript 套件。有哪些 API 可以用請看 [JavaScript API doc](https://github.com/ethereum/wiki/wiki/JavaScript-API#web3versionapi)

    ### 安裝

    npm install web3, ethereumjs-tx

    ### 官方建議的初始化方式

    var Web3 = require('web3');

    if (typeof web3 !== 'undefined') {
    web3 = new Web3(web3.currentProvider);
    } else {
    // set the provider you want from Web3.providers
    web3 = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io/<your-api-key>"));
    }

    ### 部署測試合約

    這次部署一個很簡單的合約。合約只儲存一個狀態 `data`,並可透過 `set()` 更新狀態。

    合約程式碼:

    pragma solidity ^0.4.19;

    contract SimpleStorage {
    uint public data;
    function set(uint x) public {
    data = x;
    }
    }

    合約地址:`0x5fb30123b9efedcd15094266948fb7c862279ee1`

    合約的 function signatures:

    {
    "73d4a13a": "data()",
    "60fe47b1": "set(uint256)"
    }

    ---

    ### 查詢合約狀態

    使用 `web3.eth.call`

    // Request
    var result = web3.eth.call({
    to: "0x5fb30123b9efedcd15094266948fb7c862279ee1",
    data: "0x" + "73d4a13a"
    });

    // Print Result
    console.log(parseInt(result, 16));

    Print 出來結果會是 `0`,因為還沒更新過狀態。

    ---

    ### 更新合約狀態

    使用 `web3.eth.sendRawTransaction`

    > RPC 和 web3.js 提供的 `SendTransaction` 都是連到一節點,使用節點中的帳戶發送 transaction。而如果要用自己的帳戶就要用 `sendRawTransaction`,也就是說要自己建立 transaction、自己 sign 過,再透過 `sendRawTransaction` 發送。
    #### Define raw transaction

    var rawTx = {
    nonce: '0x14',
    gasPrice: '0x3B9ACA00',
    gasLimit: '0xC20A',
    to: '0x5fb30123b9efedcd15094266948fb7c862279ee1',
    value: '0x00',
    data: '0x' + '60fe47b1' + '000000000000000000000000000000000000000000000000000000000000000a'
    }

    `rawTx` 中包含:

    - `nonce`:紀錄目前帳戶送出的交易數,用來避免 replay attack,每次送要加 1。可以用 RPC `eth_getTransactionCount` 查詢目前帳戶的 nonce。也可以用 Etherscan 查,但 Etherscan 顯示的 `No Of Transactions` 會包含送出去但沒有成功的交易,所以會不準
    - `gasPrice`:一般用 1 Gwei(= 1000000000 = 0x3B9ACA00)
    - `gasLimit`:gaslimit 估算可參考 [使用ethereum browser計算gas cost](https://medium.com/taipei-ethereum-meetup/%E4%BD%BF%E7%94%A8ethereum-browser%E8%A8%88%E7%AE%97gas-cost-9122950a04f7)
    - `to`:合約地址
    - `value`:要送的 Ether 數量,因為只是要呼叫合約所以設 0
    - `data`:丟給合約的參數。由三個部分組成:`0x``60fe47b1`和一個 32 bytes 的參數 `000000000000000000000000000000000000000000000000000000000000000a`(我要更新的值,這邊設 10)

    #### Create and Sign raw transaction

    要引入另一個套件 `ethereumjs-tx`。記得先用 npm 安裝。

    var Tx = require('ethereumjs-tx');

    建立 raw transaction。

    var tx = new Tx(rawTx);

    用自己的 private key sign。

    const privateKey = new Buffer('<your-private-key>', 'hex')
    tx.sign(privateKey);

    #### Send raw transaction

    var serializedTx = tx.serialize();
    web3.eth.sendRawTransaction('0x' + serializedTx.function function function toString() { [native code] }() { [native code] }() { [native code] }('hex'), function(err, hash) {
    if (!err) {
    console.log(hash);
    } else {
    console.log(err)
    }
    });

    成功就會回傳一個 transaction hash,像是:

    0x2a9d89b0f329853b5c0f83c83cea4cfa2e38ddd1041d9abd0afcc9af5ed1bf1b

    交易成功送出且被收進 block 後,再次查詢合約狀態,Print 出來結果就會是 **10**

    可以透過 Etherscan 確認交易有沒有被收進 block 以及合約執行的結果(可能因為參數錯誤導致執行失敗)。

    ![https://ropsten.etherscan.io/tx/0x2a9d89b0f329853b5c0f83c83cea4cfa2e38ddd1041d9abd0afcc9af5ed1bf1b](https://i.imgur.com/RdDK54y.png)

    ## References

    - [在區塊鏈上建立可更新的智慧合約(一)](https://medium.com/@twedusuck/%E5%9C%A8%E5%8D%80%E5%A1%8A%E9%8F%88%E4%B8%8A%E5%BB%BA%E7%AB%8B%E5%8F%AF%E6%9B%B4%E6%96%B0%E7%9A%84%E6%99%BA%E6%85%A7%E5%90%88%E7%B4%84-cbe015bdb339)
    - [Ethereum JSON RPC Document](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactioncount)
    - [We3.js API Document](https://github.com/ethereum/wiki/wiki/JavaScript-API)