【Ethereum 智能合約開發筆記】不用自己跑節點,使用 Infura 和 web3.js 呼叫合約

in #cn7 years ago (edited)

Infura Logo From Consensys

Infura 提供公開的 Ethereum 主網和測試網路節點。到 Infura 官網申請,只要輸入一點基本資料和 Email,就可以收到 API-key。

Infura API Key


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

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

取得合約資訊

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

要呼叫合約,至少需要:

  • 合約地址,例如 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。

使用 RPC

可以透過一個簡單的 POST 用 Infura 提供的節點呼叫一個 RPC。有哪些 RPC method 可以看 Ethereum RPC doc

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

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

其中 params 的值包含:

  • "to":合約地址
  • "data":丟給合約的參數。由三個部分組成:0x70a08231和一個 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

安裝

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
  • to:合約地址
  • value:要送的 Ether 數量,因為只是要呼叫合約所以設 0
  • data:丟給合約的參數。由三個部分組成:0x60fe47b1和一個 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.toString('hex'), function(err, hash) {
    if (!err) {
        console.log(hash);
    } else {
        console.log(err)
    }
});

Result

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

0x2a9d89b0f329853b5c0f83c83cea4cfa2e38ddd1041d9abd0afcc9af5ed1bf1b

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

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

https://ropsten.etherscan.io/tx/0x2a9d89b0f329853b5c0f83c83cea4cfa2e38ddd1041d9abd0afcc9af5ed1bf1b

References

Sort:  

Coins mentioned in post:

CoinPrice (USD)📉 24h📈 7d
AEAeternity2.179$4.49%41.71%
EOSEOS13.420$-4.43%21.64%
ETHEthereum1014.840$-4.38%-4.61%
ZRX0x1.567$-3.62%-11.2%

Congratulations @ankar! You have received a personal award!

1 Year on Steemit
Click on the badge to view your own Board of Honor on SteemitBoard.

By upvoting this notification, you can help all Steemit users. Learn how here!

6666.......主要是為了更新合約嗎?

Congratulations @ankar! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

Click here to view your Board

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @ankar! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 3 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!